Skip to content

Latest commit

 

History

History
294 lines (218 loc) · 14.2 KB

File metadata and controls

294 lines (218 loc) · 14.2 KB

Claude Code Instructions for rust-claude-codes

Development Strategy

Test-Driven Protocol Development

This project follows a test-driven development approach for implementing the Claude Code JSON protocol:

  1. Discover Protocol Through Usage: Run examples to interact with Claude and discover new message types
  2. Capture Failed Cases: Any JSON that fails to deserialize is automatically saved to test_cases/failed_deserializations/
  3. Format Test Cases: Run ./format_test_cases.sh to ensure JSON formatting
  4. Implement Missing Types: Add the necessary variants to ClaudeOutput enum in src/io.rs
  5. Verify Implementation: Run cargo test deserialization to ensure the new types deserialize correctly
  6. Lock in Progress: Successful test cases prove our protocol implementation is correct

Re-snapshotting against a new Claude CLI

Claude Code ships no published schema — the wire types live as minified zod definitions inside the Bun-compiled CLI ELF. Two scripts turn that into a drift signal:

# Full extraction (one block per schema, for the crate-side field-by-field diff):
python3 scripts/extract_claude_sdk_schemas.py -o /tmp/claude_sdk_schemas.txt

# Automated drift check against the committed snapshot (wire labels + top-level fields):
python3 scripts/check_claude_schema_drift.py           # exit 0 clean / 1 drift / 2 skip
python3 scripts/check_claude_schema_drift.py --update   # accept a new snapshot

The snapshot lives at claude-codes/tests/schemas/claude_stream_json_snapshot.txt and the nightly .github/workflows/claude-schema-drift.yml opens a claude-schema-drift-labelled issue when the installed CLI drifts from it. Full procedure — where the bundle lives, how to map schemas to crate types, what counts as drift, manual spelunking recipes — is in claude-codes/RESNAPSHOTTING.md.

Git Workflow Requirements

CRITICAL: This repository enforces a strict PR-based workflow

Getting Started

# Clone the repository
git clone https://github.com/meawoppl/rust-claude-codes
cd rust-claude-codes

# Install git hooks (required for all contributors)
./setup_hooks.sh

# Create a feature branch for your work
git checkout -b feature/your-feature-name

# Make your changes, then commit (avoid git add -A!)
git add -u  # Stage modified files only
git commit -m "Your descriptive commit message"

# Push to GitHub and create a PR
git push origin feature/your-feature-name

Branch Protection & Git Hooks

  • NEVER commit directly to main branch
  • All changes MUST go through feature branches and pull requests
  • The pre-commit hook will block direct commits to main

Pre-commit Checks

The repository has git hooks that enforce:

  • No commits to main branch
  • Code formatting with cargo fmt
  • All clippy warnings resolved
  • All tests passing
  • JSON test cases properly formatted

If you haven't already, run ./setup_hooks.sh to install these hooks.

CI/CD Requirements

All PRs must pass the following GitHub Actions checks:

  • ✅ Code formatting (cargo fmt --all -- --check)
  • ✅ Clippy linting (cargo clippy --all-targets --all-features -- -D warnings)
  • ✅ All tests passing (cargo test --all-features)
  • ✅ JSON test cases properly formatted
  • ✅ All examples compile (./build_examples.sh)
  • ✅ Documentation builds (cargo doc --no-deps)
  • ✅ MSRV compatibility (Rust 1.85+)

Protocol Implementation Guidelines

When implementing new message types:

  1. Start with the test case - Look at the failed JSON in test_cases/
  2. Identify the structure - Note field names, types, and nesting
  3. Add to ClaudeOutput enum - Create a new variant with appropriate struct
  4. Follow existing patterns - Use #[serde(skip_serializing_if = "Option::is_none")] for optional fields
  5. Test immediately - Run cargo test deserialization to verify
  6. Document the type - Add doc comments explaining when this message appears

Reference SDKs (gitignored)

The reference/ directory (gitignored) holds local clones of other codex/claude SDK consumers, kept around to study their serialization patterns when evolving our own. Refresh with git -C reference/<repo> pull as needed; not part of the workspace build.

