#
tokens: 40355/50000 123/123 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .DS_Store
├── .github
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   └── pull_request_template.md
├── .gitignore
├── .npmrc
├── custom-instructions.md
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── smithery.yaml
├── src
│   ├── data
│   │   ├── protocols
│   │   │   ├── file-repository.ts
│   │   │   ├── index.ts
│   │   │   └── project-repository.ts
│   │   └── usecases
│   │       ├── list-project-files
│   │       │   ├── list-project-files-protocols.ts
│   │       │   └── list-project-files.ts
│   │       ├── list-projects
│   │       │   ├── list-projects-protocols.ts
│   │       │   └── list-projects.ts
│   │       ├── read-file
│   │       │   ├── read-file-protocols.ts
│   │       │   └── read-file.ts
│   │       ├── update-file
│   │       │   ├── update-file-protocols.ts
│   │       │   └── update-file.ts
│   │       └── write-file
│   │           ├── write-file-protocols.ts
│   │           └── write-file.ts
│   ├── domain
│   │   ├── entities
│   │   │   ├── file.ts
│   │   │   ├── index.ts
│   │   │   └── project.ts
│   │   └── usecases
│   │       ├── index.ts
│   │       ├── list-project-files.ts
│   │       ├── list-projects.ts
│   │       ├── read-file.ts
│   │       ├── update-file.ts
│   │       └── write-file.ts
│   ├── infra
│   │   └── filesystem
│   │       ├── index.ts
│   │       └── repositories
│   │           ├── fs-file-repository.ts
│   │           └── fs-project-repository.ts
│   ├── main
│   │   ├── config
│   │   │   └── env.ts
│   │   ├── factories
│   │   │   ├── controllers
│   │   │   │   ├── index.ts
│   │   │   │   ├── list-project-files
│   │   │   │   │   ├── list-project-files-controller-factory.ts
│   │   │   │   │   └── list-project-files-validation-factory.ts
│   │   │   │   ├── list-projects
│   │   │   │   │   └── list-projects-controller-factory.ts
│   │   │   │   ├── read
│   │   │   │   │   ├── read-controller-factory.ts
│   │   │   │   │   └── read-validation-factory.ts
│   │   │   │   ├── update
│   │   │   │   │   ├── update-controller-factory.ts
│   │   │   │   │   └── update-validation-factory.ts
│   │   │   │   └── write
│   │   │   │       ├── write-controller-factory.ts
│   │   │   │       └── write-validation-factory.ts
│   │   │   └── use-cases
│   │   │       ├── index.ts
│   │   │       ├── list-project-files-factory.ts
│   │   │       ├── list-projects-factory.ts
│   │   │       ├── read-file-factory.ts
│   │   │       ├── update-file-factory.ts
│   │   │       └── write-file-factory.ts
│   │   ├── index.ts
│   │   └── protocols
│   │       └── mcp
│   │           ├── adapters
│   │           │   ├── mcp-request-adapter.ts
│   │           │   ├── mcp-router-adapter.ts
│   │           │   └── mcp-server-adapter.ts
│   │           ├── app.ts
│   │           ├── helpers
│   │           │   └── serialize-error.ts
│   │           └── routes.ts
│   ├── presentation
│   │   ├── controllers
│   │   │   ├── index.ts
│   │   │   ├── list-project-files
│   │   │   │   ├── index.ts
│   │   │   │   ├── list-project-files-controller.ts
│   │   │   │   └── protocols.ts
│   │   │   ├── list-projects
│   │   │   │   ├── index.ts
│   │   │   │   ├── list-projects-controller.ts
│   │   │   │   └── protocols.ts
│   │   │   ├── read
│   │   │   │   ├── index.ts
│   │   │   │   ├── protocols.ts
│   │   │   │   └── read-controller.ts
│   │   │   ├── update
│   │   │   │   ├── index.ts
│   │   │   │   ├── protocols.ts
│   │   │   │   └── update-controller.ts
│   │   │   └── write
│   │   │       ├── index.ts
│   │   │       ├── protocols.ts
│   │   │       └── write-controller.ts
│   │   ├── errors
│   │   │   ├── base-error.ts
│   │   │   ├── error-names.ts
│   │   │   ├── index.ts
│   │   │   ├── invalid-param-error.ts
│   │   │   ├── missing-param-error.ts
│   │   │   ├── not-found-error.ts
│   │   │   └── unexpected-error.ts
│   │   ├── helpers
│   │   │   └── index.ts
│   │   └── protocols
│   │       ├── controller.ts
│   │       ├── index.ts
│   │       ├── request.ts
│   │       ├── response.ts
│   │       └── validator.ts
│   └── validators
│       ├── constants.ts
│       ├── index.ts
│       ├── param-name-validator.ts
│       ├── path-security-validator.ts
│       ├── required-field-validator.ts
│       └── validator-composite.ts
├── tests
│   ├── data
│   │   ├── mocks
│   │   │   ├── index.ts
│   │   │   ├── mock-file-repository.ts
│   │   │   └── mock-project-repository.ts
│   │   └── usecases
│   │       ├── list-project-files
│   │       │   └── list-project-files.spec.ts
│   │       ├── list-projects
│   │       │   └── list-projects.spec.ts
│   │       ├── read-file
│   │       │   └── read-file.spec.ts
│   │       ├── update-file
│   │       │   └── update-file.spec.ts
│   │       └── write-file
│   │           └── write-file.spec.ts
│   ├── infra
│   │   └── filesystem
│   │       └── repositories
│   │           ├── fs-file-repository.test.ts
│   │           └── fs-project-repository.test.ts
│   ├── presentation
│   │   ├── controllers
│   │   │   ├── list-project-files
│   │   │   │   └── list-project-files-controller.test.ts
│   │   │   ├── list-projects
│   │   │   │   └── list-projects-controller.test.ts
│   │   │   ├── read
│   │   │   │   └── read-controller.test.ts
│   │   │   ├── update
│   │   │   │   └── update-controller.test.ts
│   │   │   └── write
│   │   │       └── write-controller.test.ts
│   │   ├── helpers
│   │   │   └── http-helpers.test.ts
│   │   └── mocks
│   │       ├── index.ts
│   │       ├── mock-list-project-files-use-case.ts
│   │       ├── mock-list-projects-use-case.ts
│   │       ├── mock-read-file-use-case.ts
│   │       ├── mock-update-file-use-case.ts
│   │       ├── mock-validator.ts
│   │       └── mock-write-file-use-case.ts
│   └── validators
│       ├── param-name-validator.test.ts
│       ├── path-security-validator.test.ts
│       ├── required-field-validator.test.ts
│       └── validator-composite.test.ts
├── tsconfig.json
└── vitest.config.ts
```

# Files

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

```
1 | node_modules
2 | .DS_Store
3 | .env
4 | .env.local
5 | .env.development.local
6 | .env.test.local
7 | .env.production.local
8 | dist
9 | coverage
```

--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------

```
1 | # Use package-lock.json for dependency tracking
2 | package-lock=true
3 | 
4 | # Save exact versions for better reproducibility
5 | save-exact=true
6 | 
```

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

```markdown
  1 | # Memory Bank MCP Server
  2 | 
  3 | [![smithery badge](https://smithery.ai/badge/@alioshr/memory-bank-mcp)](https://smithery.ai/server/@alioshr/memory-bank-mcp)
  4 | [![npm version](https://badge.fury.io/js/%40allpepper%2Fmemory-bank-mcp.svg)](https://www.npmjs.com/package/@allpepper/memory-bank-mcp)
  5 | [![npm downloads](https://img.shields.io/npm/dm/@allpepper/memory-bank-mcp.svg)](https://www.npmjs.com/package/@allpepper/memory-bank-mcp)
  6 | 
  7 | <a href="https://glama.ai/mcp/servers/ir18x1tixp"><img width="380" height="200" src="https://glama.ai/mcp/servers/ir18x1tixp/badge" alt="Memory Bank Server MCP server" /></a>
  8 | 
  9 | A Model Context Protocol (MCP) server implementation for remote memory bank management, inspired by [Cline Memory Bank](https://github.com/nickbaumann98/cline_docs/blob/main/prompting/custom%20instructions%20library/cline-memory-bank.md).
 10 | 
 11 | ## Overview
 12 | 
 13 | The Memory Bank MCP Server transforms traditional file-based memory banks into a centralized service that:
 14 | 
 15 | - Provides remote access to memory bank files via MCP protocol
 16 | - Enables multi-project memory bank management
 17 | - Maintains consistent file structure and validation
 18 | - Ensures proper isolation between project memory banks
 19 | 
 20 | ## Features
 21 | 
 22 | - **Multi-Project Support**
 23 | 
 24 |   - Project-specific directories
 25 |   - File structure enforcement
 26 |   - Path traversal prevention
 27 |   - Project listing capabilities
 28 |   - File listing per project
 29 | 
 30 | - **Remote Accessibility**
 31 | 
 32 |   - Full MCP protocol implementation
 33 |   - Type-safe operations
 34 |   - Proper error handling
 35 |   - Security through project isolation
 36 | 
 37 | - **Core Operations**
 38 |   - Read/write/update memory bank files
 39 |   - List available projects
 40 |   - List files within projects
 41 |   - Project existence validation
 42 |   - Safe read-only operations
 43 | 
 44 | ## Installation
 45 | 
 46 | To install Memory Bank Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@alioshr/memory-bank-mcp):
 47 | 
 48 | ```bash
 49 | npx -y @smithery/cli install @alioshr/memory-bank-mcp --client claude
 50 | ```
 51 | 
 52 | This will set up the MCP server configuration automatically. Alternatively, you can configure the server manually as described in the Configuration section below.
 53 | 
 54 | ## Quick Start
 55 | 
 56 | 1. Configure the MCP server in your settings (see Configuration section below)
 57 | 2. Start using the memory bank tools in your AI assistant
 58 | 
 59 | ## Using with Cline/Roo Code
 60 | 
 61 | The memory bank MCP server needs to be configured in your Cline MCP settings file. The location depends on your setup:
 62 | 
 63 | - For Cline extension: `~/Library/Application Support/Cursor/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
 64 | - For Roo Code VS Code extension: `~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json`
 65 | 
 66 | Add the following configuration to your MCP settings:
 67 | 
 68 | ```json
 69 | {
 70 |   "allpepper-memory-bank": {
 71 |     "command": "npx",
 72 |     "args": ["-y", "@allpepper/memory-bank-mcp"],
 73 |     "env": {
 74 |       "MEMORY_BANK_ROOT": "<path-to-bank>"
 75 |     },
 76 |     "disabled": false,
 77 |     "autoApprove": [
 78 |       "memory_bank_read",
 79 |       "memory_bank_write",
 80 |       "memory_bank_update",
 81 |       "list_projects",
 82 |       "list_project_files"
 83 |     ]
 84 |   }
 85 | }
 86 | ```
 87 | 
 88 | ### Configuration Details
 89 | 
 90 | - `MEMORY_BANK_ROOT`: Directory where project memory banks will be stored (e.g., `/path/to/memory-bank`)
 91 | - `disabled`: Set to `false` to enable the server
 92 | - `autoApprove`: List of operations that don't require explicit user approval:
 93 |   - `memory_bank_read`: Read memory bank files
 94 |   - `memory_bank_write`: Create new memory bank files
 95 |   - `memory_bank_update`: Update existing memory bank files
 96 |   - `list_projects`: List available projects
 97 |   - `list_project_files`: List files within a project
 98 | 
 99 | ## Using with Cursor
100 | 
101 | For Cursor, open the settings -> features -> add MCP server -> add the following:
102 | 
103 | ```shell
104 | env MEMORY_BANK_ROOT=<path-to-bank> npx -y @allpepper/memory-bank-mcp@latest
105 | ```
106 | ## Using with Claude
107 | 
108 | - Claude desktop config file: `~/Library/Application Support/Claude/claude_desktop_config.json`
109 | - Claude Code config file:  `~/.claude.json`
110 | 
111 | 1. Locate the config file
112 | 3. Locate the property called `mcpServers`
113 | 4. Paste this:
114 | 
115 | ```
116 |  "allPepper-memory-bank": {
117 |           "type": "stdio",
118 |           "command": "npx",
119 |           "args": [
120 |             "-y",
121 |             "@allpepper/memory-bank-mcp@latest"
122 |           ],
123 |           "env": {
124 |             "MEMORY_BANK_ROOT": "YOUR PATH"
125 |           }
126 |         }
127 | ```
128 | 
129 | ## Custom AI instructions
130 | 
131 | This section contains the instructions that should be pasted on the AI custom instructions, either for Cline, Claude or Cursor, or any other MCP client. You should copy and paste these rules. For reference, see [custom-instructions.md](custom-instructions.md) which contains these rules.
132 | 
133 | ## Development
134 | 
135 | Basic development commands:
136 | 
137 | ```bash
138 | # Install dependencies
139 | npm install
140 | 
141 | # Build the project
142 | npm run build
143 | 
144 | # Run tests
145 | npm run test
146 | 
147 | # Run tests in watch mode
148 | npm run test:watch
149 | 
150 | # Run the server directly with ts-node for quick testing
151 | npm run dev
152 | ```
153 | 
154 | ### Running with Docker
155 | 
156 | 1. Build the Docker image:
157 | 
158 |     ```bash
159 |     docker build -t memory-bank-mcp:local .
160 |     ```
161 | 
162 | 2. Run the Docker container for testing:
163 | 
164 |     ```bash
165 |     docker run -i --rm \
166 |       -e MEMORY_BANK_ROOT="/mnt/memory_bank" \
167 |       -v /path/to/memory-bank:/mnt/memory_bank \
168 |       --entrypoint /bin/sh \
169 |       memory-bank-mcp:local \
170 |       -c "ls -la /mnt/memory_bank"
171 |     ```
172 | 
173 | 3. Add MCP configuration, example for Roo Code:
174 | 
175 |     ```json
176 |     "allpepper-memory-bank": {
177 |       "command": "docker",
178 |       "args": [
179 |         "run", "-i", "--rm",
180 |         "-e", 
181 |         "MEMORY_BANK_ROOT",
182 |         "-v", 
183 |         "/path/to/memory-bank:/mnt/memory_bank",
184 |         "memory-bank-mcp:local"
185 |       ],
186 |       "env": {
187 |         "MEMORY_BANK_ROOT": "/mnt/memory_bank"
188 |       },
189 |       "disabled": false,
190 |       "alwaysAllow": [
191 |         "list_projects",
192 |         "list_project_files",
193 |         "memory_bank_read",
194 |         "memory_bank_update",
195 |         "memory_bank_write"
196 |       ]
197 |     }
198 |     ```
199 | 
200 | ## Contributing
201 | 
202 | Contributions are welcome! Please follow these steps:
203 | 
204 | 1. Fork the repository
205 | 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
206 | 3. Commit your changes (`git commit -m 'Add amazing feature'`)
207 | 4. Push to the branch (`git push origin feature/amazing-feature`)
208 | 5. Open a Pull Request
209 | 
210 | ### Development Guidelines
211 | 
212 | - Use TypeScript for all new code
213 | - Maintain type safety across the codebase
214 | - Add tests for new features
215 | - Update documentation as needed
216 | - Follow existing code style and patterns
217 | 
218 | ### Testing
219 | 
220 | - Write unit tests for new features
221 | - Include multi-project scenario tests
222 | - Test error cases thoroughly
223 | - Validate type constraints
224 | - Mock filesystem operations appropriately
225 | 
226 | ## License
227 | 
228 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
229 | 
230 | ## Acknowledgments
231 | 
232 | This project implements the memory bank concept originally documented in the [Cline Memory Bank](https://github.com/nickbaumann98/cline_docs/blob/main/prompting/custom%20instructions%20library/cline-memory-bank.md), extending it with remote capabilities and multi-project support.
233 | 
```

--------------------------------------------------------------------------------
/src/domain/entities/file.ts:
--------------------------------------------------------------------------------

```typescript
1 | export type File = string;
2 | 
```

--------------------------------------------------------------------------------
/src/domain/entities/project.ts:
--------------------------------------------------------------------------------

```typescript
1 | export type Project = string;
2 | 
```

--------------------------------------------------------------------------------
/src/presentation/protocols/request.ts:
--------------------------------------------------------------------------------

```typescript
1 | export interface Request<T extends any> {
2 |   body?: T;
3 | }
4 | 
```

--------------------------------------------------------------------------------
/src/domain/entities/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from "./file.js";
2 | export * from "./project.js";
3 | 
```

--------------------------------------------------------------------------------
/src/main/config/env.ts:
--------------------------------------------------------------------------------

```typescript
1 | export const env = {
2 |   rootPath: process.env.MEMORY_BANK_ROOT!,
3 | };
4 | 
```

--------------------------------------------------------------------------------
/src/presentation/controllers/read/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from "./protocols.js";
2 | export * from "./read-controller.js";
3 | 
```

--------------------------------------------------------------------------------
/src/presentation/protocols/validator.ts:
--------------------------------------------------------------------------------

```typescript
1 | export interface Validator {
2 |   validate(input?: any): Error | null;
3 | }
4 | 
```

--------------------------------------------------------------------------------
/src/presentation/controllers/write/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from "./protocols.js";
2 | export * from "./write-controller.js";
3 | 
```

--------------------------------------------------------------------------------
/src/presentation/controllers/update/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from "./protocols.js";
2 | export * from "./update-controller.js";
3 | 
```

--------------------------------------------------------------------------------
/src/data/protocols/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from "./file-repository.js";
2 | export * from "./project-repository.js";
3 | 
```

--------------------------------------------------------------------------------
/src/presentation/controllers/list-projects/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from "./list-projects-controller.js";
2 | export * from "./protocols.js";
3 | 
```

--------------------------------------------------------------------------------
/src/presentation/controllers/list-project-files/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from "./list-project-files-controller.js";
2 | export * from "./protocols.js";
3 | 
```

--------------------------------------------------------------------------------
/tests/data/mocks/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from "./mock-file-repository.js";
2 | export * from "./mock-project-repository.js";
3 | 
```

--------------------------------------------------------------------------------
/src/infra/filesystem/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from "./repositories/fs-file-repository.js";
2 | export * from "./repositories/fs-project-repository.js";
3 | 
```

--------------------------------------------------------------------------------
/src/presentation/protocols/response.ts:
--------------------------------------------------------------------------------

```typescript
1 | export interface Response<T extends any | Error | null = any | Error | null> {
2 |   body?: T;
3 |   statusCode: number;
4 | }
5 | 
```

--------------------------------------------------------------------------------
/src/presentation/protocols/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from "./controller.js";
2 | export * from "./request.js";
3 | export * from "./response.js";
4 | export * from "./validator.js";
5 | 
```

--------------------------------------------------------------------------------
/src/domain/usecases/list-projects.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { Project } from "../entities/index.js";
2 | 
3 | export interface ListProjectsUseCase {
4 |   listProjects(): Promise<Project[]>;
5 | }
6 | 
```

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

```typescript
1 | export * from "./param-name-validator.js";
2 | export * from "./required-field-validator.js";
3 | export * from "./validator-composite.js";
4 | 
```

--------------------------------------------------------------------------------
/src/presentation/protocols/controller.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { Request, Response } from "./index.js";
2 | 
3 | export interface Controller<T, R> {
4 |   handle(request: Request<T>): Promise<Response<R>>;
5 | }
6 | 
```

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

```typescript
1 | #!/usr/bin/env node
2 | 
3 | import app from "./protocols/mcp/app.js";
4 | 
5 | app.start().catch((error) => {
6 |   console.error(error);
7 |   process.exit(1);
8 | });
9 | 
```

--------------------------------------------------------------------------------
/src/domain/usecases/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from "./list-project-files.js";
2 | export * from "./list-projects.js";
3 | export * from "./read-file.js";
4 | export * from "./update-file.js";
5 | export * from "./write-file.js";
6 | 
```

--------------------------------------------------------------------------------
/src/validators/constants.ts:
--------------------------------------------------------------------------------

```typescript
1 | /**
2 |  * Regular expression for validating project and file names
3 |  * Allows only alphanumeric characters, underscores, and hyphens
4 |  */
5 | export const NAME_REGEX = /^[a-zA-Z0-9_.-]+$/;
6 | 
```

--------------------------------------------------------------------------------
/src/presentation/errors/base-error.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { ErrorName } from "./error-names.js";
2 | 
3 | export abstract class BaseError extends Error {
4 |   constructor(message: string, name: ErrorName) {
5 |     super(message);
6 |     this.name = name;
7 |   }
8 | }
9 | 
```

--------------------------------------------------------------------------------
/src/presentation/errors/error-names.ts:
--------------------------------------------------------------------------------

```typescript
1 | export enum ErrorName {
2 |   INVALID_PARAM_ERROR = "InvalidParamError",
3 |   NOT_FOUND_ERROR = "NotFoundError",
4 |   UNEXPECTED_ERROR = "UnexpectedError",
5 |   MISSING_PARAM_ERROR = "MissingParamError",
6 | }
7 | 
```

--------------------------------------------------------------------------------
/src/main/factories/use-cases/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from "./list-project-files-factory.js";
2 | export * from "./list-projects-factory.js";
3 | export * from "./read-file-factory.js";
4 | export * from "./update-file-factory.js";
5 | export * from "./write-file-factory.js";
6 | 
```

--------------------------------------------------------------------------------
/src/domain/usecases/read-file.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { File } from "../entities/index.js";
 2 | export interface ReadFileParams {
 3 |   projectName: string;
 4 |   fileName: string;
 5 | }
 6 | 
 7 | export interface ReadFileUseCase {
 8 |   readFile(params: ReadFileParams): Promise<File | null>;
 9 | }
10 | 
```

--------------------------------------------------------------------------------
/src/presentation/controllers/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | // Export all controller modules
2 | export * from "./list-project-files/index.js";
3 | export * from "./list-projects/index.js";
4 | export * from "./read/index.js";
5 | export * from "./update/index.js";
6 | export * from "./write/index.js";
7 | 
```

--------------------------------------------------------------------------------
/src/domain/usecases/list-project-files.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { File } from "../entities/index.js";
2 | export interface ListProjectFilesParams {
3 |   projectName: string;
4 | }
5 | 
6 | export interface ListProjectFilesUseCase {
7 |   listProjectFiles(params: ListProjectFilesParams): Promise<File[]>;
8 | }
9 | 
```

--------------------------------------------------------------------------------
/src/presentation/errors/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from "./base-error.js";
2 | export * from "./error-names.js";
3 | export * from "./invalid-param-error.js";
4 | export * from "./missing-param-error.js";
5 | export * from "./not-found-error.js";
6 | export * from "./unexpected-error.js";
7 | 
```

--------------------------------------------------------------------------------
/src/data/protocols/project-repository.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { Project } from "../../domain/entities/index.js";
2 | 
3 | export interface ProjectRepository {
4 |   listProjects(): Promise<Project[]>;
5 |   projectExists(name: string): Promise<boolean>;
6 |   ensureProject(name: string): Promise<void>;
7 | }
8 | 
```

--------------------------------------------------------------------------------
/src/domain/usecases/write-file.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { File } from "../entities/index.js";
 2 | export interface WriteFileParams {
 3 |   projectName: string;
 4 |   fileName: string;
 5 |   content: string;
 6 | }
 7 | 
 8 | export interface WriteFileUseCase {
 9 |   writeFile(params: WriteFileParams): Promise<File | null>;
10 | }
11 | 
```

--------------------------------------------------------------------------------
/src/presentation/errors/not-found-error.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { BaseError } from "./base-error.js";
2 | import { ErrorName } from "./error-names.js";
3 | 
4 | export class NotFoundError extends BaseError {
5 |   constructor(name: string) {
6 |     super(`Resource not found: ${name}`, ErrorName.NOT_FOUND_ERROR);
7 |   }
8 | }
9 | 
```

--------------------------------------------------------------------------------
/src/presentation/controllers/list-projects/protocols.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { ListProjectsUseCase } from "../../../domain/usecases/list-projects.js";
2 | import { Controller, Response } from "../../protocols/index.js";
3 | 
4 | export type ListProjectsResponse = string[];
5 | 
6 | export { Controller, ListProjectsUseCase, Response };
7 | 
```

--------------------------------------------------------------------------------
/src/data/usecases/read-file/read-file-protocols.ts:
--------------------------------------------------------------------------------

```typescript
1 | import {
2 |   ReadFileParams,
3 |   ReadFileUseCase,
4 | } from "../../../domain/usecases/index.js";
5 | import { FileRepository, ProjectRepository } from "../../protocols/index.js";
6 | 
7 | export { FileRepository, ProjectRepository, ReadFileParams, ReadFileUseCase };
8 | 
```

--------------------------------------------------------------------------------
/src/domain/usecases/update-file.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { File } from "../entities/index.js";
 2 | 
 3 | export interface UpdateFileParams {
 4 |   projectName: string;
 5 |   fileName: string;
 6 |   content: string;
 7 | }
 8 | 
 9 | export interface UpdateFileUseCase {
10 |   updateFile(params: UpdateFileParams): Promise<File | null>;
11 | }
12 | 
```

--------------------------------------------------------------------------------
/src/data/usecases/write-file/write-file-protocols.ts:
--------------------------------------------------------------------------------

```typescript
1 | import {
2 |   WriteFileParams,
3 |   WriteFileUseCase,
4 | } from "../../../domain/usecases/index.js";
5 | import { FileRepository, ProjectRepository } from "../../protocols/index.js";
6 | 
7 | export { FileRepository, ProjectRepository, WriteFileParams, WriteFileUseCase };
8 | 
```

--------------------------------------------------------------------------------
/src/main/protocols/mcp/app.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServerAdapter } from "./adapters/mcp-server-adapter.js";
 2 | import routes from "./routes.js";
 3 | 
 4 | const router = routes();
 5 | const app = new McpServerAdapter(router);
 6 | 
 7 | app.register({
 8 |   name: "memory-bank",
 9 |   version: "1.0.0",
10 | });
11 | 
12 | export default app;
13 | 
```

--------------------------------------------------------------------------------
/src/data/usecases/list-projects/list-projects-protocols.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { Project } from "../../../domain/entities/index.js";
2 | import { ListProjectsUseCase } from "../../../domain/usecases/index.js";
3 | import { ProjectRepository } from "../../protocols/index.js";
4 | 
5 | export { ListProjectsUseCase, Project, ProjectRepository };
6 | 
```

--------------------------------------------------------------------------------
/src/presentation/errors/invalid-param-error.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { BaseError } from "./base-error.js";
2 | import { ErrorName } from "./error-names.js";
3 | 
4 | export class InvalidParamError extends BaseError {
5 |   constructor(paramName: string) {
6 |     super(`Invalid parameter: ${paramName}`, ErrorName.INVALID_PARAM_ERROR);
7 |   }
8 | }
9 | 
```

--------------------------------------------------------------------------------
/src/presentation/errors/missing-param-error.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { BaseError } from "./base-error.js";
2 | import { ErrorName } from "./error-names.js";
3 | 
4 | export class MissingParamError extends BaseError {
5 |   constructor(paramName: string) {
6 |     super(`Missing parameter: ${paramName}`, ErrorName.MISSING_PARAM_ERROR);
7 |   }
8 | }
9 | 
```

--------------------------------------------------------------------------------
/src/data/usecases/update-file/update-file-protocols.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {
 2 |   UpdateFileParams,
 3 |   UpdateFileUseCase,
 4 | } from "../../../domain/usecases/index.js";
 5 | import { FileRepository, ProjectRepository } from "../../protocols/index.js";
 6 | 
 7 | export {
 8 |   FileRepository,
 9 |   ProjectRepository,
10 |   UpdateFileParams,
11 |   UpdateFileUseCase,
12 | };
13 | 
```

--------------------------------------------------------------------------------
/tests/presentation/mocks/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from "./mock-list-project-files-use-case.js";
2 | export * from "./mock-list-projects-use-case.js";
3 | export * from "./mock-read-file-use-case.js";
4 | export * from "./mock-update-file-use-case.js";
5 | export * from "./mock-validator.js";
6 | export * from "./mock-write-file-use-case.js";
7 | 
```

--------------------------------------------------------------------------------
/src/data/usecases/list-project-files/list-project-files-protocols.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {
 2 |   ListProjectFilesParams,
 3 |   ListProjectFilesUseCase,
 4 | } from "../../../domain/usecases/index.js";
 5 | import { FileRepository, ProjectRepository } from "../../protocols/index.js";
 6 | 
 7 | export {
 8 |   FileRepository,
 9 |   ListProjectFilesParams,
10 |   ListProjectFilesUseCase,
11 |   ProjectRepository,
12 | };
13 | 
```

--------------------------------------------------------------------------------
/src/presentation/errors/unexpected-error.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { BaseError } from "./base-error.js";
 2 | import { ErrorName } from "./error-names.js";
 3 | 
 4 | export class UnexpectedError extends BaseError {
 5 |   constructor(originalError: unknown) {
 6 |     super(
 7 |       `An unexpected error occurred: ${originalError}`,
 8 |       ErrorName.UNEXPECTED_ERROR
 9 |     );
10 |   }
11 | }
12 | 
```

--------------------------------------------------------------------------------
/src/main/factories/controllers/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from "./list-project-files/list-project-files-controller-factory.js";
2 | export * from "./list-projects/list-projects-controller-factory.js";
3 | export * from "./read/read-controller-factory.js";
4 | export * from "./update/update-controller-factory.js";
5 | export * from "./write/write-controller-factory.js";
6 | 
```

--------------------------------------------------------------------------------
/src/data/usecases/list-projects/list-projects.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {
 2 |   ListProjectsUseCase,
 3 |   Project,
 4 |   ProjectRepository,
 5 | } from "./list-projects-protocols.js";
 6 | 
 7 | export class ListProjects implements ListProjectsUseCase {
 8 |   constructor(private readonly projectRepository: ProjectRepository) {}
 9 | 
10 |   async listProjects(): Promise<Project[]> {
11 |     return this.projectRepository.listProjects();
12 |   }
13 | }
14 | 
```

--------------------------------------------------------------------------------
/tests/presentation/mocks/mock-list-projects-use-case.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ListProjectsUseCase } from "../../../src/domain/usecases/list-projects.js";
 2 | 
 3 | export class MockListProjectsUseCase implements ListProjectsUseCase {
 4 |   async listProjects(): Promise<string[]> {
 5 |     return ["project1", "project2"];
 6 |   }
 7 | }
 8 | 
 9 | export const makeListProjectsUseCase = (): ListProjectsUseCase => {
10 |   return new MockListProjectsUseCase();
11 | };
12 | 
```

--------------------------------------------------------------------------------
/tests/presentation/mocks/mock-validator.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Validator } from "../../../src/presentation/protocols/index.js";
 2 | 
 3 | export class MockValidator<T> implements Validator<T> {
 4 |   validate<S extends T>(input: S): null;
 5 |   validate(input?: any): Error;
 6 |   validate(input?: any): Error | null {
 7 |     return null;
 8 |   }
 9 | }
10 | 
11 | export const makeValidator = <T>(): Validator<T> => {
12 |   return new MockValidator<T>();
13 | };
14 | 
```

--------------------------------------------------------------------------------
/src/main/factories/controllers/list-projects/list-projects-controller-factory.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { ListProjectsController } from "../../../../presentation/controllers/list-projects/list-projects-controller.js";
2 | import { makeListProjects } from "../../use-cases/list-projects-factory.js";
3 | 
4 | export const makeListProjectsController = () => {
5 |   const listProjectsUseCase = makeListProjects();
6 |   return new ListProjectsController(listProjectsUseCase);
7 | };
8 | 
```

--------------------------------------------------------------------------------
/src/presentation/controllers/write/protocols.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WriteFileUseCase } from "../../../domain/usecases/write-file.js";
 2 | import {
 3 |   Controller,
 4 |   Request,
 5 |   Response,
 6 |   Validator,
 7 | } from "../../protocols/index.js";
 8 | 
 9 | export interface WriteRequest {
10 |   projectName: string;
11 |   fileName: string;
12 |   content: string;
13 | }
14 | 
15 | export type WriteResponse = string;
16 | 
17 | export { Controller, Request, Response, Validator, WriteFileUseCase };
18 | 
```

--------------------------------------------------------------------------------
/src/main/factories/controllers/list-project-files/list-project-files-validation-factory.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Validator } from "../../../../presentation/protocols/validator.js";
 2 | import { ValidatorComposite } from "../../../../validators/validator-composite.js";
 3 | 
 4 | const makeValidations = (): Validator[] => {
 5 |   return [];
 6 | };
 7 | 
 8 | export const makeListProjectFilesValidation = (): Validator => {
 9 |   const validations = makeValidations();
10 |   return new ValidatorComposite(validations);
11 | };
12 | 
```

--------------------------------------------------------------------------------
/src/presentation/controllers/list-project-files/protocols.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ListProjectFilesUseCase } from "../../../domain/usecases/list-project-files.js";
 2 | import {
 3 |   Controller,
 4 |   Request,
 5 |   Response,
 6 |   Validator,
 7 | } from "../../protocols/index.js";
 8 | 
 9 | export interface ListProjectFilesRequest {
10 |   projectName: string;
11 | }
12 | 
13 | export type ListProjectFilesResponse = string[];
14 | 
15 | export { Controller, ListProjectFilesUseCase, Request, Response, Validator };
16 | 
```

--------------------------------------------------------------------------------
/src/main/factories/use-cases/list-projects-factory.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { ListProjects } from "../../../data/usecases/list-projects/list-projects.js";
2 | import { FsProjectRepository } from "../../../infra/filesystem/repositories/fs-project-repository.js";
3 | import { env } from "../../config/env.js";
4 | 
5 | export const makeListProjects = () => {
6 |   const projectRepository = new FsProjectRepository(env.rootPath);
7 |   return new ListProjects(projectRepository);
8 | };
9 | 
```

--------------------------------------------------------------------------------
/src/validators/validator-composite.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Validator } from "../presentation/protocols/validator.js";
 2 | 
 3 | export class ValidatorComposite implements Validator {
 4 |   constructor(private readonly validators: Array<Validator>) {}
 5 | 
 6 |   validate(input?: any): Error | null {
 7 |     for (const validator of this.validators) {
 8 |       const error = validator.validate(input);
 9 |       if (error) {
10 |         return error;
11 |       }
12 |     }
13 |     return null;
14 |   }
15 | }
16 | 
```

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

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

--------------------------------------------------------------------------------
/src/main/factories/controllers/read/read-controller-factory.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ReadController } from "../../../../presentation/controllers/read/read-controller.js";
 2 | import { makeReadFile } from "../../use-cases/read-file-factory.js";
 3 | import { makeReadValidation } from "./read-validation-factory.js";
 4 | 
 5 | export const makeReadController = () => {
 6 |   const validator = makeReadValidation();
 7 |   const readFileUseCase = makeReadFile();
 8 | 
 9 |   return new ReadController(readFileUseCase, validator);
10 | };
11 | 
```

--------------------------------------------------------------------------------
/tests/presentation/mocks/mock-read-file-use-case.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ReadFileUseCase } from "../../../src/domain/usecases/read-file.js";
 2 | import { ReadRequest } from "../../../src/presentation/controllers/read/protocols.js";
 3 | 
 4 | export class MockReadFileUseCase implements ReadFileUseCase {
 5 |   async readFile(params: ReadRequest): Promise<string | null> {
 6 |     return "file content";
 7 |   }
 8 | }
 9 | 
10 | export const makeReadFileUseCase = (): ReadFileUseCase => {
11 |   return new MockReadFileUseCase();
12 | };
13 | 
```

--------------------------------------------------------------------------------
/tests/presentation/mocks/mock-write-file-use-case.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WriteFileUseCase } from "../../../src/domain/usecases/write-file.js";
 2 | import { WriteRequest } from "../../../src/presentation/controllers/write/protocols.js";
 3 | 
 4 | export class MockWriteFileUseCase implements WriteFileUseCase {
 5 |   async writeFile(params: WriteRequest): Promise<string | null> {
 6 |     return null;
 7 |   }
 8 | }
 9 | 
10 | export const makeWriteFileUseCase = (): WriteFileUseCase => {
11 |   return new MockWriteFileUseCase();
12 | };
13 | 
```

--------------------------------------------------------------------------------
/src/data/protocols/file-repository.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { File } from "../../domain/entities/index.js";
 2 | 
 3 | export interface FileRepository {
 4 |   listFiles(projectName: string): Promise<File[]>;
 5 |   loadFile(projectName: string, fileName: string): Promise<File | null>;
 6 |   writeFile(
 7 |     projectName: string,
 8 |     fileName: string,
 9 |     content: string
10 |   ): Promise<File | null>;
11 |   updateFile(
12 |     projectName: string,
13 |     fileName: string,
14 |     content: string
15 |   ): Promise<File | null>;
16 | }
17 | 
```

--------------------------------------------------------------------------------
/src/main/factories/controllers/write/write-controller-factory.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WriteController } from "../../../../presentation/controllers/write/write-controller.js";
 2 | import { makeWriteFile } from "../../use-cases/write-file-factory.js";
 3 | import { makeWriteValidation } from "./write-validation-factory.js";
 4 | 
 5 | export const makeWriteController = () => {
 6 |   const validator = makeWriteValidation();
 7 |   const writeFileUseCase = makeWriteFile();
 8 | 
 9 |   return new WriteController(writeFileUseCase, validator);
10 | };
11 | 
```

--------------------------------------------------------------------------------
/src/main/factories/controllers/update/update-controller-factory.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { UpdateController } from "../../../../presentation/controllers/update/update-controller.js";
 2 | import { makeUpdateFile } from "../../use-cases/update-file-factory.js";
 3 | import { makeUpdateValidation } from "./update-validation-factory.js";
 4 | 
 5 | export const makeUpdateController = () => {
 6 |   const validator = makeUpdateValidation();
 7 |   const updateFileUseCase = makeUpdateFile();
 8 | 
 9 |   return new UpdateController(updateFileUseCase, validator);
10 | };
11 | 
```

