#
tokens: 29911/50000 123/123 files
lines: off (toggle) GitHub
raw markdown copy
# 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:
--------------------------------------------------------------------------------

```
node_modules
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
dist
coverage
```

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

```
# Use package-lock.json for dependency tracking
package-lock=true

# Save exact versions for better reproducibility
save-exact=true

```

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

```markdown
# Memory Bank MCP Server

[![smithery badge](https://smithery.ai/badge/@alioshr/memory-bank-mcp)](https://smithery.ai/server/@alioshr/memory-bank-mcp)
[![npm version](https://badge.fury.io/js/%40allpepper%2Fmemory-bank-mcp.svg)](https://www.npmjs.com/package/@allpepper/memory-bank-mcp)
[![npm downloads](https://img.shields.io/npm/dm/@allpepper/memory-bank-mcp.svg)](https://www.npmjs.com/package/@allpepper/memory-bank-mcp)

<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>

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).

## Overview

The Memory Bank MCP Server transforms traditional file-based memory banks into a centralized service that:

- Provides remote access to memory bank files via MCP protocol
- Enables multi-project memory bank management
- Maintains consistent file structure and validation
- Ensures proper isolation between project memory banks

## Features

- **Multi-Project Support**

  - Project-specific directories
  - File structure enforcement
  - Path traversal prevention
  - Project listing capabilities
  - File listing per project

- **Remote Accessibility**

  - Full MCP protocol implementation
  - Type-safe operations
  - Proper error handling
  - Security through project isolation

- **Core Operations**
  - Read/write/update memory bank files
  - List available projects
  - List files within projects
  - Project existence validation
  - Safe read-only operations

## Installation

To install Memory Bank Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@alioshr/memory-bank-mcp):

```bash
npx -y @smithery/cli install @alioshr/memory-bank-mcp --client claude
```

This will set up the MCP server configuration automatically. Alternatively, you can configure the server manually as described in the Configuration section below.

## Quick Start

1. Configure the MCP server in your settings (see Configuration section below)
2. Start using the memory bank tools in your AI assistant

## Using with Cline/Roo Code

The memory bank MCP server needs to be configured in your Cline MCP settings file. The location depends on your setup:

- For Cline extension: `~/Library/Application Support/Cursor/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
- For Roo Code VS Code extension: `~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json`

Add the following configuration to your MCP settings:

```json
{
  "allpepper-memory-bank": {
    "command": "npx",
    "args": ["-y", "@allpepper/memory-bank-mcp"],
    "env": {
      "MEMORY_BANK_ROOT": "<path-to-bank>"
    },
    "disabled": false,
    "autoApprove": [
      "memory_bank_read",
      "memory_bank_write",
      "memory_bank_update",
      "list_projects",
      "list_project_files"
    ]
  }
}
```

### Configuration Details

- `MEMORY_BANK_ROOT`: Directory where project memory banks will be stored (e.g., `/path/to/memory-bank`)
- `disabled`: Set to `false` to enable the server
- `autoApprove`: List of operations that don't require explicit user approval:
  - `memory_bank_read`: Read memory bank files
  - `memory_bank_write`: Create new memory bank files
  - `memory_bank_update`: Update existing memory bank files
  - `list_projects`: List available projects
  - `list_project_files`: List files within a project

## Using with Cursor

For Cursor, open the settings -> features -> add MCP server -> add the following:

```shell
env MEMORY_BANK_ROOT=<path-to-bank> npx -y @allpepper/memory-bank-mcp@latest
```
## Using with Claude

- Claude desktop config file: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Claude Code config file:  `~/.claude.json`

1. Locate the config file
3. Locate the property called `mcpServers`
4. Paste this:

```
 "allPepper-memory-bank": {
          "type": "stdio",
          "command": "npx",
          "args": [
            "-y",
            "@allpepper/memory-bank-mcp@latest"
          ],
          "env": {
            "MEMORY_BANK_ROOT": "YOUR PATH"
          }
        }
```

## Custom AI instructions

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.

## Development

Basic development commands:

```bash
# Install dependencies
npm install

# Build the project
npm run build

# Run tests
npm run test

# Run tests in watch mode
npm run test:watch

# Run the server directly with ts-node for quick testing
npm run dev
```

### Running with Docker

1. Build the Docker image:

    ```bash
    docker build -t memory-bank-mcp:local .
    ```

2. Run the Docker container for testing:

    ```bash
    docker run -i --rm \
      -e MEMORY_BANK_ROOT="/mnt/memory_bank" \
      -v /path/to/memory-bank:/mnt/memory_bank \
      --entrypoint /bin/sh \
      memory-bank-mcp:local \
      -c "ls -la /mnt/memory_bank"
    ```

3. Add MCP configuration, example for Roo Code:

    ```json
    "allpepper-memory-bank": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-e", 
        "MEMORY_BANK_ROOT",
        "-v", 
        "/path/to/memory-bank:/mnt/memory_bank",
        "memory-bank-mcp:local"
      ],
      "env": {
        "MEMORY_BANK_ROOT": "/mnt/memory_bank"
      },
      "disabled": false,
      "alwaysAllow": [
        "list_projects",
        "list_project_files",
        "memory_bank_read",
        "memory_bank_update",
        "memory_bank_write"
      ]
    }
    ```

## Contributing

Contributions are welcome! Please follow these steps:

1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

### Development Guidelines

- Use TypeScript for all new code
- Maintain type safety across the codebase
- Add tests for new features
- Update documentation as needed
- Follow existing code style and patterns

### Testing

- Write unit tests for new features
- Include multi-project scenario tests
- Test error cases thoroughly
- Validate type constraints
- Mock filesystem operations appropriately

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Acknowledgments

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.

```

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

```typescript
export type File = string;

```

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

```typescript
export type Project = string;

```

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

```typescript
export interface Request<T extends any> {
  body?: T;
}

```

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

```typescript
export * from "./file.js";
export * from "./project.js";

```

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

```typescript
export const env = {
  rootPath: process.env.MEMORY_BANK_ROOT!,
};

```

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

```typescript
export * from "./protocols.js";
export * from "./read-controller.js";

```

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

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

```

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

```typescript
export * from "./protocols.js";
export * from "./write-controller.js";

```

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

```typescript
export * from "./protocols.js";
export * from "./update-controller.js";

```

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

```typescript
export * from "./file-repository.js";
export * from "./project-repository.js";

```

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

```typescript
export * from "./list-projects-controller.js";
export * from "./protocols.js";

```

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

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

```

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

```typescript
export * from "./mock-file-repository.js";
export * from "./mock-project-repository.js";

```

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

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

```

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

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

```

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

```typescript
export * from "./controller.js";
export * from "./request.js";
export * from "./response.js";
export * from "./validator.js";

```

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

```typescript
import { Project } from "../entities/index.js";

export interface ListProjectsUseCase {
  listProjects(): Promise<Project[]>;
}

```

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

```typescript
export * from "./param-name-validator.js";
export * from "./required-field-validator.js";
export * from "./validator-composite.js";

```

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

```typescript
import { Request, Response } from "./index.js";

export interface Controller<T, R> {
  handle(request: Request<T>): Promise<Response<R>>;
}

```

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

```typescript
#!/usr/bin/env node

import app from "./protocols/mcp/app.js";

app.start().catch((error) => {
  console.error(error);
  process.exit(1);
});

```

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

```typescript
export * from "./list-project-files.js";
export * from "./list-projects.js";
export * from "./read-file.js";
export * from "./update-file.js";
export * from "./write-file.js";

```

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

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

```

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

```typescript
import { ErrorName } from "./error-names.js";

export abstract class BaseError extends Error {
  constructor(message: string, name: ErrorName) {
    super(message);
    this.name = name;
  }
}

```

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

```typescript
export enum ErrorName {
  INVALID_PARAM_ERROR = "InvalidParamError",
  NOT_FOUND_ERROR = "NotFoundError",
  UNEXPECTED_ERROR = "UnexpectedError",
  MISSING_PARAM_ERROR = "MissingParamError",
}

```

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

```typescript
export * from "./list-project-files-factory.js";
export * from "./list-projects-factory.js";
export * from "./read-file-factory.js";
export * from "./update-file-factory.js";
export * from "./write-file-factory.js";

```

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

```typescript
import { File } from "../entities/index.js";
export interface ReadFileParams {
  projectName: string;
  fileName: string;
}

export interface ReadFileUseCase {
  readFile(params: ReadFileParams): Promise<File | null>;
}

```

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

```typescript
// Export all controller modules
export * from "./list-project-files/index.js";
export * from "./list-projects/index.js";
export * from "./read/index.js";
export * from "./update/index.js";
export * from "./write/index.js";

```

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

```typescript
import { File } from "../entities/index.js";
export interface ListProjectFilesParams {
  projectName: string;
}

export interface ListProjectFilesUseCase {
  listProjectFiles(params: ListProjectFilesParams): Promise<File[]>;
}

```

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

```typescript
export * from "./base-error.js";
export * from "./error-names.js";
export * from "./invalid-param-error.js";
export * from "./missing-param-error.js";
export * from "./not-found-error.js";
export * from "./unexpected-error.js";

```

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

```typescript
import { Project } from "../../domain/entities/index.js";

export interface ProjectRepository {
  listProjects(): Promise<Project[]>;
  projectExists(name: string): Promise<boolean>;
  ensureProject(name: string): Promise<void>;
}

```

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

```typescript
import { File } from "../entities/index.js";
export interface WriteFileParams {
  projectName: string;
  fileName: string;
  content: string;
}

export interface WriteFileUseCase {
  writeFile(params: WriteFileParams): Promise<File | null>;
}

```

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

```typescript
import { BaseError } from "./base-error.js";
import { ErrorName } from "./error-names.js";

export class NotFoundError extends BaseError {
  constructor(name: string) {
    super(`Resource not found: ${name}`, ErrorName.NOT_FOUND_ERROR);
  }
}

```

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

```typescript
import { ListProjectsUseCase } from "../../../domain/usecases/list-projects.js";
import { Controller, Response } from "../../protocols/index.js";

export type ListProjectsResponse = string[];

export { Controller, ListProjectsUseCase, Response };

```

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

```typescript
import {
  ReadFileParams,
  ReadFileUseCase,
} from "../../../domain/usecases/index.js";
import { FileRepository, ProjectRepository } from "../../protocols/index.js";

export { FileRepository, ProjectRepository, ReadFileParams, ReadFileUseCase };

```

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

```typescript
import { File } from "../entities/index.js";

export interface UpdateFileParams {
  projectName: string;
  fileName: string;
  content: string;
}

export interface UpdateFileUseCase {
  updateFile(params: UpdateFileParams): Promise<File | null>;
}

```

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

```typescript
import {
  WriteFileParams,
  WriteFileUseCase,
} from "../../../domain/usecases/index.js";
import { FileRepository, ProjectRepository } from "../../protocols/index.js";

export { FileRepository, ProjectRepository, WriteFileParams, WriteFileUseCase };

```

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

```typescript
import { McpServerAdapter } from "./adapters/mcp-server-adapter.js";
import routes from "./routes.js";

const router = routes();
const app = new McpServerAdapter(router);

app.register({
  name: "memory-bank",
  version: "1.0.0",
});

export default app;

```

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

```typescript
import { Project } from "../../../domain/entities/index.js";
import { ListProjectsUseCase } from "../../../domain/usecases/index.js";
import { ProjectRepository } from "../../protocols/index.js";

export { ListProjectsUseCase, Project, ProjectRepository };

```

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

```typescript
import { BaseError } from "./base-error.js";
import { ErrorName } from "./error-names.js";

export class InvalidParamError extends BaseError {
  constructor(paramName: string) {
    super(`Invalid parameter: ${paramName}`, ErrorName.INVALID_PARAM_ERROR);
  }
}

```

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

```typescript
import { BaseError } from "./base-error.js";
import { ErrorName } from "./error-names.js";

export class MissingParamError extends BaseError {
  constructor(paramName: string) {
    super(`Missing parameter: ${paramName}`, ErrorName.MISSING_PARAM_ERROR);
  }
}

```

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

```typescript
import {
  UpdateFileParams,
  UpdateFileUseCase,
} from "../../../domain/usecases/index.js";
import { FileRepository, ProjectRepository } from "../../protocols/index.js";

export {
  FileRepository,
  ProjectRepository,
  UpdateFileParams,
  UpdateFileUseCase,
};

```

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

```typescript
export * from "./mock-list-project-files-use-case.js";
export * from "./mock-list-projects-use-case.js";
export * from "./mock-read-file-use-case.js";
export * from "./mock-update-file-use-case.js";
export * from "./mock-validator.js";
export * from "./mock-write-file-use-case.js";

```

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

```typescript
import {
  ListProjectFilesParams,
  ListProjectFilesUseCase,
} from "../../../domain/usecases/index.js";
import { FileRepository, ProjectRepository } from "../../protocols/index.js";

export {
  FileRepository,
  ListProjectFilesParams,
  ListProjectFilesUseCase,
  ProjectRepository,
};

```

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