reference/openai-codex/ — OpenAI's own monorepo (THE source of truth)

  • codex-rs/app-server-protocol/src/protocol/v{1,2}.rs — Hand-written serde-annotated Rust structs for every wire type. schemars derives produce a JSON Schema.
  • codex-rs/app-server-protocol/src/bin/{export,write_schema_fixtures}.rs — Binaries that emit the schema. The shipped codex CLI exposes this as codex app-server generate-json-schema --out DIR (emits codex_app_server_protocol.v2.schemas.json) and codex app-server generate-ts for TypeScript.
  • sdk/python/src/openai_codex/generated/v2_all.py — 8,295 lines, 128 notification types auto-generated by datamodel_code_generator from the JSON Schema. Pipeline lives at sdk/python/scripts/update_sdk_artifacts.py:517-532. The matching dispatch table is at generated/notification_registry.py.
  • sdk/python/src/openai_codex/client.py:449-460 — Dispatch logic. On unknown method OR typed-decode failure, routes to UnknownNotification(params=params_dict) (silent downgrade, no error).

reference/codex-sdk-go/ — Third-party Go SDK

  • rpc/notifications_gen.go — Generated dispatch map; unmarshal errors are caught and the function returns Notification{Method, Raw: params}, nil. Raw frame preserved, but errors are silently swallowed.
  • internal/codegen/main.go — Their codegen entry-point, same JSON Schema source.

reference/ai-sdk-provider-codex-cli/ — Vercel AI SDK provider (TypeScript)

  • src/app-server/rpc/client.ts:656-665 — Zod-validated dispatch. On validation failure, silently drops the notification with a warn log. Weakest design of the four.

reference/codex-sdk-elixir/ — Third-party Elixir SDK

  • lib/codex/app_server/notification_adapter.ex:499-505 — Hand-written pattern matching with Events.AppServerNotification{method, params} catch-all. Similar to our Unknown { method, params } fallback.

Dispatch / error-handling philosophy across the four

SDK Unknown method Typed-decode failure Raw frame preserved on failure
Python (official) Silent downgrade to UnknownNotification Silent downgrade (same path) In .payload.params
Go Silent downgrade with .Raw Silent (error swallowed) In .Raw
TypeScript Dropped entirely Dropped No
Elixir Silent downgrade Falls back to raw In params
Our codex-codes Silent → Unknown { method, params } Errors loudly via Error::Deserialization(ParseError) Yes (ParseError.raw_line, .raw_json, .method)

Ours is the strictest and most informative — but also the least forgiving of server version skew. See "prioritized improvements" below.

Prioritized improvements to codex-codes (from deep-dive vs. references)

  1. Consume the upstream JSON Schema rather than hand-writing types. Either codegen against codex_app_server_protocol.v2.schemas.json like the Python SDK, or — cleaner since we're also Rust — depend on the app-server-protocol crate directly and re-export. We currently model ~15 notification variants; Python has 128. Big ~88% coverage gap.
  2. Optionally downgrade typed-decode failures to Unknown behind a flag, instead of bubbling Error::Deserialization. Match Python/Go/Elixir behaviour for forward-compat-by-default, but keep our strict ParseError path opt-in for bug-report fidelity.
  3. Track the server's reported version from the initialize response and expose it as client.server_version() -> Option<&str>. Log on startup; warn on significant skew. Currently we don't surface it.
  4. Audit FileUpdateChange / PatchChangeKind against codex-rs types — issue #128 noted a type value not in add|delete|update. Likely our enum is missing variants the wire now sends.
  5. Add the obviously-missing notifications the Python registry has: item/fileChange/patchUpdated, turn/plan/updated, turn/diff/updated, item/plan/delta, MCP-related (mcpServer/oauthLogin/completed), account-related (account/login/completed).
  6. Document the Unknown variant contract in messages.rs — make explicit that callers should ignore unknowns rather than treat them as errors, so version skew remains a soft failure.

Code Quality Standards

IMPORTANT: Before every commit, you MUST:

  1. Run cargo fmt --all to format all Rust code
  2. Run cargo clippy --all-targets --all-features -- -D warnings and fix all warnings
  3. Ensure all tests pass with cargo test --all

CRITICAL: ALWAYS run cargo fmt --all and cargo clippy --all-targets --all-features -- -D warnings before EVERY commit without exception. This is non-negotiable.

NOTE: CI will fail if there are clippy warnings or formatting issues, so please fix them before committing.

Git Commit Guidelines

⚠️ ABSOLUTELY FORBIDDEN: NEVER USE git add -A ⚠️

CRITICAL: The git add -A command is STRICTLY PROHIBITED in this repository!