--------------------------------------------------------------------------------
/tests/presentation/mocks/mock-update-file-use-case.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { UpdateFileUseCase } from "../../../src/domain/usecases/update-file.js";
 2 | import { UpdateRequest } from "../../../src/presentation/controllers/update/protocols.js";
 3 | 
 4 | export class MockUpdateFileUseCase implements UpdateFileUseCase {
 5 |   async updateFile(params: UpdateRequest): Promise<string | null> {
 6 |     return "updated content";
 7 |   }
 8 | }
 9 | 
10 | export const makeUpdateFileUseCase = (): UpdateFileUseCase => {
11 |   return new MockUpdateFileUseCase();
12 | };
13 | 
```

--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { defineConfig } from "vitest/config";
 2 | 
 3 | export default defineConfig({
 4 |   test: {
 5 |     globals: true,
 6 |     environment: "node",
 7 |     include: ["**/*.spec.ts", "**/*.test.ts"],
 8 |     exclude: ["**/node_modules/**", "**/dist/**"],
 9 |     coverage: {
10 |       provider: "v8",
11 |       reporter: ["text", "json", "html"],
12 |       exclude: [
13 |         "**/node_modules/**",
14 |         "**/dist/**",
15 |         "**/*.d.ts",
16 |         "**/*.spec.ts",
17 |         "**/*.test.ts",
18 |       ],
19 |     },
20 |   },
21 | });
22 | 
```