```typescript
import { BaseError } from "./base-error.js";
import { ErrorName } from "./error-names.js";

export class UnexpectedError extends BaseError {
  constructor(originalError: unknown) {
    super(
      `An unexpected error occurred: ${originalError}`,
      ErrorName.UNEXPECTED_ERROR
    );
  }
}

```

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

```typescript
export * from "./list-project-files/list-project-files-controller-factory.js";
export * from "./list-projects/list-projects-controller-factory.js";
export * from "./read/read-controller-factory.js";
export * from "./update/update-controller-factory.js";
export * from "./write/write-controller-factory.js";

```

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

```typescript
import {
  ListProjectsUseCase,
  Project,
  ProjectRepository,
} from "./list-projects-protocols.js";

export class ListProjects implements ListProjectsUseCase {
  constructor(private readonly projectRepository: ProjectRepository) {}

  async listProjects(): Promise<Project[]> {
    return this.projectRepository.listProjects();
  }
}

```

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

```typescript
import { ListProjectsUseCase } from "../../../src/domain/usecases/list-projects.js";

export class MockListProjectsUseCase implements ListProjectsUseCase {
  async listProjects(): Promise<string[]> {
    return ["project1", "project2"];
  }
}

export const makeListProjectsUseCase = (): ListProjectsUseCase => {
  return new MockListProjectsUseCase();
};

```

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

```typescript
import { Validator } from "../../../src/presentation/protocols/index.js";

export class MockValidator<T> implements Validator<T> {
  validate<S extends T>(input: S): null;
  validate(input?: any): Error;
  validate(input?: any): Error | null {
    return null;
  }
}

export const makeValidator = <T>(): Validator<T> => {
  return new MockValidator<T>();
};

```

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

```typescript
import { ListProjectsController } from "../../../../presentation/controllers/list-projects/list-projects-controller.js";
import { makeListProjects } from "../../use-cases/list-projects-factory.js";

export const makeListProjectsController = () => {
  const listProjectsUseCase = makeListProjects();
  return new ListProjectsController(listProjectsUseCase);
};

```

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

```typescript
import { WriteFileUseCase } from "../../../domain/usecases/write-file.js";
import {
  Controller,
  Request,
  Response,
  Validator,
} from "../../protocols/index.js";

export interface WriteRequest {
  projectName: string;
  fileName: string;
  content: string;
}

export type WriteResponse = string;

export { Controller, Request, Response, Validator, WriteFileUseCase };

```

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

```typescript
import { Validator } from "../../../../presentation/protocols/validator.js";
import { ValidatorComposite } from "../../../../validators/validator-composite.js";

const makeValidations = (): Validator[] => {
  return [];
};

export const makeListProjectFilesValidation = (): Validator => {
  const validations = makeValidations();
  return new ValidatorComposite(validations);
};

```

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

```typescript
import { ListProjectFilesUseCase } from "../../../domain/usecases/list-project-files.js";
import {
  Controller,
  Request,
  Response,
  Validator,
} from "../../protocols/index.js";

export interface ListProjectFilesRequest {
  projectName: string;
}

export type ListProjectFilesResponse = string[];

export { Controller, ListProjectFilesUseCase, Request, Response, Validator };

```

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

```typescript
import { ListProjects } from "../../../data/usecases/list-projects/list-projects.js";
import { FsProjectRepository } from "../../../infra/filesystem/repositories/fs-project-repository.js";
import { env } from "../../config/env.js";

export const makeListProjects = () => {
  const projectRepository = new FsProjectRepository(env.rootPath);
  return new ListProjects(projectRepository);
};

```

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

```typescript
import { Validator } from "../presentation/protocols/validator.js";

export class ValidatorComposite implements Validator {
  constructor(private readonly validators: Array<Validator>) {}

  validate(input?: any): Error | null {
    for (const validator of this.validators) {
      const error = validator.validate(input);
      if (error) {
        return error;
      }
    }
    return null;
  }
}

```

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

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "node16",
    "moduleResolution": "node16",
    "lib": ["ES2022"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.test.ts"]
} 
```

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

```typescript
import { ReadController } from "../../../../presentation/controllers/read/read-controller.js";
import { makeReadFile } from "../../use-cases/read-file-factory.js";
import { makeReadValidation } from "./read-validation-factory.js";

export const makeReadController = () => {
  const validator = makeReadValidation();
  const readFileUseCase = makeReadFile();

  return new ReadController(readFileUseCase, validator);
};

```

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

```typescript
import { ReadFileUseCase } from "../../../src/domain/usecases/read-file.js";
import { ReadRequest } from "../../../src/presentation/controllers/read/protocols.js";

export class MockReadFileUseCase implements ReadFileUseCase {
  async readFile(params: ReadRequest): Promise<string | null> {
    return "file content";
  }
}

export const makeReadFileUseCase = (): ReadFileUseCase => {
  return new MockReadFileUseCase();
};

```

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

```typescript
import { WriteFileUseCase } from "../../../src/domain/usecases/write-file.js";
import { WriteRequest } from "../../../src/presentation/controllers/write/protocols.js";

export class MockWriteFileUseCase implements WriteFileUseCase {
  async writeFile(params: WriteRequest): Promise<string | null> {
    return null;
  }
}

export const makeWriteFileUseCase = (): WriteFileUseCase => {
  return new MockWriteFileUseCase();
};

```

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

```typescript
import { File } from "../../domain/entities/index.js";

export interface FileRepository {
  listFiles(projectName: string): Promise<File[]>;
  loadFile(projectName: string, fileName: string): Promise<File | null>;
  writeFile(
    projectName: string,
    fileName: string,
    content: string
  ): Promise<File | null>;
  updateFile(
    projectName: string,
    fileName: string,
    content: string
  ): Promise<File | null>;
}

```

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

```typescript
import { WriteController } from "../../../../presentation/controllers/write/write-controller.js";
import { makeWriteFile } from "../../use-cases/write-file-factory.js";
import { makeWriteValidation } from "./write-validation-factory.js";

export const makeWriteController = () => {
  const validator = makeWriteValidation();
  const writeFileUseCase = makeWriteFile();

  return new WriteController(writeFileUseCase, validator);
};

```

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

```typescript
import { UpdateController } from "../../../../presentation/controllers/update/update-controller.js";
import { makeUpdateFile } from "../../use-cases/update-file-factory.js";
import { makeUpdateValidation } from "./update-validation-factory.js";

export const makeUpdateController = () => {
  const validator = makeUpdateValidation();
  const updateFileUseCase = makeUpdateFile();

  return new UpdateController(updateFileUseCase, validator);
};

```

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

```typescript
import { UpdateFileUseCase } from "../../../src/domain/usecases/update-file.js";
import { UpdateRequest } from "../../../src/presentation/controllers/update/protocols.js";

export class MockUpdateFileUseCase implements UpdateFileUseCase {
  async updateFile(params: UpdateRequest): Promise<string | null> {
    return "updated content";
  }
}

export const makeUpdateFileUseCase = (): UpdateFileUseCase => {
  return new MockUpdateFileUseCase();
};

```

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

```typescript
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    globals: true,
    environment: "node",
    include: ["**/*.spec.ts", "**/*.test.ts"],
    exclude: ["**/node_modules/**", "**/dist/**"],
    coverage: {
      provider: "v8",
      reporter: ["text", "json", "html"],
      exclude: [
        "**/node_modules/**",
        "**/dist/**",
        "**/*.d.ts",
        "**/*.spec.ts",
        "**/*.test.ts",
      ],
    },
  },
});

```

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

```typescript
import { UpdateFileUseCase } from "../../../domain/usecases/update-file.js";
import { NotFoundError } from "../../errors/index.js";
import {
  Controller,
  Request,
  Response,
  Validator,
} from "../../protocols/index.js";

export interface UpdateRequest {
  projectName: string;
  fileName: string;
  content: string;
}

export type UpdateResponse = string;
export type RequestValidator = Validator;

export { Controller, NotFoundError, Request, Response, UpdateFileUseCase };

```

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

```typescript
import { ReadFile } from "../../../data/usecases/read-file/read-file.js";
import { FsFileRepository } from "../../../infra/filesystem/index.js";
import { FsProjectRepository } from "../../../infra/filesystem/repositories/fs-project-repository.js";
import { env } from "../../config/env.js";

export const makeReadFile = () => {
  const projectRepository = new FsProjectRepository(env.rootPath);
  const fileRepository = new FsFileRepository(env.rootPath);

  return new ReadFile(fileRepository, projectRepository);
};

```

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

```typescript
import { MissingParamError } from "../presentation/errors/index.js";
import { Validator } from "../presentation/protocols/validator.js";

export class RequiredFieldValidator implements Validator {
  constructor(private readonly fieldName: string) {}

  validate(input?: any): Error | null {
    if (
      !input ||
      (input[this.fieldName] !== 0 &&
        input[this.fieldName] !== false &&
        !input[this.fieldName])
    ) {
      return new MissingParamError(this.fieldName);
    }
    return null;
  }
}

```

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

```typescript
import { WriteFile } from "../../../data/usecases/write-file/write-file.js";
import { FsFileRepository } from "../../../infra/filesystem/index.js";
import { FsProjectRepository } from "../../../infra/filesystem/repositories/fs-project-repository.js";
import { env } from "../../config/env.js";

export const makeWriteFile = () => {
  const projectRepository = new FsProjectRepository(env.rootPath);
  const fileRepository = new FsFileRepository(env.rootPath);

  return new WriteFile(fileRepository, projectRepository);
};

```

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

```typescript
import { UpdateFile } from "../../../data/usecases/update-file/update-file.js";
import { FsFileRepository } from "../../../infra/filesystem/index.js";
import { FsProjectRepository } from "../../../infra/filesystem/repositories/fs-project-repository.js";
import { env } from "../../config/env.js";

export const makeUpdateFile = () => {
  const projectRepository = new FsProjectRepository(env.rootPath);
  const fileRepository = new FsFileRepository(env.rootPath);

  return new UpdateFile(fileRepository, projectRepository);
};

```

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

```typescript
import { ListProjectFilesUseCase } from "../../../src/domain/usecases/list-project-files.js";
import { ListProjectFilesRequest } from "../../../src/presentation/controllers/list-project-files/protocols.js";

export class MockListProjectFilesUseCase implements ListProjectFilesUseCase {
  async listProjectFiles(params: ListProjectFilesRequest): Promise<string[]> {
    return ["file1.txt", "file2.txt"];
  }
}

export const makeListProjectFilesUseCase = (): ListProjectFilesUseCase => {
  return new MockListProjectFilesUseCase();
};

```

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

```typescript
import { ReadFileUseCase } from "../../../domain/usecases/read-file.js";
import { NotFoundError } from "../../errors/index.js";
import {
  Controller,
  Request,
  Response,
  Validator,
} from "../../protocols/index.js";
export interface ReadRequest {
  /**
   * The name of the project containing the file.
   */
  projectName: string;

  /**
   * The name of the file to read.
   */
  fileName: string;
}

export type ReadResponse = string;

export {
  Controller,
  NotFoundError,
  ReadFileUseCase,
  Request,
  Response,
  Validator,
};

```

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

```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
FROM node:20-alpine AS builder

# Copy the entire project
COPY . /app
WORKDIR /app

# Use build cache for faster builds
RUN --mount=type=cache,target=/root/.npm npm ci

FROM node:20-alpine AS release

COPY --from=builder /app/dist /app/dist
COPY --from=builder /app/package.json /app/package.json
COPY --from=builder /app/package-lock.json /app/package-lock.json

WORKDIR /app

RUN npm ci --ignore-scripts --omit=dev

ENTRYPOINT ["node", "dist/main/index.js"]

```

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

```typescript
import { NotFoundError, UnexpectedError } from "../errors/index.js";
import { type Response } from "../protocols/index.js";

export const badRequest = (error: Error): Response => ({
  statusCode: 400,
  body: error,
});

export const notFound = (resourceName: string): Response => ({
  statusCode: 404,
  body: new NotFoundError(resourceName),
});

export const serverError = (error: Error): Response => ({
  statusCode: 500,
  body: new UnexpectedError(error),
});

export const ok = (data: any): Response => ({
  statusCode: 200,
  body: data,
});

```

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

```typescript
import { ListProjectFiles } from "../../../data/usecases/list-project-files/list-project-files.js";
import { FsFileRepository } from "../../../infra/filesystem/index.js";
import { FsProjectRepository } from "../../../infra/filesystem/repositories/fs-project-repository.js";
import { env } from "../../config/env.js";

export const makeListProjectFiles = () => {
  const projectRepository = new FsProjectRepository(env.rootPath);
  const fileRepository = new FsFileRepository(env.rootPath);

  return new ListProjectFiles(fileRepository, projectRepository);
};

```

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

```typescript
import { ListProjectFilesController } from "../../../../presentation/controllers/list-project-files/list-project-files-controller.js";
import { makeListProjectFiles } from "../../use-cases/list-project-files-factory.js";
import { makeListProjectFilesValidation } from "./list-project-files-validation-factory.js";

export const makeListProjectFilesController = () => {
  const validator = makeListProjectFilesValidation();
  const listProjectFilesUseCase = makeListProjectFiles();

  return new ListProjectFilesController(listProjectFilesUseCase, validator);
};

