Files
Vincent Bourdon 9af114e391 Initial import
2026-06-09 16:14:55 +02:00

256 lines
9.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# quantum-bridge-mcp — Development Rules
## Project
Pure Rust MCP quantum simulator. See `docs/superpowers/specs/2026-04-28-quantum-bridge-mcp-design.md` for the full spec.
Stack: `rmcp` (MCP SDK) + `oq3_semantics` (QASM3 parser) + `spinoza` (statevector engine) + `tokio`.
## Bootstrap (greenfield project)
`src/` and `Cargo.toml` do not exist yet. To initialize:
```bash
cargo new --name quantum-bridge-mcp . # creates src/main.rs + Cargo.toml
```
Key dependencies to add to `Cargo.toml` (check current versions on crates.io):
```toml
[dependencies]
rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk" }
oq3_semantics = "0.1" # official Qiskit OpenQASM3 parser in Rust
spinoza = "2" # pure Rust statevector engine
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "1"
tracing = "0.1"
tracing-subscriber = "0.3"
[dev-dependencies]
proptest = "1"
criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]
name = "simulation"
harness = false
```
## Architecture
```
src/
├── main.rs # Entry point: MCP stdio server (rmcp), reads from stdin, writes to stdout
├── tools/
│ ├── mod.rs # MCP tool registration
│ ├── list_backends.rs # list_backends handler
│ ├── validate_circuit.rs # validate_circuit handler
│ └── run_circuit.rs # run_circuit handler
├── validator.rs # oq3_semantics wrapper → structured diagnostics
├── executor.rs # OpenQASM 3 AST → Spinoza gates → counts
└── error.rs # thiserror error types
tests/
├── integration/ # MCP JSON-RPC roundtrip, tool schema conformance
├── reference/ # QASM3 circuits + expected statevectors (generated by scripts/gen_reference.py)
│ └── invalid/ # Invalid circuits + exact expected error messages (golden files)
└── proptest/ # Property-based tests (unitarity, normalization)
benches/
└── simulation.rs # Criterion benchmarks (Bell < 5 ms, 20 qubits < 500 ms)
scripts/
└── gen_reference.py # Prerequisites: Python 3.11 + pip install qiskit qiskit-aer
.github/workflows/
├── ci.yml # Rust tests + Qiskit cross-validation
└── release.yml # cargo-dist (precompiled binaries)
```
Central trait: `Backend` (executor.rs). `LocalSimulator` implements `Backend` in V1.
MCP tools depend on `Backend`, never on `LocalSimulator` directly.
---
## 1. Test-Driven Development — absolute rule
**Red → Green → Refactor. In that order. Always.**
1. Write the failing test first.
2. Write the minimum code to make it pass.
3. Refactor without breaking the tests.
Never ship functional code without a test covering it. A feature without a test does not exist.
### Types of tests to write (see spec §8)
- **Unit**: individual gates, isolated behaviors — inside the module (`#[cfg(test)]`)
- **Statistical**: probabilistic counts with chi² test (N=10k shots, fixed seed)
- **Qiskit cross-validation**: `tests/reference/` — QASM3 circuits + expected statevectors generated by `scripts/gen_reference.py`
- **Property-based**: quantum invariants with `proptest` (unitarity, normalization)
- **Golden files**: invalid circuits → exact error messages in `tests/reference/invalid/`
- **MCP protocol**: JSON-RPC roundtrip, schema conformance
- **Benchmarks**: Criterion in `benches/`, SLA Bell < 5 ms, 20 qubits < 500 ms
### Test rules
- Tests must be deterministic. Use a fixed seed for any probabilistic simulation.
- One test = one behavioral assertion. Do not test multiple things in a single test.
- Test names describe behavior: `apply_h_to_zero_gives_equal_superposition`, not `test_h`.
- Never comment out a failing test — fix it or remove the feature.
---
## 2. Clean Code
- Expressive names: `parse_circuit` not `parse`, `num_qubits` not `nq`, `QubitIndex` not `usize`.
- One function = one responsibility. If the name contains "and", it's two functions.
- Max 3 parameters; beyond that: config struct. No boolean parameters: use an enum or two functions.
- Comments = *why* only (hidden constraint, non-obvious invariant). Not "what".
- No commented-out dead code. No `TODO`/`FIXME` committed to main.
---
## 3. SOLID Principles (project applications)
- **SRP**: `validator.rs` = parse+validate only. `executor.rs` = translate AST+simulate only. Tools = orchestration only.
- **OCP**: adding IBM (V1.5) does not touch `executor.rs``Backend` trait is mandatory.
- **LSP**: every `impl Backend` respects the contract: `run()` never panics, returns `Result`.
- **ISP**: separate traits `CanValidate` / `CanExecute` / `CanIntrospect`. No fat trait.
- **DIP**: tools depend on `&dyn Backend`, not on `LocalSimulator`. Gates injected via `&dyn GateSet`.
---
## 4. Rust Architecture
### Error handling
- **No `unwrap()` or `expect()` in production code.** Only in tests (if truly justified).
- All errors propagate via `Result<T, E>` with an explicit error type (not `Box<dyn Error>`).
- Use `thiserror` to define error types. Each variant has a clear message.
- Errors exposed via MCP include: structured code, human-readable message, circuit position if applicable.
```rust
// Good
#[derive(thiserror::Error, Debug)]
pub enum ValidationError {
#[error("gate '{gate}' not supported (line {line})")]
UnsupportedGate { gate: String, line: usize },
}
// Forbidden
fn parse(input: &str) -> bool { ... } // error swallowed
```
### Modules and visibility
- Everything is private by default. `pub` is intentional, not the default.
- A module's public API = its contract. Minimize it.
- No cascading `pub use` without intent. Re-export only what belongs to the public interface.
### Types and newtypes
- Use newtypes for domain concepts: `QubitIndex(usize)`, `ShotCount(u32)` rather than bare `usize` everywhere.
- Do not pass raw primitives where a domain type exists.
- `Config` structs are separate from `State` structs. Do not mix configuration and mutable state.
### No magic numbers
```rust
// Forbidden
if qubits > 28 { ... }
// Good
const MAX_LOCAL_QUBITS: usize = 28;
if qubits > MAX_LOCAL_QUBITS { ... }
```
---
## 5. Refactoring Rules
- Refactor **after** tests pass, never before.
- A refactoring does not change observable behavior. If the tests change, it's no longer a refactoring.
- The rule of three: first time, just code. Second time, code and grimace. Third time, abstract.
- Do not abstract prematurely. Three similar lines are not necessarily an abstraction.
---
## 6. Git & Commits
- **One commit = one coherent intent.** Do not mix feature + fix + refactoring in the same commit.
- Commit messages in English, conventional format: `feat:`, `fix:`, `test:`, `refactor:`, `chore:`, `docs:`.
- Tests and the production code they cover go in the same commit.
- Never commit code that breaks tests (`cargo test` must pass).
- Never commit code that does not compile.
- Prefer readable atomic commits over a single large WIP commit.
---
## 7. Development Commands
```bash
# Bootstrap (first setup) — verifies dependencies compile
cargo build
# Run the MCP server (stdio) locally
cargo run
# f32 mode: +30% speed, +1 qubit at equal RAM (negligible precision loss)
cargo run -- --f32
# Claude Desktop config (~/.config/claude/claude_desktop_config.json)
# { "mcpServers": { "quantum-bridge": { "command": "/path/to/quantum-bridge-mcp" } } }
# Tests (all)
cargo test
# Tests + Qiskit cross-validation
cargo test && python3 scripts/gen_reference.py --validate
# Benchmarks
cargo bench
# Strict lint
cargo clippy -- -D warnings
# Format
cargo fmt --check
# Full check (before commit)
cargo fmt --check && cargo clippy -- -D warnings && cargo test
```
CI rejects any code that fails any of these checks.
---
## 8. Quantum Gotchas
- **Endianness / bit-ordering**: Qiskit and Spinoza may use opposite qubit-ordering conventions. Cross-validation (`tests/reference/`) is there to catch this. Never assume — verify against golden files.
- **Probabilities vs counts**: `run_circuit` returns counts (integers), not probabilities. Normalize on the Claude side if needed.
- **Fixed seed in tests**: any probabilistic simulation in tests uses a fixed seed. Without this, statistical tests are flaky.
- **Qubit limit**: statevector = `2^n × 16 bytes`. 25 qubits = 512 MB. Validate `num_qubits ≤ MAX_LOCAL_QUBITS` before simulation.
---
## 9. Environment
Environment variables:
- `RUST_LOG=debug` — enables tracing logs during development
- `IBM_QUANTUM_TOKEN` — IBM token (V1.5 only, not used in V1)
Prerequisites for cross-validation (CI only):
```bash
python3 -m pip install qiskit qiskit-aer # Python 3.11+
python3 scripts/gen_reference.py # generates tests/reference/
```
---
## 10. What Is Forbidden
- `panic!()`, `unwrap()`, `expect()` in production code
- `todo!()`, `unimplemented!()` committed to main
- `unsafe` without a comment block justifying why and proving safety
- Dependencies added without justification in the commit message
- Disabled tests (`#[ignore]`) without a linked issue and resolution date
- Dead code (`#[allow(dead_code)]`) without justification