--------------------------------------------------------------------------------
/src/presentation/controllers/update/protocols.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { UpdateFileUseCase } from "../../../domain/usecases/update-file.js";
 2 | import { NotFoundError } from "../../errors/index.js";
 3 | import {
 4 |   Controller,
 5 |   Request,
 6 |   Response,
 7 |   Validator,
 8 | } from "../../protocols/index.js";
 9 | 
10 | export interface UpdateRequest {
11 |   projectName: string;
12 |   fileName: string;
13 |   content: string;
14 | }
15 | 
16 | export type UpdateResponse = string;
17 | export type RequestValidator = Validator;
18 | 
19 | export { Controller, NotFoundError, Request, Response, UpdateFileUseCase };
20 | 
```

--------------------------------------------------------------------------------
/src/main/factories/use-cases/read-file-factory.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ReadFile } from "../../../data/usecases/read-file/read-file.js";
 2 | import { FsFileRepository } from "../../../infra/filesystem/index.js";
 3 | import { FsProjectRepository } from "../../../infra/filesystem/repositories/fs-project-repository.js";
 4 | import { env } from "../../config/env.js";
 5 | 
 6 | export const makeReadFile = () => {
 7 |   const projectRepository = new FsProjectRepository(env.rootPath);
 8 |   const fileRepository = new FsFileRepository(env.rootPath);
 9 | 
10 |   return new ReadFile(fileRepository, projectRepository);
11 | };
12 | 
```

--------------------------------------------------------------------------------
/src/validators/required-field-validator.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { MissingParamError } from "../presentation/errors/index.js";
 2 | import { Validator } from "../presentation/protocols/validator.js";
 3 | 
 4 | export class RequiredFieldValidator implements Validator {
 5 |   constructor(private readonly fieldName: string) {}
 6 | 
 7 |   validate(input?: any): Error | null {
 8 |     if (
 9 |       !input ||
10 |       (input[this.fieldName] !== 0 &&
11 |         input[this.fieldName] !== false &&
12 |         !input[this.fieldName])
13 |     ) {
14 |       return new MissingParamError(this.fieldName);
15 |     }
16 |     return null;
17 |   }
18 | }
19 | 
```

--------------------------------------------------------------------------------
/src/main/factories/use-cases/write-file-factory.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WriteFile } from "../../../data/usecases/write-file/write-file.js";
 2 | import { FsFileRepository } from "../../../infra/filesystem/index.js";
 3 | import { FsProjectRepository } from "../../../infra/filesystem/repositories/fs-project-repository.js";
 4 | import { env } from "../../config/env.js";
 5 | 
 6 | export const makeWriteFile = () => {
 7 |   const projectRepository = new FsProjectRepository(env.rootPath);
 8 |   const fileRepository = new FsFileRepository(env.rootPath);
 9 | 
10 |   return new WriteFile(fileRepository, projectRepository);
11 | };
12 | 
```

--------------------------------------------------------------------------------
/src/main/factories/use-cases/update-file-factory.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { UpdateFile } from "../../../data/usecases/update-file/update-file.js";
 2 | import { FsFileRepository } from "../../../infra/filesystem/index.js";
 3 | import { FsProjectRepository } from "../../../infra/filesystem/repositories/fs-project-repository.js";
 4 | import { env } from "../../config/env.js";
 5 | 
 6 | export const makeUpdateFile = () => {
 7 |   const projectRepository = new FsProjectRepository(env.rootPath);
 8 |   const fileRepository = new FsFileRepository(env.rootPath);
 9 | 
10 |   return new UpdateFile(fileRepository, projectRepository);
11 | };
12 | 
```

--------------------------------------------------------------------------------
/tests/presentation/mocks/mock-list-project-files-use-case.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ListProjectFilesUseCase } from "../../../src/domain/usecases/list-project-files.js";
 2 | import { ListProjectFilesRequest } from "../../../src/presentation/controllers/list-project-files/protocols.js";
 3 | 
 4 | export class MockListProjectFilesUseCase implements ListProjectFilesUseCase {
 5 |   async listProjectFiles(params: ListProjectFilesRequest): Promise<string[]> {
 6 |     return ["file1.txt", "file2.txt"];
 7 |   }
 8 | }
 9 | 
10 | export const makeListProjectFilesUseCase = (): ListProjectFilesUseCase => {
11 |   return new MockListProjectFilesUseCase();
12 | };
13 | 
```

--------------------------------------------------------------------------------
/src/presentation/controllers/read/protocols.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ReadFileUseCase } from "../../../domain/usecases/read-file.js";
 2 | import { NotFoundError } from "../../errors/index.js";
 3 | import {
 4 |   Controller,
 5 |   Request,
 6 |   Response,
 7 |   Validator,
 8 | } from "../../protocols/index.js";
 9 | export interface ReadRequest {
10 |   /**
11 |    * The name of the project containing the file.
12 |    */
13 |   projectName: string;
14 | 
15 |   /**
16 |    * The name of the file to read.
17 |    */
18 |   fileName: string;
19 | }
20 | 
21 | export type ReadResponse = string;
22 | 
23 | export {
24 |   Controller,
25 |   NotFoundError,
26 |   ReadFileUseCase,
27 |   Request,
28 |   Response,
29 |   Validator,
30 | };
31 | 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
 2 | FROM node:20-alpine AS builder
 3 | 
 4 | # Copy the entire project
 5 | COPY . /app
 6 | WORKDIR /app
 7 | 
 8 | # Use build cache for faster builds
 9 | RUN --mount=type=cache,target=/root/.npm npm ci
10 | 
11 | FROM node:20-alpine AS release
12 | 
13 | COPY --from=builder /app/dist /app/dist
14 | COPY --from=builder /app/package.json /app/package.json
15 | COPY --from=builder /app/package-lock.json /app/package-lock.json
16 | 
17 | WORKDIR /app
18 | 
19 | RUN npm ci --ignore-scripts --omit=dev
20 | 
21 | ENTRYPOINT ["node", "dist/main/index.js"]
22 | 
```

--------------------------------------------------------------------------------
/src/presentation/helpers/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { NotFoundError, UnexpectedError } from "../errors/index.js";
 2 | import { type Response } from "../protocols/index.js";
 3 | 
 4 | export const badRequest = (error: Error): Response => ({
 5 |   statusCode: 400,
 6 |   body: error,
 7 | });
 8 | 
 9 | export const notFound = (resourceName: string): Response => ({
10 |   statusCode: 404,
11 |   body: new NotFoundError(resourceName),
12 | });
13 | 
14 | export const serverError = (error: Error): Response => ({
15 |   statusCode: 500,
16 |   body: new UnexpectedError(error),
17 | });
18 | 
19 | export const ok = (data: any): Response => ({
20 |   statusCode: 200,
21 |   body: data,
22 | });
23 | 
```

--------------------------------------------------------------------------------
/src/main/factories/use-cases/list-project-files-factory.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ListProjectFiles } from "../../../data/usecases/list-project-files/list-project-files.js";
 2 | import { FsFileRepository } from "../../../infra/filesystem/index.js";
 3 | import { FsProjectRepository } from "../../../infra/filesystem/repositories/fs-project-repository.js";
 4 | import { env } from "../../config/env.js";
 5 | 
 6 | export const makeListProjectFiles = () => {
 7 |   const projectRepository = new FsProjectRepository(env.rootPath);
 8 |   const fileRepository = new FsFileRepository(env.rootPath);
 9 | 
10 |   return new ListProjectFiles(fileRepository, projectRepository);
11 | };
12 | 
```

--------------------------------------------------------------------------------
/src/main/factories/controllers/list-project-files/list-project-files-controller-factory.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ListProjectFilesController } from "../../../../presentation/controllers/list-project-files/list-project-files-controller.js";
 2 | import { makeListProjectFiles } from "../../use-cases/list-project-files-factory.js";
 3 | import { makeListProjectFilesValidation } from "./list-project-files-validation-factory.js";
 4 | 
 5 | export const makeListProjectFilesController = () => {
 6 |   const validator = makeListProjectFilesValidation();
 7 |   const listProjectFilesUseCase = makeListProjectFiles();
 8 | 
 9 |   return new ListProjectFilesController(listProjectFilesUseCase, validator);
10 | };
11 | 
```

--------------------------------------------------------------------------------
/src/presentation/controllers/list-projects/list-projects-controller.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ok, serverError } from "../../helpers/index.js";
 2 | import {
 3 |   Controller,
 4 |   ListProjectsResponse,
 5 |   ListProjectsUseCase,
 6 |   Response,
 7 | } from "./protocols.js";
 8 | 
 9 | export class ListProjectsController
10 |   implements Controller<void, ListProjectsResponse>
11 | {
12 |   constructor(private readonly listProjectsUseCase: ListProjectsUseCase) {}
13 | 
14 |   async handle(): Promise<Response<ListProjectsResponse>> {
15 |     try {
16 |       const projects = await this.listProjectsUseCase.listProjects();
17 |       return ok(projects);
18 |     } catch (error) {
19 |       return serverError(error as Error);
20 |     }
21 |   }
22 | }
23 | 
```

--------------------------------------------------------------------------------
/tests/data/mocks/mock-project-repository.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ProjectRepository } from "../../../src/data/protocols/project-repository.js";
 2 | import { Project } from "../../../src/domain/entities/project.js";
 3 | 
 4 | export class MockProjectRepository implements ProjectRepository {
 5 |   private projects = ["project-1", "project-2"];
 6 | 
 7 |   async listProjects(): Promise<Project[]> {
 8 |     return this.projects;
 9 |   }
10 | 
11 |   async projectExists(name: string): Promise<boolean> {
12 |     return this.projects.includes(name);
13 |   }
14 | 
15 |   async ensureProject(name: string): Promise<void> {
16 |     if (!this.projects.includes(name)) {
17 |       this.projects.push(name);
18 |     }
19 |   }
20 | }
21 | 
```

--------------------------------------------------------------------------------
/src/data/usecases/read-file/read-file.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {
 2 |   FileRepository,
 3 |   ProjectRepository,
 4 |   ReadFileParams,
 5 |   ReadFileUseCase,
 6 | } from "./read-file-protocols.js";
 7 | 
 8 | export class ReadFile implements ReadFileUseCase {
 9 |   constructor(
10 |     private readonly fileRepository: FileRepository,
11 |     private readonly projectRepository: ProjectRepository
12 |   ) {}
13 | 
14 |   async readFile(params: ReadFileParams): Promise<string | null> {
15 |     const { projectName, fileName } = params;
16 | 
17 |     const projectExists = await this.projectRepository.projectExists(
18 |       projectName
19 |     );
20 |     if (!projectExists) {
21 |       return null;
22 |     }
23 | 
24 |     return this.fileRepository.loadFile(projectName, fileName);
25 |   }
26 | }
27 | 
```

--------------------------------------------------------------------------------
/src/validators/path-security-validator.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { InvalidParamError } from "../presentation/errors/index.js";
 2 | import { Validator } from "../presentation/protocols/validator.js";
 3 | 
 4 | export class PathSecurityValidator implements Validator {
 5 |   constructor(private readonly fieldName: string) {}
 6 | 
 7 |   validate(input?: any): Error | null {
 8 |     if (!input || !input[this.fieldName]) {
 9 |       return null;
10 |     }
11 | 
12 |     const value = input[this.fieldName];
13 |     if (
14 |       typeof value === "string" &&
15 |       (value.includes("..") || value.includes("/"))
16 |     ) {
17 |       return new InvalidParamError(
18 |         `${this.fieldName} contains invalid path segments`
19 |       );
20 |     }
21 | 
22 |     return null;
23 |   }
24 | }
25 | 
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
 2 | 
 3 | startCommand:
 4 |   type: stdio
 5 |   configSchema:
 6 |     # JSON Schema defining the configuration options for the MCP.
 7 |     type: object
 8 |     required:
 9 |       - memoryBankRoot
10 |     properties:
11 |       memoryBankRoot:
12 |         type: string
13 |         description: The root directory for memory bank projects.
14 |   commandFunction:
15 |     # A function that produces the CLI command to start the MCP on stdio.
16 |     |-
17 |     (config) => ({command:'node',args:['dist/index.js'],env:{MEMORY_BANK_ROOT:config.memoryBankRoot}})
18 | 
19 | # Add the build section for Docker support
20 | build:
21 |   dockerBuildPath: ./
22 | 
```

--------------------------------------------------------------------------------
/src/validators/param-name-validator.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { InvalidParamError } from "../presentation/errors/index.js";
 2 | import { Validator } from "../presentation/protocols/validator.js";
 3 | import { NAME_REGEX } from "./constants.js";
 4 | 
 5 | export class ParamNameValidator implements Validator {
 6 |   constructor(
 7 |     private readonly fieldName: string,
 8 |     private readonly regex: RegExp = NAME_REGEX
 9 |   ) {}
10 | 
11 |   validate(input?: any): Error | null {
12 |     if (!input || !input[this.fieldName]) {
13 |       return null;
14 |     }
15 | 
16 |     const paramName = input[this.fieldName];
17 |     const isValid = this.regex.test(paramName);
18 | 
19 |     if (!isValid) {
20 |       return new InvalidParamError(paramName);
21 |     }
22 | 
23 |     return null;
24 |   }
25 | }
26 | 
```

--------------------------------------------------------------------------------
/src/main/factories/controllers/read/read-validation-factory.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Validator } from "../../../../presentation/protocols/validator.js";
 2 | import {
 3 |   RequiredFieldValidator,
 4 |   ValidatorComposite,
 5 | } from "../../../../validators/index.js";
 6 | import { PathSecurityValidator } from "../../../../validators/path-security-validator.js";
 7 | 
 8 | const makeValidations = (): Validator[] => {
 9 |   return [
10 |     new RequiredFieldValidator("projectName"),
11 |     new RequiredFieldValidator("fileName"),
12 |     new PathSecurityValidator("projectName"),
13 |     new PathSecurityValidator("fileName"),
14 |   ];
15 | };
16 | 
17 | export const makeReadValidation = (): Validator => {
18 |   const validations = makeValidations();
19 |   return new ValidatorComposite(validations);
20 | };
21 | 
```

--------------------------------------------------------------------------------
/src/data/usecases/list-project-files/list-project-files.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {
 2 |   FileRepository,
 3 |   ListProjectFilesParams,
 4 |   ListProjectFilesUseCase,
 5 |   ProjectRepository,
 6 | } from "./list-project-files-protocols.js";
 7 | 
 8 | export class ListProjectFiles implements ListProjectFilesUseCase {
 9 |   constructor(
10 |     private readonly fileRepository: FileRepository,
11 |     private readonly projectRepository: ProjectRepository
12 |   ) {}
13 | 
14 |   async listProjectFiles(params: ListProjectFilesParams): Promise<string[]> {
15 |     const { projectName } = params;
16 |     const projectExists = await this.projectRepository.projectExists(
17 |       projectName
18 |     );
19 | 
20 |     if (!projectExists) {
21 |       return [];
22 |     }
23 | 
24 |     return this.fileRepository.listFiles(projectName);
25 |   }
26 | }
27 | 
```

--------------------------------------------------------------------------------
/src/main/factories/controllers/write/write-validation-factory.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Validator } from "../../../../presentation/protocols/validator.js";
 2 | import {
 3 |   ParamNameValidator,
 4 |   RequiredFieldValidator,
 5 |   ValidatorComposite,
 6 | } from "../../../../validators/index.js";
 7 | import { PathSecurityValidator } from "../../../../validators/path-security-validator.js";
 8 | 
 9 | const makeValidations = (): Validator[] => {
10 |   return [
11 |     new RequiredFieldValidator("projectName"),
12 |     new RequiredFieldValidator("fileName"),
13 |     new RequiredFieldValidator("content"),
14 |     new ParamNameValidator("projectName"),
15 |     new ParamNameValidator("fileName"),
16 |     new PathSecurityValidator("projectName"),
17 |     new PathSecurityValidator("fileName"),
18 |   ];
19 | };
20 | 
21 | export const makeWriteValidation = (): Validator => {
22 |   const validations = makeValidations();
23 |   return new ValidatorComposite(validations);
24 | };
25 | 
```

--------------------------------------------------------------------------------
/src/main/factories/controllers/update/update-validation-factory.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Validator } from "../../../../presentation/protocols/validator.js";
 2 | import {
 3 |   ParamNameValidator,
 4 |   RequiredFieldValidator,
 5 |   ValidatorComposite,
 6 | } from "../../../../validators/index.js";
 7 | import { PathSecurityValidator } from "../../../../validators/path-security-validator.js";
 8 | 
 9 | const makeValidations = (): Validator[] => {
10 |   return [
11 |     new RequiredFieldValidator("projectName"),
12 |     new RequiredFieldValidator("fileName"),
13 |     new RequiredFieldValidator("content"),
14 |     new ParamNameValidator("projectName"),
15 |     new ParamNameValidator("fileName"),
16 |     new PathSecurityValidator("projectName"),
17 |     new PathSecurityValidator("fileName"),
18 |   ];
19 | };
20 | 
21 | export const makeUpdateValidation = (): Validator => {
22 |   const validations = makeValidations();
23 |   return new ValidatorComposite(validations);
24 | };
25 | 
```

--------------------------------------------------------------------------------
/src/data/usecases/write-file/write-file.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {
 2 |   FileRepository,
 3 |   ProjectRepository,
 4 |   WriteFileParams,
 5 |   WriteFileUseCase,
 6 | } from "./write-file-protocols.js";
 7 | 
 8 | export class WriteFile implements WriteFileUseCase {
 9 |   constructor(
10 |     private readonly fileRepository: FileRepository,
11 |     private readonly projectRepository: ProjectRepository
12 |   ) {}
13 | 
14 |   async writeFile(params: WriteFileParams): Promise<string | null> {
15 |     const { projectName, fileName, content } = params;
16 | 
17 |     await this.projectRepository.ensureProject(projectName);
18 | 
19 |     const existingFile = await this.fileRepository.loadFile(
20 |       projectName,
21 |       fileName
22 |     );
23 |     if (existingFile !== null) {
24 |       return null;
25 |     }
26 | 
27 |     await this.fileRepository.writeFile(projectName, fileName, content);
28 |     return await this.fileRepository.loadFile(projectName, fileName);
29 |   }
30 | }
31 | 
```

--------------------------------------------------------------------------------
/src/main/protocols/mcp/helpers/serialize-error.ts:
--------------------------------------------------------------------------------

```typescript
 1 | interface SerializedError {
 2 |   name: string;
 3 |   error: string;
 4 |   stack?: string;
 5 |   cause?: string | SerializedError;
 6 |   code?: string | number;
 7 | }
 8 | 
 9 | export const serializeError = (
10 |   error: unknown,
11 |   includeStack = false
12 | ): SerializedError => {
13 |   if (error instanceof Error) {
14 |     const serialized: SerializedError = {
15 |       name: error.name,
16 |       error: error.message,
17 |     };
18 | 
19 |     if (includeStack) {
20 |       serialized.stack = error.stack;
21 |     }
22 | 
23 |     if ("cause" in error && error.cause) {
24 |       serialized.cause =
25 |         error.cause instanceof Error
26 |           ? serializeError(error.cause, includeStack)
27 |           : String(error.cause);
28 |     }
29 | 
30 |     if (
31 |       "code" in error &&
32 |       (typeof error.code === "string" || typeof error.code === "number")
33 |     ) {
34 |       serialized.code = error.code;
35 |     }
36 | 
37 |     return serialized;
38 |   }
39 | 
40 |   return {
41 |     name: "UnknownError",
42 |     error: String(error),
43 |   };
44 | };
45 | 
```