```

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

```typescript
import { ok, serverError } from "../../helpers/index.js";
import {
  Controller,
  ListProjectsResponse,
  ListProjectsUseCase,
  Response,
} from "./protocols.js";

export class ListProjectsController
  implements Controller<void, ListProjectsResponse>
{
  constructor(private readonly listProjectsUseCase: ListProjectsUseCase) {}

  async handle(): Promise<Response<ListProjectsResponse>> {
    try {
      const projects = await this.listProjectsUseCase.listProjects();
      return ok(projects);
    } catch (error) {
      return serverError(error as Error);
    }
  }
}

```

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

```typescript
import { ProjectRepository } from "../../../src/data/protocols/project-repository.js";
import { Project } from "../../../src/domain/entities/project.js";

export class MockProjectRepository implements ProjectRepository {
  private projects = ["project-1", "project-2"];

  async listProjects(): Promise<Project[]> {
    return this.projects;
  }

  async projectExists(name: string): Promise<boolean> {
    return this.projects.includes(name);
  }

  async ensureProject(name: string): Promise<void> {
    if (!this.projects.includes(name)) {
      this.projects.push(name);
    }
  }
}

```

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

```typescript
import {
  FileRepository,
  ProjectRepository,
  ReadFileParams,
  ReadFileUseCase,
} from "./read-file-protocols.js";

export class ReadFile implements ReadFileUseCase {
  constructor(
    private readonly fileRepository: FileRepository,
    private readonly projectRepository: ProjectRepository
  ) {}

  async readFile(params: ReadFileParams): Promise<string | null> {
    const { projectName, fileName } = params;

    const projectExists = await this.projectRepository.projectExists(
      projectName
    );
    if (!projectExists) {
      return null;
    }

    return this.fileRepository.loadFile(projectName, fileName);
  }
}

```

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

```typescript
import { InvalidParamError } from "../presentation/errors/index.js";
import { Validator } from "../presentation/protocols/validator.js";

export class PathSecurityValidator implements Validator {
  constructor(private readonly fieldName: string) {}

  validate(input?: any): Error | null {
    if (!input || !input[this.fieldName]) {
      return null;
    }

    const value = input[this.fieldName];
    if (
      typeof value === "string" &&
      (value.includes("..") || value.includes("/"))
    ) {
      return new InvalidParamError(
        `${this.fieldName} contains invalid path segments`
      );
    }

    return null;
  }
}

```

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

```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml

startCommand:
  type: stdio
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    required:
      - memoryBankRoot
    properties:
      memoryBankRoot:
        type: string
        description: The root directory for memory bank projects.
  commandFunction:
    # A function that produces the CLI command to start the MCP on stdio.
    |-
    (config) => ({command:'node',args:['dist/index.js'],env:{MEMORY_BANK_ROOT:config.memoryBankRoot}})

# Add the build section for Docker support
build:
  dockerBuildPath: ./

```

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

```typescript
import { InvalidParamError } from "../presentation/errors/index.js";
import { Validator } from "../presentation/protocols/validator.js";
import { NAME_REGEX } from "./constants.js";

export class ParamNameValidator implements Validator {
  constructor(
    private readonly fieldName: string,
    private readonly regex: RegExp = NAME_REGEX
  ) {}

  validate(input?: any): Error | null {
    if (!input || !input[this.fieldName]) {
      return null;
    }

    const paramName = input[this.fieldName];
    const isValid = this.regex.test(paramName);

    if (!isValid) {
      return new InvalidParamError(paramName);
    }

    return null;
  }
}

```

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

```typescript
import { Validator } from "../../../../presentation/protocols/validator.js";
import {
  RequiredFieldValidator,
  ValidatorComposite,
} from "../../../../validators/index.js";
import { PathSecurityValidator } from "../../../../validators/path-security-validator.js";

const makeValidations = (): Validator[] => {
  return [
    new RequiredFieldValidator("projectName"),
    new RequiredFieldValidator("fileName"),
    new PathSecurityValidator("projectName"),
    new PathSecurityValidator("fileName"),
  ];
};

export const makeReadValidation = (): Validator => {
  const validations = makeValidations();
  return new ValidatorComposite(validations);
};

```

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

```typescript
import {
  FileRepository,
  ListProjectFilesParams,
  ListProjectFilesUseCase,
  ProjectRepository,
} from "./list-project-files-protocols.js";

export class ListProjectFiles implements ListProjectFilesUseCase {
  constructor(
    private readonly fileRepository: FileRepository,
    private readonly projectRepository: ProjectRepository
  ) {}

  async listProjectFiles(params: ListProjectFilesParams): Promise<string[]> {
    const { projectName } = params;
    const projectExists = await this.projectRepository.projectExists(
      projectName
    );

    if (!projectExists) {
      return [];
    }

    return this.fileRepository.listFiles(projectName);
  }
}

```

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

```typescript
import { Validator } from "../../../../presentation/protocols/validator.js";
import {
  ParamNameValidator,
  RequiredFieldValidator,
  ValidatorComposite,
} from "../../../../validators/index.js";
import { PathSecurityValidator } from "../../../../validators/path-security-validator.js";

const makeValidations = (): Validator[] => {
  return [
    new RequiredFieldValidator("projectName"),
    new RequiredFieldValidator("fileName"),
    new RequiredFieldValidator("content"),
    new ParamNameValidator("projectName"),
    new ParamNameValidator("fileName"),
    new PathSecurityValidator("projectName"),
    new PathSecurityValidator("fileName"),
  ];
};

export const makeWriteValidation = (): Validator => {
  const validations = makeValidations();
  return new ValidatorComposite(validations);
};

```

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

```typescript
import { Validator } from "../../../../presentation/protocols/validator.js";
import {
  ParamNameValidator,
  RequiredFieldValidator,
  ValidatorComposite,
} from "../../../../validators/index.js";
import { PathSecurityValidator } from "../../../../validators/path-security-validator.js";

const makeValidations = (): Validator[] => {
  return [
    new RequiredFieldValidator("projectName"),
    new RequiredFieldValidator("fileName"),
    new RequiredFieldValidator("content"),
    new ParamNameValidator("projectName"),
    new ParamNameValidator("fileName"),
    new PathSecurityValidator("projectName"),
    new PathSecurityValidator("fileName"),
  ];
};

export const makeUpdateValidation = (): Validator => {
  const validations = makeValidations();
  return new ValidatorComposite(validations);
};

```

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

```typescript
import {
  FileRepository,
  ProjectRepository,
  WriteFileParams,
  WriteFileUseCase,
} from "./write-file-protocols.js";

export class WriteFile implements WriteFileUseCase {
  constructor(
    private readonly fileRepository: FileRepository,
    private readonly projectRepository: ProjectRepository
  ) {}

  async writeFile(params: WriteFileParams): Promise<string | null> {
    const { projectName, fileName, content } = params;

    await this.projectRepository.ensureProject(projectName);

    const existingFile = await this.fileRepository.loadFile(
      projectName,
      fileName
    );
    if (existingFile !== null) {
      return null;
    }

    await this.fileRepository.writeFile(projectName, fileName, content);
    return await this.fileRepository.loadFile(projectName, fileName);
  }
}

```

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

```typescript
interface SerializedError {
  name: string;
  error: string;
  stack?: string;
  cause?: string | SerializedError;
  code?: string | number;
}

export const serializeError = (
  error: unknown,
  includeStack = false
): SerializedError => {
  if (error instanceof Error) {
    const serialized: SerializedError = {
      name: error.name,
      error: error.message,
    };

    if (includeStack) {
      serialized.stack = error.stack;
    }

    if ("cause" in error && error.cause) {
      serialized.cause =
        error.cause instanceof Error
          ? serializeError(error.cause, includeStack)
          : String(error.cause);
    }

    if (
      "code" in error &&
      (typeof error.code === "string" || typeof error.code === "number")
    ) {
      serialized.code = error.code;
    }

    return serialized;
  }

  return {
    name: "UnknownError",
    error: String(error),
  };
};

```

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

```typescript
import {
  FileRepository,
  ProjectRepository,
  UpdateFileParams,
  UpdateFileUseCase,
} from "./update-file-protocols.js";

export class UpdateFile implements UpdateFileUseCase {
  constructor(
    private readonly fileRepository: FileRepository,
    private readonly projectRepository: ProjectRepository
  ) {}

  async updateFile(params: UpdateFileParams): Promise<string | null> {
    const { projectName, fileName, content } = params;

    const projectExists = await this.projectRepository.projectExists(
      projectName
    );
    if (!projectExists) {
      return null;
    }

    const existingFile = await this.fileRepository.loadFile(
      projectName,
      fileName
    );
    if (existingFile === null) {
      return null;
    }

    await this.fileRepository.updateFile(projectName, fileName, content);
    return await this.fileRepository.loadFile(projectName, fileName);
  }
}

```

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

```markdown
---
name: Bug report
about: Create a report to help us improve
title: ""
labels: "bug"
assignees: ""
---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:

1. Configure MCP server with '...'
2. Initialize project with '...'
3. Try to access '...'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Environment:**

- OS: [e.g., macOS, Windows, Linux]
- Node Version: [e.g., 18.x]
- Package Version: [e.g., 0.1.0]
- MCP Integration: [e.g., Cline extension, Claude desktop]

**Memory Bank Configuration:**

```json
// Your MCP server configuration
{
  "memory-bank": {
    ...
  }
}
```

**Error Output**

```
Paste any error messages or logs here
```

**Additional context**
Add any other context about the problem here, such as project structure or specific memory bank files affected.

```

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

```typescript
import { badRequest, notFound, ok, serverError } from "../../helpers/index.js";
import {
  Controller,
  ReadFileUseCase,
  ReadRequest,
  ReadResponse,
  Request,
  Response,
  Validator,
} from "./protocols.js";

export class ReadController implements Controller<ReadRequest, ReadResponse> {
  constructor(
    private readonly readFileUseCase: ReadFileUseCase,
    private readonly validator: Validator
  ) {}

  async handle(request: Request<ReadRequest>): Promise<Response<ReadResponse>> {
    try {
      const validationError = this.validator.validate(request.body);
      if (validationError) {
        return badRequest(validationError);
      }

      const { projectName, fileName } = request.body!;

      const content = await this.readFileUseCase.readFile({
        projectName,
        fileName,
      });

      if (content === null) {
        return notFound(fileName);
      }

      return ok(content);
    } catch (error) {
      return serverError(error as Error);
    }
  }
}

```

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

```typescript
import {
  Request as MCPRequest,
  ServerResult as MCPResponse,
} from "@modelcontextprotocol/sdk/types.js";
import { Controller } from "../../../../presentation/protocols/controller.js";
import { serializeError } from "../helpers/serialize-error.js";
import { MCPRequestHandler } from "./mcp-router-adapter.js";

export const adaptMcpRequestHandler = async <
  T extends any,
  R extends Error | any
>(
  controller: Controller<T, R>
): Promise<MCPRequestHandler> => {
  return async (request: MCPRequest): Promise<MCPResponse> => {
    const { params } = request;
    const body = params?.arguments as T;
    const response = await controller.handle({
      body,
    });

    const isError = response.statusCode < 200 || response.statusCode >= 300;

    return {
      tools: [],
      isError,
      content: [
        {
          type: "text",
          text: isError
            ? JSON.stringify(serializeError(response.body))
            : response.body?.toString(),
        },
      ],
    };
  };
};

```

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

```typescript
import { badRequest, ok, serverError } from "../../helpers/index.js";
import {
  Controller,
  ListProjectFilesRequest,
  ListProjectFilesResponse,
  ListProjectFilesUseCase,
  Request,
  Response,
  Validator,
} from "./protocols.js";

export class ListProjectFilesController
  implements Controller<ListProjectFilesRequest, ListProjectFilesResponse>
{
  constructor(
    private readonly listProjectFilesUseCase: ListProjectFilesUseCase,
    private readonly validator: Validator
  ) {}

  async handle(
    request: Request<ListProjectFilesRequest>
  ): Promise<Response<ListProjectFilesResponse>> {
    try {
      const validationError = this.validator.validate(request.body);
      if (validationError) {
        return badRequest(validationError);
      }

      const { projectName } = request.body!;

      const files = await this.listProjectFilesUseCase.listProjectFiles({
        projectName,
      });

      return ok(files);
    } catch (error) {
      return serverError(error as Error);
    }
  }
}

```

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

```typescript
import { badRequest, ok, serverError } from "../../helpers/index.js";
import {
  Controller,
  Request,
  Response,
  Validator,
  WriteFileUseCase,
  WriteRequest,
  WriteResponse,
} from "./protocols.js";

export class WriteController
  implements Controller<WriteRequest, WriteResponse>
{
  constructor(
    private readonly writeFileUseCase: WriteFileUseCase,
    private readonly validator: Validator
  ) {}

  async handle(
    request: Request<WriteRequest>
  ): Promise<Response<WriteResponse>> {
    try {
      const validationError = this.validator.validate(request.body);
      if (validationError) {
        return badRequest(validationError);
      }

      const { projectName, fileName, content } = request.body!;

      await this.writeFileUseCase.writeFile({
        projectName,
        fileName,
        content,
      });

      return ok(
        `File ${fileName} written successfully to project ${projectName}`
      );
    } catch (error) {
      return serverError(error as Error);
    }
  }
}

