Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 0 additions & 78 deletions .cursor/rules.md

This file was deleted.

26 changes: 26 additions & 0 deletions .cursor/rules/architecture.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
description: Module responsibilities and dependency direction
alwaysApply: true
---

# Architecture

- `domain.py` holds all data structures (not `models.py`, which is ambiguous in an LLM project)
- `openrouter.py` is the HTTP client layer (separate from orchestration)
- `simulation.py` holds all dry-run fakes (LLM responses and search results)
- `prompts.py` contains prompt builders (pure functions) and output parsers (review verdict, synthesis decision)
- `compression.py` handles both low-level text compression and prompt fitting within token budgets
- `progress.py` defines the `ProgressCallback` protocol and `NoOpProgress` fallback
- `reviewers.py` holds the reviewer-to-candidate assignment algorithm
- `exclamations.py` holds the Simpsons-quote prefix helper used to dress up error messages
- Cross-group model overlap is allowed: a model may generate and review in the same round
- Compression priority: candidates first, then reviews, then context; the task instruction is never compressed

## Dependency direction

- `cli.py` depends on `core/` and `ui/`; nothing in `core/` or `ui/` imports from `cli.py`
- `ui/tui.py` depends on `core/progress.py` (protocol) and `core/domain.py` (types); it never imports from `core/orchestrator.py`
- Within `core/`, `orchestrator.py` is the only module that imports from `openrouter.py`, `compression.py`, and `reviewers.py`; `search.py` is imported by `orchestrator.py` only but may itself import `simulation.py` for its dry-run path
- `prompts.py` depends only on `domain.py`; it has no runtime dependencies on other core modules
- `exclamations.py` is a leaf module (stdlib-only); it may be imported from anywhere in `core/`
- `config.py` loads `crossfire.toml` with CLI override precedence: CLI flags > TOML values > defaults; per-mode overrides go in `[modes.<mode>.*]`, missing roles fall back to global `[models.*]` defaults
20 changes: 20 additions & 0 deletions .cursor/rules/error-handling.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
description: Error handling patterns — structured logging, exception hierarchy, exclaim wrapper
alwaysApply: true
---

# Error handling

- Always use structured `log.log_*` functions; never use `log.get_logger().warning(...)` or similar direct calls
- Group related catch-all exceptions into named module-level tuples (e.g. `_RETRIABLE_ERRORS`) rather than inline multi-type except clauses
- Methods should return new state, not silently mutate instance attributes (especially on frozen dataclasses)
- `RunFailedError` for fatal orchestration failures; `RuntimeError` for recoverable per-model failures and configuration errors surfaced before or during a run (e.g. missing API keys)
- `RefusalError` for generator refusals detected by `_REFUSAL_REGEX`; on refusal, attempt a replacement model before dropping the generator
- Synthesis regression detection: `_is_synthesis_regression` checks both refusal phrases and token-length ratio (<50% of the previous round); on regression the previous round's synthesis is carried forward
- Exception messages inside `core/` are wrapped in `exclaim(...)` from `exclamations.py` for a consistent tone; CLI-layer `click.echo(..., err=True)` messages stay plain

# Secrets

- Never hardcode API keys, tokens, or credentials in source code
- API keys are read from environment variables at runtime (`OPENROUTER_API_KEY`, `TAVILY_API_KEY`); never log or archive them
- Never commit `.env` files; they belong in `.gitignore`
12 changes: 12 additions & 0 deletions .cursor/rules/naming.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
description: Naming conventions — no abbreviations, no single-letter variables
alwaysApply: true
---

# Naming

- No abbreviations or acronyms in names: `configuration` not `config`, `parameters` not `params`, `index` not `idx`, `length` not `len` (except when using said built-in function)
- No single-letter variables: `match` not `m`, `line` not `ln`, `candidate` not `c` (short names are acceptable inside list/dict/set comprehensions)
- Spell out `exception` in except clauses (not `exc`)
- Regex constants use `_REGEX` suffix (not `_RE`)
- Class names spelled out: `Configuration` not `Config`, `Parameters` not `Params`
13 changes: 13 additions & 0 deletions .cursor/rules/prompts.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
description: Prompt design constraints for generation, review, and synthesis
alwaysApply: true
---

# Prompts

- `MODE_RULES` is the single source of constraints (never duplicate rules in system prompts)
- Generator system prompts: identity only, e.g. "You are a careful engineer who thinks several steps ahead"
- Reviewer system prompts: punchy adversarial identity and checklist (no overlap with `MODE_RULES`)
- `_BANNED_PHRASES`: context-free filler only; words with legitimate uses are excluded or marked `(metaphorical)`
- Write mode: enforce asymmetry (vary section lengths, never mirror structure)
- Code mode: language-agnostic (the user specifies their stack)
27 changes: 27 additions & 0 deletions .cursor/rules/style.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
description: Code style — type annotations, formatting, Python version constraints
alwaysApply: true
---

# Style

- User edits are final: never revert, re-add, or modify code the user has changed or removed
- Readability is paramount: when in doubt, optimize for the reader, not the writer
- Always include type annotations: function signatures, return types, and local variables
- Python 3.12 only (3.13+ fails: transitive incompatibility in `docformatter` → `untokenize`)
- Ruff line length: 120
- `frozen=True` on immutable dataclasses (including `CrossfireConfiguration`)
- Top-level imports only (no lazy imports inside method bodies)
- `...` for Protocol method stubs, `pass` for concrete no-op implementations
- All files must end with a trailing newline (for git)

# Docstrings

- Function/method docstrings use third person ("Rewrites the instruction" not "Rewrite the instruction")
- One-liner and module docstrings: end with a full stop
- British OED spelling throughout (-ize/-yse)
- Human-readable and concise, not technically precise but unintelligible

# Markdown

- Each sentence goes on its own line (no mid-sentence line breaks); let word wrapping handle display
16 changes: 16 additions & 0 deletions .cursor/rules/testing.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
description: Testing conventions — pytest patterns, fixtures, assertions
alwaysApply: true
---

# Testing

- Module docstrings follow the pattern `"""Tests for <topic>."""`
- Skip test-function docstrings when the test name already explains the intent
- Prefer `@pytest.mark.parametrize` over copy-pasted test functions with different inputs
- Use fixtures from `conftest.py` rather than hardcoding shared setup inline
- New shared test objects belong in `conftest.py` as fixtures, not as module-level constants in test files
- One assertion concept per test (multiple `assert` lines are fine if they test one logical thing)
- Dry-run determinism via SHA-256 hashing of all call parameters
- Test files may import private functions when needed (e.g. `_build_review_triage`)
- All tests + linting + typing must pass before merging
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @ianreppel
28 changes: 2 additions & 26 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
run: uv python install ${{ matrix.python-version }}

- name: Install dependencies
run: uv sync --all-extras
run: uv sync

- name: Run pre-commit checks
run: |
Expand All @@ -50,28 +50,4 @@ jobs:
with:
files: "**/*.md"
inline: warning
strict: false

security:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v3

- name: Set up Python
run: uv python install 3.12

- name: Install dependencies
run: uv sync --all-extras

- name: Run security checks
run: |
uv run pip install bandit safety
uv run bandit -r crossfire/ -f json -o bandit-report.json || true
uv run safety check --json --output safety-report.json || true

uv run bandit -r crossfire/ -ll
uv run safety check
strict: false
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@ runs/
coverage.xml
htmlcov/

# Cursor
.cursor/plans/

# macOS
.DS_Store
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.8
hooks:
- id: ruff-format
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.15.0
hooks:
Expand Down
Loading
Loading