--------------------------------------------------------------------------------
/src/data/usecases/update-file/update-file.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {
 2 |   FileRepository,
 3 |   ProjectRepository,
 4 |   UpdateFileParams,
 5 |   UpdateFileUseCase,
 6 | } from "./update-file-protocols.js";
 7 | 
 8 | export class UpdateFile implements UpdateFileUseCase {
 9 |   constructor(
10 |     private readonly fileRepository: FileRepository,
11 |     private readonly projectRepository: ProjectRepository
12 |   ) {}
13 | 
14 |   async updateFile(params: UpdateFileParams): Promise<string | null> {
15 |     const { projectName, fileName, content } = params;
16 | 
17 |     const projectExists = await this.projectRepository.projectExists(
18 |       projectName
19 |     );
20 |     if (!projectExists) {
21 |       return null;
22 |     }
23 | 
24 |     const existingFile = await this.fileRepository.loadFile(
25 |       projectName,
26 |       fileName
27 |     );
28 |     if (existingFile === null) {
29 |       return null;
30 |     }
31 | 
32 |     await this.fileRepository.updateFile(projectName, fileName, content);
33 |     return await this.fileRepository.loadFile(projectName, fileName);
34 |   }
35 | }
36 | 
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | name: Bug report
 3 | about: Create a report to help us improve
 4 | title: ""
 5 | labels: "bug"
 6 | assignees: ""
 7 | ---
 8 | 
 9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 | 
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 | 
15 | 1. Configure MCP server with '...'
16 | 2. Initialize project with '...'
17 | 3. Try to access '...'
18 | 4. See error
19 | 
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 | 
23 | **Environment:**
24 | 
25 | - OS: [e.g., macOS, Windows, Linux]
26 | - Node Version: [e.g., 18.x]
27 | - Package Version: [e.g., 0.1.0]
28 | - MCP Integration: [e.g., Cline extension, Claude desktop]
29 | 
30 | **Memory Bank Configuration:**
31 | 
32 | ```json
33 | // Your MCP server configuration
34 | {
35 |   "memory-bank": {
36 |     ...
37 |   }
38 | }
39 | ```
40 | 
41 | **Error Output**
42 | 
43 | ```
44 | Paste any error messages or logs here
45 | ```
46 | 
47 | **Additional context**
48 | Add any other context about the problem here, such as project structure or specific memory bank files affected.
49 | 
```

--------------------------------------------------------------------------------
/src/presentation/controllers/read/read-controller.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { badRequest, notFound, ok, serverError } from "../../helpers/index.js";
 2 | import {
 3 |   Controller,
 4 |   ReadFileUseCase,
 5 |   ReadRequest,
 6 |   ReadResponse,
 7 |   Request,
 8 |   Response,
 9 |   Validator,
10 | } from "./protocols.js";
11 | 
12 | export class ReadController implements Controller<ReadRequest, ReadResponse> {
13 |   constructor(
14 |     private readonly readFileUseCase: ReadFileUseCase,
15 |     private readonly validator: Validator
16 |   ) {}
17 | 
18 |   async handle(request: Request<ReadRequest>): Promise<Response<ReadResponse>> {
19 |     try {
20 |       const validationError = this.validator.validate(request.body);
21 |       if (validationError) {
22 |         return badRequest(validationError);
23 |       }
24 | 
25 |       const { projectName, fileName } = request.body!;
26 | 
27 |       const content = await this.readFileUseCase.readFile({
28 |         projectName,
29 |         fileName,
30 |       });
31 | 
32 |       if (content === null) {
33 |         return notFound(fileName);
34 |       }
35 | 
36 |       return ok(content);
37 |     } catch (error) {
38 |       return serverError(error as Error);
39 |     }
40 |   }
41 | }
42 | 
```

--------------------------------------------------------------------------------
/src/main/protocols/mcp/adapters/mcp-request-adapter.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {
 2 |   Request as MCPRequest,
 3 |   ServerResult as MCPResponse,
 4 | } from "@modelcontextprotocol/sdk/types.js";
 5 | import { Controller } from "../../../../presentation/protocols/controller.js";
 6 | import { serializeError } from "../helpers/serialize-error.js";
 7 | import { MCPRequestHandler } from "./mcp-router-adapter.js";
 8 | 
 9 | export const adaptMcpRequestHandler = async <
10 |   T extends any,
11 |   R extends Error | any
12 | >(
13 |   controller: Controller<T, R>
14 | ): Promise<MCPRequestHandler> => {
15 |   return async (request: MCPRequest): Promise<MCPResponse> => {
16 |     const { params } = request;
17 |     const body = params?.arguments as T;
18 |     const response = await controller.handle({
19 |       body,
20 |     });
21 | 
22 |     const isError = response.statusCode < 200 || response.statusCode >= 300;
23 | 
24 |     return {
25 |       tools: [],
26 |       isError,
27 |       content: [
28 |         {
29 |           type: "text",
30 |           text: isError
31 |             ? JSON.stringify(serializeError(response.body))
32 |             : response.body?.toString(),
33 |         },
34 |       ],
35 |     };
36 |   };
37 | };
38 | 
```

--------------------------------------------------------------------------------
/src/presentation/controllers/list-project-files/list-project-files-controller.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { badRequest, ok, serverError } from "../../helpers/index.js";
 2 | import {
 3 |   Controller,
 4 |   ListProjectFilesRequest,
 5 |   ListProjectFilesResponse,
 6 |   ListProjectFilesUseCase,
 7 |   Request,
 8 |   Response,
 9 |   Validator,
10 | } from "./protocols.js";
11 | 
12 | export class ListProjectFilesController
13 |   implements Controller<ListProjectFilesRequest, ListProjectFilesResponse>
14 | {
15 |   constructor(
16 |     private readonly listProjectFilesUseCase: ListProjectFilesUseCase,
17 |     private readonly validator: Validator
18 |   ) {}
19 | 
20 |   async handle(
21 |     request: Request<ListProjectFilesRequest>
22 |   ): Promise<Response<ListProjectFilesResponse>> {
23 |     try {
24 |       const validationError = this.validator.validate(request.body);
25 |       if (validationError) {
26 |         return badRequest(validationError);
27 |       }
28 | 
29 |       const { projectName } = request.body!;
30 | 
31 |       const files = await this.listProjectFilesUseCase.listProjectFiles({
32 |         projectName,
33 |       });
34 | 
35 |       return ok(files);
36 |     } catch (error) {
37 |       return serverError(error as Error);
38 |     }
39 |   }
40 | }
41 | 
```

--------------------------------------------------------------------------------
/src/presentation/controllers/write/write-controller.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { badRequest, ok, serverError } from "../../helpers/index.js";
 2 | import {
 3 |   Controller,
 4 |   Request,
 5 |   Response,
 6 |   Validator,
 7 |   WriteFileUseCase,
 8 |   WriteRequest,
 9 |   WriteResponse,
10 | } from "./protocols.js";
11 | 
12 | export class WriteController
13 |   implements Controller<WriteRequest, WriteResponse>
14 | {
15 |   constructor(
16 |     private readonly writeFileUseCase: WriteFileUseCase,
17 |     private readonly validator: Validator
18 |   ) {}
19 | 
20 |   async handle(
21 |     request: Request<WriteRequest>
22 |   ): Promise<Response<WriteResponse>> {
23 |     try {
24 |       const validationError = this.validator.validate(request.body);
25 |       if (validationError) {
26 |         return badRequest(validationError);
27 |       }
28 | 
29 |       const { projectName, fileName, content } = request.body!;
30 | 
31 |       await this.writeFileUseCase.writeFile({
32 |         projectName,
33 |         fileName,
34 |         content,
35 |       });
36 | 
37 |       return ok(
38 |         `File ${fileName} written successfully to project ${projectName}`
39 |       );
40 |     } catch (error) {
41 |       return serverError(error as Error);
42 |     }
43 |   }
44 | }
45 | 
```

--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Description
 2 | 
 3 | Please include a summary of the changes and which issue is fixed. Include relevant motivation and context.
 4 | 
 5 | Fixes # (issue)
 6 | 
 7 | ## Type of change
 8 | 
 9 | Please delete options that are not relevant.
10 | 
11 | - [ ] Bug fix (non-breaking change which fixes an issue)
12 | - [ ] New feature (non-breaking change which adds functionality)
13 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
14 | - [ ] Documentation update
15 | 
16 | ## Checklist
17 | 
18 | - [ ] My code follows the style guidelines of this project
19 | - [ ] I have performed a self-review of my code
20 | - [ ] I have added tests that prove my fix is effective or that my feature works
21 | - [ ] New and existing unit tests pass locally with my changes
22 | - [ ] I have updated the documentation accordingly
23 | - [ ] My changes maintain project isolation and security
24 | - [ ] I have tested my changes with the MCP server running
25 | - [ ] I have verified the memory bank file operations still work correctly
26 | 
27 | ## Additional Notes
28 | 
29 | Add any other context about the PR here.
30 | 
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | name: Feature request
 3 | about: Suggest an idea for this project
 4 | title: ""
 5 | labels: "enhancement"
 6 | assignees: ""
 7 | ---
 8 | 
 9 | **Is your feature request related to a problem? Please describe.**
10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11 | 
12 | **Describe the solution you'd like**
13 | A clear and concise description of what you want to happen.
14 | 
15 | **Describe alternatives you've considered**
16 | A clear and concise description of any alternative solutions or features you've considered.
17 | 
18 | **Memory Bank Impact**
19 | How would this feature affect:
20 | 
21 | - Project isolation/security
22 | - Memory bank file structure
23 | - MCP tool interactions
24 | - User workflow
25 | - Configuration requirements
26 | 
27 | **Additional context**
28 | Add any other context or screenshots about the feature request here.
29 | 
30 | **Example Usage**
31 | If applicable, provide an example of how you envision using this feature:
32 | 
33 | ```typescript
34 | // Example code or configuration
35 | ```
36 | 
37 | or
38 | 
39 | ```json
40 | // Example MCP tool usage or configuration
41 | {
42 |   "tool_name": "new_feature",
43 |   "arguments": {
44 |     "param1": "value1"
45 |   }
46 | }
47 | ```
48 | 
```

--------------------------------------------------------------------------------
/src/main/protocols/mcp/adapters/mcp-router-adapter.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {
 2 |   Request as MCPRequest,
 3 |   ServerResult as MCPResponse,
 4 |   Tool,
 5 | } from "@modelcontextprotocol/sdk/types.js";
 6 | 
 7 | export type MCPRequestHandler = (request: MCPRequest) => Promise<MCPResponse>;
 8 | 
 9 | export type MCPRoute = {
10 |   schema: Tool;
11 |   handler: Promise<MCPRequestHandler>;
12 | };
13 | 
14 | export class McpRouterAdapter {
15 |   private tools: Map<string, MCPRoute> = new Map();
16 | 
17 |   public getToolHandler(name: string): MCPRoute["handler"] | undefined {
18 |     return this.tools.get(name)?.handler;
19 |   }
20 | 
21 |   private mapTools(callback: (name: string) => any) {
22 |     return Array.from(this.tools.keys()).map(callback);
23 |   }
24 | 
25 |   public getToolsSchemas() {
26 |     return Array.from(this.tools.keys()).map(
27 |       (name) => this.tools.get(name)?.schema
28 |     );
29 |   }
30 | 
31 |   public getToolCapabilities() {
32 |     return Array.from(this.tools.keys()).reduce((acc, name: string) => {
33 |       acc[name] = this.tools.get(name)?.schema!;
34 |       return acc;
35 |     }, {} as Record<string, Tool>);
36 |   }
37 | 
38 |   public setTool({ schema, handler }: MCPRoute): McpRouterAdapter {
39 |     this.tools.set(schema.name, { schema, handler });
40 |     return this;
41 |   }
42 | }
43 | 
```

--------------------------------------------------------------------------------
/src/presentation/controllers/update/update-controller.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { badRequest, notFound, ok, serverError } from "../../helpers/index.js";
 2 | import {
 3 |   Controller,
 4 |   Request,
 5 |   RequestValidator,
 6 |   Response,
 7 |   UpdateFileUseCase,
 8 |   UpdateRequest,
 9 |   UpdateResponse,
10 | } from "./protocols.js";
11 | 
12 | export class UpdateController
13 |   implements Controller<UpdateRequest, UpdateResponse>
14 | {
15 |   constructor(
16 |     private readonly updateFileUseCase: UpdateFileUseCase,
17 |     private readonly validator: RequestValidator
18 |   ) {}
19 | 
20 |   async handle(
21 |     request: Request<UpdateRequest>
22 |   ): Promise<Response<UpdateResponse>> {
23 |     try {
24 |       const validationError = this.validator.validate(request.body);
25 |       if (validationError) {
26 |         return badRequest(validationError);
27 |       }
28 | 
29 |       const { projectName, fileName, content } = request.body!;
30 | 
31 |       const result = await this.updateFileUseCase.updateFile({
32 |         projectName,
33 |         fileName,
34 |         content,
35 |       });
36 | 
37 |       if (result === null) {
38 |         return notFound(fileName);
39 |       }
40 | 
41 |       return ok(
42 |         `File ${fileName} updated successfully in project ${projectName}`
43 |       );
44 |     } catch (error) {
45 |       return serverError(error as Error);
46 |     }
47 |   }
48 | }
49 | 
```

--------------------------------------------------------------------------------
/tests/data/usecases/list-projects/list-projects.spec.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { beforeEach, describe, expect, test, vi } from "vitest";
 2 | import { ProjectRepository } from "../../../../src/data/protocols/project-repository.js";
 3 | import { ListProjects } from "../../../../src/data/usecases/list-projects/list-projects.js";
 4 | import { MockProjectRepository } from "../../mocks/index.js";
 5 | 
 6 | describe("ListProjects UseCase", () => {
 7 |   let sut: ListProjects;
 8 |   let projectRepositoryStub: ProjectRepository;
 9 | 
10 |   beforeEach(() => {
11 |     projectRepositoryStub = new MockProjectRepository();
12 |     sut = new ListProjects(projectRepositoryStub);
13 |   });
14 | 
15 |   test("should call ProjectRepository.listProjects()", async () => {
16 |     const listProjectsSpy = vi.spyOn(projectRepositoryStub, "listProjects");
17 | 
18 |     await sut.listProjects();
19 | 
20 |     expect(listProjectsSpy).toHaveBeenCalledTimes(1);
21 |   });
22 | 
23 |   test("should return a list of projects on success", async () => {
24 |     const projects = await sut.listProjects();
25 | 
26 |     expect(projects).toEqual(["project-1", "project-2"]);
27 |   });
28 | 
29 |   test("should propagate errors if repository throws", async () => {
30 |     const error = new Error("Repository error");
31 |     vi.spyOn(projectRepositoryStub, "listProjects").mockRejectedValueOnce(
32 |       error
33 |     );
34 | 
35 |     await expect(sut.listProjects()).rejects.toThrow(error);
36 |   });
37 | });
38 | 
```

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

```json
 1 | {
 2 |   "name": "@allpepper/memory-bank-mcp",
 3 |   "version": "0.2.1",
 4 |   "description": "MCP server for remote management of project memory banks",
 5 |   "repository": {
 6 |     "type": "git",
 7 |     "url": "git+https://github.com/alioshr/memory-bank-mcp.git"
 8 |   },
 9 |   "keywords": [
10 |     "mcp",
11 |     "memory-bank",
12 |     "project-management",
13 |     "documentation",
14 |     "cline"
15 |   ],
16 |   "bugs": {
17 |     "url": "https://github.com/alioshr/memory-bank-mcp/issues"
18 |   },
19 |   "homepage": "https://github.com/alioshr/memory-bank-mcp#readme",
20 |   "main": "dist/main/index.js",
21 |   "files": [
22 |     "dist"
23 |   ],
24 |   "author": "Aliosh Pimenta (alioshr)",
25 |   "license": "MIT",
26 |   "type": "module",
27 |   "bin": {
28 |     "mcp-server-memory-bank": "dist/main/index.js"
29 |   },
30 |   "scripts": {
31 |     "build": "tsc && shx chmod +x dist/**/*.js",
32 |     "prepare": "npm run build",
33 |     "dev": "ts-node src/main/index.ts",
34 |     "test": "vitest run",
35 |     "test:watch": "vitest",
36 |     "test:ui": "vitest --ui",
37 |     "test:coverage": "vitest run --coverage"
38 |   },
39 |   "dependencies": {
40 |     "@modelcontextprotocol/sdk": "^1.5.0",
41 |     "fs-extra": "^11.2.0"
42 |   },
43 |   "devDependencies": {
44 |     "@types/fs-extra": "^11.0.4",
45 |     "@types/node": "^20.11.19",
46 |     "@vitest/coverage-istanbul": "^3.0.8",
47 |     "@vitest/coverage-v8": "^3.1.1",
48 |     "@vitest/ui": "^3.0.8",
49 |     "shx": "^0.4.0",
50 |     "ts-node": "^10.9.2",
51 |     "typescript": "^5.8.2",
52 |     "vitest": "^3.0.8"
53 |   }
54 | }
55 | 
```

--------------------------------------------------------------------------------
/tests/presentation/helpers/http-helpers.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, expect, it } from "vitest";
 2 | import {
 3 |   NotFoundError,
 4 |   UnexpectedError,
 5 | } from "../../../src/presentation/errors/index.js";
 6 | import {
 7 |   badRequest,
 8 |   notFound,
 9 |   ok,
10 |   serverError,
11 | } from "../../../src/presentation/helpers/index.js";
12 | 
13 | describe("HTTP Helpers", () => {
14 |   describe("badRequest", () => {
15 |     it("should return 400 status code and the error", () => {
16 |       const error = new Error("any_error");
17 |       const response = badRequest(error);
18 |       expect(response).toEqual({
19 |         statusCode: 400,
20 |         body: error,
21 |       });
22 |     });
23 |   });
24 | 
25 |   describe("notFound", () => {
26 |     it("should return 404 status code and the error", () => {
27 |       const response = notFound("any_error");
28 |       expect(response).toEqual({
29 |         statusCode: 404,
30 |         body: new NotFoundError("any_error"),
31 |       });
32 |     });
33 |   });
34 | 
35 |   describe("serverError", () => {
36 |     it("should return 500 status code and wrap the error in UnexpectedError", () => {
37 |       const error = new Error("any_error");
38 |       const response = serverError(error);
39 |       expect(response).toEqual({
40 |         statusCode: 500,
41 |         body: new UnexpectedError(error),
42 |       });
43 |     });
44 |   });
45 | 
46 |   describe("ok", () => {
47 |     it("should return 200 status code and the data", () => {
48 |       const data = { name: "any_name" };
49 |       const response = ok(data);
50 |       expect(response).toEqual({
51 |         statusCode: 200,
52 |         body: data,
53 |       });
54 |     });
55 |   });
56 | });
57 | 
```