```

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

```markdown
# Description

Please include a summary of the changes and which issue is fixed. Include relevant motivation and context.

Fixes # (issue)

## Type of change

Please delete options that are not relevant.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update

## Checklist

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] I have updated the documentation accordingly
- [ ] My changes maintain project isolation and security
- [ ] I have tested my changes with the MCP server running
- [ ] I have verified the memory bank file operations still work correctly

## Additional Notes

Add any other context about the PR here.

```

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

```markdown
---
name: Feature request
about: Suggest an idea for this project
title: ""
labels: "enhancement"
assignees: ""
---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Memory Bank Impact**
How would this feature affect:

- Project isolation/security
- Memory bank file structure
- MCP tool interactions
- User workflow
- Configuration requirements

**Additional context**
Add any other context or screenshots about the feature request here.

**Example Usage**
If applicable, provide an example of how you envision using this feature:

```typescript
// Example code or configuration
```

or

```json
// Example MCP tool usage or configuration
{
  "tool_name": "new_feature",
  "arguments": {
    "param1": "value1"
  }
}
```

```

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

```typescript
import {
  Request as MCPRequest,
  ServerResult as MCPResponse,
  Tool,
} from "@modelcontextprotocol/sdk/types.js";

export type MCPRequestHandler = (request: MCPRequest) => Promise<MCPResponse>;

export type MCPRoute = {
  schema: Tool;
  handler: Promise<MCPRequestHandler>;
};

export class McpRouterAdapter {
  private tools: Map<string, MCPRoute> = new Map();

  public getToolHandler(name: string): MCPRoute["handler"] | undefined {
    return this.tools.get(name)?.handler;
  }

  private mapTools(callback: (name: string) => any) {
    return Array.from(this.tools.keys()).map(callback);
  }

  public getToolsSchemas() {
    return Array.from(this.tools.keys()).map(
      (name) => this.tools.get(name)?.schema
    );
  }

  public getToolCapabilities() {
    return Array.from(this.tools.keys()).reduce((acc, name: string) => {
      acc[name] = this.tools.get(name)?.schema!;
      return acc;
    }, {} as Record<string, Tool>);
  }

  public setTool({ schema, handler }: MCPRoute): McpRouterAdapter {
    this.tools.set(schema.name, { schema, handler });
    return this;
  }
}

```

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

```typescript
import { badRequest, notFound, ok, serverError } from "../../helpers/index.js";
import {
  Controller,
  Request,
  RequestValidator,
  Response,
  UpdateFileUseCase,
  UpdateRequest,
  UpdateResponse,
} from "./protocols.js";

export class UpdateController
  implements Controller<UpdateRequest, UpdateResponse>
{
  constructor(
    private readonly updateFileUseCase: UpdateFileUseCase,
    private readonly validator: RequestValidator
  ) {}

  async handle(
    request: Request<UpdateRequest>
  ): Promise<Response<UpdateResponse>> {
    try {
      const validationError = this.validator.validate(request.body);
      if (validationError) {
        return badRequest(validationError);
      }

      const { projectName, fileName, content } = request.body!;

      const result = await this.updateFileUseCase.updateFile({
        projectName,
        fileName,
        content,
      });

      if (result === null) {
        return notFound(fileName);
      }

      return ok(
        `File ${fileName} updated successfully in project ${projectName}`
      );
    } catch (error) {
      return serverError(error as Error);
    }
  }
}

```

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

```typescript
import { beforeEach, describe, expect, test, vi } from "vitest";
import { ProjectRepository } from "../../../../src/data/protocols/project-repository.js";
import { ListProjects } from "../../../../src/data/usecases/list-projects/list-projects.js";
import { MockProjectRepository } from "../../mocks/index.js";

describe("ListProjects UseCase", () => {
  let sut: ListProjects;
  let projectRepositoryStub: ProjectRepository;

  beforeEach(() => {
    projectRepositoryStub = new MockProjectRepository();
    sut = new ListProjects(projectRepositoryStub);
  });

  test("should call ProjectRepository.listProjects()", async () => {
    const listProjectsSpy = vi.spyOn(projectRepositoryStub, "listProjects");

    await sut.listProjects();

    expect(listProjectsSpy).toHaveBeenCalledTimes(1);
  });

  test("should return a list of projects on success", async () => {
    const projects = await sut.listProjects();

    expect(projects).toEqual(["project-1", "project-2"]);
  });

  test("should propagate errors if repository throws", async () => {
    const error = new Error("Repository error");
    vi.spyOn(projectRepositoryStub, "listProjects").mockRejectedValueOnce(
      error
    );

    await expect(sut.listProjects()).rejects.toThrow(error);
  });
});

```

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

```json
{
  "name": "@allpepper/memory-bank-mcp",
  "version": "0.2.1",
  "description": "MCP server for remote management of project memory banks",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/alioshr/memory-bank-mcp.git"
  },
  "keywords": [
    "mcp",
    "memory-bank",
    "project-management",
    "documentation",
    "cline"
  ],
  "bugs": {
    "url": "https://github.com/alioshr/memory-bank-mcp/issues"
  },
  "homepage": "https://github.com/alioshr/memory-bank-mcp#readme",
  "main": "dist/main/index.js",
  "files": [
    "dist"
  ],
  "author": "Aliosh Pimenta (alioshr)",
  "license": "MIT",
  "type": "module",
  "bin": {
    "mcp-server-memory-bank": "dist/main/index.js"
  },
  "scripts": {
    "build": "tsc && shx chmod +x dist/**/*.js",
    "prepare": "npm run build",
    "dev": "ts-node src/main/index.ts",
    "test": "vitest run",
    "test:watch": "vitest",
    "test:ui": "vitest --ui",
    "test:coverage": "vitest run --coverage"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.5.0",
    "fs-extra": "^11.2.0"
  },
  "devDependencies": {
    "@types/fs-extra": "^11.0.4",
    "@types/node": "^20.11.19",
    "@vitest/coverage-istanbul": "^3.0.8",
    "@vitest/coverage-v8": "^3.1.1",
    "@vitest/ui": "^3.0.8",
    "shx": "^0.4.0",
    "ts-node": "^10.9.2",
    "typescript": "^5.8.2",
    "vitest": "^3.0.8"
  }
}

```

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

```typescript
import { describe, expect, it } from "vitest";
import {
  NotFoundError,
  UnexpectedError,
} from "../../../src/presentation/errors/index.js";
import {
  badRequest,
  notFound,
  ok,
  serverError,
} from "../../../src/presentation/helpers/index.js";

describe("HTTP Helpers", () => {
  describe("badRequest", () => {
    it("should return 400 status code and the error", () => {
      const error = new Error("any_error");
      const response = badRequest(error);
      expect(response).toEqual({
        statusCode: 400,
        body: error,
      });
    });
  });

  describe("notFound", () => {
    it("should return 404 status code and the error", () => {
      const response = notFound("any_error");
      expect(response).toEqual({
        statusCode: 404,
        body: new NotFoundError("any_error"),
      });
    });
  });

  describe("serverError", () => {
    it("should return 500 status code and wrap the error in UnexpectedError", () => {
      const error = new Error("any_error");
      const response = serverError(error);
      expect(response).toEqual({
        statusCode: 500,
        body: new UnexpectedError(error),
      });
    });
  });

  describe("ok", () => {
    it("should return 200 status code and the data", () => {
      const data = { name: "any_name" };
      const response = ok(data);
      expect(response).toEqual({
        statusCode: 200,
        body: data,
      });
    });
  });
});

```

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

```typescript
import { FileRepository } from "../../../src/data/protocols/file-repository.js";

export class MockFileRepository implements FileRepository {
  private projectFiles: Record<string, Record<string, string>> = {
    "project-1": {
      "file1.md": "Content of file1.md",
      "file2.md": "Content of file2.md",
    },
    "project-2": {
      "fileA.md": "Content of fileA.md",
      "fileB.md": "Content of fileB.md",
    },
  };

  async listFiles(projectName: string): Promise<string[]> {
    return Object.keys(this.projectFiles[projectName] || {});
  }

  async loadFile(
    projectName: string,
    fileName: string
  ): Promise<string | null> {
    if (
      this.projectFiles[projectName] &&
      this.projectFiles[projectName][fileName]
    ) {
      return this.projectFiles[projectName][fileName];
    }
    return null;
  }

  async writeFile(
    projectName: string,
    fileName: string,
    content: string
  ): Promise<string | null> {
    if (!this.projectFiles[projectName]) {
      this.projectFiles[projectName] = {};
    }
    this.projectFiles[projectName][fileName] = content;
    return content;
  }

  async updateFile(
    projectName: string,
    fileName: string,
    content: string
  ): Promise<string | null> {
    if (
      this.projectFiles[projectName] &&
      this.projectFiles[projectName][fileName]
    ) {
      this.projectFiles[projectName][fileName] = content;
      return content;
    }
    return null;
  }
}

```

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

```typescript
import { describe, expect, it, vi } from "vitest";
import { ListProjectsController } from "../../../../src/presentation/controllers/list-projects/list-projects-controller.js";
import { UnexpectedError } from "../../../../src/presentation/errors/index.js";
import { makeListProjectsUseCase } from "../../mocks/index.js";

const makeSut = () => {
  const listProjectsUseCaseStub = makeListProjectsUseCase();
  const sut = new ListProjectsController(listProjectsUseCaseStub);
  return {
    sut,
    listProjectsUseCaseStub,
  };
};

describe("ListProjectsController", () => {
  it("should call ListProjectsUseCase", async () => {
    const { sut, listProjectsUseCaseStub } = makeSut();
    const listProjectsSpy = vi.spyOn(listProjectsUseCaseStub, "listProjects");
    await sut.handle();
    expect(listProjectsSpy).toHaveBeenCalled();
  });

  it("should return 500 if ListProjectsUseCase throws", async () => {
    const { sut, listProjectsUseCaseStub } = makeSut();
    vi.spyOn(listProjectsUseCaseStub, "listProjects").mockRejectedValueOnce(
      new Error("any_error")
    );
    const response = await sut.handle();
    expect(response).toEqual({
      statusCode: 500,
      body: new UnexpectedError(new Error("any_error")),
    });
  });

  it("should return 200 with projects on success", async () => {
    const { sut } = makeSut();
    const response = await sut.handle();
    expect(response).toEqual({
      statusCode: 200,
      body: ["project1", "project2"],
    });
  });
});

```

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

```typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ErrorCode,
  ListToolsRequestSchema,
  McpError,
  ServerResult as MCPResponse,
} from "@modelcontextprotocol/sdk/types.js";
import { McpRouterAdapter } from "./mcp-router-adapter.js";

export class McpServerAdapter {
  private server: Server | null = null;

  constructor(private readonly mcpRouter: McpRouterAdapter) {}

  public register({ name, version }: { name: string; version: string }) {
    this.server = new Server(
      {
        name,
        version,
      },
      {
        capabilities: {
          tools: this.mcpRouter.getToolCapabilities(),
        },
      }
    );

    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: this.mcpRouter.getToolsSchemas(),
    }));

    this.server.setRequestHandler(
      CallToolRequestSchema,
      async (request): Promise<MCPResponse> => {
        const { name } = request.params;
        const handler = await this.mcpRouter.getToolHandler(name);
        if (!handler) {
          throw new McpError(
            ErrorCode.MethodNotFound,
            `Tool ${name} not found`
          );
        }
        return await handler(request);
      }
    );
  }

  async start(): Promise<void> {
    if (!this.server) {
      throw new Error("Server not initialized");
    }

    const transport = new StdioServerTransport();
    try {
      await this.server.connect(transport);
      console.log("Memory Bank MCP server running on stdio");
    } catch (error) {
      console.error(error);
    }
  }
}

```

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

```typescript
import fs from "fs-extra";
import path from "path";
import { ProjectRepository } from "../../../data/protocols/project-repository.js";
import { Project } from "../../../domain/entities/index.js";

/**
 * Filesystem implementation of the ProjectRepository protocol
 */
export class FsProjectRepository implements ProjectRepository {
  /**
   * Creates a new FsProjectRepository
   * @param rootDir The root directory where all projects are stored
   */
  constructor(private readonly rootDir: string) {}

  /**
   * Builds a path to a project directory
   * @param projectName The name of the project
   * @returns The full path to the project directory
   * @private
   */
  private buildProjectPath(projectName: string): string {
    return path.join(this.rootDir, projectName);
  }

  /**
   * Lists all available projects
   * @returns An array of Project objects
   */
  async listProjects(): Promise<Project[]> {
    const entries = await fs.readdir(this.rootDir, { withFileTypes: true });
    const projects: Project[] = entries
      .filter((entry) => entry.isDirectory())
      .map((entry) => entry.name);

    return projects;
  }

