Thank you for your interest in contributing to OpenSpec-rs! This document provides guidelines and instructions for contributing.
- Code of Conduct
- How to Contribute
- Development Setup
- Project Structure
- Code Style
- Testing
- Commit Messages
- Pull Request Process
- Syncing with Upstream
Be respectful and constructive. We welcome contributions from everyone.
- Check if the issue already exists in GitHub Issues
- If not, create a new issue with:
- Clear title and description
- Steps to reproduce (for bugs)
- Expected vs actual behavior
- Your environment (OS, Rust version)
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Make your changes
- Run tests and lints
- Submit a pull request
- Rust 1.75+ (MSRV - Minimum Supported Rust Version)
- Git (for version control)
- Just (optional, for running commands -
cargo install just)
# Clone your fork
git clone https://github.com/YOUR_USERNAME/OpenSpec-rs.git
cd OpenSpec-rs
# Initialize submodule (optional, for upstream reference)
git submodule update --init --recursive
# Build
cargo build
# Run tests
cargo test
# Run the binary
cargo run -- <command># Run all tests
cargo test
# Run specific test
cargo test test_schema_parsing
# Run with verbose output
cargo test -- --nocapture
# Check code without building
cargo check
# Run linter
cargo clippy
# Format code
cargo fmt
# Check formatting
cargo fmt --check
# Build release binary
cargo build --releaseOpenSpec-rs/
├── src/
│ ├── main.rs # Entry point
│ ├── cli/ # CLI command implementations
│ │ ├── mod.rs
│ │ ├── init.rs # init command
│ │ ├── new.rs # new change command
│ │ ├── status.rs # status command
│ │ ├── list.rs # list command
│ │ ├── show.rs # show command
│ │ ├── validate.rs # validate command
│ │ ├── archive.rs # archive command
│ │ ├── config.rs # config command
│ │ ├── completion.rs # shell completion
│ │ └── update.rs # update command
│ ├── core/ # Core functionality
│ │ ├── mod.rs
│ │ ├── schema.rs # Schema parsing and validation
│ │ ├── artifact.rs # Artifact graph and status
│ │ ├── parser.rs # Markdown spec parser
│ │ ├── config.rs # Configuration management
│ │ └── change.rs # Change discovery
│ ├── templates/ # Embedded templates
│ │ ├── mod.rs
│ │ ├── skills.rs # AI skill templates
│ │ └── schema.rs # Embedded schema YAML
│ ├── ai_tools/ # AI tool generators
│ │ ├── mod.rs
│ │ └── config.rs # Tool configurations
│ ├── telemetry/ # Optional telemetry
│ │ └── mod.rs
│ └── utils/ # Utilities
│ ├── mod.rs
│ ├── output.rs # Colored output
│ ├── error.rs # Error types
│ └── spinner.rs # Progress indicators
├── templates/
│ └── skills/ # Skill template markdown files
├── tests/
│ └── integration_tests.rs # Integration tests
├── vendor/
│ └── OpenSpec/ # Upstream TypeScript source (submodule)
├── openspec/ # OpenSpec project files
├── .opencode/ # OpenCode AI configuration
├── docs/ # Documentation
├── Cargo.toml
└── README.md
- Follow standard Rust conventions (
cargo fmt) - Use
clippyfor linting:cargo clippy -- -D warnings - Document public APIs with
///doc comments
- Keep functions small - Each function should do one thing well
- Use meaningful names - Prefer
change_directoryoverdir - Handle errors properly - Use
Result<T, E>andthiserror - No panics in library code - Return
Resultinstead - Minimize dependencies - Only add what's necessary
- Create a new file in
src/cli/(e.g.,src/cli/mycommand.rs) - Define the command struct with
clapderive macros - Implement the
runmethod - Add the module to
src/cli/mod.rs - Add the variant to
src/cli/Argsenum - Add tests in
tests/integration_tests.rs
Example:
// src/cli/mycommand.rs
use crate::utils::error::Result;
#[derive(Debug, clap::Args)]
pub struct MyCommandArgs {
#[arg(short, long)]
pub option: Option<String>,
}
pub fn run(args: MyCommandArgs, ctx: &crate::core::config::Context) -> Result<()> {
// Implementation
Ok(())
}# All tests
cargo test
# Unit tests only
cargo test --lib
# Integration tests only
cargo test --test integration_tests
# Specific test
cargo test test_init_creates_openspec_directory- Place unit tests in the same file using
#[cfg(test)] mod tests { ... } - Place integration tests in
tests/integration_tests.rs - Use
tempfilefor tests that create files/directories - Test both success and error cases
Example:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_valid_yaml() {
let input = "name: test\nversion: \"1.0\"";
let result = parse_schema(input);
assert!(result.is_ok());
}
#[test]
fn test_parse_invalid_yaml() {
let input = "invalid: [unclosed";
let result = parse_schema(input);
assert!(result.is_err());
}
}Follow conventional commit format:
<type>: <description>
[optional body]
[optional footer]
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Code style changes (formatting, etc.)refactor: Code refactoringtest: Adding/updating testschore: Maintenance tasks
feat: add --json flag to status command
fix: handle missing .openspec.yaml gracefully
docs: update installation instructions
test: add integration tests for archive command
- Create a feature branch from
main - Make focused changes - One feature/fix per PR
- Write/update tests for your changes
- Update documentation if needed
- Run the test suite:
cargo test && cargo clippy - Submit the PR with a clear description
- Code compiles (
cargo build) - Tests pass (
cargo test) - No clippy warnings (
cargo clippy) - Code is formatted (
cargo fmt) - Documentation updated if needed
- CHANGELOG.md updated (for significant changes)
The vendor/OpenSpec/ submodule contains the upstream TypeScript implementation. When syncing:
-
Update the submodule:
cd vendor/OpenSpec git fetch origin git checkout <new-tag> cd ../.. git add vendor/OpenSpec
-
Compare and port changes:
diff -r vendor/OpenSpec/src/commands src/cli/
-
Update
docs/STATE.mdwith the new sync version -
Add tests for new/changed functionality
Open an issue for questions or discussion about contributions.
By contributing, you agree that your contributions will be licensed under the MIT License.