--------------------------------------------------------------------------------
/tests/data/mocks/mock-file-repository.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { FileRepository } from "../../../src/data/protocols/file-repository.js";
 2 | 
 3 | export class MockFileRepository implements FileRepository {
 4 |   private projectFiles: Record<string, Record<string, string>> = {
 5 |     "project-1": {
 6 |       "file1.md": "Content of file1.md",
 7 |       "file2.md": "Content of file2.md",
 8 |     },
 9 |     "project-2": {
10 |       "fileA.md": "Content of fileA.md",
11 |       "fileB.md": "Content of fileB.md",
12 |     },
13 |   };
14 | 
15 |   async listFiles(projectName: string): Promise<string[]> {
16 |     return Object.keys(this.projectFiles[projectName] || {});
17 |   }
18 | 
19 |   async loadFile(
20 |     projectName: string,
21 |     fileName: string
22 |   ): Promise<string | null> {
23 |     if (
24 |       this.projectFiles[projectName] &&
25 |       this.projectFiles[projectName][fileName]
26 |     ) {
27 |       return this.projectFiles[projectName][fileName];
28 |     }
29 |     return null;
30 |   }
31 | 
32 |   async writeFile(
33 |     projectName: string,
34 |     fileName: string,
35 |     content: string
36 |   ): Promise<string | null> {
37 |     if (!this.projectFiles[projectName]) {
38 |       this.projectFiles[projectName] = {};
39 |     }
40 |     this.projectFiles[projectName][fileName] = content;
41 |     return content;
42 |   }
43 | 
44 |   async updateFile(
45 |     projectName: string,
46 |     fileName: string,
47 |     content: string
48 |   ): Promise<string | null> {
49 |     if (
50 |       this.projectFiles[projectName] &&
51 |       this.projectFiles[projectName][fileName]
52 |     ) {
53 |       this.projectFiles[projectName][fileName] = content;
54 |       return content;
55 |     }
56 |     return null;
57 |   }
58 | }
59 | 
```

--------------------------------------------------------------------------------
/tests/presentation/controllers/list-projects/list-projects-controller.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, expect, it, vi } from "vitest";
 2 | import { ListProjectsController } from "../../../../src/presentation/controllers/list-projects/list-projects-controller.js";
 3 | import { UnexpectedError } from "../../../../src/presentation/errors/index.js";
 4 | import { makeListProjectsUseCase } from "../../mocks/index.js";
 5 | 
 6 | const makeSut = () => {
 7 |   const listProjectsUseCaseStub = makeListProjectsUseCase();
 8 |   const sut = new ListProjectsController(listProjectsUseCaseStub);
 9 |   return {
10 |     sut,
11 |     listProjectsUseCaseStub,
12 |   };
13 | };
14 | 
15 | describe("ListProjectsController", () => {
16 |   it("should call ListProjectsUseCase", async () => {
17 |     const { sut, listProjectsUseCaseStub } = makeSut();
18 |     const listProjectsSpy = vi.spyOn(listProjectsUseCaseStub, "listProjects");
19 |     await sut.handle();
20 |     expect(listProjectsSpy).toHaveBeenCalled();
21 |   });
22 | 
23 |   it("should return 500 if ListProjectsUseCase throws", async () => {
24 |     const { sut, listProjectsUseCaseStub } = makeSut();
25 |     vi.spyOn(listProjectsUseCaseStub, "listProjects").mockRejectedValueOnce(
26 |       new Error("any_error")
27 |     );
28 |     const response = await sut.handle();
29 |     expect(response).toEqual({
30 |       statusCode: 500,
31 |       body: new UnexpectedError(new Error("any_error")),
32 |     });
33 |   });
34 | 
35 |   it("should return 200 with projects on success", async () => {
36 |     const { sut } = makeSut();
37 |     const response = await sut.handle();
38 |     expect(response).toEqual({
39 |       statusCode: 200,
40 |       body: ["project1", "project2"],
41 |     });
42 |   });
43 | });
44 | 
```

--------------------------------------------------------------------------------
/src/main/protocols/mcp/adapters/mcp-server-adapter.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
 2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 3 | import {
 4 |   CallToolRequestSchema,
 5 |   ErrorCode,
 6 |   ListToolsRequestSchema,
 7 |   McpError,
 8 |   ServerResult as MCPResponse,
 9 | } from "@modelcontextprotocol/sdk/types.js";
10 | import { McpRouterAdapter } from "./mcp-router-adapter.js";
11 | 
12 | export class McpServerAdapter {
13 |   private server: Server | null = null;
14 | 
15 |   constructor(private readonly mcpRouter: McpRouterAdapter) {}
16 | 
17 |   public register({ name, version }: { name: string; version: string }) {
18 |     this.server = new Server(
19 |       {
20 |         name,
21 |         version,
22 |       },
23 |       {
24 |         capabilities: {
25 |           tools: this.mcpRouter.getToolCapabilities(),
26 |         },
27 |       }
28 |     );
29 | 
30 |     this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
31 |       tools: this.mcpRouter.getToolsSchemas(),
32 |     }));
33 | 
34 |     this.server.setRequestHandler(
35 |       CallToolRequestSchema,
36 |       async (request): Promise<MCPResponse> => {
37 |         const { name } = request.params;
38 |         const handler = await this.mcpRouter.getToolHandler(name);
39 |         if (!handler) {
40 |           throw new McpError(
41 |             ErrorCode.MethodNotFound,
42 |             `Tool ${name} not found`
43 |           );
44 |         }
45 |         return await handler(request);
46 |       }
47 |     );
48 |   }
49 | 
50 |   async start(): Promise<void> {
51 |     if (!this.server) {
52 |       throw new Error("Server not initialized");
53 |     }
54 | 
55 |     const transport = new StdioServerTransport();
56 |     try {
57 |       await this.server.connect(transport);
58 |       console.log("Memory Bank MCP server running on stdio");
59 |     } catch (error) {
60 |       console.error(error);
61 |     }
62 |   }
63 | }
64 | 
```

--------------------------------------------------------------------------------
/src/infra/filesystem/repositories/fs-project-repository.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import fs from "fs-extra";
 2 | import path from "path";
 3 | import { ProjectRepository } from "../../../data/protocols/project-repository.js";
 4 | import { Project } from "../../../domain/entities/index.js";
 5 | 
 6 | /**
 7 |  * Filesystem implementation of the ProjectRepository protocol
 8 |  */
 9 | export class FsProjectRepository implements ProjectRepository {
10 |   /**
11 |    * Creates a new FsProjectRepository
12 |    * @param rootDir The root directory where all projects are stored
13 |    */
14 |   constructor(private readonly rootDir: string) {}
15 | 
16 |   /**
17 |    * Builds a path to a project directory
18 |    * @param projectName The name of the project
19 |    * @returns The full path to the project directory
20 |    * @private
21 |    */
22 |   private buildProjectPath(projectName: string): string {
23 |     return path.join(this.rootDir, projectName);
24 |   }
25 | 
26 |   /**
27 |    * Lists all available projects
28 |    * @returns An array of Project objects
29 |    */
30 |   async listProjects(): Promise<Project[]> {
31 |     const entries = await fs.readdir(this.rootDir, { withFileTypes: true });
32 |     const projects: Project[] = entries
33 |       .filter((entry) => entry.isDirectory())
34 |       .map((entry) => entry.name);
35 | 
36 |     return projects;
37 |   }
38 | 
39 |   /**
40 |    * Checks if a project exists
41 |    * @param name The name of the project
42 |    * @returns True if the project exists, false otherwise
43 |    */
44 |   async projectExists(name: string): Promise<boolean> {
45 |     const projectPath = this.buildProjectPath(name);
46 |     // If path doesn't exist, fs.stat will throw an error which will propagate
47 |     const stat = await fs.stat(projectPath);
48 |     return stat.isDirectory();
49 |   }
50 | 
51 |   /**
52 |    * Ensures a project directory exists, creating it if necessary
53 |    * @param name The name of the project
54 |    */
55 |   async ensureProject(name: string): Promise<void> {
56 |     const projectPath = this.buildProjectPath(name);
57 |     await fs.ensureDir(projectPath);
58 |   }
59 | }
60 | 
```

--------------------------------------------------------------------------------
/tests/validators/validator-composite.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { beforeEach, describe, expect, it } from "vitest";
 2 | import { Validator } from "../../src/presentation/protocols/validator.js";
 3 | import { ValidatorComposite } from "../../src/validators/validator-composite.js";
 4 | 
 5 | interface TestInput {
 6 |   field: string;
 7 | }
 8 | 
 9 | class ValidatorStub implements Validator {
10 |   error: Error | null = null;
11 |   callCount = 0;
12 |   input: any = null;
13 | 
14 |   validate(input?: any): Error | null {
15 |     this.callCount++;
16 |     this.input = input;
17 |     return this.error;
18 |   }
19 | }
20 | 
21 | describe("ValidatorComposite", () => {
22 |   let validator1: ValidatorStub;
23 |   let validator2: ValidatorStub;
24 |   let sut: ValidatorComposite;
25 | 
26 |   beforeEach(() => {
27 |     validator1 = new ValidatorStub();
28 |     validator2 = new ValidatorStub();
29 |     sut = new ValidatorComposite([validator1, validator2]);
30 |   });
31 | 
32 |   it("should call validate with correct input in all validators", () => {
33 |     const input = { field: "any_value" };
34 |     sut.validate(input);
35 |     expect(validator1.input).toBe(input);
36 |     expect(validator2.input).toBe(input);
37 |   });
38 | 
39 |   it("should return the first error if any validator fails", () => {
40 |     const error = new Error("validator_error");
41 |     validator1.error = error;
42 | 
43 |     const result = sut.validate({ field: "any_value" });
44 | 
45 |     expect(result).toBe(error);
46 |   });
47 | 
48 |   it("should return the second validator error if the first validator passes", () => {
49 |     const error = new Error("validator_error");
50 |     validator2.error = error;
51 | 
52 |     const result = sut.validate({ field: "any_value" });
53 | 
54 |     expect(result).toBe(error);
55 |   });
56 | 
57 |   it("should return null if all validators pass", () => {
58 |     const result = sut.validate({ field: "any_value" });
59 | 
60 |     expect(result).toBeNull();
61 |   });
62 | 
63 |   it("should call validators in the order they were passed to the constructor", () => {
64 |     sut.validate({ field: "any_value" });
65 | 
66 |     expect(validator1.callCount).toBe(1);
67 |     expect(validator2.callCount).toBe(1);
68 |   });
69 | 
70 |   it("should stop validating after first error is found", () => {
71 |     validator1.error = new Error("validator_error");
72 | 
73 |     sut.validate({ field: "any_value" });
74 | 
75 |     expect(validator1.callCount).toBe(1);
76 |     expect(validator2.callCount).toBe(0);
77 |   });
78 | });
79 | 
```

--------------------------------------------------------------------------------
/tests/infra/filesystem/repositories/fs-project-repository.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import fs from "fs-extra";
 2 | import os from "os";
 3 | import path from "path";
 4 | import { afterEach, beforeEach, describe, expect, it } from "vitest";
 5 | import { FsProjectRepository } from "../../../../src/infra/filesystem/repositories/fs-project-repository.js";
 6 | 
 7 | describe("FsProjectRepository", () => {
 8 |   let tempDir: string;
 9 |   let repository: FsProjectRepository;
10 | 
11 |   beforeEach(() => {
12 |     // Create a temporary directory for tests
13 |     tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "memory-bank-test-"));
14 |     repository = new FsProjectRepository(tempDir);
15 |   });
16 | 
17 |   afterEach(() => {
18 |     // Clean up after tests
19 |     fs.removeSync(tempDir);
20 |   });
21 | 
22 |   describe("listProjects", () => {
23 |     it("should return an empty array when no projects exist", async () => {
24 |       const result = await repository.listProjects();
25 |       expect(result).toEqual([]);
26 |     });
27 | 
28 |     it("should return project directories as Project objects", async () => {
29 |       // Create test directories
30 |       await fs.mkdir(path.join(tempDir, "project1"));
31 |       await fs.mkdir(path.join(tempDir, "project2"));
32 |       // Create a file to ensure it's not returned
33 |       await fs.writeFile(path.join(tempDir, "not-a-project.txt"), "test");
34 | 
35 |       const result = await repository.listProjects();
36 | 
37 |       expect(result).toHaveLength(2);
38 |       expect(result).toEqual(expect.arrayContaining(["project1", "project2"]));
39 |     });
40 |   });
41 | 
42 |   describe("projectExists", () => {
43 |     it("should throw an error when project path cannot be accessed", async () => {
44 |       // We now let errors propagate, so stat errors will throw
45 |       const nonExistentProject = "non-existent-project";
46 | 
47 |       await expect(
48 |         repository.projectExists(nonExistentProject)
49 |       ).rejects.toThrow();
50 |     });
51 | 
52 |     it("should return true when project exists", async () => {
53 |       await fs.mkdir(path.join(tempDir, "existing-project"));
54 | 
55 |       const result = await repository.projectExists("existing-project");
56 | 
57 |       expect(result).toBe(true);
58 |     });
59 |   });
60 | 
61 |   describe("ensureProject", () => {
62 |     it("should create project directory if it does not exist", async () => {
63 |       await repository.ensureProject("new-project");
64 | 
65 |       const projectPath = path.join(tempDir, "new-project");
66 |       const exists = await fs.pathExists(projectPath);
67 | 
68 |       expect(exists).toBe(true);
69 |     });
70 | 
71 |     it("should not throw if project directory already exists", async () => {
72 |       await fs.mkdir(path.join(tempDir, "existing-project"));
73 | 
74 |       await expect(
75 |         repository.ensureProject("existing-project")
76 |       ).resolves.not.toThrow();
77 |     });
78 |   });
79 | });
80 | 
```

--------------------------------------------------------------------------------
/tests/data/usecases/list-project-files/list-project-files.spec.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { beforeEach, describe, expect, test, vi } from "vitest";
 2 | import { FileRepository } from "../../../../src/data/protocols/file-repository.js";
 3 | import { ProjectRepository } from "../../../../src/data/protocols/project-repository.js";
 4 | import { ListProjectFiles } from "../../../../src/data/usecases/list-project-files/list-project-files.js";
 5 | import { ListProjectFilesParams } from "../../../../src/domain/usecases/list-project-files.js";
 6 | import {
 7 |   MockFileRepository,
 8 |   MockProjectRepository,
 9 | } from "../../mocks/index.js";
10 | 
11 | describe("ListProjectFiles UseCase", () => {
12 |   let sut: ListProjectFiles;
13 |   let fileRepositoryStub: FileRepository;
14 |   let projectRepositoryStub: ProjectRepository;
15 | 
16 |   beforeEach(() => {
17 |     fileRepositoryStub = new MockFileRepository();
18 |     projectRepositoryStub = new MockProjectRepository();
19 |     sut = new ListProjectFiles(fileRepositoryStub, projectRepositoryStub);
20 |   });
21 | 
22 |   test("should call ProjectRepository.projectExists with correct projectName", async () => {
23 |     const projectExistsSpy = vi.spyOn(projectRepositoryStub, "projectExists");
24 |     const params: ListProjectFilesParams = { projectName: "project-1" };
25 | 
26 |     await sut.listProjectFiles(params);
27 | 
28 |     expect(projectExistsSpy).toHaveBeenCalledWith("project-1");
29 |   });
30 | 
31 |   test("should return empty array if project does not exist", async () => {
32 |     vi.spyOn(projectRepositoryStub, "projectExists").mockResolvedValueOnce(
33 |       false
34 |     );
35 |     const params: ListProjectFilesParams = {
36 |       projectName: "non-existent-project",
37 |     };
38 | 
39 |     const result = await sut.listProjectFiles(params);
40 | 
41 |     expect(result).toEqual([]);
42 |   });
43 | 
44 |   test("should call FileRepository.listFiles with correct projectName if project exists", async () => {
45 |     vi.spyOn(projectRepositoryStub, "projectExists").mockResolvedValueOnce(
46 |       true
47 |     );
48 |     const listFilesSpy = vi.spyOn(fileRepositoryStub, "listFiles");
49 |     const params: ListProjectFilesParams = { projectName: "project-1" };
50 | 
51 |     await sut.listProjectFiles(params);
52 | 
53 |     expect(listFilesSpy).toHaveBeenCalledWith("project-1");
54 |   });
55 | 
56 |   test("should return files list on success", async () => {
57 |     const params: ListProjectFilesParams = { projectName: "project-1" };
58 | 
59 |     const files = await sut.listProjectFiles(params);
60 | 
61 |     expect(files).toEqual(["file1.md", "file2.md"]);
62 |   });
63 | 
64 |   test("should propagate errors if repository throws", async () => {
65 |     const error = new Error("Repository error");
66 |     vi.spyOn(projectRepositoryStub, "projectExists").mockRejectedValueOnce(
67 |       error
68 |     );
69 |     const params: ListProjectFilesParams = { projectName: "project-1" };
70 | 
71 |     await expect(sut.listProjectFiles(params)).rejects.toThrow(error);
72 |   });
73 | });
74 | 
```

--------------------------------------------------------------------------------
/tests/validators/path-security-validator.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, expect, it } from "vitest";
 2 | import { InvalidParamError } from "../../src/presentation/errors/index.js";
 3 | import { PathSecurityValidator } from "../../src/validators/path-security-validator.js";
 4 | 
 5 | describe("PathSecurityValidator", () => {
 6 |   it("should return null if field is not provided", () => {
 7 |     const sut = new PathSecurityValidator("field");
 8 |     const input = {};
 9 |     const error = sut.validate(input);
10 | 
11 |     expect(error).toBeNull();
12 |   });
13 | 
14 |   it("should return null if input is null", () => {
15 |     const sut = new PathSecurityValidator("field");
16 |     const error = sut.validate(null);
17 | 
18 |     expect(error).toBeNull();
19 |   });
20 | 
21 |   it("should return null if input is undefined", () => {
22 |     const sut = new PathSecurityValidator("field");
23 |     const error = sut.validate(undefined);
24 | 
25 |     expect(error).toBeNull();
26 |   });
27 | 
28 |   it("should return InvalidParamError if field contains directory traversal (..)", () => {
29 |     const sut = new PathSecurityValidator("field");
30 |     const input = { field: "something/../etc/passwd" };
31 |     const error = sut.validate(input);
32 | 
33 |     expect(error).toBeInstanceOf(InvalidParamError);
34 |     expect(error?.message).toBe(
35 |       "Invalid parameter: field contains invalid path segments"
36 |     );
37 |   });
38 | 
39 |   it("should return InvalidParamError if field contains directory traversal (..) even without slashes", () => {
40 |     const sut = new PathSecurityValidator("field");
41 |     const input = { field: "something..etc" };
42 |     const error = sut.validate(input);
43 | 
44 |     expect(error).toBeInstanceOf(InvalidParamError);
45 |     expect(error?.message).toBe(
46 |       "Invalid parameter: field contains invalid path segments"
47 |     );
48 |   });
49 | 
50 |   it("should return InvalidParamError if field contains forward slashes", () => {
51 |     const sut = new PathSecurityValidator("field");
52 |     const input = { field: "path/to/file" };
53 |     const error = sut.validate(input);
54 | 
55 |     expect(error).toBeInstanceOf(InvalidParamError);
56 |     expect(error?.message).toBe(
57 |       "Invalid parameter: field contains invalid path segments"
58 |     );
59 |   });
60 | 
61 |   it("should return null if field is a valid string without path segments", () => {
62 |     const sut = new PathSecurityValidator("field");
63 |     const input = { field: "validname123" };
64 |     const error = sut.validate(input);
65 | 
66 |     expect(error).toBeNull();
67 |   });
68 | 
69 |   it("should return null if field contains periods but not double periods", () => {
70 |     const sut = new PathSecurityValidator("field");
71 |     const input = { field: "filename.txt" };
72 |     const error = sut.validate(input);
73 | 
74 |     expect(error).toBeNull();
75 |   });
76 | 
77 |   it("should ignore non-string fields", () => {
78 |     const sut = new PathSecurityValidator("field");
79 |     const input = { field: 123 as any };
80 |     const error = sut.validate(input);
81 | 
82 |     expect(error).toBeNull();
83 |   });
84 | });
85 | 
```