  /**
   * Checks if a project exists
   * @param name The name of the project
   * @returns True if the project exists, false otherwise
   */
  async projectExists(name: string): Promise<boolean> {
    const projectPath = this.buildProjectPath(name);
    // If path doesn't exist, fs.stat will throw an error which will propagate
    const stat = await fs.stat(projectPath);
    return stat.isDirectory();
  }

  /**
   * Ensures a project directory exists, creating it if necessary
   * @param name The name of the project
   */
  async ensureProject(name: string): Promise<void> {
    const projectPath = this.buildProjectPath(name);
    await fs.ensureDir(projectPath);
  }
}

```

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

```typescript
import { beforeEach, describe, expect, it } from "vitest";
import { Validator } from "../../src/presentation/protocols/validator.js";
import { ValidatorComposite } from "../../src/validators/validator-composite.js";

interface TestInput {
  field: string;
}

class ValidatorStub implements Validator {
  error: Error | null = null;
  callCount = 0;
  input: any = null;

  validate(input?: any): Error | null {
    this.callCount++;
    this.input = input;
    return this.error;
  }
}

describe("ValidatorComposite", () => {
  let validator1: ValidatorStub;
  let validator2: ValidatorStub;
  let sut: ValidatorComposite;

  beforeEach(() => {
    validator1 = new ValidatorStub();
    validator2 = new ValidatorStub();
    sut = new ValidatorComposite([validator1, validator2]);
  });

  it("should call validate with correct input in all validators", () => {
    const input = { field: "any_value" };
    sut.validate(input);
    expect(validator1.input).toBe(input);
    expect(validator2.input).toBe(input);
  });

  it("should return the first error if any validator fails", () => {
    const error = new Error("validator_error");
    validator1.error = error;

    const result = sut.validate({ field: "any_value" });

    expect(result).toBe(error);
  });

  it("should return the second validator error if the first validator passes", () => {
    const error = new Error("validator_error");
    validator2.error = error;

    const result = sut.validate({ field: "any_value" });

    expect(result).toBe(error);
  });

  it("should return null if all validators pass", () => {
    const result = sut.validate({ field: "any_value" });

    expect(result).toBeNull();
  });

  it("should call validators in the order they were passed to the constructor", () => {
    sut.validate({ field: "any_value" });

    expect(validator1.callCount).toBe(1);
    expect(validator2.callCount).toBe(1);
  });

  it("should stop validating after first error is found", () => {
    validator1.error = new Error("validator_error");

    sut.validate({ field: "any_value" });

    expect(validator1.callCount).toBe(1);
    expect(validator2.callCount).toBe(0);
  });
});

```

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

```typescript
import fs from "fs-extra";
import os from "os";
import path from "path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { FsProjectRepository } from "../../../../src/infra/filesystem/repositories/fs-project-repository.js";

describe("FsProjectRepository", () => {
  let tempDir: string;
  let repository: FsProjectRepository;

  beforeEach(() => {
    // Create a temporary directory for tests
    tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "memory-bank-test-"));
    repository = new FsProjectRepository(tempDir);
  });

  afterEach(() => {
    // Clean up after tests
    fs.removeSync(tempDir);
  });

  describe("listProjects", () => {
    it("should return an empty array when no projects exist", async () => {
      const result = await repository.listProjects();
      expect(result).toEqual([]);
    });

    it("should return project directories as Project objects", async () => {
      // Create test directories
      await fs.mkdir(path.join(tempDir, "project1"));
      await fs.mkdir(path.join(tempDir, "project2"));
      // Create a file to ensure it's not returned
      await fs.writeFile(path.join(tempDir, "not-a-project.txt"), "test");

      const result = await repository.listProjects();

      expect(result).toHaveLength(2);
      expect(result).toEqual(expect.arrayContaining(["project1", "project2"]));
    });
  });

  describe("projectExists", () => {
    it("should throw an error when project path cannot be accessed", async () => {
      // We now let errors propagate, so stat errors will throw
      const nonExistentProject = "non-existent-project";

      await expect(
        repository.projectExists(nonExistentProject)
      ).rejects.toThrow();
    });

    it("should return true when project exists", async () => {
      await fs.mkdir(path.join(tempDir, "existing-project"));

      const result = await repository.projectExists("existing-project");

      expect(result).toBe(true);
    });
  });

  describe("ensureProject", () => {
    it("should create project directory if it does not exist", async () => {
      await repository.ensureProject("new-project");

      const projectPath = path.join(tempDir, "new-project");
      const exists = await fs.pathExists(projectPath);

      expect(exists).toBe(true);
    });

    it("should not throw if project directory already exists", async () => {
      await fs.mkdir(path.join(tempDir, "existing-project"));

      await expect(
        repository.ensureProject("existing-project")
      ).resolves.not.toThrow();
    });
  });
});

```

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

```typescript
import { beforeEach, describe, expect, test, vi } from "vitest";
import { FileRepository } from "../../../../src/data/protocols/file-repository.js";
import { ProjectRepository } from "../../../../src/data/protocols/project-repository.js";
import { ListProjectFiles } from "../../../../src/data/usecases/list-project-files/list-project-files.js";
import { ListProjectFilesParams } from "../../../../src/domain/usecases/list-project-files.js";
import {
  MockFileRepository,
  MockProjectRepository,
} from "../../mocks/index.js";

describe("ListProjectFiles UseCase", () => {
  let sut: ListProjectFiles;
  let fileRepositoryStub: FileRepository;
  let projectRepositoryStub: ProjectRepository;

  beforeEach(() => {
    fileRepositoryStub = new MockFileRepository();
    projectRepositoryStub = new MockProjectRepository();
    sut = new ListProjectFiles(fileRepositoryStub, projectRepositoryStub);
  });

  test("should call ProjectRepository.projectExists with correct projectName", async () => {
    const projectExistsSpy = vi.spyOn(projectRepositoryStub, "projectExists");
    const params: ListProjectFilesParams = { projectName: "project-1" };

    await sut.listProjectFiles(params);

    expect(projectExistsSpy).toHaveBeenCalledWith("project-1");
  });

  test("should return empty array if project does not exist", async () => {
    vi.spyOn(projectRepositoryStub, "projectExists").mockResolvedValueOnce(
      false
    );
    const params: ListProjectFilesParams = {
      projectName: "non-existent-project",
    };

    const result = await sut.listProjectFiles(params);

    expect(result).toEqual([]);
  });

  test("should call FileRepository.listFiles with correct projectName if project exists", async () => {
    vi.spyOn(projectRepositoryStub, "projectExists").mockResolvedValueOnce(
      true
    );
    const listFilesSpy = vi.spyOn(fileRepositoryStub, "listFiles");
    const params: ListProjectFilesParams = { projectName: "project-1" };

    await sut.listProjectFiles(params);

    expect(listFilesSpy).toHaveBeenCalledWith("project-1");
  });

  test("should return files list on success", async () => {
    const params: ListProjectFilesParams = { projectName: "project-1" };

    const files = await sut.listProjectFiles(params);

    expect(files).toEqual(["file1.md", "file2.md"]);
  });

  test("should propagate errors if repository throws", async () => {
    const error = new Error("Repository error");
    vi.spyOn(projectRepositoryStub, "projectExists").mockRejectedValueOnce(
      error
    );
    const params: ListProjectFilesParams = { projectName: "project-1" };

    await expect(sut.listProjectFiles(params)).rejects.toThrow(error);
  });
});

```

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

```typescript
import { describe, expect, it } from "vitest";
import { InvalidParamError } from "../../src/presentation/errors/index.js";
import { PathSecurityValidator } from "../../src/validators/path-security-validator.js";

describe("PathSecurityValidator", () => {
  it("should return null if field is not provided", () => {
    const sut = new PathSecurityValidator("field");
    const input = {};
    const error = sut.validate(input);

    expect(error).toBeNull();
  });

  it("should return null if input is null", () => {
    const sut = new PathSecurityValidator("field");
    const error = sut.validate(null);

    expect(error).toBeNull();
  });

  it("should return null if input is undefined", () => {
    const sut = new PathSecurityValidator("field");
    const error = sut.validate(undefined);

    expect(error).toBeNull();
  });

  it("should return InvalidParamError if field contains directory traversal (..)", () => {
    const sut = new PathSecurityValidator("field");
    const input = { field: "something/../etc/passwd" };
    const error = sut.validate(input);

    expect(error).toBeInstanceOf(InvalidParamError);
    expect(error?.message).toBe(
      "Invalid parameter: field contains invalid path segments"
    );
  });

  it("should return InvalidParamError if field contains directory traversal (..) even without slashes", () => {
    const sut = new PathSecurityValidator("field");
    const input = { field: "something..etc" };
    const error = sut.validate(input);

    expect(error).toBeInstanceOf(InvalidParamError);
    expect(error?.message).toBe(
      "Invalid parameter: field contains invalid path segments"
    );
  });

  it("should return InvalidParamError if field contains forward slashes", () => {
    const sut = new PathSecurityValidator("field");
    const input = { field: "path/to/file" };
    const error = sut.validate(input);

    expect(error).toBeInstanceOf(InvalidParamError);
    expect(error?.message).toBe(
      "Invalid parameter: field contains invalid path segments"
    );
  });

  it("should return null if field is a valid string without path segments", () => {
    const sut = new PathSecurityValidator("field");
    const input = { field: "validname123" };
    const error = sut.validate(input);

    expect(error).toBeNull();
  });

  it("should return null if field contains periods but not double periods", () => {
    const sut = new PathSecurityValidator("field");
    const input = { field: "filename.txt" };
    const error = sut.validate(input);

    expect(error).toBeNull();
  });

  it("should ignore non-string fields", () => {
    const sut = new PathSecurityValidator("field");
    const input = { field: 123 as any };
    const error = sut.validate(input);

    expect(error).toBeNull();
  });
});

```

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

```typescript
import { describe, expect, it } from "vitest";
import { MissingParamError } from "../../src/presentation/errors/index.js";
import { RequiredFieldValidator } from "../../src/validators/required-field-validator.js";

describe("RequiredFieldValidator", () => {
  it("should return MissingParamError if field is not provided", () => {
    const sut = new RequiredFieldValidator("field");
    const input = {};
    const error = sut.validate(input);

    expect(error).toBeInstanceOf(MissingParamError);
    expect(error?.message).toBe("Missing parameter: field");
  });

  it("should return MissingParamError if field is undefined", () => {
    const sut = new RequiredFieldValidator("field");
    const input = { field: undefined };
    const error = sut.validate(input);

    expect(error).toBeInstanceOf(MissingParamError);
    expect(error?.message).toBe("Missing parameter: field");
  });

  it("should return MissingParamError if field is null", () => {
    const sut = new RequiredFieldValidator("field");
    const input = { field: null };
    const error = sut.validate(input);

    expect(error).toBeInstanceOf(MissingParamError);
    expect(error?.message).toBe("Missing parameter: field");
  });

  it("should return MissingParamError if field is empty string", () => {
    const sut = new RequiredFieldValidator("fieldEmpty");
    const input = { fieldEmpty: "" };
    const error = sut.validate(input);

    expect(error).toBeInstanceOf(MissingParamError);
    expect(error?.message).toBe("Missing parameter: fieldEmpty");
  });

  it("should return MissingParamError if input is null", () => {
    const sut = new RequiredFieldValidator("field");
    const error = sut.validate(null);

    expect(error).toBeInstanceOf(MissingParamError);
    expect(error?.message).toBe("Missing parameter: field");
  });

  it("should return MissingParamError if input is undefined", () => {
    const sut = new RequiredFieldValidator("field");
    const error = sut.validate(undefined);

    expect(error).toBeInstanceOf(MissingParamError);
    expect(error?.message).toBe("Missing parameter: field");
  });

  it("should not return error if field is zero", () => {
    const sut = new RequiredFieldValidator("fieldZero");
    const input = { fieldZero: 0 };
    const error = sut.validate(input);

    expect(error).toBeNull();
  });

  it("should not return error if field is false", () => {
    const sut = new RequiredFieldValidator("fieldFalse");
    const input = { fieldFalse: false };
    const error = sut.validate(input);

    expect(error).toBeNull();
  });

  it("should not return error if field is provided", () => {
    const sut = new RequiredFieldValidator("field");
    const input = { field: "any_value" };
    const error = sut.validate(input);

    expect(error).toBeNull();
  });
});

```

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

```typescript
import { describe, expect, it } from "vitest";
import { InvalidParamError } from "../../src/presentation/errors/index.js";
import { ParamNameValidator } from "../../src/validators/param-name-validator.js";