WHY THIS MATTERS:

  • git add -A stages ALL files including untracked files, temp files, build artifacts, and other random crap
  • This has repeatedly caused issues with unwanted files being committed
  • It can expose sensitive information, break builds, and pollute the repository

WHAT TO USE INSTEAD:

  • git add -u - Stages only modified tracked files (PREFERRED)
  • git add <specific-file> - Stage specific files by name
  • git add src/ - Stage specific directories if needed
  • ALWAYS run git status first to review what will be staged

UNACCEPTABLE:

git add -A          # NEVER DO THIS
git add --all       # NEVER DO THIS
git add .           # AVOID THIS TOO

CORRECT:

git status          # Review changes first
git add -u          # Stage modified files only
git add src/io.rs   # Or stage specific files

Remember: It's better to run git add multiple times for specific files than to accidentally commit garbage with -A.

Rust Development Standards

Code Organization

  • Follow Rust naming conventions (snake_case for functions/variables, CamelCase for types)
  • Use descriptive variable names
  • Keep functions focused and under 50 lines when possible
  • Document public APIs with rustdoc comments
  • Place shared code in appropriate modules to avoid duplication

Error Handling

  • Use Result types for fallible operations
  • Provide meaningful error messages
  • Implement proper error propagation with ? operator
  • Consider using thiserror or anyhow for error management

Testing Requirements

  • Write unit tests for all business logic
  • Use #[cfg(test)] modules for test code
  • Mock external dependencies in tests
  • Aim for high test coverage
  • Use property-based testing with quickcheck or proptest where applicable

Performance Considerations

  • Profile before optimizing
  • Use &str instead of String when ownership isn't needed
  • Prefer iterators over collecting into intermediate vectors
  • Use Cow for potentially-borrowed data
  • Consider using Arc or Rc for shared ownership when appropriate

Async Programming

  • Use tokio for async runtime when needed
  • Properly handle async errors
  • Avoid blocking operations in async contexts
  • Use tokio::spawn for concurrent tasks

Workflow Commands

When I say:

  • "complete": Run cargo fmt --all, fix clippy issues with cargo clippy --all-targets --all-features -- -D warnings, then commit and push
  • "freshen": Pull main and merge into the current branch
  • "merge main": Pull the remote main branch and merge it into the current working branch
  • "integration tests": Run cargo test --features integration-tests (requires Claude CLI installed and API key configured)

Development Philosophy

Incremental Development

  • Build from smallest testable pieces
  • Validate each component before integration
  • Layer functionality incrementally
  • Maintain working state at each step

Code Quality Over Speed

  • Don't take shortcuts that compromise quality
  • Handle edge cases properly
  • Consider error paths thoroughly
  • Write code that's maintainable and clear

Testing First

  • Write tests alongside implementation
  • Test edge cases and error conditions
  • Ensure tests are deterministic and reliable
  • Keep tests focused and independent

Important Reminders

  • Don't use esoteric scripts to edit code - use direct read/write operations
  • Always run fmt and clippy before committing
  • Never create files unless absolutely necessary
  • Prefer editing existing files over creating new ones
  • Update documentation when making significant changes

Version Management

When updating the version number in Cargo.toml:

  1. Update the version field in Cargo.toml
  2. Run cargo build to regenerate Cargo.lock with the new version
  3. Commit both Cargo.toml and Cargo.lock together
  4. Use a commit message like: "chore: bump version to X.Y.Z"

This ensures the lockfile stays in sync with the version number.

Version bump policy

Patch bumps (0.x.Y → 0.x.Y+1, or x.y.Z → x.y.Z+1) are fine to do without asking.

For anything larger — minor (0.x → 0.x+1, or x.y → x.y+1) or major (x → x+1) — ask first and wait for explicit approval before bumping. These reflect breaking or significant API changes and the user wants to be in the loop on the framing / changelog narrative before the version moves.

Publishing

  • NEVER use cargo publish --allow-dirty — ensure the working tree is clean before publishing
  • If there are untracked files, either .gitignore them or clean them up first

Dependency Management

When adding or updating dependencies:

  • ALWAYS use cargo add or cargo remove commands instead of manually editing Cargo.toml
  • For optional dependencies: cargo add <package> --optional
  • To update all dependencies: cargo update
  • To update specific dependency: cargo update <package>

Examples:

cargo add serde --features derive      # Add with features
cargo add tokio --optional             # Add as optional
cargo remove old-package               # Remove a dependency
cargo update                           # Update all to latest compatible