--------------------------------------------------------------------------------
/tests/validators/required-field-validator.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, expect, it } from "vitest";
 2 | import { MissingParamError } from "../../src/presentation/errors/index.js";
 3 | import { RequiredFieldValidator } from "../../src/validators/required-field-validator.js";
 4 | 
 5 | describe("RequiredFieldValidator", () => {
 6 |   it("should return MissingParamError if field is not provided", () => {
 7 |     const sut = new RequiredFieldValidator("field");
 8 |     const input = {};
 9 |     const error = sut.validate(input);
10 | 
11 |     expect(error).toBeInstanceOf(MissingParamError);
12 |     expect(error?.message).toBe("Missing parameter: field");
13 |   });
14 | 
15 |   it("should return MissingParamError if field is undefined", () => {
16 |     const sut = new RequiredFieldValidator("field");
17 |     const input = { field: undefined };
18 |     const error = sut.validate(input);
19 | 
20 |     expect(error).toBeInstanceOf(MissingParamError);
21 |     expect(error?.message).toBe("Missing parameter: field");
22 |   });
23 | 
24 |   it("should return MissingParamError if field is null", () => {
25 |     const sut = new RequiredFieldValidator("field");
26 |     const input = { field: null };
27 |     const error = sut.validate(input);
28 | 
29 |     expect(error).toBeInstanceOf(MissingParamError);
30 |     expect(error?.message).toBe("Missing parameter: field");
31 |   });
32 | 
33 |   it("should return MissingParamError if field is empty string", () => {
34 |     const sut = new RequiredFieldValidator("fieldEmpty");
35 |     const input = { fieldEmpty: "" };
36 |     const error = sut.validate(input);
37 | 
38 |     expect(error).toBeInstanceOf(MissingParamError);
39 |     expect(error?.message).toBe("Missing parameter: fieldEmpty");
40 |   });
41 | 
42 |   it("should return MissingParamError if input is null", () => {
43 |     const sut = new RequiredFieldValidator("field");
44 |     const error = sut.validate(null);
45 | 
46 |     expect(error).toBeInstanceOf(MissingParamError);
47 |     expect(error?.message).toBe("Missing parameter: field");
48 |   });
49 | 
50 |   it("should return MissingParamError if input is undefined", () => {
51 |     const sut = new RequiredFieldValidator("field");
52 |     const error = sut.validate(undefined);
53 | 
54 |     expect(error).toBeInstanceOf(MissingParamError);
55 |     expect(error?.message).toBe("Missing parameter: field");
56 |   });
57 | 
58 |   it("should not return error if field is zero", () => {
59 |     const sut = new RequiredFieldValidator("fieldZero");
60 |     const input = { fieldZero: 0 };
61 |     const error = sut.validate(input);
62 | 
63 |     expect(error).toBeNull();
64 |   });
65 | 
66 |   it("should not return error if field is false", () => {
67 |     const sut = new RequiredFieldValidator("fieldFalse");
68 |     const input = { fieldFalse: false };
69 |     const error = sut.validate(input);
70 | 
71 |     expect(error).toBeNull();
72 |   });
73 | 
74 |   it("should not return error if field is provided", () => {
75 |     const sut = new RequiredFieldValidator("field");
76 |     const input = { field: "any_value" };
77 |     const error = sut.validate(input);
78 | 
79 |     expect(error).toBeNull();
80 |   });
81 | });
82 | 
```

--------------------------------------------------------------------------------
/tests/validators/param-name-validator.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, expect, it } from "vitest";
 2 | import { InvalidParamError } from "../../src/presentation/errors/index.js";
 3 | import { ParamNameValidator } from "../../src/validators/param-name-validator.js";
 4 | 
 5 | describe("ParamNameValidator", () => {
 6 |   it("should return null if field is not provided", () => {
 7 |     const sut = new ParamNameValidator("field");
 8 |     const input = {};
 9 |     const error = sut.validate(input);
10 | 
11 |     expect(error).toBeNull();
12 |   });
13 | 
14 |   it("should return null if input is null", () => {
15 |     const sut = new ParamNameValidator("field");
16 |     const error = sut.validate(null);
17 | 
18 |     expect(error).toBeNull();
19 |   });
20 | 
21 |   it("should return null if input is undefined", () => {
22 |     const sut = new ParamNameValidator("field");
23 |     const error = sut.validate(undefined);
24 | 
25 |     expect(error).toBeNull();
26 |   });
27 | 
28 |   it("should return InvalidParamError if field doesn't match regex (contains special characters)", () => {
29 |     const sut = new ParamNameValidator("field");
30 |     const input = { field: "invalid/name" };
31 |     const error = sut.validate(input);
32 | 
33 |     expect(error).toBeInstanceOf(InvalidParamError);
34 |     expect(error?.message).toBe("Invalid parameter: invalid/name");
35 |   });
36 | 
37 |   it("should return InvalidParamError if field doesn't match regex (contains spaces)", () => {
38 |     const sut = new ParamNameValidator("field");
39 |     const input = { field: "invalid name" };
40 |     const error = sut.validate(input);
41 | 
42 |     expect(error).toBeInstanceOf(InvalidParamError);
43 |     expect(error?.message).toBe("Invalid parameter: invalid name");
44 |   });
45 | 
46 |   it("should return null if field matches NAME_REGEX (alphanumeric only)", () => {
47 |     const sut = new ParamNameValidator("field");
48 |     const input = { field: "validname123" };
49 |     const error = sut.validate(input);
50 | 
51 |     expect(error).toBeNull();
52 |   });
53 | 
54 |   it("should return null if field matches NAME_REGEX (with underscores)", () => {
55 |     const sut = new ParamNameValidator("field");
56 |     const input = { field: "valid_name_123" };
57 |     const error = sut.validate(input);
58 | 
59 |     expect(error).toBeNull();
60 |   });
61 | 
62 |   it("should return null if field matches NAME_REGEX (with hyphens)", () => {
63 |     const sut = new ParamNameValidator("field");
64 |     const input = { field: "valid-name-123" };
65 |     const error = sut.validate(input);
66 | 
67 |     expect(error).toBeNull();
68 |   });
69 | 
70 |   it("should use provided regex instead of default NAME_REGEX if given", () => {
71 |     // Custom regex that only allows lowercase letters
72 |     const customRegex = /^[a-z]+$/;
73 |     const sut = new ParamNameValidator("field", customRegex);
74 | 
75 |     const validInput = { field: "validname" };
76 |     const validError = sut.validate(validInput);
77 |     expect(validError).toBeNull();
78 | 
79 |     const invalidInput = { field: "Invalid123" };
80 |     const invalidError = sut.validate(invalidInput);
81 |     expect(invalidError).toBeInstanceOf(InvalidParamError);
82 |   });
83 | });
84 | 
```

--------------------------------------------------------------------------------
/src/infra/filesystem/repositories/fs-file-repository.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import fs from "fs-extra";
  2 | import path from "path";
  3 | import { FileRepository } from "../../../data/protocols/file-repository.js";
  4 | import { File } from "../../../domain/entities/index.js";
  5 | /**
  6 |  * Filesystem implementation of the FileRepository protocol
  7 |  */
  8 | export class FsFileRepository implements FileRepository {
  9 |   /**
 10 |    * Creates a new FsFileRepository
 11 |    * @param rootDir The root directory where all projects are stored
 12 |    */
 13 |   constructor(private readonly rootDir: string) {}
 14 | 
 15 |   /**
 16 |    * Lists all files in a project
 17 |    * @param projectName The name of the project
 18 |    * @returns An array of file names
 19 |    */
 20 |   async listFiles(projectName: string): Promise<File[]> {
 21 |     const projectPath = path.join(this.rootDir, projectName);
 22 | 
 23 |     const projectExists = await fs.pathExists(projectPath);
 24 |     if (!projectExists) {
 25 |       return [];
 26 |     }
 27 | 
 28 |     const entries = await fs.readdir(projectPath, { withFileTypes: true });
 29 |     return entries.filter((entry) => entry.isFile()).map((entry) => entry.name);
 30 |   }
 31 | 
 32 |   /**
 33 |    * Loads the content of a file
 34 |    * @param projectName The name of the project
 35 |    * @param fileName The name of the file
 36 |    * @returns The content of the file or null if the file doesn't exist
 37 |    */
 38 |   async loadFile(
 39 |     projectName: string,
 40 |     fileName: string
 41 |   ): Promise<string | null> {
 42 |     const filePath = path.join(this.rootDir, projectName, fileName);
 43 | 
 44 |     const fileExists = await fs.pathExists(filePath);
 45 |     if (!fileExists) {
 46 |       return null;
 47 |     }
 48 | 
 49 |     const content = await fs.readFile(filePath, "utf-8");
 50 |     return content;
 51 |   }
 52 | 
 53 |   /**
 54 |    * Writes a new file
 55 |    * @param projectName The name of the project
 56 |    * @param fileName The name of the file
 57 |    * @param content The content to write
 58 |    * @returns The content of the file after writing, or null if the file already exists
 59 |    */
 60 |   async writeFile(
 61 |     projectName: string,
 62 |     fileName: string,
 63 |     content: string
 64 |   ): Promise<File | null> {
 65 |     const projectPath = path.join(this.rootDir, projectName);
 66 |     await fs.ensureDir(projectPath);
 67 | 
 68 |     const filePath = path.join(projectPath, fileName);
 69 | 
 70 |     const fileExists = await fs.pathExists(filePath);
 71 |     if (fileExists) {
 72 |       return null;
 73 |     }
 74 | 
 75 |     await fs.writeFile(filePath, content, "utf-8");
 76 | 
 77 |     return await this.loadFile(projectName, fileName);
 78 |   }
 79 | 
 80 |   /**
 81 |    * Updates an existing file
 82 |    * @param projectName The name of the project
 83 |    * @param fileName The name of the file
 84 |    * @param content The new content
 85 |    * @returns The content of the file after updating, or null if the file doesn't exist
 86 |    */
 87 |   async updateFile(
 88 |     projectName: string,
 89 |     fileName: string,
 90 |     content: string
 91 |   ): Promise<File | null> {
 92 |     const filePath = path.join(this.rootDir, projectName, fileName);
 93 | 
 94 |     const fileExists = await fs.pathExists(filePath);
 95 |     if (!fileExists) {
 96 |       return null;
 97 |     }
 98 | 
 99 |     await fs.writeFile(filePath, content, "utf-8");
100 | 
101 |     return await this.loadFile(projectName, fileName);
102 |   }
103 | }
104 | 
```

--------------------------------------------------------------------------------
/tests/data/usecases/read-file/read-file.spec.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { beforeEach, describe, expect, test, vi } from "vitest";
 2 | import { FileRepository } from "../../../../src/data/protocols/file-repository.js";
 3 | import { ProjectRepository } from "../../../../src/data/protocols/project-repository.js";
 4 | import { ReadFile } from "../../../../src/data/usecases/read-file/read-file.js";
 5 | import { ReadFileParams } from "../../../../src/domain/usecases/read-file.js";
 6 | import {
 7 |   MockFileRepository,
 8 |   MockProjectRepository,
 9 | } from "../../mocks/index.js";
10 | 
11 | describe("ReadFile UseCase", () => {
12 |   let sut: ReadFile;
13 |   let fileRepositoryStub: FileRepository;
14 |   let projectRepositoryStub: ProjectRepository;
15 | 
16 |   beforeEach(() => {
17 |     fileRepositoryStub = new MockFileRepository();
18 |     projectRepositoryStub = new MockProjectRepository();
19 |     sut = new ReadFile(fileRepositoryStub, projectRepositoryStub);
20 |   });
21 | 
22 |   test("should call ProjectRepository.projectExists with correct projectName", async () => {
23 |     const projectExistsSpy = vi.spyOn(projectRepositoryStub, "projectExists");
24 |     const params: ReadFileParams = {
25 |       projectName: "project-1",
26 |       fileName: "file1.md",
27 |     };
28 | 
29 |     await sut.readFile(params);
30 | 
31 |     expect(projectExistsSpy).toHaveBeenCalledWith("project-1");
32 |   });
33 | 
34 |   test("should return null if project does not exist", async () => {
35 |     vi.spyOn(projectRepositoryStub, "projectExists").mockResolvedValueOnce(
36 |       false
37 |     );
38 |     const params: ReadFileParams = {
39 |       projectName: "non-existent-project",
40 |       fileName: "file1.md",
41 |     };
42 | 
43 |     const result = await sut.readFile(params);
44 | 
45 |     expect(result).toBeNull();
46 |   });
47 | 
48 |   test("should call FileRepository.loadFile with correct params if project exists", async () => {
49 |     const loadFileSpy = vi.spyOn(fileRepositoryStub, "loadFile");
50 |     const params: ReadFileParams = {
51 |       projectName: "project-1",
52 |       fileName: "file1.md",
53 |     };
54 | 
55 |     await sut.readFile(params);
56 | 
57 |     expect(loadFileSpy).toHaveBeenCalledWith("project-1", "file1.md");
58 |   });
59 | 
60 |   test("should return file content on success", async () => {
61 |     const params: ReadFileParams = {
62 |       projectName: "project-1",
63 |       fileName: "file1.md",
64 |     };
65 | 
66 |     const content = await sut.readFile(params);
67 | 
68 |     expect(content).toBe("Content of file1.md");
69 |   });
70 | 
71 |   test("should return null if file does not exist", async () => {
72 |     vi.spyOn(fileRepositoryStub, "loadFile").mockResolvedValueOnce(null);
73 |     const params: ReadFileParams = {
74 |       projectName: "project-1",
75 |       fileName: "non-existent-file.md",
76 |     };
77 | 
78 |     const content = await sut.readFile(params);
79 | 
80 |     expect(content).toBeNull();
81 |   });
82 | 
83 |   test("should propagate errors if repository throws", async () => {
84 |     const error = new Error("Repository error");
85 |     vi.spyOn(projectRepositoryStub, "projectExists").mockRejectedValueOnce(
86 |       error
87 |     );
88 |     const params: ReadFileParams = {
89 |       projectName: "project-1",
90 |       fileName: "file1.md",
91 |     };
92 | 
93 |     await expect(sut.readFile(params)).rejects.toThrow(error);
94 |   });
95 | });
96 | 
```

--------------------------------------------------------------------------------
/tests/presentation/controllers/list-project-files/list-project-files-controller.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, expect, it, vi } from "vitest";
  2 | import { ListProjectFilesController } from "../../../../src/presentation/controllers/list-project-files/list-project-files-controller.js";
  3 | import { ListProjectFilesRequest } from "../../../../src/presentation/controllers/list-project-files/protocols.js";
  4 | import { UnexpectedError } from "../../../../src/presentation/errors/index.js";
  5 | import {
  6 |   makeListProjectFilesUseCase,
  7 |   makeValidator,
  8 | } from "../../mocks/index.js";
  9 | 
 10 | const makeSut = () => {
 11 |   const validatorStub = makeValidator<ListProjectFilesRequest>();
 12 |   const listProjectFilesUseCaseStub = makeListProjectFilesUseCase();
 13 |   const sut = new ListProjectFilesController(
 14 |     listProjectFilesUseCaseStub,
 15 |     validatorStub
 16 |   );
 17 |   return {
 18 |     sut,
 19 |     validatorStub,
 20 |     listProjectFilesUseCaseStub,
 21 |   };
 22 | };
 23 | 
 24 | describe("ListProjectFilesController", () => {
 25 |   it("should call validator with correct values", async () => {
 26 |     const { sut, validatorStub } = makeSut();
 27 |     const validateSpy = vi.spyOn(validatorStub, "validate");
 28 |     const request = {
 29 |       body: {
 30 |         projectName: "any_project",
 31 |       },
 32 |     };
 33 |     await sut.handle(request);
 34 |     expect(validateSpy).toHaveBeenCalledWith(request.body);
 35 |   });
 36 | 
 37 |   it("should return 400 if validator returns an error", async () => {
 38 |     const { sut, validatorStub } = makeSut();
 39 |     vi.spyOn(validatorStub, "validate").mockReturnValueOnce(
 40 |       new Error("any_error")
 41 |     );
 42 |     const request = {
 43 |       body: {
 44 |         projectName: "any_project",
 45 |       },
 46 |     };
 47 |     const response = await sut.handle(request);
 48 |     expect(response).toEqual({
 49 |       statusCode: 400,
 50 |       body: new Error("any_error"),
 51 |     });
 52 |   });
 53 | 
 54 |   it("should call ListProjectFilesUseCase with correct values", async () => {
 55 |     const { sut, listProjectFilesUseCaseStub } = makeSut();
 56 |     const listProjectFilesSpy = vi.spyOn(
 57 |       listProjectFilesUseCaseStub,
 58 |       "listProjectFiles"
 59 |     );
 60 |     const request = {
 61 |       body: {
 62 |         projectName: "any_project",
 63 |       },
 64 |     };
 65 |     await sut.handle(request);
 66 |     expect(listProjectFilesSpy).toHaveBeenCalledWith({
 67 |       projectName: "any_project",
 68 |     });
 69 |   });
 70 | 
 71 |   it("should return 500 if ListProjectFilesUseCase throws", async () => {
 72 |     const { sut, listProjectFilesUseCaseStub } = makeSut();
 73 |     vi.spyOn(
 74 |       listProjectFilesUseCaseStub,
 75 |       "listProjectFiles"
 76 |     ).mockRejectedValueOnce(new Error("any_error"));
 77 |     const request = {
 78 |       body: {
 79 |         projectName: "any_project",
 80 |       },
 81 |     };
 82 |     const response = await sut.handle(request);
 83 |     expect(response).toEqual({
 84 |       statusCode: 500,
 85 |       body: new UnexpectedError(new Error("any_error")),
 86 |     });
 87 |   });
 88 | 
 89 |   it("should return 200 with files on success", async () => {
 90 |     const { sut } = makeSut();
 91 |     const request = {
 92 |       body: {
 93 |         projectName: "any_project",
 94 |       },
 95 |     };
 96 |     const response = await sut.handle(request);
 97 |     expect(response).toEqual({
 98 |       statusCode: 200,
 99 |       body: ["file1.txt", "file2.txt"],
100 |     });
101 |   });
102 | });
103 | 
```

--------------------------------------------------------------------------------
/src/main/protocols/mcp/routes.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {
  2 |   makeListProjectFilesController,
  3 |   makeListProjectsController,
  4 |   makeReadController,
  5 |   makeUpdateController,
  6 |   makeWriteController,
  7 | } from "../../factories/controllers/index.js";
  8 | import { adaptMcpRequestHandler } from "./adapters/mcp-request-adapter.js";
  9 | import { McpRouterAdapter } from "./adapters/mcp-router-adapter.js";
 10 | 
 11 | export default () => {
 12 |   const router = new McpRouterAdapter();
 13 | 
 14 |   router.setTool({
 15 |     schema: {
 16 |       name: "list_projects",
 17 |       description: "List all projects in the memory bank",
 18 |       inputSchema: {
 19 |         type: "object",
 20 |         properties: {},
 21 |         required: [],
 22 |       },
 23 |     },
 24 |     handler: adaptMcpRequestHandler(makeListProjectsController()),
 25 |   });
 26 | 
 27 |   router.setTool({
 28 |     schema: {
 29 |       name: "list_project_files",
 30 |       description: "List all files within a specific project",
 31 |       inputSchema: {
 32 |         type: "object",
 33 |         properties: {
 34 |           projectName: {
 35 |             type: "string",
 36 |             description: "The name of the project",
 37 |           },
 38 |         },
 39 |         required: ["projectName"],
 40 |       },
 41 |     },
 42 |     handler: adaptMcpRequestHandler(makeListProjectFilesController()),
 43 |   });
 44 | 
 45 |   router.setTool({
 46 |     schema: {
 47 |       name: "memory_bank_read",
 48 |       description: "Read a memory bank file for a specific project",
 49 |       inputSchema: {
 50 |         type: "object",
 51 |         properties: {
 52 |           projectName: {
 53 |             type: "string",
 54 |             description: "The name of the project",
 55 |           },
 56 |           fileName: {
 57 |             type: "string",
 58 |             description: "The name of the file",
 59 |           },
 60 |         },
 61 |         required: ["projectName", "fileName"],
 62 |       },
 63 |     },
 64 |     handler: adaptMcpRequestHandler(makeReadController()),
 65 |   });
 66 | 
 67 |   router.setTool({
 68 |     schema: {
 69 |       name: "memory_bank_write",
 70 |       description: "Create a new memory bank file for a specific project",
 71 |       inputSchema: {
 72 |         type: "object",
 73 |         properties: {
 74 |           projectName: {
 75 |             type: "string",
 76 |             description: "The name of the project",
 77 |           },
 78 |           fileName: {
 79 |             type: "string",
 80 |             description: "The name of the file",
 81 |           },
 82 |           content: {
 83 |             type: "string",
 84 |             description: "The content of the file",
 85 |           },
 86 |         },
 87 |         required: ["projectName", "fileName", "content"],
 88 |       },
 89 |     },
 90 |     handler: adaptMcpRequestHandler(makeWriteController()),
 91 |   });
 92 | 
 93 |   router.setTool({
 94 |     schema: {
 95 |       name: "memory_bank_update",
 96 |       description: "Update an existing memory bank file for a specific project",
 97 |       inputSchema: {
 98 |         type: "object",
 99 |         properties: {
100 |           projectName: {
101 |             type: "string",
102 |             description: "The name of the project",
103 |           },
104 |           fileName: {
105 |             type: "string",
106 |             description: "The name of the file",
107 |           },
108 |           content: {
109 |             type: "string",
110 |             description: "The content of the file",
111 |           },
112 |         },
113 |         required: ["projectName", "fileName", "content"],
114 |       },
115 |     },
116 |     handler: adaptMcpRequestHandler(makeUpdateController()),
117 |   });
118 | 
119 |   return router;
120 | };
121 | 
```