describe("ParamNameValidator", () => {
  it("should return null if field is not provided", () => {
    const sut = new ParamNameValidator("field");
    const input = {};
    const error = sut.validate(input);

    expect(error).toBeNull();
  });

  it("should return null if input is null", () => {
    const sut = new ParamNameValidator("field");
    const error = sut.validate(null);

    expect(error).toBeNull();
  });

  it("should return null if input is undefined", () => {
    const sut = new ParamNameValidator("field");
    const error = sut.validate(undefined);

    expect(error).toBeNull();
  });

  it("should return InvalidParamError if field doesn't match regex (contains special characters)", () => {
    const sut = new ParamNameValidator("field");
    const input = { field: "invalid/name" };
    const error = sut.validate(input);

    expect(error).toBeInstanceOf(InvalidParamError);
    expect(error?.message).toBe("Invalid parameter: invalid/name");
  });

  it("should return InvalidParamError if field doesn't match regex (contains spaces)", () => {
    const sut = new ParamNameValidator("field");
    const input = { field: "invalid name" };
    const error = sut.validate(input);

    expect(error).toBeInstanceOf(InvalidParamError);
    expect(error?.message).toBe("Invalid parameter: invalid name");
  });

  it("should return null if field matches NAME_REGEX (alphanumeric only)", () => {
    const sut = new ParamNameValidator("field");
    const input = { field: "validname123" };
    const error = sut.validate(input);

    expect(error).toBeNull();
  });

  it("should return null if field matches NAME_REGEX (with underscores)", () => {
    const sut = new ParamNameValidator("field");
    const input = { field: "valid_name_123" };
    const error = sut.validate(input);

    expect(error).toBeNull();
  });

  it("should return null if field matches NAME_REGEX (with hyphens)", () => {
    const sut = new ParamNameValidator("field");
    const input = { field: "valid-name-123" };
    const error = sut.validate(input);

    expect(error).toBeNull();
  });

  it("should use provided regex instead of default NAME_REGEX if given", () => {
    // Custom regex that only allows lowercase letters
    const customRegex = /^[a-z]+$/;
    const sut = new ParamNameValidator("field", customRegex);

    const validInput = { field: "validname" };
    const validError = sut.validate(validInput);
    expect(validError).toBeNull();

    const invalidInput = { field: "Invalid123" };
    const invalidError = sut.validate(invalidInput);
    expect(invalidError).toBeInstanceOf(InvalidParamError);
  });
});

```

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

```typescript
import fs from "fs-extra";
import path from "path";
import { FileRepository } from "../../../data/protocols/file-repository.js";
import { File } from "../../../domain/entities/index.js";
/**
 * Filesystem implementation of the FileRepository protocol
 */
export class FsFileRepository implements FileRepository {
  /**
   * Creates a new FsFileRepository
   * @param rootDir The root directory where all projects are stored
   */
  constructor(private readonly rootDir: string) {}

  /**
   * Lists all files in a project
   * @param projectName The name of the project
   * @returns An array of file names
   */
  async listFiles(projectName: string): Promise<File[]> {
    const projectPath = path.join(this.rootDir, projectName);

    const projectExists = await fs.pathExists(projectPath);
    if (!projectExists) {
      return [];
    }

    const entries = await fs.readdir(projectPath, { withFileTypes: true });
    return entries.filter((entry) => entry.isFile()).map((entry) => entry.name);
  }

  /**
   * Loads the content of a file
   * @param projectName The name of the project
   * @param fileName The name of the file
   * @returns The content of the file or null if the file doesn't exist
   */
  async loadFile(
    projectName: string,
    fileName: string
  ): Promise<string | null> {
    const filePath = path.join(this.rootDir, projectName, fileName);

    const fileExists = await fs.pathExists(filePath);
    if (!fileExists) {
      return null;
    }

    const content = await fs.readFile(filePath, "utf-8");
    return content;
  }

  /**
   * Writes a new file
   * @param projectName The name of the project
   * @param fileName The name of the file
   * @param content The content to write
   * @returns The content of the file after writing, or null if the file already exists
   */
  async writeFile(
    projectName: string,
    fileName: string,
    content: string
  ): Promise<File | null> {
    const projectPath = path.join(this.rootDir, projectName);
    await fs.ensureDir(projectPath);

    const filePath = path.join(projectPath, fileName);

    const fileExists = await fs.pathExists(filePath);
    if (fileExists) {
      return null;
    }

    await fs.writeFile(filePath, content, "utf-8");

    return await this.loadFile(projectName, fileName);
  }

  /**
   * Updates an existing file
   * @param projectName The name of the project
   * @param fileName The name of the file
   * @param content The new content
   * @returns The content of the file after updating, or null if the file doesn't exist
   */
  async updateFile(
    projectName: string,
    fileName: string,
    content: string
  ): Promise<File | null> {
    const filePath = path.join(this.rootDir, projectName, fileName);

    const fileExists = await fs.pathExists(filePath);
    if (!fileExists) {
      return null;
    }

    await fs.writeFile(filePath, content, "utf-8");

    return await this.loadFile(projectName, fileName);
  }
}

```

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

```typescript
import { beforeEach, describe, expect, test, vi } from "vitest";
import { FileRepository } from "../../../../src/data/protocols/file-repository.js";
import { ProjectRepository } from "../../../../src/data/protocols/project-repository.js";
import { ReadFile } from "../../../../src/data/usecases/read-file/read-file.js";
import { ReadFileParams } from "../../../../src/domain/usecases/read-file.js";
import {
  MockFileRepository,
  MockProjectRepository,
} from "../../mocks/index.js";

describe("ReadFile UseCase", () => {
  let sut: ReadFile;
  let fileRepositoryStub: FileRepository;
  let projectRepositoryStub: ProjectRepository;

  beforeEach(() => {
    fileRepositoryStub = new MockFileRepository();
    projectRepositoryStub = new MockProjectRepository();
    sut = new ReadFile(fileRepositoryStub, projectRepositoryStub);
  });

  test("should call ProjectRepository.projectExists with correct projectName", async () => {
    const projectExistsSpy = vi.spyOn(projectRepositoryStub, "projectExists");
    const params: ReadFileParams = {
      projectName: "project-1",
      fileName: "file1.md",
    };

    await sut.readFile(params);

    expect(projectExistsSpy).toHaveBeenCalledWith("project-1");
  });

  test("should return null if project does not exist", async () => {
    vi.spyOn(projectRepositoryStub, "projectExists").mockResolvedValueOnce(
      false
    );
    const params: ReadFileParams = {
      projectName: "non-existent-project",
      fileName: "file1.md",
    };

    const result = await sut.readFile(params);

    expect(result).toBeNull();
  });

  test("should call FileRepository.loadFile with correct params if project exists", async () => {
    const loadFileSpy = vi.spyOn(fileRepositoryStub, "loadFile");
    const params: ReadFileParams = {
      projectName: "project-1",
      fileName: "file1.md",
    };

    await sut.readFile(params);

    expect(loadFileSpy).toHaveBeenCalledWith("project-1", "file1.md");
  });

  test("should return file content on success", async () => {
    const params: ReadFileParams = {
      projectName: "project-1",
      fileName: "file1.md",
    };

    const content = await sut.readFile(params);

    expect(content).toBe("Content of file1.md");
  });

  test("should return null if file does not exist", async () => {
    vi.spyOn(fileRepositoryStub, "loadFile").mockResolvedValueOnce(null);
    const params: ReadFileParams = {
      projectName: "project-1",
      fileName: "non-existent-file.md",
    };

    const content = await sut.readFile(params);

    expect(content).toBeNull();
  });

  test("should propagate errors if repository throws", async () => {
    const error = new Error("Repository error");
    vi.spyOn(projectRepositoryStub, "projectExists").mockRejectedValueOnce(
      error
    );
    const params: ReadFileParams = {
      projectName: "project-1",
      fileName: "file1.md",
    };

    await expect(sut.readFile(params)).rejects.toThrow(error);
  });
});

```

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

```typescript
import { describe, expect, it, vi } from "vitest";
import { ListProjectFilesController } from "../../../../src/presentation/controllers/list-project-files/list-project-files-controller.js";
import { ListProjectFilesRequest } from "../../../../src/presentation/controllers/list-project-files/protocols.js";
import { UnexpectedError } from "../../../../src/presentation/errors/index.js";
import {
  makeListProjectFilesUseCase,
  makeValidator,
} from "../../mocks/index.js";

const makeSut = () => {
  const validatorStub = makeValidator<ListProjectFilesRequest>();
  const listProjectFilesUseCaseStub = makeListProjectFilesUseCase();
  const sut = new ListProjectFilesController(
    listProjectFilesUseCaseStub,
    validatorStub
  );
  return {
    sut,
    validatorStub,
    listProjectFilesUseCaseStub,
  };
};

describe("ListProjectFilesController", () => {
  it("should call validator with correct values", async () => {
    const { sut, validatorStub } = makeSut();
    const validateSpy = vi.spyOn(validatorStub, "validate");
    const request = {
      body: {
        projectName: "any_project",
      },
    };
    await sut.handle(request);
    expect(validateSpy).toHaveBeenCalledWith(request.body);
  });

  it("should return 400 if validator returns an error", async () => {
    const { sut, validatorStub } = makeSut();
    vi.spyOn(validatorStub, "validate").mockReturnValueOnce(
      new Error("any_error")
    );
    const request = {
      body: {
        projectName: "any_project",
      },
    };
    const response = await sut.handle(request);
    expect(response).toEqual({
      statusCode: 400,
      body: new Error("any_error"),
    });
  });

  it("should call ListProjectFilesUseCase with correct values", async () => {
    const { sut, listProjectFilesUseCaseStub } = makeSut();
    const listProjectFilesSpy = vi.spyOn(
      listProjectFilesUseCaseStub,
      "listProjectFiles"
    );
    const request = {
      body: {
        projectName: "any_project",
      },
    };
    await sut.handle(request);
    expect(listProjectFilesSpy).toHaveBeenCalledWith({
      projectName: "any_project",
    });
  });

  it("should return 500 if ListProjectFilesUseCase throws", async () => {
    const { sut, listProjectFilesUseCaseStub } = makeSut();
    vi.spyOn(
      listProjectFilesUseCaseStub,
      "listProjectFiles"
    ).mockRejectedValueOnce(new Error("any_error"));
    const request = {
      body: {
        projectName: "any_project",
      },
    };
    const response = await sut.handle(request);
    expect(response).toEqual({
      statusCode: 500,
      body: new UnexpectedError(new Error("any_error")),
    });
  });

  it("should return 200 with files on success", async () => {
    const { sut } = makeSut();
    const request = {
      body: {
        projectName: "any_project",
      },
    };
    const response = await sut.handle(request);
    expect(response).toEqual({
      statusCode: 200,
      body: ["file1.txt", "file2.txt"],
    });
  });
});

```

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

```typescript
import {
  makeListProjectFilesController,
  makeListProjectsController,
  makeReadController,
  makeUpdateController,
  makeWriteController,
} from "../../factories/controllers/index.js";
import { adaptMcpRequestHandler } from "./adapters/mcp-request-adapter.js";
import { McpRouterAdapter } from "./adapters/mcp-router-adapter.js";

export default () => {
  const router = new McpRouterAdapter();

  router.setTool({
    schema: {
      name: "list_projects",
      description: "List all projects in the memory bank",
      inputSchema: {
        type: "object",
        properties: {},
        required: [],
      },
    },
    handler: adaptMcpRequestHandler(makeListProjectsController()),
  });

  router.setTool({
    schema: {
      name: "list_project_files",
      description: "List all files within a specific project",
      inputSchema: {
        type: "object",
        properties: {
          projectName: {
            type: "string",
            description: "The name of the project",
          },
        },
        required: ["projectName"],
      },
    },
    handler: adaptMcpRequestHandler(makeListProjectFilesController()),
  });

  router.setTool({
    schema: {
      name: "memory_bank_read",
      description: "Read a memory bank file for a specific project",
      inputSchema: {
        type: "object",
        properties: {
          projectName: {
            type: "string",
            description: "The name of the project",
          },
          fileName: {
            type: "string",
            description: "The name of the file",
          },
        },
        required: ["projectName", "fileName"],
      },
    },
    handler: adaptMcpRequestHandler(makeReadController()),
  });

  router.setTool({
    schema: {
      name: "memory_bank_write",
      description: "Create a new memory bank file for a specific project",
      inputSchema: {
        type: "object",
        properties: {
          projectName: {
            type: "string",
            description: "The name of the project",
          },
          fileName: {
            type: "string",
            description: "The name of the file",
          },
          content: {
            type: "string",
            description: "The content of the file",
          },
        },
        required: ["projectName", "fileName", "content"],
      },
    },
    handler: adaptMcpRequestHandler(makeWriteController()),
  });

  router.setTool({
    schema: {
      name: "memory_bank_update",
      description: "Update an existing memory bank file for a specific project",
      inputSchema: {
        type: "object",
        properties: {
          projectName: {
            type: "string",
            description: "The name of the project",
          },
          fileName: {
            type: "string",
            description: "The name of the file",
          },
          content: {
            type: "string",
            description: "The content of the file",
          },
        },
        required: ["projectName", "fileName", "content"],
      },
    },
    handler: adaptMcpRequestHandler(makeUpdateController()),
  });

  return router;
};

```

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

```typescript
import { describe, expect, it, vi } from "vitest";
import { WriteRequest } from "../../../../src/presentation/controllers/write/protocols.js";
import { WriteController } from "../../../../src/presentation/controllers/write/write-controller.js";
import { UnexpectedError } from "../../../../src/presentation/errors/index.js";
import { makeValidator, makeWriteFileUseCase } from "../../mocks/index.js";

