Skip to main content
Glama
SAMI-CODEAI

Competitive Programming Mentor MCP Server

README.md
<p align="center">
  <h1 align="center">πŸ† Competitive Programming Mentor β€” MCP Server</h1>
  <p align="center">
    A production-grade <strong>Model Context Protocol (MCP)</strong> server that decomposes competitive programming expertise into 23 modular, provider-independent tools β€” orchestrated by any MCP-compatible client.
  </p>
</p>

<p align="center">
  <img src="https://img.shields.io/badge/python-3.11+-blue?logo=python&logoColor=white" />
  <img src="https://img.shields.io/badge/MCP-FastMCP_3.x-green?logo=data:image/svg+xml;base64,PHN2Zy8+" />
  <img src="https://img.shields.io/badge/LLM-OpenAI_%7C_Ollama-orange" />
  <img src="https://img.shields.io/badge/validation-Pydantic_v2-red?logo=pydantic" />
  <img src="https://img.shields.io/badge/cache-Two--Tier-purple" />
</p>

---


[![M8ven Score](https://m8ven.ai/badge/mcp/sami-codeai-mcp-server-for-competitive-programming-6880al)](https://m8ven.ai/mcp/sami-codeai-mcp-server-for-competitive-programming-6880al)

https://m8ven.ai/mcp/sami-codeai-mcp-server-for-competitive-programming-6880al?utm_source=new_listing&utm_medium=email&utm_campaign=new_listing
https://glama.ai/mcp/servers/SAMI-CODEAI/MCP-Server-For-Competitive-Programming
## πŸ“‹ Table of Contents

- [Why MCP?](#-why-mcp)
- [Architecture](#-architecture)
- [Tool Catalog](#-tool-catalog-23-tools)
- [Project Structure](#-project-structure)
- [Getting Started](#-getting-started)
- [Configuration](#-configuration)
- [Workflows](#-workflows)
- [How It Works](#-how-it-works-deep-dive)
- [Testing](#-testing)
- [Contributing](#-contributing)

---

## πŸ€” Why MCP?

Traditional AI coding assistants force all reasoning through **one massive prompt**. This approach suffers from:

| Problem | Impact |
|---------|--------|
| Monolithic prompts | Impossible to test, cache, or reuse individual capabilities |
| Provider lock-in | Switching from OpenAI β†’ Ollama requires rewriting everything |
| No structured output | Raw text responses require fragile regex parsing |
| Redundant LLM calls | Identical problems re-analyzed every time |

**This MCP server solves all four.** Each capability is an independent tool with its own schema, prompt template, cache key, and validation pipeline.

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     MCP Protocol      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Claude Desktop  │◄─────────────────────►│   CP Mentor MCP Server   β”‚
β”‚  Cursor IDE      β”‚   JSON-RPC / stdio    β”‚                          β”‚
β”‚  Claude CLI      β”‚                       β”‚  23 Tools Β· 3 Resources  β”‚
β”‚  Any MCP Client  β”‚                       β”‚  3 Prompts Β· 2-Tier Cacheβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                        β”‚
                                           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                           β”‚   LLM Provider Layer     β”‚
                                           β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
                                           β”‚  β”‚ OpenAI β”‚ β”‚  Ollama  β”‚ β”‚
                                           β”‚  β”‚gpt-4o  β”‚ β”‚llama3.1 β”‚ β”‚
                                           β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
                                           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

---

## πŸ— Architecture

```mermaid
graph TD
    Client["MCP Client<br/>(Cursor Β· Claude Desktop Β· CLI)"]
    Server["FastMCP Server<br/>server.py"]
    Cache{"Two-Tier Cache<br/>Memory TTL + SQLite Disk"}
    Prompt["Jinja2 Prompt Manager<br/>prompts/*.md"]
    Provider["LLM Provider Factory<br/>OpenAI | Ollama"]
    Validator{"Self-Correcting Validator<br/>Pydantic v2 Schemas"}
    Formatter["Response Formatter<br/>+ _meta tracing"]

    Client -->|"tool call (JSON-RPC)"| Server
    Server -->|"check cache"| Cache
    Cache -->|"HIT"| Formatter
    Cache -->|"MISS"| Prompt
    Prompt -->|"rendered prompt"| Provider
    Provider -->|"raw JSON"| Validator
    Validator -->|"schema fail β†’ retry prompt"| Provider
    Validator -->|"schema pass"| Cache
    Cache -->|"store result"| Formatter
    Formatter -->|"structured response"| Client
```

### Core Design Principles

| Principle | Implementation |
|-----------|---------------|
| **Tool-Service Decoupling** | Tools contain zero business logic β€” they delegate to `PromptManager β†’ Provider β†’ Validator β†’ Cache β†’ Formatter` |
| **Provider Independence** | `BaseLLMProvider` abstract class ensures zero OpenAI/Ollama imports in tool code |
| **Schema-First Validation** | Every tool has a dedicated Pydantic `BaseModel` β€” LLM outputs are validated and auto-corrected |
| **Two-Tier Caching** | In-memory `TTLCache` (ΞΌs latency) + persistent `diskcache` (survives restarts) |
| **Fail-Safe Registration** | Each tool module is `try/except` imported β€” one broken tool doesn't crash the server |

---

## πŸ”§ Tool Catalog (23 Tools)

### πŸ” Analysis (4 tools)

| Tool | Description | Schema |
|------|-------------|--------|
| `detect_patterns` | Identifies algorithmic patterns (DP, Graph, Greedy, Math, etc.) from problem text | `PatternResponse` |
| `extract_constraints` | Parses numeric bounds (N, M, K, Q) and system limits (time/memory) | `ConstraintResponse` |
| `estimate_difficulty` | Approximates competitive programming difficulty rating | `DifficultyResponse` |
| `identify_topics` | Tags primary/secondary topic categories | `TopicsResponse` |

### πŸ“ Planning (4 tools)

| Tool | Description | Schema |
|------|-------------|--------|
| `suggest_algorithms` | Recommends candidate algorithms based on constraints | `AlgorithmsListResponse` |
| `compare_algorithms` | Builds a trade-off comparison matrix across candidates | `AlgorithmsComparisonResponse` |
| `choose_best_algorithm` | Selects the optimal algorithm with justification | `BestAlgorithmResponse` |
| `estimate_runtime` | Calculates operation count vs. time budget feasibility | `RuntimeEstimateResponse` |

### πŸ’» Code Generation (3 tools)

| Tool | Description | Schema |
|------|-------------|--------|
| `generate_solution` | Produces optimized, contest-ready code in the target language | `SolutionResponse` |
| `generate_pseudocode` | Outputs language-agnostic structural pseudocode | `PseudocodeResponse` |
| `generate_multi_language` | Generates C++, Java, and Rust implementations simultaneously | `MultiLangResponse` |

### βœ… Verification (3 tools)

| Tool | Description | Schema |
|------|-------------|--------|
| `dry_run` | Traces variable states step-by-step through sample inputs | `DryRunResponse` |
| `prove_correctness` | Provides formal correctness proofs (loop invariants, induction) | `CorrectnessProofResponse` |
| `analyze_complexity` | Computes asymptotic time/space complexity with justification | `ComplexityResponse` |

### πŸ§ͺ Testing (3 tools)

| Tool | Description | Schema |
|------|-------------|--------|
| `generate_testcases` | Creates input/output test pairs covering standard scenarios | `TestcasesResponse` |
| `generate_edge_cases` | Targets boundary conditions, zero-cases, and overflow scenarios | `EdgeCasesResponse` |
| `stress_testing` | Generates randomized brute-force stress test configurations | `StressTestResponse` |

### πŸ”Ž Code Review (3 tools)

| Tool | Description | Schema |
|------|-------------|--------|
| `review_solution` | Full code review with correctness, efficiency, and style feedback | `ReviewResponse` |
| `find_bug` | Pinpoints logical, runtime, or compile-time bugs | `BugResponse` |
| `optimize_solution` | Suggests constant-factor and algorithmic optimizations | `OptimizationResponse` |

### πŸ“š Learning (3 tools)

| Tool | Description | Schema |
|------|-------------|--------|
| `get_hint` | Progressive hint system (nudge β†’ approach β†’ partial solution) | `HintResponse` |
| `explain_algorithm` | Educational breakdown with examples, when-to-use heuristics | `ExplanationResponse` |
| `recommend_next_problem` | Suggests follow-up problems to reinforce learned concepts | `RecommendationResponse` |

---

## πŸ“ Project Structure

```
Competitive Programming Mentor MCP Server/
β”‚
β”œβ”€β”€ app.py                     # Entry point β€” mcp.run()
β”œβ”€β”€ server.py                  # FastMCP app, tool/resource/prompt registration
β”œβ”€β”€ config.py                  # Pydantic-settings configuration from .env
β”œβ”€β”€ pyproject.toml             # Dependencies & build config
β”œβ”€β”€ .env.example               # Environment template
β”‚
β”œβ”€β”€ services/                  # Core business logic layer
β”‚   β”œβ”€β”€ llm/
β”‚   β”‚   β”œβ”€β”€ base_provider.py       # Abstract LLM interface
β”‚   β”‚   β”œβ”€β”€ openai_provider.py     # OpenAI gpt-4o-mini adapter
β”‚   β”‚   β”œβ”€β”€ ollama_provider.py     # Ollama local LLM adapter
β”‚   β”‚   └── provider_factory.py    # Factory: .env β†’ Provider instance
β”‚   β”œβ”€β”€ prompt_manager.py          # Jinja2 template renderer
β”‚   β”œβ”€β”€ validator.py               # Pydantic validation + self-correcting retry
β”‚   β”œβ”€β”€ cache.py                   # Two-tier cache (TTLCache + diskcache)
β”‚   β”œβ”€β”€ problem_parser.py          # Regex constraint extractor (N, M, K, Q)
β”‚   └── formatter.py               # Response normalization + _meta tags
β”‚
β”œβ”€β”€ tools/                     # MCP tool implementations (23 tools)
β”‚   β”œβ”€β”€ analysis/                  # detect_patterns, extract_constraints, ...
β”‚   β”œβ”€β”€ planning/                  # suggest_algorithms, compare_algorithms, ...
β”‚   β”œβ”€β”€ generation/                # generate_solution, generate_pseudocode, ...
β”‚   β”œβ”€β”€ verification/              # dry_run, prove_correctness, ...
β”‚   β”œβ”€β”€ testing/                   # generate_testcases, generate_edge_cases, ...
β”‚   β”œβ”€β”€ review/                    # review_solution, find_bug, optimize_solution
β”‚   └── learning/                  # get_hint, explain_algorithm, recommend_problem
β”‚
β”œβ”€β”€ schemas/                   # Pydantic response models (one per tool)
β”œβ”€β”€ prompts/                   # Jinja2 markdown templates (one per tool + personas)
β”œβ”€β”€ resources/                 # Static knowledge base (Markdown files)
β”‚   β”œβ”€β”€ algorithms/graphs/         # dijkstra.md
β”‚   β”œβ”€β”€ data_structures/           # segment_tree.md
β”‚   └── patterns/                  # sliding_window.md
β”‚
β”œβ”€β”€ tests/                     # Pytest test suite
β”‚   β”œβ”€β”€ test_parser.py             # Constraint parsing tests
β”‚   β”œβ”€β”€ test_cache.py              # Two-tier cache tests
β”‚   └── test_validator.py          # Schema validation + retry tests
β”‚
└── docs/
    β”œβ”€β”€ ARCHITECTURE.md            # System design documentation
    └── TOOLS.md                   # Tool reference catalog
```

---

## πŸš€ Getting Started

### Prerequisites

- **Python 3.11+**
- **[uv](https://docs.astral.sh/uv/)** (recommended) or `pip`
- **OpenAI API key** _or_ **[Ollama](https://ollama.ai)** installed locally

### Installation

```bash
# Clone the repository
git clone https://github.com/your-username/competitive-programming-mcp.git
cd competitive-programming-mcp

# Install dependencies with uv
uv sync --all-extras

# Copy and configure environment
cp .env.example .env
# Edit .env with your API keys / Ollama settings
```

### Quick Start

```bash
# Start the MCP server (stdio transport)
uv run app.py

# Or run in development mode with auto-reload
fastmcp dev app.py

# Run tests
uv run pytest
```

---

## βš™ Configuration

All settings are managed via `.env` and loaded through **Pydantic Settings**:

```ini
# ─── LLM Provider ────────────────────────────────────
LLM_PROVIDER=openai              # "openai" or "ollama"

# ─── OpenAI (when LLM_PROVIDER=openai) ───────────────
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o-mini         # ~$0.15/1M input tokens
OPENAI_MAX_TOKENS=4096
OPENAI_TEMPERATURE=0.2           # Low = deterministic code

# ─── Ollama (when LLM_PROVIDER=ollama) ────────────────
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_MODEL=llama3.1:8b         # Run: ollama pull llama3.1:8b

# ─── Cache ────────────────────────────────────────────
CACHE_ENABLED=true
CACHE_TTL_SECONDS=3600           # 1-hour TTL
CACHE_DISK_DIR=.cache            # SQLite-backed persistent cache

# ─── Server ──────────────────────────────────────────
LOG_LEVEL=INFO                   # DEBUG | INFO | WARNING | ERROR
```

---

## πŸ“– Workflows

### Workflow 1: Claude Desktop Integration

Add the following to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "cp-mentor": {
      "command": "uv",
      "args": [
        "--directory",
        "YOUR_ABSOLUTE_PATH\\Competitive Programming Mentor MCP Server",
        "run",
        "app.py"
      ]
    }
  }
}
```

> **Note on Config Locations:**
> - Standard Windows: `%APPDATA%\Claude\claude_desktop_config.json`
> - Windows Store App: `C:\Users\YOUR_NAME\AppData\Local\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\claude_desktop_config.json`

Restart Claude Desktop completely. 
*Note: In the latest versions of Claude Desktop, the plug (πŸ”Œ) icon has been removed from the UI. MCP tools are simply loaded silently in the background. Just ask Claude to "Use your cp-mentor tools to solve X" and you will see an approval pop-up!*

### Workflow 2: Claude CLI Integration

If you use the `claude` terminal tool, run:
```bash
claude mcp add cp-mentor uv --directory "/path/to/project" run app.py
claude   # Start a session
```

> **Important for local models:** If you are using `ollama` with the Claude CLI, make sure you launch it with a **large enough model (e.g., 9B+ parameters)** that supports tool calling. Small models (like 4B or 1B) will crash or fail to invoke MCP tools properly.
> Example: `ollama launch claude --model qwen2.5:14b`

### Workflow 3: Cursor IDE Integration

1. Open **Cursor β†’ Settings β†’ Features β†’ MCP**
2. Click **+ Add New MCP Server**
3. Set **Name**: `cp-mentor`
4. Set **Type**: `command`
5. Set **Command**: `uv --directory "/path/to/project" run app.py`

### Workflow 4: Full Problem-Solving Pipeline

```
User provides a problem (e.g., LeetCode / Codeforces)
        β”‚
        β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚ Step 1       β”‚  detect_patterns(problem)
  β”‚ Analyze      β”‚  extract_constraints(problem)
  β”‚              β”‚  identify_topics(problem)
  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚ Step 2       β”‚  suggest_algorithms(problem)
  β”‚ Plan         β”‚  compare_algorithms(problem, candidates)
  β”‚              β”‚  choose_best_algorithm(problem, candidates)
  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚ Step 3       β”‚  generate_solution(problem, approach, language)
  β”‚ Generate     β”‚  generate_pseudocode(problem)
  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚ Step 4       β”‚  dry_run(problem, code)
  β”‚ Verify       β”‚  prove_correctness(problem)
  β”‚              β”‚  analyze_complexity(problem, code)
  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚ Step 5       β”‚  generate_testcases(problem)
  β”‚ Test         β”‚  generate_edge_cases(problem)
  β”‚              β”‚  stress_testing(problem)
  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚ Step 6       β”‚  review_solution(problem, code)
  β”‚ Review       β”‚  optimize_solution(problem, code)
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

---

## πŸ”¬ How It Works (Deep Dive)

### 1. Request Flow

When a client invokes `detect_patterns(problem="...")`, the following pipeline executes:

```
1. FastMCP receives JSON-RPC call via stdio transport
2. Tool function in tools/analysis/detect_patterns.py is invoked
3. CacheService.get("detect_patterns", {problem: "..."}) β†’ checks memory, then disk
4. On MISS: PromptManager renders prompts/detect_patterns.md with Jinja2
5. ProviderFactory returns OpenAIProvider or OllamaProvider
6. Provider.structured_output(prompt, PatternResponse) calls the LLM API
7. Validator checks response against PatternResponse Pydantic schema
8. On validation failure: auto-correcting retry with error feedback prompt
9. CacheService.set() stores result in both memory and disk tiers
10. Formatter wraps response with _meta (tool name, model, cache status, latency)
11. Structured JSON returned to client
```

### 2. Self-Correcting Validator

The validator implements a retry loop that feeds Pydantic validation errors back to the LLM:

```python
# Simplified flow
for attempt in range(max_retries):
    raw_json = await provider.structured_output(prompt, schema)
    try:
        return schema.model_validate_json(raw_json)  # Success
    except ValidationError as e:
        prompt = f"Fix these errors: {e.errors()}\nOriginal: {raw_json}"
        # Retry with corrective context
```

### 3. Two-Tier Cache

```
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  get(key) ────────►│  Memory (TTL)   │──── HIT ────► return value
                    β”‚  ~256 entries   β”‚
                    β”‚  ΞΌs latency     β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚ MISS
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  Disk (SQLite)  │──── HIT ────► promote to memory
                    β”‚  Persistent     β”‚               + return value
                    β”‚  ms latency     β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚ MISS
                            β–Ό
                    Call LLM Provider
```

Cache keys are deterministic: `f"{tool_name}:{sha256(sorted_json(inputs))[:16]}"`

### 4. Provider Abstraction

```python
class BaseLLMProvider(ABC):
    @abstractmethod
    async def generate(self, prompt: str, system: str = "") -> str: ...

    @abstractmethod
    async def structured_output(self, prompt: str, response_model: type[T], system: str = "") -> T: ...

    @property
    @abstractmethod
    def model_name(self) -> str: ...
```

- **OpenAIProvider**: Uses `client.beta.chat.completions.parse()` for native JSON schema generation
- **OllamaProvider**: Uses `openai`-compatible endpoint with regex JSON extraction fallback

---

## πŸ§ͺ Testing

```bash
# Run all tests
$env:PYTHONPATH="."    # PowerShell
uv run pytest

# Run with verbose output
uv run pytest -v

# Test specific module
uv run pytest tests/test_parser.py
uv run pytest tests/test_cache.py
uv run pytest tests/test_validator.py
```

### Test Coverage

| Module | Tests | What's Verified |
|--------|-------|----------------|
| `problem_parser` | 2 | Constraint regex parsing (including `2 * 10^5` notation), default handling |
| `cache` | 2 | Memory/disk hit/miss, tier promotion, Windows file lock cleanup |
| `validator` | 2 | Schema compliance on first try, self-correcting retry on malformed JSON |

---

## πŸ›  Tech Stack

| Component | Technology | Purpose |
|-----------|-----------|---------|
| MCP Framework | `fastmcp >= 2.0` | Server SDK, tool registration, stdio transport |
| LLM Client | `openai >= 1.30` | OpenAI + Ollama-compatible API calls |
| Validation | `pydantic >= 2.7` | Typed schemas for all 23 tool outputs |
| Configuration | `pydantic-settings >= 2.3` | `.env` β†’ typed config singleton |
| Templating | `jinja2 >= 3.1` | Prompt template rendering |
| Memory Cache | `cachetools >= 5.3` | In-memory TTL cache |
| Disk Cache | `diskcache >= 5.6` | SQLite-backed persistent cache |
| Retry Logic | `tenacity >= 8.3` | Exponential backoff for LLM API calls |
| Testing | `pytest + pytest-asyncio` | Async-compatible test framework |

---

## πŸ“„ License

This project is provided as-is for educational and competitive programming purposes.

---

<p align="center">
  Built with 🧠 by <strong>sami_codeai</strong> β€” Turning competitive programming into composable AI tools.
</p>

TDQS

B3.3/5.0

Scored across 23 tools

Disambiguation5/5

Each tool has a clear, distinct purpose, covering various aspects of competitive programming from analysis to code generation and debugging. No two tools appear to do the same thing.

Naming Consistency5/5

All tool names follow a consistent snake_case verb_noun pattern (e.g., analyze_complexity, generate_solution, recommend_next_problem), making it easy for an agent to predict functionality.

Tool Count4/5

23 tools is slightly above the typical ideal range, but given the broad scope of competitive programming mentoring (analysis, generation, testing, debugging, learning), it is justified and not excessive.

Completeness5/5

The tool set covers the full lifecycle of problem solving: understanding constraints, detecting patterns, selecting algorithms, generating solutions, testing, debugging, optimizing, and even recommending further practice. No obvious gaps.

Maintenance

ActivitySlowing
ResponsivenessNo issues