--------------------------------------------------------------------------------
/tests/presentation/controllers/write/write-controller.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, expect, it, vi } from "vitest";
  2 | import { WriteRequest } from "../../../../src/presentation/controllers/write/protocols.js";
  3 | import { WriteController } from "../../../../src/presentation/controllers/write/write-controller.js";
  4 | import { UnexpectedError } from "../../../../src/presentation/errors/index.js";
  5 | import { makeValidator, makeWriteFileUseCase } from "../../mocks/index.js";
  6 | 
  7 | const makeSut = () => {
  8 |   const validatorStub = makeValidator<WriteRequest>();
  9 |   const writeFileUseCaseStub = makeWriteFileUseCase();
 10 |   const sut = new WriteController(writeFileUseCaseStub, validatorStub);
 11 |   return {
 12 |     sut,
 13 |     validatorStub,
 14 |     writeFileUseCaseStub,
 15 |   };
 16 | };
 17 | 
 18 | describe("WriteController", () => {
 19 |   it("should call validator with correct values", async () => {
 20 |     const { sut, validatorStub } = makeSut();
 21 |     const validateSpy = vi.spyOn(validatorStub, "validate");
 22 |     const request = {
 23 |       body: {
 24 |         projectName: "any_project",
 25 |         fileName: "any_file",
 26 |         content: "any_content",
 27 |       },
 28 |     };
 29 |     await sut.handle(request);
 30 |     expect(validateSpy).toHaveBeenCalledWith(request.body);
 31 |   });
 32 | 
 33 |   it("should return 400 if validator returns an error", async () => {
 34 |     const { sut, validatorStub } = makeSut();
 35 |     vi.spyOn(validatorStub, "validate").mockReturnValueOnce(
 36 |       new Error("any_error")
 37 |     );
 38 |     const request = {
 39 |       body: {
 40 |         projectName: "any_project",
 41 |         fileName: "any_file",
 42 |         content: "any_content",
 43 |       },
 44 |     };
 45 |     const response = await sut.handle(request);
 46 |     expect(response).toEqual({
 47 |       statusCode: 400,
 48 |       body: new Error("any_error"),
 49 |     });
 50 |   });
 51 | 
 52 |   it("should call WriteFileUseCase with correct values", async () => {
 53 |     const { sut, writeFileUseCaseStub } = makeSut();
 54 |     const writeFileSpy = vi.spyOn(writeFileUseCaseStub, "writeFile");
 55 |     const request = {
 56 |       body: {
 57 |         projectName: "any_project",
 58 |         fileName: "any_file",
 59 |         content: "any_content",
 60 |       },
 61 |     };
 62 |     await sut.handle(request);
 63 |     expect(writeFileSpy).toHaveBeenCalledWith({
 64 |       projectName: "any_project",
 65 |       fileName: "any_file",
 66 |       content: "any_content",
 67 |     });
 68 |   });
 69 | 
 70 |   it("should return 500 if WriteFileUseCase throws", async () => {
 71 |     const { sut, writeFileUseCaseStub } = makeSut();
 72 |     vi.spyOn(writeFileUseCaseStub, "writeFile").mockRejectedValueOnce(
 73 |       new Error("any_error")
 74 |     );
 75 |     const request = {
 76 |       body: {
 77 |         projectName: "any_project",
 78 |         fileName: "any_file",
 79 |         content: "any_content",
 80 |       },
 81 |     };
 82 |     const response = await sut.handle(request);
 83 |     expect(response).toEqual({
 84 |       statusCode: 500,
 85 |       body: new UnexpectedError(new Error("any_error")),
 86 |     });
 87 |   });
 88 | 
 89 |   it("should return 200 if valid data is provided", async () => {
 90 |     const { sut } = makeSut();
 91 |     const request = {
 92 |       body: {
 93 |         projectName: "any_project",
 94 |         fileName: "any_file",
 95 |         content: "any_content",
 96 |       },
 97 |     };
 98 |     const response = await sut.handle(request);
 99 |     expect(response).toEqual({
100 |       statusCode: 200,
101 |       body: "File any_file written successfully to project any_project",
102 |     });
103 |   });
104 | });
105 | 
```

--------------------------------------------------------------------------------
/tests/presentation/controllers/read/read-controller.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, expect, it, vi } from "vitest";
  2 | import { ReadRequest } from "../../../../src/presentation/controllers/read/protocols.js";
  3 | import { ReadController } from "../../../../src/presentation/controllers/read/read-controller.js";
  4 | import {
  5 |   NotFoundError,
  6 |   UnexpectedError,
  7 | } from "../../../../src/presentation/errors/index.js";
  8 | import { makeReadFileUseCase, makeValidator } from "../../mocks/index.js";
  9 | 
 10 | const makeSut = () => {
 11 |   const validatorStub = makeValidator<ReadRequest>();
 12 |   const readFileUseCaseStub = makeReadFileUseCase();
 13 |   const sut = new ReadController(readFileUseCaseStub, validatorStub);
 14 |   return {
 15 |     sut,
 16 |     validatorStub,
 17 |     readFileUseCaseStub,
 18 |   };
 19 | };
 20 | 
 21 | describe("ReadController", () => {
 22 |   it("should call validator with correct values", async () => {
 23 |     const { sut, validatorStub } = makeSut();
 24 |     const validateSpy = vi.spyOn(validatorStub, "validate");
 25 |     const request = {
 26 |       body: {
 27 |         projectName: "any_project",
 28 |         fileName: "any_file",
 29 |       },
 30 |     };
 31 |     await sut.handle(request);
 32 |     expect(validateSpy).toHaveBeenCalledWith(request.body);
 33 |   });
 34 | 
 35 |   it("should return 400 if validator returns an error", async () => {
 36 |     const { sut, validatorStub } = makeSut();
 37 |     vi.spyOn(validatorStub, "validate").mockReturnValueOnce(
 38 |       new Error("any_error")
 39 |     );
 40 |     const request = {
 41 |       body: {
 42 |         projectName: "any_project",
 43 |         fileName: "any_file",
 44 |       },
 45 |     };
 46 |     const response = await sut.handle(request);
 47 |     expect(response).toEqual({
 48 |       statusCode: 400,
 49 |       body: new Error("any_error"),
 50 |     });
 51 |   });
 52 | 
 53 |   it("should call ReadFileUseCase with correct values", async () => {
 54 |     const { sut, readFileUseCaseStub } = makeSut();
 55 |     const readFileSpy = vi.spyOn(readFileUseCaseStub, "readFile");
 56 |     const request = {
 57 |       body: {
 58 |         projectName: "any_project",
 59 |         fileName: "any_file",
 60 |       },
 61 |     };
 62 |     await sut.handle(request);
 63 |     expect(readFileSpy).toHaveBeenCalledWith({
 64 |       projectName: "any_project",
 65 |       fileName: "any_file",
 66 |     });
 67 |   });
 68 | 
 69 |   it("should return 404 if ReadFileUseCase returns null", async () => {
 70 |     const { sut, readFileUseCaseStub } = makeSut();
 71 |     vi.spyOn(readFileUseCaseStub, "readFile").mockResolvedValueOnce(null);
 72 |     const request = {
 73 |       body: {
 74 |         projectName: "any_project",
 75 |         fileName: "any_file",
 76 |       },
 77 |     };
 78 |     const response = await sut.handle(request);
 79 |     expect(response).toEqual({
 80 |       statusCode: 404,
 81 |       body: new NotFoundError("any_file"),
 82 |     });
 83 |   });
 84 | 
 85 |   it("should return 500 if ReadFileUseCase throws", async () => {
 86 |     const { sut, readFileUseCaseStub } = makeSut();
 87 |     vi.spyOn(readFileUseCaseStub, "readFile").mockRejectedValueOnce(
 88 |       new Error("any_error")
 89 |     );
 90 |     const request = {
 91 |       body: {
 92 |         projectName: "any_project",
 93 |         fileName: "any_file",
 94 |       },
 95 |     };
 96 |     const response = await sut.handle(request);
 97 |     expect(response).toEqual({
 98 |       statusCode: 500,
 99 |       body: new UnexpectedError(new Error("any_error")),
100 |     });
101 |   });
102 | 
103 |   it("should return 200 if valid data is provided", async () => {
104 |     const { sut } = makeSut();
105 |     const request = {
106 |       body: {
107 |         projectName: "any_project",
108 |         fileName: "any_file",
109 |       },
110 |     };
111 |     const response = await sut.handle(request);
112 |     expect(response).toEqual({
113 |       statusCode: 200,
114 |       body: "file content",
115 |     });
116 |   });
117 | });
118 | 
```

--------------------------------------------------------------------------------
/tests/data/usecases/update-file/update-file.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { beforeEach, describe, expect, test, vi } from "vitest";
  2 | import { FileRepository } from "../../../../src/data/protocols/file-repository.js";
  3 | import { ProjectRepository } from "../../../../src/data/protocols/project-repository.js";
  4 | import { UpdateFile } from "../../../../src/data/usecases/update-file/update-file.js";
  5 | import { UpdateFileParams } from "../../../../src/domain/usecases/update-file.js";
  6 | import {
  7 |   MockFileRepository,
  8 |   MockProjectRepository,
  9 | } from "../../mocks/index.js";
 10 | 
 11 | describe("UpdateFile UseCase", () => {
 12 |   let sut: UpdateFile;
 13 |   let fileRepositoryStub: FileRepository;
 14 |   let projectRepositoryStub: ProjectRepository;
 15 | 
 16 |   beforeEach(() => {
 17 |     fileRepositoryStub = new MockFileRepository();
 18 |     projectRepositoryStub = new MockProjectRepository();
 19 |     sut = new UpdateFile(fileRepositoryStub, projectRepositoryStub);
 20 |   });
 21 | 
 22 |   test("should call ProjectRepository.projectExists with correct projectName", async () => {
 23 |     const projectExistsSpy = vi.spyOn(projectRepositoryStub, "projectExists");
 24 |     const params: UpdateFileParams = {
 25 |       projectName: "project-1",
 26 |       fileName: "file1.md",
 27 |       content: "Updated content",
 28 |     };
 29 | 
 30 |     await sut.updateFile(params);
 31 | 
 32 |     expect(projectExistsSpy).toHaveBeenCalledWith("project-1");
 33 |   });
 34 | 
 35 |   test("should return null if project does not exist", async () => {
 36 |     vi.spyOn(projectRepositoryStub, "projectExists").mockResolvedValueOnce(
 37 |       false
 38 |     );
 39 |     const params: UpdateFileParams = {
 40 |       projectName: "non-existent-project",
 41 |       fileName: "file1.md",
 42 |       content: "Updated content",
 43 |     };
 44 | 
 45 |     const result = await sut.updateFile(params);
 46 | 
 47 |     expect(result).toBeNull();
 48 |   });
 49 | 
 50 |   test("should check if file exists before updating", async () => {
 51 |     const loadFileSpy = vi.spyOn(fileRepositoryStub, "loadFile");
 52 |     const params: UpdateFileParams = {
 53 |       projectName: "project-1",
 54 |       fileName: "file1.md",
 55 |       content: "Updated content",
 56 |     };
 57 | 
 58 |     await sut.updateFile(params);
 59 | 
 60 |     expect(loadFileSpy).toHaveBeenCalledWith("project-1", "file1.md");
 61 |   });
 62 | 
 63 |   test("should return null if file does not exist", async () => {
 64 |     vi.spyOn(fileRepositoryStub, "loadFile").mockResolvedValueOnce(null);
 65 |     const params: UpdateFileParams = {
 66 |       projectName: "project-1",
 67 |       fileName: "non-existent-file.md",
 68 |       content: "Updated content",
 69 |     };
 70 | 
 71 |     const result = await sut.updateFile(params);
 72 | 
 73 |     expect(result).toBeNull();
 74 |   });
 75 | 
 76 |   test("should call FileRepository.updateFile with correct params if file exists", async () => {
 77 |     const updateFileSpy = vi.spyOn(fileRepositoryStub, "updateFile");
 78 |     const params: UpdateFileParams = {
 79 |       projectName: "project-1",
 80 |       fileName: "file1.md",
 81 |       content: "Updated content",
 82 |     };
 83 | 
 84 |     await sut.updateFile(params);
 85 | 
 86 |     expect(updateFileSpy).toHaveBeenCalledWith(
 87 |       "project-1",
 88 |       "file1.md",
 89 |       "Updated content"
 90 |     );
 91 |   });
 92 | 
 93 |   test("should return file content on successful file update", async () => {
 94 |     const params: UpdateFileParams = {
 95 |       projectName: "project-1",
 96 |       fileName: "file1.md",
 97 |       content: "Updated content",
 98 |     };
 99 | 
100 |     const result = await sut.updateFile(params);
101 | 
102 |     expect(result).toBe("Updated content");
103 |   });
104 | 
105 |   test("should propagate errors if repository throws", async () => {
106 |     const error = new Error("Repository error");
107 |     vi.spyOn(projectRepositoryStub, "projectExists").mockRejectedValueOnce(
108 |       error
109 |     );
110 |     const params: UpdateFileParams = {
111 |       projectName: "project-1",
112 |       fileName: "file1.md",
113 |       content: "Updated content",
114 |     };
115 | 
116 |     await expect(sut.updateFile(params)).rejects.toThrow(error);
117 |   });
118 | });
119 | 
```

--------------------------------------------------------------------------------
/tests/presentation/controllers/update/update-controller.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, expect, it, vi } from "vitest";
  2 | import { UpdateRequest } from "../../../../src/presentation/controllers/update/protocols.js";
  3 | import { UpdateController } from "../../../../src/presentation/controllers/update/update-controller.js";
  4 | import {
  5 |   NotFoundError,
  6 |   UnexpectedError,
  7 | } from "../../../../src/presentation/errors/index.js";
  8 | import { makeUpdateFileUseCase, makeValidator } from "../../mocks/index.js";
  9 | 
 10 | const makeSut = () => {
 11 |   const validatorStub = makeValidator<UpdateRequest>();
 12 |   const updateFileUseCaseStub = makeUpdateFileUseCase();
 13 |   const sut = new UpdateController(updateFileUseCaseStub, validatorStub);
 14 |   return {
 15 |     sut,
 16 |     validatorStub,
 17 |     updateFileUseCaseStub,
 18 |   };
 19 | };
 20 | 
 21 | describe("UpdateController", () => {
 22 |   it("should call validator with correct values", async () => {
 23 |     const { sut, validatorStub } = makeSut();
 24 |     const validateSpy = vi.spyOn(validatorStub, "validate");
 25 |     const request = {
 26 |       body: {
 27 |         projectName: "any_project",
 28 |         fileName: "any_file",
 29 |         content: "any_content",
 30 |       },
 31 |     };
 32 |     await sut.handle(request);
 33 |     expect(validateSpy).toHaveBeenCalledWith(request.body);
 34 |   });
 35 | 
 36 |   it("should return 400 if validator returns an error", async () => {
 37 |     const { sut, validatorStub } = makeSut();
 38 |     vi.spyOn(validatorStub, "validate").mockReturnValueOnce(
 39 |       new Error("any_error")
 40 |     );
 41 |     const request = {
 42 |       body: {
 43 |         projectName: "any_project",
 44 |         fileName: "any_file",
 45 |         content: "any_content",
 46 |       },
 47 |     };
 48 |     const response = await sut.handle(request);
 49 |     expect(response).toEqual({
 50 |       statusCode: 400,
 51 |       body: new Error("any_error"),
 52 |     });
 53 |   });
 54 | 
 55 |   it("should call UpdateFileUseCase with correct values", async () => {
 56 |     const { sut, updateFileUseCaseStub } = makeSut();
 57 |     const updateFileSpy = vi.spyOn(updateFileUseCaseStub, "updateFile");
 58 |     const request = {
 59 |       body: {
 60 |         projectName: "any_project",
 61 |         fileName: "any_file",
 62 |         content: "any_content",
 63 |       },
 64 |     };
 65 |     await sut.handle(request);
 66 |     expect(updateFileSpy).toHaveBeenCalledWith({
 67 |       projectName: "any_project",
 68 |       fileName: "any_file",
 69 |       content: "any_content",
 70 |     });
 71 |   });
 72 | 
 73 |   it("should return 404 if UpdateFileUseCase returns null", async () => {
 74 |     const { sut, updateFileUseCaseStub } = makeSut();
 75 |     vi.spyOn(updateFileUseCaseStub, "updateFile").mockResolvedValueOnce(null);
 76 |     const request = {
 77 |       body: {
 78 |         projectName: "any_project",
 79 |         fileName: "any_file",
 80 |         content: "any_content",
 81 |       },
 82 |     };
 83 |     const response = await sut.handle(request);
 84 |     expect(response).toEqual({
 85 |       statusCode: 404,
 86 |       body: new NotFoundError("any_file"),
 87 |     });
 88 |   });
 89 | 
 90 |   it("should return 500 if UpdateFileUseCase throws", async () => {
 91 |     const { sut, updateFileUseCaseStub } = makeSut();
 92 |     vi.spyOn(updateFileUseCaseStub, "updateFile").mockRejectedValueOnce(
 93 |       new Error("any_error")
 94 |     );
 95 |     const request = {
 96 |       body: {
 97 |         projectName: "any_project",
 98 |         fileName: "any_file",
 99 |         content: "any_content",
100 |       },
101 |     };
102 |     const response = await sut.handle(request);
103 |     expect(response).toEqual({
104 |       statusCode: 500,
105 |       body: new UnexpectedError(new Error("any_error")),
106 |     });
107 |   });
108 | 
109 |   it("should return 200 if valid data is provided", async () => {
110 |     const { sut } = makeSut();
111 |     const request = {
112 |       body: {
113 |         projectName: "any_project",
114 |         fileName: "any_file",
115 |         content: "any_content",
116 |       },
117 |     };
118 |     const response = await sut.handle(request);
119 |     expect(response).toEqual({
120 |       statusCode: 200,
121 |       body: "File any_file updated successfully in project any_project",
122 |     });
123 |   });
124 | });
125 | 
```