const makeSut = () => {
  const validatorStub = makeValidator<WriteRequest>();
  const writeFileUseCaseStub = makeWriteFileUseCase();
  const sut = new WriteController(writeFileUseCaseStub, validatorStub);
  return {
    sut,
    validatorStub,
    writeFileUseCaseStub,
  };
};

describe("WriteController", () => {
  it("should call validator with correct values", async () => {
    const { sut, validatorStub } = makeSut();
    const validateSpy = vi.spyOn(validatorStub, "validate");
    const request = {
      body: {
        projectName: "any_project",
        fileName: "any_file",
        content: "any_content",
      },
    };
    await sut.handle(request);
    expect(validateSpy).toHaveBeenCalledWith(request.body);
  });

  it("should return 400 if validator returns an error", async () => {
    const { sut, validatorStub } = makeSut();
    vi.spyOn(validatorStub, "validate").mockReturnValueOnce(
      new Error("any_error")
    );
    const request = {
      body: {
        projectName: "any_project",
        fileName: "any_file",
        content: "any_content",
      },
    };
    const response = await sut.handle(request);
    expect(response).toEqual({
      statusCode: 400,
      body: new Error("any_error"),
    });
  });

  it("should call WriteFileUseCase with correct values", async () => {
    const { sut, writeFileUseCaseStub } = makeSut();
    const writeFileSpy = vi.spyOn(writeFileUseCaseStub, "writeFile");
    const request = {
      body: {
        projectName: "any_project",
        fileName: "any_file",
        content: "any_content",
      },
    };
    await sut.handle(request);
    expect(writeFileSpy).toHaveBeenCalledWith({
      projectName: "any_project",
      fileName: "any_file",
      content: "any_content",
    });
  });

  it("should return 500 if WriteFileUseCase throws", async () => {
    const { sut, writeFileUseCaseStub } = makeSut();
    vi.spyOn(writeFileUseCaseStub, "writeFile").mockRejectedValueOnce(
      new Error("any_error")
    );
    const request = {
      body: {
        projectName: "any_project",
        fileName: "any_file",
        content: "any_content",
      },
    };
    const response = await sut.handle(request);
    expect(response).toEqual({
      statusCode: 500,
      body: new UnexpectedError(new Error("any_error")),
    });
  });

  it("should return 200 if valid data is provided", async () => {
    const { sut } = makeSut();
    const request = {
      body: {
        projectName: "any_project",
        fileName: "any_file",
        content: "any_content",
      },
    };
    const response = await sut.handle(request);
    expect(response).toEqual({
      statusCode: 200,
      body: "File any_file written successfully to project any_project",
    });
  });
});

```

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

```typescript
import { describe, expect, it, vi } from "vitest";
import { ReadRequest } from "../../../../src/presentation/controllers/read/protocols.js";
import { ReadController } from "../../../../src/presentation/controllers/read/read-controller.js";
import {
  NotFoundError,
  UnexpectedError,
} from "../../../../src/presentation/errors/index.js";
import { makeReadFileUseCase, makeValidator } from "../../mocks/index.js";

const makeSut = () => {
  const validatorStub = makeValidator<ReadRequest>();
  const readFileUseCaseStub = makeReadFileUseCase();
  const sut = new ReadController(readFileUseCaseStub, validatorStub);
  return {
    sut,
    validatorStub,
    readFileUseCaseStub,
  };
};

describe("ReadController", () => {
  it("should call validator with correct values", async () => {
    const { sut, validatorStub } = makeSut();
    const validateSpy = vi.spyOn(validatorStub, "validate");
    const request = {
      body: {
        projectName: "any_project",
        fileName: "any_file",
      },
    };
    await sut.handle(request);
    expect(validateSpy).toHaveBeenCalledWith(request.body);
  });

  it("should return 400 if validator returns an error", async () => {
    const { sut, validatorStub } = makeSut();
    vi.spyOn(validatorStub, "validate").mockReturnValueOnce(
      new Error("any_error")
    );
    const request = {
      body: {
        projectName: "any_project",
        fileName: "any_file",
      },
    };
    const response = await sut.handle(request);
    expect(response).toEqual({
      statusCode: 400,
      body: new Error("any_error"),
    });
  });

  it("should call ReadFileUseCase with correct values", async () => {
    const { sut, readFileUseCaseStub } = makeSut();
    const readFileSpy = vi.spyOn(readFileUseCaseStub, "readFile");
    const request = {
      body: {
        projectName: "any_project",
        fileName: "any_file",
      },
    };
    await sut.handle(request);
    expect(readFileSpy).toHaveBeenCalledWith({
      projectName: "any_project",
      fileName: "any_file",
    });
  });

  it("should return 404 if ReadFileUseCase returns null", async () => {
    const { sut, readFileUseCaseStub } = makeSut();
    vi.spyOn(readFileUseCaseStub, "readFile").mockResolvedValueOnce(null);
    const request = {
      body: {
        projectName: "any_project",
        fileName: "any_file",
      },
    };
    const response = await sut.handle(request);
    expect(response).toEqual({
      statusCode: 404,
      body: new NotFoundError("any_file"),
    });
  });

  it("should return 500 if ReadFileUseCase throws", async () => {
    const { sut, readFileUseCaseStub } = makeSut();
    vi.spyOn(readFileUseCaseStub, "readFile").mockRejectedValueOnce(
      new Error("any_error")
    );
    const request = {
      body: {
        projectName: "any_project",
        fileName: "any_file",
      },
    };
    const response = await sut.handle(request);
    expect(response).toEqual({
      statusCode: 500,
      body: new UnexpectedError(new Error("any_error")),
    });
  });

  it("should return 200 if valid data is provided", async () => {
    const { sut } = makeSut();
    const request = {
      body: {
        projectName: "any_project",
        fileName: "any_file",
      },
    };
    const response = await sut.handle(request);
    expect(response).toEqual({
      statusCode: 200,
      body: "file content",
    });
  });
});

```

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

```typescript
import { beforeEach, describe, expect, test, vi } from "vitest";
import { FileRepository } from "../../../../src/data/protocols/file-repository.js";
import { ProjectRepository } from "../../../../src/data/protocols/project-repository.js";
import { UpdateFile } from "../../../../src/data/usecases/update-file/update-file.js";
import { UpdateFileParams } from "../../../../src/domain/usecases/update-file.js";
import {
  MockFileRepository,
  MockProjectRepository,
} from "../../mocks/index.js";

describe("UpdateFile UseCase", () => {
  let sut: UpdateFile;
  let fileRepositoryStub: FileRepository;
  let projectRepositoryStub: ProjectRepository;

  beforeEach(() => {
    fileRepositoryStub = new MockFileRepository();
    projectRepositoryStub = new MockProjectRepository();
    sut = new UpdateFile(fileRepositoryStub, projectRepositoryStub);
  });

  test("should call ProjectRepository.projectExists with correct projectName", async () => {
    const projectExistsSpy = vi.spyOn(projectRepositoryStub, "projectExists");
    const params: UpdateFileParams = {
      projectName: "project-1",
      fileName: "file1.md",
      content: "Updated content",
    };

    await sut.updateFile(params);

    expect(projectExistsSpy).toHaveBeenCalledWith("project-1");
  });

  test("should return null if project does not exist", async () => {
    vi.spyOn(projectRepositoryStub, "projectExists").mockResolvedValueOnce(
      false
    );
    const params: UpdateFileParams = {
      projectName: "non-existent-project",
      fileName: "file1.md",
      content: "Updated content",
    };

    const result = await sut.updateFile(params);

    expect(result).toBeNull();
  });

  test("should check if file exists before updating", async () => {
    const loadFileSpy = vi.spyOn(fileRepositoryStub, "loadFile");
    const params: UpdateFileParams = {
      projectName: "project-1",
      fileName: "file1.md",
      content: "Updated content",
    };

    await sut.updateFile(params);

    expect(loadFileSpy).toHaveBeenCalledWith("project-1", "file1.md");
  });

  test("should return null if file does not exist", async () => {
    vi.spyOn(fileRepositoryStub, "loadFile").mockResolvedValueOnce(null);
    const params: UpdateFileParams = {
      projectName: "project-1",
      fileName: "non-existent-file.md",
      content: "Updated content",
    };

    const result = await sut.updateFile(params);

    expect(result).toBeNull();
  });

  test("should call FileRepository.updateFile with correct params if file exists", async () => {
    const updateFileSpy = vi.spyOn(fileRepositoryStub, "updateFile");
    const params: UpdateFileParams = {
      projectName: "project-1",
      fileName: "file1.md",
      content: "Updated content",
    };

    await sut.updateFile(params);

    expect(updateFileSpy).toHaveBeenCalledWith(
      "project-1",
      "file1.md",
      "Updated content"
    );
  });

  test("should return file content on successful file update", async () => {
    const params: UpdateFileParams = {
      projectName: "project-1",
      fileName: "file1.md",
      content: "Updated content",
    };

    const result = await sut.updateFile(params);

    expect(result).toBe("Updated content");
  });

  test("should propagate errors if repository throws", async () => {
    const error = new Error("Repository error");
    vi.spyOn(projectRepositoryStub, "projectExists").mockRejectedValueOnce(
      error
    );
    const params: UpdateFileParams = {
      projectName: "project-1",
      fileName: "file1.md",
      content: "Updated content",
    };

    await expect(sut.updateFile(params)).rejects.toThrow(error);
  });
});

```

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

```typescript
import { describe, expect, it, vi } from "vitest";
import { UpdateRequest } from "../../../../src/presentation/controllers/update/protocols.js";
import { UpdateController } from "../../../../src/presentation/controllers/update/update-controller.js";
import {
  NotFoundError,
  UnexpectedError,
} from "../../../../src/presentation/errors/index.js";
import { makeUpdateFileUseCase, makeValidator } from "../../mocks/index.js";

const makeSut = () => {
  const validatorStub = makeValidator<UpdateRequest>();
  const updateFileUseCaseStub = makeUpdateFileUseCase();
  const sut = new UpdateController(updateFileUseCaseStub, validatorStub);
  return {
    sut,
    validatorStub,
    updateFileUseCaseStub,
  };
};

describe("UpdateController", () => {
  it("should call validator with correct values", async () => {
    const { sut, validatorStub } = makeSut();
    const validateSpy = vi.spyOn(validatorStub, "validate");
    const request = {
      body: {
        projectName: "any_project",
        fileName: "any_file",
        content: "any_content",
      },
    };
    await sut.handle(request);
    expect(validateSpy).toHaveBeenCalledWith(request.body);
  });

  it("should return 400 if validator returns an error", async () => {
    const { sut, validatorStub } = makeSut();
    vi.spyOn(validatorStub, "validate").mockReturnValueOnce(
      new Error("any_error")
    );
    const request = {
      body: {
        projectName: "any_project",
        fileName: "any_file",
        content: "any_content",
      },
    };
    const response = await sut.handle(request);
    expect(response).toEqual({
      statusCode: 400,
      body: new Error("any_error"),
    });
  });

  it("should call UpdateFileUseCase with correct values", async () => {
    const { sut, updateFileUseCaseStub } = makeSut();
    const updateFileSpy = vi.spyOn(updateFileUseCaseStub, "updateFile");
    const request = {
      body: {
        projectName: "any_project",
        fileName: "any_file",
        content: "any_content",
      },
    };
    await sut.handle(request);
    expect(updateFileSpy).toHaveBeenCalledWith({
      projectName: "any_project",
      fileName: "any_file",
      content: "any_content",
    });
  });

  it("should return 404 if UpdateFileUseCase returns null", async () => {
    const { sut, updateFileUseCaseStub } = makeSut();
    vi.spyOn(updateFileUseCaseStub, "updateFile").mockResolvedValueOnce(null);
    const request = {
      body: {
        projectName: "any_project",
        fileName: "any_file",
        content: "any_content",
      },
    };
    const response = await sut.handle(request);
    expect(response).toEqual({
      statusCode: 404,
      body: new NotFoundError("any_file"),
    });
  });

  it("should return 500 if UpdateFileUseCase throws", async () => {
    const { sut, updateFileUseCaseStub } = makeSut();
    vi.spyOn(updateFileUseCaseStub, "updateFile").mockRejectedValueOnce(
      new Error("any_error")
    );
    const request = {
      body: {
        projectName: "any_project",
        fileName: "any_file",
        content: "any_content",
      },
    };
    const response = await sut.handle(request);
    expect(response).toEqual({
      statusCode: 500,
      body: new UnexpectedError(new Error("any_error")),
    });
  });

  it("should return 200 if valid data is provided", async () => {
    const { sut } = makeSut();
    const request = {
      body: {
        projectName: "any_project",
        fileName: "any_file",
        content: "any_content",
      },
    };
    const response = await sut.handle(request);
    expect(response).toEqual({
      statusCode: 200,
      body: "File any_file updated successfully in project any_project",
    });
  });
});

```

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

```typescript
import { beforeEach, describe, expect, test, vi } from "vitest";
import { FileRepository } from "../../../../src/data/protocols/file-repository.js";
import { ProjectRepository } from "../../../../src/data/protocols/project-repository.js";
import { WriteFile } from "../../../../src/data/usecases/write-file/write-file.js";
import { WriteFileParams } from "../../../../src/domain/usecases/write-file.js";
import {
  MockFileRepository,
  MockProjectRepository,
} from "../../mocks/index.js";

describe("WriteFile UseCase", () => {
  let sut: WriteFile;
  let fileRepositoryStub: FileRepository;
  let projectRepositoryStub: ProjectRepository;

  beforeEach(() => {
    fileRepositoryStub = new MockFileRepository();
    projectRepositoryStub = new MockProjectRepository();
    sut = new WriteFile(fileRepositoryStub, projectRepositoryStub);
  });

  test("should call ProjectRepository.ensureProject with correct projectName", async () => {
    const ensureProjectSpy = vi.spyOn(projectRepositoryStub, "ensureProject");
    const params: WriteFileParams = {
      projectName: "new-project",
      fileName: "new-file.md",
      content: "New content",
    };

    vi.spyOn(fileRepositoryStub, "loadFile")
      .mockResolvedValueOnce(null) // First call checking if file exists
      .mockResolvedValueOnce("New content"); // Second call returning the saved content

    await sut.writeFile(params);

    expect(ensureProjectSpy).toHaveBeenCalledWith("new-project");
  });

  test("should check if file exists before writing", async () => {
    const loadFileSpy = vi.spyOn(fileRepositoryStub, "loadFile");
    const params: WriteFileParams = {
      projectName: "project-1",
      fileName: "new-file.md",
      content: "New content",
    };

    await sut.writeFile(params);

    expect(loadFileSpy).toHaveBeenCalledWith("project-1", "new-file.md");
  });

  test("should return null if file already exists", async () => {
    const params: WriteFileParams = {
      projectName: "project-1",
      fileName: "file1.md",
      content: "New content",
    };

    const result = await sut.writeFile(params);

    expect(result).toBeNull();
  });

  test("should call FileRepository.writeFile with correct params if file does not exist", async () => {
    const writeFileSpy = vi.spyOn(fileRepositoryStub, "writeFile");
    const params: WriteFileParams = {
      projectName: "project-1",
      fileName: "new-file.md",
      content: "New content",
    };

    vi.spyOn(fileRepositoryStub, "loadFile")
      .mockResolvedValueOnce(null) // First call checking if file exists
      .mockResolvedValueOnce("New content"); // Second call returning the saved content

    await sut.writeFile(params);

    expect(writeFileSpy).toHaveBeenCalledWith(
      "project-1",
      "new-file.md",
      "New content"
    );
  });

  test("should return file content on successful file creation", async () => {
    const params: WriteFileParams = {
      projectName: "project-1",
      fileName: "new-file.md",
      content: "New content",
    };

    vi.spyOn(fileRepositoryStub, "loadFile")
      .mockResolvedValueOnce(null) // First call checking if file exists
      .mockResolvedValueOnce("New content"); // Second call returning the saved content

    const result = await sut.writeFile(params);

    expect(result).toBe("New content");
  });

  test("should propagate errors if repository throws", async () => {
    const error = new Error("Repository error");
    vi.spyOn(projectRepositoryStub, "ensureProject").mockRejectedValueOnce(
      error
    );
    const params: WriteFileParams = {
      projectName: "project-1",
      fileName: "new-file.md",
      content: "New content",
    };

    await expect(sut.writeFile(params)).rejects.toThrow(error);
  });
});

```

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

```markdown
# Memory Bank via MCP

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.

## Key Commands

1. "follow your custom instructions"

   - Triggers Pre-Flight Validation (\*a)
   - Follows Memory Bank Access Pattern (\*f)
   - Executes appropriate Mode flow (Plan/Act)

2. "initialize memory bank"

   - Follows Pre-Flight Validation (\*a)
   - Creates new project if needed
   - Establishes core files structure (\*f)

3. "update memory bank"
   - Triggers Documentation Updates (\*d)
   - Performs full file re-read
   - Updates based on current state

## Memory Bank lyfe cycle:

```mermaid
flowchart TD
    A[Start] --> B["Pre-Flight Validation (*a)"]
    B --> C{Project Exists?}
    C -->|Yes| D[Check Core Files]
    C -->|No| E[Create Project] --> H[Create Missing Files]

    D --> F{All Files Present?}
    F -->|Yes| G["Access Memory Bank (*f)"]
    F -->|No| H[Create Missing Files]

    H --> G
    G --> I["Plan Mode (*b)"]
    G --> J["Act Mode (*c)"]

    I --> K[List Projects]
    K --> L[Select Context]
    L --> M[Develop Strategy]

    J --> N[Read .clinerules]
    N --> O[Execute Task]
    O --> P["Update Documentation (*d)"]

    P --> Q{Update Needed?}
    Q -->|Patterns/Changes| R[Read All Files]
    Q -->|User Request| R
    R --> S[Update Memory Bank]

    S --> T["Learning Process (*e)"]
    T --> U[Identify Patterns]
    U --> V[Validate with User]
    V --> W[Update .clinerules]
    W --> X[Apply Patterns]
    X --> O

    %% Intelligence Connections
    W -.->|Continuous Learning| N
    X -.->|Informed Execution| O
```

## Phase Index & Requirements

a) **Pre-Flight Validation**

- **Triggers:** Automatic before any operation
- **Checks:**
  - Project directory existence
  - Core files presence (projectbrief.md, productContext.md, etc.)
  - Custom documentation inventory

b) **Plan Mode**

- **Inputs:** Filesystem/list_directory results
- **Outputs:** Strategy documented in activeContext.md
- **Format Rules:** Validate paths with forward slashes

c) **Act Mode**

- **JSON Operations:**
  ```json
  {
    "projectName": "project-id",
    "fileName": "progress.md",
    "content": "Escaped\\ncontent"
  }
  ```
- **Requirements:**
  - Use \\n for newlines
  - Pure JSON (no XML)
  - Boolean values lowercase (true/false)

d) **Documentation Updates**

- **Triggers:**
  - ≥25% code impact changes
  - New pattern discovery
  - User request "update memory bank"
  - Context ambiguity detected
- **Process:** Full file re-read before update

e) **Project Intelligence**

- **.clinerules Requirements:**
  - Capture critical implementation paths
  - Document user workflow preferences
  - Track tool usage patterns
  - Record project-specific decisions
- **Cycle:** Continuous validate → update → apply

f) **Memory Bank Structure**

```mermaid
flowchart TD
    PB[projectbrief.md\nCore requirements/goals] --> PC[productContext.md\nProblem context/solutions]
    PB --> SP[systemPatterns.md\nArchitecture/patterns]
    PB --> TC[techContext.md\nTech stack/setup]

    PC --> AC[activeContext.md\nCurrent focus/decisions]
    SP --> AC
    TC --> AC

    AC --> P[progress.md\nStatus/roadmap]

    %% Custom files section
    subgraph CF[Custom Files]
        CF1[features/*.md\nFeature specs]
        CF2[api/*.md\nAPI documentation]
        CF3[deployment/*.md\nDeployment guides]
    end

    %% Connect custom files to main structure
    AC -.-> CF
    CF -.-> P

    style PB fill:#e066ff,stroke:#333,stroke-width:2px
    style AC fill:#4d94ff,stroke:#333,stroke-width:2px
    style P fill:#2eb82e,stroke:#333,stroke-width:2px
    style CF fill:#fff,stroke:#333,stroke-width:1px,stroke-dasharray: 5 5
    style CF1 fill:#fff,stroke:#333
    style CF2 fill:#fff,stroke:#333
    style CF3 fill:#fff,stroke:#333
```

- **File Relationships:**
  - projectbrief.md feeds into all context files
  - All context files inform activeContext.md
  - progress.md tracks implementation based on active context
- **Color Coding:**
  - Purple: Foundation documents
  - Blue: Active work documents
  - Green: Status tracking
  - Dashed: Custom documentation (flexible/optional)
- **Access Pattern:**

  - Always read in hierarchical order
  - Update in reverse order (progress → active → others)
  - .clinerules accessed throughout process
  - Custom files integrated based on project needs

- **Custom Files:**
  - Can be added when specific documentation needs arise
  - Common examples:
    - Feature specifications
    - API documentation
    - Integration guides
    - Testing strategies
    - Deployment procedures
  - Should follow main structure's naming patterns
  - Must be referenced in activeContext.md when added

```

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

```typescript
import fs from "fs-extra";
import os from "os";
import path from "path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { FsFileRepository } from "../../../../src/infra/filesystem/repositories/fs-file-repository.js";

describe("FsFileRepository", () => {
  let tempDir: string;
  let repository: FsFileRepository;
  const projectName = "test-project";
  const fileName = "test.md";
  const fileContent = "Test content";

  beforeEach(async () => {
    // Create a temporary directory for tests
    tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "memory-bank-test-"));
    repository = new FsFileRepository(tempDir);

    // Create a test project directory
    await fs.mkdir(path.join(tempDir, projectName));
  });

  afterEach(() => {
    // Clean up after tests
    fs.removeSync(tempDir);
  });

  describe("listFiles", () => {
    it("should return an empty array for a project that doesn't exist", async () => {
      const result = await repository.listFiles("non-existent-project");
      expect(result).toEqual([]);
    });

    it("should return an empty array when no files exist in the project", async () => {
      const result = await repository.listFiles(projectName);
      expect(result).toEqual([]);
    });

    it("should return file names within the project directory", async () => {
      // Create test files
      await fs.writeFile(path.join(tempDir, projectName, "file1.md"), "test");
      await fs.writeFile(path.join(tempDir, projectName, "file2.txt"), "test");
      // Create a directory to ensure it's not returned
      await fs.mkdir(path.join(tempDir, projectName, "not-a-file"));

      const result = await repository.listFiles(projectName);

      expect(result).toHaveLength(2);
      expect(result).toEqual(expect.arrayContaining(["file1.md", "file2.txt"]));
    });
  });

  describe("loadFile", () => {
    it("should return null when the file doesn't exist", async () => {
      const result = await repository.loadFile(projectName, "non-existent.md");
      expect(result).toBeNull();
    });

    it("should return null when the project doesn't exist", async () => {
      const result = await repository.loadFile(
        "non-existent-project",
        fileName
      );
      expect(result).toBeNull();
    });

    it("should return the file content when the file exists", async () => {
      // Create a test file
      await fs.writeFile(
        path.join(tempDir, projectName, fileName),
        fileContent
      );

      const result = await repository.loadFile(projectName, fileName);

      expect(result).toBe(fileContent);
    });
  });

  describe("writeFile", () => {
    it("should create the project directory if it doesn't exist", async () => {
      const newProjectName = "new-project";
      const newFilePath = path.join(tempDir, newProjectName, fileName);

      await repository.writeFile(newProjectName, fileName, fileContent);

      const exists = await fs.pathExists(newFilePath);
      expect(exists).toBe(true);
    });

    it("should write file content to the specified file", async () => {
      await repository.writeFile(projectName, fileName, fileContent);

      const content = await fs.readFile(
        path.join(tempDir, projectName, fileName),
        "utf-8"
      );
      expect(content).toBe(fileContent);
    });

    it("should return the file content after writing", async () => {
      const result = await repository.writeFile(
        projectName,
        fileName,
        fileContent
      );

      expect(result).toBe(fileContent);
    });

    it("should return null if the file already exists", async () => {
      // Create a test file first
      await fs.writeFile(
        path.join(tempDir, projectName, fileName),
        "Original content"
      );

      const result = await repository.writeFile(
        projectName,
        fileName,
        fileContent
      );

      expect(result).toBeNull();

      // Verify content wasn't changed
      const content = await fs.readFile(
        path.join(tempDir, projectName, fileName),
        "utf-8"
      );
      expect(content).toBe("Original content");
    });
  });

  describe("updateFile", () => {
    it("should return null when the file doesn't exist", async () => {
      const result = await repository.updateFile(
        projectName,
        "non-existent.md",
        fileContent
      );
      expect(result).toBeNull();
    });

    it("should return null when the project doesn't exist", async () => {
      const result = await repository.updateFile(
        "non-existent-project",
        fileName,
        fileContent
      );
      expect(result).toBeNull();
    });

    it("should update file content for an existing file", async () => {
      // Create a test file first
      await fs.writeFile(
        path.join(tempDir, projectName, fileName),
        "Original content"
      );

      const updatedContent = "Updated content";
      const result = await repository.updateFile(
        projectName,
        fileName,
        updatedContent
      );

      expect(result).toBe(updatedContent);

      // Verify content was changed
      const content = await fs.readFile(
        path.join(tempDir, projectName, fileName),
        "utf-8"
      );
      expect(content).toBe(updatedContent);
    });

    it("should return the updated file content", async () => {
      // Create a test file first
      await fs.writeFile(
        path.join(tempDir, projectName, fileName),
        "Original content"
      );

      const updatedContent = "Updated content";
      const result = await repository.updateFile(
        projectName,
        fileName,
        updatedContent
      );

      expect(result).toBe(updatedContent);
    });
  });
});

```