--------------------------------------------------------------------------------
/tests/data/usecases/write-file/write-file.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { beforeEach, describe, expect, test, vi } from "vitest";
  2 | import { FileRepository } from "../../../../src/data/protocols/file-repository.js";
  3 | import { ProjectRepository } from "../../../../src/data/protocols/project-repository.js";
  4 | import { WriteFile } from "../../../../src/data/usecases/write-file/write-file.js";
  5 | import { WriteFileParams } from "../../../../src/domain/usecases/write-file.js";
  6 | import {
  7 |   MockFileRepository,
  8 |   MockProjectRepository,
  9 | } from "../../mocks/index.js";
 10 | 
 11 | describe("WriteFile UseCase", () => {
 12 |   let sut: WriteFile;
 13 |   let fileRepositoryStub: FileRepository;
 14 |   let projectRepositoryStub: ProjectRepository;
 15 | 
 16 |   beforeEach(() => {
 17 |     fileRepositoryStub = new MockFileRepository();
 18 |     projectRepositoryStub = new MockProjectRepository();
 19 |     sut = new WriteFile(fileRepositoryStub, projectRepositoryStub);
 20 |   });
 21 | 
 22 |   test("should call ProjectRepository.ensureProject with correct projectName", async () => {
 23 |     const ensureProjectSpy = vi.spyOn(projectRepositoryStub, "ensureProject");
 24 |     const params: WriteFileParams = {
 25 |       projectName: "new-project",
 26 |       fileName: "new-file.md",
 27 |       content: "New content",
 28 |     };
 29 | 
 30 |     vi.spyOn(fileRepositoryStub, "loadFile")
 31 |       .mockResolvedValueOnce(null) // First call checking if file exists
 32 |       .mockResolvedValueOnce("New content"); // Second call returning the saved content
 33 | 
 34 |     await sut.writeFile(params);
 35 | 
 36 |     expect(ensureProjectSpy).toHaveBeenCalledWith("new-project");
 37 |   });
 38 | 
 39 |   test("should check if file exists before writing", async () => {
 40 |     const loadFileSpy = vi.spyOn(fileRepositoryStub, "loadFile");
 41 |     const params: WriteFileParams = {
 42 |       projectName: "project-1",
 43 |       fileName: "new-file.md",
 44 |       content: "New content",
 45 |     };
 46 | 
 47 |     await sut.writeFile(params);
 48 | 
 49 |     expect(loadFileSpy).toHaveBeenCalledWith("project-1", "new-file.md");
 50 |   });
 51 | 
 52 |   test("should return null if file already exists", async () => {
 53 |     const params: WriteFileParams = {
 54 |       projectName: "project-1",
 55 |       fileName: "file1.md",
 56 |       content: "New content",
 57 |     };
 58 | 
 59 |     const result = await sut.writeFile(params);
 60 | 
 61 |     expect(result).toBeNull();
 62 |   });
 63 | 
 64 |   test("should call FileRepository.writeFile with correct params if file does not exist", async () => {
 65 |     const writeFileSpy = vi.spyOn(fileRepositoryStub, "writeFile");
 66 |     const params: WriteFileParams = {
 67 |       projectName: "project-1",
 68 |       fileName: "new-file.md",
 69 |       content: "New content",
 70 |     };
 71 | 
 72 |     vi.spyOn(fileRepositoryStub, "loadFile")
 73 |       .mockResolvedValueOnce(null) // First call checking if file exists
 74 |       .mockResolvedValueOnce("New content"); // Second call returning the saved content
 75 | 
 76 |     await sut.writeFile(params);
 77 | 
 78 |     expect(writeFileSpy).toHaveBeenCalledWith(
 79 |       "project-1",
 80 |       "new-file.md",
 81 |       "New content"
 82 |     );
 83 |   });
 84 | 
 85 |   test("should return file content on successful file creation", async () => {
 86 |     const params: WriteFileParams = {
 87 |       projectName: "project-1",
 88 |       fileName: "new-file.md",
 89 |       content: "New content",
 90 |     };
 91 | 
 92 |     vi.spyOn(fileRepositoryStub, "loadFile")
 93 |       .mockResolvedValueOnce(null) // First call checking if file exists
 94 |       .mockResolvedValueOnce("New content"); // Second call returning the saved content
 95 | 
 96 |     const result = await sut.writeFile(params);
 97 | 
 98 |     expect(result).toBe("New content");
 99 |   });
100 | 
101 |   test("should propagate errors if repository throws", async () => {
102 |     const error = new Error("Repository error");
103 |     vi.spyOn(projectRepositoryStub, "ensureProject").mockRejectedValueOnce(
104 |       error
105 |     );
106 |     const params: WriteFileParams = {
107 |       projectName: "project-1",
108 |       fileName: "new-file.md",
109 |       content: "New content",
110 |     };
111 | 
112 |     await expect(sut.writeFile(params)).rejects.toThrow(error);
113 |   });
114 | });
115 | 
```

--------------------------------------------------------------------------------
/custom-instructions.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Memory Bank via MCP
  2 | 
  3 | I'm an expert engineer whose memory resets between sessions. I rely ENTIRELY on my Memory Bank, accessed via MCP tools, and MUST read ALL memory bank files before EVERY task.
  4 | 
  5 | ## Key Commands
  6 | 
  7 | 1. "follow your custom instructions"
  8 | 
  9 |    - Triggers Pre-Flight Validation (\*a)
 10 |    - Follows Memory Bank Access Pattern (\*f)
 11 |    - Executes appropriate Mode flow (Plan/Act)
 12 | 
 13 | 2. "initialize memory bank"
 14 | 
 15 |    - Follows Pre-Flight Validation (\*a)
 16 |    - Creates new project if needed
 17 |    - Establishes core files structure (\*f)
 18 | 
 19 | 3. "update memory bank"
 20 |    - Triggers Documentation Updates (\*d)
 21 |    - Performs full file re-read
 22 |    - Updates based on current state
 23 | 
 24 | ## Memory Bank lyfe cycle:
 25 | 
 26 | ```mermaid
 27 | flowchart TD
 28 |     A[Start] --> B["Pre-Flight Validation (*a)"]
 29 |     B --> C{Project Exists?}
 30 |     C -->|Yes| D[Check Core Files]
 31 |     C -->|No| E[Create Project] --> H[Create Missing Files]
 32 | 
 33 |     D --> F{All Files Present?}
 34 |     F -->|Yes| G["Access Memory Bank (*f)"]
 35 |     F -->|No| H[Create Missing Files]
 36 | 
 37 |     H --> G
 38 |     G --> I["Plan Mode (*b)"]
 39 |     G --> J["Act Mode (*c)"]
 40 | 
 41 |     I --> K[List Projects]
 42 |     K --> L[Select Context]
 43 |     L --> M[Develop Strategy]
 44 | 
 45 |     J --> N[Read .clinerules]
 46 |     N --> O[Execute Task]
 47 |     O --> P["Update Documentation (*d)"]
 48 | 
 49 |     P --> Q{Update Needed?}
 50 |     Q -->|Patterns/Changes| R[Read All Files]
 51 |     Q -->|User Request| R
 52 |     R --> S[Update Memory Bank]
 53 | 
 54 |     S --> T["Learning Process (*e)"]
 55 |     T --> U[Identify Patterns]
 56 |     U --> V[Validate with User]
 57 |     V --> W[Update .clinerules]
 58 |     W --> X[Apply Patterns]
 59 |     X --> O
 60 | 
 61 |     %% Intelligence Connections
 62 |     W -.->|Continuous Learning| N
 63 |     X -.->|Informed Execution| O
 64 | ```
 65 | 
 66 | ## Phase Index & Requirements
 67 | 
 68 | a) **Pre-Flight Validation**
 69 | 
 70 | - **Triggers:** Automatic before any operation
 71 | - **Checks:**
 72 |   - Project directory existence
 73 |   - Core files presence (projectbrief.md, productContext.md, etc.)
 74 |   - Custom documentation inventory
 75 | 
 76 | b) **Plan Mode**
 77 | 
 78 | - **Inputs:** Filesystem/list_directory results
 79 | - **Outputs:** Strategy documented in activeContext.md
 80 | - **Format Rules:** Validate paths with forward slashes
 81 | 
 82 | c) **Act Mode**
 83 | 
 84 | - **JSON Operations:**
 85 |   ```json
 86 |   {
 87 |     "projectName": "project-id",
 88 |     "fileName": "progress.md",
 89 |     "content": "Escaped\\ncontent"
 90 |   }
 91 |   ```
 92 | - **Requirements:**
 93 |   - Use \\n for newlines
 94 |   - Pure JSON (no XML)
 95 |   - Boolean values lowercase (true/false)
 96 | 
 97 | d) **Documentation Updates**
 98 | 
 99 | - **Triggers:**
100 |   - ≥25% code impact changes
101 |   - New pattern discovery
102 |   - User request "update memory bank"
103 |   - Context ambiguity detected
104 | - **Process:** Full file re-read before update
105 | 
106 | e) **Project Intelligence**
107 | 
108 | - **.clinerules Requirements:**
109 |   - Capture critical implementation paths
110 |   - Document user workflow preferences
111 |   - Track tool usage patterns
112 |   - Record project-specific decisions
113 | - **Cycle:** Continuous validate → update → apply
114 | 
115 | f) **Memory Bank Structure**
116 | 
117 | ```mermaid
118 | flowchart TD
119 |     PB[projectbrief.md\nCore requirements/goals] --> PC[productContext.md\nProblem context/solutions]
120 |     PB --> SP[systemPatterns.md\nArchitecture/patterns]
121 |     PB --> TC[techContext.md\nTech stack/setup]
122 | 
123 |     PC --> AC[activeContext.md\nCurrent focus/decisions]
124 |     SP --> AC
125 |     TC --> AC
126 | 
127 |     AC --> P[progress.md\nStatus/roadmap]
128 | 
129 |     %% Custom files section
130 |     subgraph CF[Custom Files]
131 |         CF1[features/*.md\nFeature specs]
132 |         CF2[api/*.md\nAPI documentation]
133 |         CF3[deployment/*.md\nDeployment guides]
134 |     end
135 | 
136 |     %% Connect custom files to main structure
137 |     AC -.-> CF
138 |     CF -.-> P
139 | 
140 |     style PB fill:#e066ff,stroke:#333,stroke-width:2px
141 |     style AC fill:#4d94ff,stroke:#333,stroke-width:2px
142 |     style P fill:#2eb82e,stroke:#333,stroke-width:2px
143 |     style CF fill:#fff,stroke:#333,stroke-width:1px,stroke-dasharray: 5 5
144 |     style CF1 fill:#fff,stroke:#333
145 |     style CF2 fill:#fff,stroke:#333
146 |     style CF3 fill:#fff,stroke:#333
147 | ```
148 | 
149 | - **File Relationships:**
150 |   - projectbrief.md feeds into all context files
151 |   - All context files inform activeContext.md
152 |   - progress.md tracks implementation based on active context
153 | - **Color Coding:**
154 |   - Purple: Foundation documents
155 |   - Blue: Active work documents
156 |   - Green: Status tracking
157 |   - Dashed: Custom documentation (flexible/optional)
158 | - **Access Pattern:**
159 | 
160 |   - Always read in hierarchical order
161 |   - Update in reverse order (progress → active → others)
162 |   - .clinerules accessed throughout process
163 |   - Custom files integrated based on project needs
164 | 
165 | - **Custom Files:**
166 |   - Can be added when specific documentation needs arise
167 |   - Common examples:
168 |     - Feature specifications
169 |     - API documentation
170 |     - Integration guides
171 |     - Testing strategies
172 |     - Deployment procedures
173 |   - Should follow main structure's naming patterns
174 |   - Must be referenced in activeContext.md when added
175 | 
```

--------------------------------------------------------------------------------
/tests/infra/filesystem/repositories/fs-file-repository.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import fs from "fs-extra";
  2 | import os from "os";
  3 | import path from "path";
  4 | import { afterEach, beforeEach, describe, expect, it } from "vitest";
  5 | import { FsFileRepository } from "../../../../src/infra/filesystem/repositories/fs-file-repository.js";
  6 | 
  7 | describe("FsFileRepository", () => {
  8 |   let tempDir: string;
  9 |   let repository: FsFileRepository;
 10 |   const projectName = "test-project";
 11 |   const fileName = "test.md";
 12 |   const fileContent = "Test content";
 13 | 
 14 |   beforeEach(async () => {
 15 |     // Create a temporary directory for tests
 16 |     tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "memory-bank-test-"));
 17 |     repository = new FsFileRepository(tempDir);
 18 | 
 19 |     // Create a test project directory
 20 |     await fs.mkdir(path.join(tempDir, projectName));
 21 |   });
 22 | 
 23 |   afterEach(() => {
 24 |     // Clean up after tests
 25 |     fs.removeSync(tempDir);
 26 |   });
 27 | 
 28 |   describe("listFiles", () => {
 29 |     it("should return an empty array for a project that doesn't exist", async () => {
 30 |       const result = await repository.listFiles("non-existent-project");
 31 |       expect(result).toEqual([]);
 32 |     });
 33 | 
 34 |     it("should return an empty array when no files exist in the project", async () => {
 35 |       const result = await repository.listFiles(projectName);
 36 |       expect(result).toEqual([]);
 37 |     });
 38 | 
 39 |     it("should return file names within the project directory", async () => {
 40 |       // Create test files
 41 |       await fs.writeFile(path.join(tempDir, projectName, "file1.md"), "test");
 42 |       await fs.writeFile(path.join(tempDir, projectName, "file2.txt"), "test");
 43 |       // Create a directory to ensure it's not returned
 44 |       await fs.mkdir(path.join(tempDir, projectName, "not-a-file"));
 45 | 
 46 |       const result = await repository.listFiles(projectName);
 47 | 
 48 |       expect(result).toHaveLength(2);
 49 |       expect(result).toEqual(expect.arrayContaining(["file1.md", "file2.txt"]));
 50 |     });
 51 |   });
 52 | 
 53 |   describe("loadFile", () => {
 54 |     it("should return null when the file doesn't exist", async () => {
 55 |       const result = await repository.loadFile(projectName, "non-existent.md");
 56 |       expect(result).toBeNull();
 57 |     });
 58 | 
 59 |     it("should return null when the project doesn't exist", async () => {
 60 |       const result = await repository.loadFile(
 61 |         "non-existent-project",
 62 |         fileName
 63 |       );
 64 |       expect(result).toBeNull();
 65 |     });
 66 | 
 67 |     it("should return the file content when the file exists", async () => {
 68 |       // Create a test file
 69 |       await fs.writeFile(
 70 |         path.join(tempDir, projectName, fileName),
 71 |         fileContent
 72 |       );
 73 | 
 74 |       const result = await repository.loadFile(projectName, fileName);
 75 | 
 76 |       expect(result).toBe(fileContent);
 77 |     });
 78 |   });
 79 | 
 80 |   describe("writeFile", () => {
 81 |     it("should create the project directory if it doesn't exist", async () => {
 82 |       const newProjectName = "new-project";
 83 |       const newFilePath = path.join(tempDir, newProjectName, fileName);
 84 | 
 85 |       await repository.writeFile(newProjectName, fileName, fileContent);
 86 | 
 87 |       const exists = await fs.pathExists(newFilePath);
 88 |       expect(exists).toBe(true);
 89 |     });
 90 | 
 91 |     it("should write file content to the specified file", async () => {
 92 |       await repository.writeFile(projectName, fileName, fileContent);
 93 | 
 94 |       const content = await fs.readFile(
 95 |         path.join(tempDir, projectName, fileName),
 96 |         "utf-8"
 97 |       );
 98 |       expect(content).toBe(fileContent);
 99 |     });
100 | 
101 |     it("should return the file content after writing", async () => {
102 |       const result = await repository.writeFile(
103 |         projectName,
104 |         fileName,
105 |         fileContent
106 |       );
107 | 
108 |       expect(result).toBe(fileContent);
109 |     });
110 | 
111 |     it("should return null if the file already exists", async () => {
112 |       // Create a test file first
113 |       await fs.writeFile(
114 |         path.join(tempDir, projectName, fileName),
115 |         "Original content"
116 |       );
117 | 
118 |       const result = await repository.writeFile(
119 |         projectName,
120 |         fileName,
121 |         fileContent
122 |       );
123 | 
124 |       expect(result).toBeNull();
125 | 
126 |       // Verify content wasn't changed
127 |       const content = await fs.readFile(
128 |         path.join(tempDir, projectName, fileName),
129 |         "utf-8"
130 |       );
131 |       expect(content).toBe("Original content");
132 |     });
133 |   });
134 | 
135 |   describe("updateFile", () => {
136 |     it("should return null when the file doesn't exist", async () => {
137 |       const result = await repository.updateFile(
138 |         projectName,
139 |         "non-existent.md",
140 |         fileContent
141 |       );
142 |       expect(result).toBeNull();
143 |     });
144 | 
145 |     it("should return null when the project doesn't exist", async () => {
146 |       const result = await repository.updateFile(
147 |         "non-existent-project",
148 |         fileName,
149 |         fileContent
150 |       );
151 |       expect(result).toBeNull();
152 |     });
153 | 
154 |     it("should update file content for an existing file", async () => {
155 |       // Create a test file first
156 |       await fs.writeFile(
157 |         path.join(tempDir, projectName, fileName),
158 |         "Original content"
159 |       );
160 | 
161 |       const updatedContent = "Updated content";
162 |       const result = await repository.updateFile(
163 |         projectName,
164 |         fileName,
165 |         updatedContent
166 |       );
167 | 
168 |       expect(result).toBe(updatedContent);
169 | 
170 |       // Verify content was changed
171 |       const content = await fs.readFile(
172 |         path.join(tempDir, projectName, fileName),
173 |         "utf-8"
174 |       );
175 |       expect(content).toBe(updatedContent);
176 |     });
177 | 
178 |     it("should return the updated file content", async () => {
179 |       // Create a test file first
180 |       await fs.writeFile(
181 |         path.join(tempDir, projectName, fileName),
182 |         "Original content"
183 |       );
184 | 
185 |       const updatedContent = "Updated content";
186 |       const result = await repository.updateFile(
187 |         projectName,
188 |         fileName,
189 |         updatedContent
190 |       );
191 | 
192 |       expect(result).toBe(updatedContent);
193 |     });
194 |   });
195 | });
196 | 
```