From 227e354ab22b26fde5653c62c916e1f2c66cf6e6 Mon Sep 17 00:00:00 2001 From: konard Date: Sat, 27 Dec 2025 17:25:11 +0100 Subject: [PATCH 1/3] Initial commit with task details Adding CLAUDE.md with task information for AI processing. This file will be removed when the task is complete. Issue: https://github.com/linksplatform/core-rs/issues/6 --- CLAUDE.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..20eeb52 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +Issue to solve: https://github.com/linksplatform/core-rs/issues/6 +Your prepared branch: issue-6-015a7fd0a8da +Your prepared working directory: /tmp/gh-issue-solver-1766852709674 + +Proceed. \ No newline at end of file From 6bc4bb40d8d777e19ecbf493e23ab22c472d0ab9 Mon Sep 17 00:00:00 2001 From: konard Date: Sat, 27 Dec 2025 17:34:04 +0100 Subject: [PATCH 2/3] feat: add modern CI/CD pipeline from template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply CI/CD pipeline from rust-ai-driven-development-pipeline-template: - Add GitHub Actions workflow (.github/workflows/release.yml): - Lint and format checking with rustfmt and clippy - Multi-platform testing (Ubuntu, macOS, Windows) - Automatic release on version change - Manual release via workflow_dispatch - Uses nightly toolchain for unstable features - Add CI/CD utility scripts: - bump_version.py: Version bumping utility - check_file_size.py: Rust file size validation (max 1000 lines) - collect_changelog.py: Changelog fragment collection - create_github_release.py: GitHub release automation - version_and_commit.py: CI/CD version management - Add changelog management: - CHANGELOG.md with Keep a Changelog format - changelog.d/ directory for fragment-based changelog system - Add development configuration: - .pre-commit-config.yaml for code quality hooks - CONTRIBUTING.md with development guidelines - Updated .gitignore with comprehensive patterns Fixes #6 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/release.yml | 257 +++++++++++++++ .gitignore | 56 +++- .pre-commit-config.yaml | 34 ++ CHANGELOG.md | 22 ++ CONTRIBUTING.md | 298 ++++++++++++++++++ .../20251227_173319_add_cicd_pipeline.md | 9 + changelog.d/README.md | 51 +++ scripts/bump_version.py | 105 ++++++ scripts/check_file_size.py | 98 ++++++ scripts/collect_changelog.py | 132 ++++++++ scripts/create_github_release.py | 102 ++++++ scripts/version_and_commit.py | 162 ++++++++++ 12 files changed, 1325 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release.yml create mode 100644 .pre-commit-config.yaml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 changelog.d/20251227_173319_add_cicd_pipeline.md create mode 100644 changelog.d/README.md create mode 100644 scripts/bump_version.py create mode 100644 scripts/check_file_size.py create mode 100644 scripts/collect_changelog.py create mode 100644 scripts/create_github_release.py create mode 100644 scripts/version_and_commit.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..5172c09 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,257 @@ +name: CI/CD Pipeline + +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] + workflow_dispatch: + inputs: + bump_type: + description: 'Version bump type' + required: true + type: choice + options: + - patch + - minor + - major + description: + description: 'Release description (optional)' + required: false + type: string + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + +jobs: + # REQUIRED CI CHECKS - All must pass before release + # These jobs ensure code quality and tests pass before any release + + # Linting and formatting + lint: + name: Lint and Format Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@nightly + with: + components: rustfmt, clippy + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Check formatting + run: cargo fmt --all -- --check + + - name: Run Clippy + run: cargo clippy --all-targets --all-features + + - name: Check file size limit + run: python3 scripts/check_file_size.py + + # Test on multiple OS + test: + name: Test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@nightly + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Run tests + run: cargo test --all-features --verbose + + - name: Run doc tests + run: cargo test --doc --verbose + + # Build package - only runs if lint and test pass + build: + name: Build Package + runs-on: ubuntu-latest + needs: [lint, test] + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@nightly + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-build- + + - name: Build release + run: cargo build --release --verbose + + - name: Check package + run: cargo package --list + + # Check for changelog fragments in PRs + changelog: + name: Changelog Fragment Check + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check for changelog fragments + run: | + # Get list of fragment files (excluding README and template) + FRAGMENTS=$(find changelog.d -name "*.md" ! -name "README.md" 2>/dev/null | wc -l) + + # Get changed files in PR + CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) + + # Check if any source files changed (excluding docs and config) + SOURCE_CHANGED=$(echo "$CHANGED_FILES" | grep -E "^(src/|tests/|scripts/|examples/)" | wc -l) + + if [ "$SOURCE_CHANGED" -gt 0 ] && [ "$FRAGMENTS" -eq 0 ]; then + echo "::warning::No changelog fragment found. Please add a changelog entry in changelog.d/" + echo "" + echo "To create a changelog fragment:" + echo " Create a new .md file in changelog.d/ with your changes" + echo "" + echo "See changelog.d/README.md for more information." + # Note: This is a warning, not a failure, to allow flexibility + # Change 'exit 0' to 'exit 1' to make it required + exit 0 + fi + + echo "Changelog check passed" + + # Automatic release on push to main (if version changed) + auto-release: + name: Auto Release + needs: [lint, test, build] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@nightly + + - name: Check if version changed + id: version_check + run: | + # Get current version from Cargo.toml + CURRENT_VERSION=$(grep -Po '(?<=^version = ")[^"]*' Cargo.toml) + echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + + # Check if tag exists + if git rev-parse "v$CURRENT_VERSION" >/dev/null 2>&1; then + echo "Tag v$CURRENT_VERSION already exists, skipping release" + echo "should_release=false" >> $GITHUB_OUTPUT + else + echo "New version detected: $CURRENT_VERSION" + echo "should_release=true" >> $GITHUB_OUTPUT + fi + + - name: Build release + if: steps.version_check.outputs.should_release == 'true' + run: cargo build --release + + - name: Create GitHub Release + if: steps.version_check.outputs.should_release == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + python3 scripts/create_github_release.py \ + --version "${{ steps.version_check.outputs.current_version }}" \ + --repository "${{ github.repository }}" + + # Manual release via workflow_dispatch - only after CI passes + manual-release: + name: Manual Release + needs: [lint, test, build] + if: github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Rust + uses: dtolnay/rust-toolchain@nightly + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Collect changelog fragments + run: | + # Check if there are any fragments to collect + FRAGMENTS=$(find changelog.d -name "*.md" ! -name "README.md" 2>/dev/null | wc -l) + if [ "$FRAGMENTS" -gt 0 ]; then + echo "Found $FRAGMENTS changelog fragment(s), collecting..." + python3 scripts/collect_changelog.py + else + echo "No changelog fragments found, skipping collection" + fi + + - name: Version and commit + id: version + run: | + python3 scripts/version_and_commit.py \ + --bump-type "${{ github.event.inputs.bump_type }}" \ + --description "${{ github.event.inputs.description }}" + + - name: Build release + if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' + run: cargo build --release + + - name: Create GitHub Release + if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + python3 scripts/create_github_release.py \ + --version "${{ steps.version.outputs.new_version }}" \ + --repository "${{ github.repository }}" diff --git a/.gitignore b/.gitignore index ca98cd9..fc183aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,56 @@ -/target/ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# See https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# Generated by cargo mutants +# Contains mutation testing data +**/mutants.out*/ + +# IDE and editor files +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS files +.DS_Store +Thumbs.db + +# Python virtual environments (for scripts) +.venv/ +venv/ +__pycache__/ +*.pyc +*.pyo + +# Coverage reports +*.lcov +coverage/ +tarpaulin-report.html + +# Benchmark results +criterion/ + +# Documentation build output +doc/ + +# Local development files +.env +.env.local +*.local + +# Log files +*.log +logs/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..ce4da86 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,34 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: check-merge-conflict + - id: check-toml + - id: debug-statements + + - repo: local + hooks: + - id: cargo-fmt + name: cargo fmt + entry: cargo fmt --all -- + language: system + types: [rust] + pass_filenames: false + + - id: cargo-clippy + name: cargo clippy + entry: cargo clippy --all-targets --all-features -- -D warnings + language: system + types: [rust] + pass_filenames: false + + - id: cargo-test + name: cargo test + entry: cargo test + language: system + types: [rust] + pass_filenames: false diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a8980da --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + + +## [0.1.0-beta.3] - Previous Release + +### Added + +- Core data types and traits for Links Platform +- `LinkType` trait for numeric link identifiers +- `Links` trait for CRUD operations on doublet links storage +- `Flow` type for iteration control (Continue/Break) +- `Query` wrapper with copy-on-write semantics +- `Point` structure for repeating elements +- `Hybrid` type for internal/external link references +- `LinksConstants` for storage configuration +- `AddrToRaw` / `RawToAddr` converters diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5405e1f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,298 @@ +# Contributing to platform-data + +Thank you for your interest in contributing! This document provides guidelines and instructions for contributing to this project. + +## Development Setup + +1. **Fork and clone the repository** + + ```bash + git clone https://github.com/YOUR-USERNAME/core-rs.git + cd core-rs + ``` + +2. **Install Rust** + + Install Rust using rustup (if not already installed): + + ```bash + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + ``` + +3. **Install development tools** + + ```bash + rustup component add rustfmt clippy + ``` + +4. **Install pre-commit hooks** (optional but recommended) + + ```bash + pip install pre-commit + pre-commit install + ``` + +5. **Build the project** + + ```bash + cargo build + ``` + +## Development Workflow + +1. **Create a feature branch** + + ```bash + git checkout -b feature/my-feature + ``` + +2. **Make your changes** + + - Write code following the project's style guidelines + - Add tests for any new functionality + - Update documentation as needed + +3. **Run quality checks** + + ```bash + # Format code + cargo fmt + + # Run Clippy lints + cargo clippy --all-targets --all-features + + # Check file sizes + python3 scripts/check_file_size.py + + # Run all checks together + cargo fmt --check && cargo clippy --all-targets --all-features && python3 scripts/check_file_size.py + ``` + +4. **Run tests** + + ```bash + # Run all tests + cargo test + + # Run tests with verbose output + cargo test --verbose + + # Run doc tests + cargo test --doc + + # Run a specific test + cargo test test_name + ``` + +5. **Add a changelog fragment** + + For any user-facing changes, create a changelog fragment: + + ```bash + # Create a new file in changelog.d/ + # Format: YYYYMMDD_HHMMSS_description.md + touch changelog.d/$(date +%Y%m%d_%H%M%S)_my_change.md + ``` + + Edit the file to document your changes: + + ```markdown + ### Added + - Description of new feature + + ### Fixed + - Description of bug fix + ``` + + **Why fragments?** This prevents merge conflicts in CHANGELOG.md when multiple PRs are open simultaneously. + +6. **Commit your changes** + + ```bash + git add . + git commit -m "feat: add new feature" + ``` + + Pre-commit hooks will automatically run and check your code. + +7. **Push and create a Pull Request** + + ```bash + git push origin feature/my-feature + ``` + + Then create a Pull Request on GitHub. + +## Code Style Guidelines + +This project uses: + +- **rustfmt** for code formatting +- **Clippy** for linting with pedantic and nursery lints enabled +- **cargo test** for testing + +### Code Standards + +- Follow Rust idioms and best practices +- Use documentation comments (`///`) for all public APIs +- Write tests for all new functionality +- Keep functions focused and reasonably sized +- Keep files under 1000 lines +- Use meaningful variable and function names + +### Documentation Format + +Use Rust documentation comments: + +```rust +/// Brief description of the function. +/// +/// Longer description if needed. +/// +/// # Arguments +/// +/// * `arg1` - Description of arg1 +/// * `arg2` - Description of arg2 +/// +/// # Returns +/// +/// Description of return value +/// +/// # Errors +/// +/// Description of when errors are returned +/// +/// # Examples +/// +/// ``` +/// use platform_data::example_function; +/// let result = example_function(1, 2); +/// assert_eq!(result, 3); +/// ``` +pub fn example_function(arg1: i32, arg2: i32) -> i32 { + arg1 + arg2 +} +``` + +## Testing Guidelines + +- Write tests for all new features +- Maintain or improve test coverage +- Use descriptive test names +- Organize tests in modules when appropriate +- Use `#[cfg(test)]` for test-only code + +Example test structure: + +```rust +#[cfg(test)] +mod tests { + use super::*; + + mod my_feature_tests { + use super::*; + + #[test] + fn test_basic_functionality() { + assert_eq!(my_function(), expected_result); + } + + #[test] + fn test_edge_case() { + assert_eq!(my_function(edge_case_input), expected_result); + } + } +} +``` + +## Pull Request Process + +1. Ensure all tests pass locally +2. Update documentation if needed +3. Add a changelog fragment (see step 5 in Development Workflow) +4. Ensure the PR description clearly describes the changes +5. Link any related issues in the PR description +6. Wait for CI checks to pass +7. Address any review feedback + +## Changelog Management + +This project uses a fragment-based changelog system similar to [Scriv](https://scriv.readthedocs.io/) (Python) and [Changesets](https://github.com/changesets/changesets) (JavaScript). + +### Creating a Fragment + +```bash +# Create a new fragment with timestamp +touch changelog.d/$(date +%Y%m%d_%H%M%S)_description.md +``` + +### Fragment Categories + +Use these categories in your fragments: + +- **Added**: New features +- **Changed**: Changes to existing functionality +- **Deprecated**: Features that will be removed in future +- **Removed**: Features that were removed +- **Fixed**: Bug fixes +- **Security**: Security-related changes + +### During Release + +Fragments are automatically collected into CHANGELOG.md during the release process. The release workflow: + +1. Collects all fragments +2. Updates CHANGELOG.md with the new version entry +3. Removes processed fragment files +4. Bumps the version in Cargo.toml +5. Creates a git tag and GitHub release + +## Project Structure + +``` +. +├── .github/workflows/ # GitHub Actions CI/CD +├── changelog.d/ # Changelog fragments +│ ├── README.md # Fragment instructions +│ └── *.md # Individual changelog fragments +├── scripts/ # Utility scripts +├── src/ # Library source code +├── tests/ # Integration tests +├── .gitignore # Git ignore patterns +├── .pre-commit-config.yaml # Pre-commit hooks +├── Cargo.toml # Project configuration +├── CHANGELOG.md # Project changelog +├── CONTRIBUTING.md # This file +├── LICENSE # Unlicense (public domain) +└── README.md # Project README +``` + +## Release Process + +This project uses semantic versioning (MAJOR.MINOR.PATCH): + +- **MAJOR**: Breaking changes +- **MINOR**: New features (backward compatible) +- **PATCH**: Bug fixes (backward compatible) + +Releases are managed through GitHub releases. To trigger a release: + +1. Manually trigger the release workflow with a version bump type +2. Or: Update the version in Cargo.toml and push to main + +## Getting Help + +- Open an issue for bugs or feature requests +- Use discussions for questions and general help +- Check existing issues and PRs before creating new ones +- Get support on [Discord](https://discord.gg/eEXJyjWv5e) +- Ask questions at [stackoverflow.com/tags/links-platform](https://stackoverflow.com/tags/links-platform) + +## Code of Conduct + +- Be respectful and inclusive +- Provide constructive feedback +- Focus on what is best for the community +- Show empathy towards other community members + +Thank you for contributing! diff --git a/changelog.d/20251227_173319_add_cicd_pipeline.md b/changelog.d/20251227_173319_add_cicd_pipeline.md new file mode 100644 index 0000000..1c80a43 --- /dev/null +++ b/changelog.d/20251227_173319_add_cicd_pipeline.md @@ -0,0 +1,9 @@ +### Added +- Modern CI/CD pipeline from rust-ai-driven-development-pipeline-template +- GitHub Actions workflow for automated testing, linting, and releases +- Multi-platform testing (Ubuntu, macOS, Windows) +- Pre-commit hooks configuration for code quality +- Fragment-based changelog system +- Automated release workflow with version management +- File size checking for Rust source files +- Contributing guidelines (CONTRIBUTING.md) diff --git a/changelog.d/README.md b/changelog.d/README.md new file mode 100644 index 0000000..dad26e5 --- /dev/null +++ b/changelog.d/README.md @@ -0,0 +1,51 @@ +# Changelog Fragments + +This directory contains changelog fragments that will be collected into `CHANGELOG.md` during releases. + +## How to Add a Changelog Fragment + +When making changes that should be documented in the changelog, create a fragment file: + +```bash +# Create a new fragment with timestamp +touch changelog.d/$(date +%Y%m%d_%H%M%S)_description.md + +# Or manually create a file matching the pattern: YYYYMMDD_HHMMSS_description.md +``` + +## Fragment Format + +Each fragment should contain relevant sections. Use the appropriate sections: + +```markdown +### Added +- Description of new feature + +### Changed +- Description of change to existing functionality + +### Fixed +- Description of bug fix + +### Removed +- Description of removed feature + +### Deprecated +- Description of deprecated feature + +### Security +- Description of security fix +``` + +## Why Fragments? + +Using changelog fragments (similar to [Changesets](https://github.com/changesets/changesets) in JavaScript and [Scriv](https://scriv.readthedocs.io/) in Python): + +1. **No merge conflicts**: Multiple PRs can add fragments without conflicts +2. **Per-PR documentation**: Each PR documents its own changes +3. **Automated collection**: Fragments are automatically collected during release +4. **Consistent format**: Template ensures consistent changelog entries + +## During Release + +Fragments are automatically collected into `CHANGELOG.md` by running the collection script. This is handled automatically by the release workflow. diff --git a/scripts/bump_version.py b/scripts/bump_version.py new file mode 100644 index 0000000..e748c27 --- /dev/null +++ b/scripts/bump_version.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +"""Bump version in Cargo.toml. + +A simple utility script for bumping the version number. +""" + +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path + + +def get_current_version() -> tuple[int, int, int]: + """Get current version from Cargo.toml. + + Returns: + Tuple of (major, minor, patch) + """ + cargo_toml = Path("Cargo.toml") + if not cargo_toml.exists(): + print("Error: Cargo.toml not found", file=sys.stderr) + sys.exit(1) + + content = cargo_toml.read_text() + match = re.search(r'^version\s*=\s*"(\d+)\.(\d+)\.(\d+)"', content, re.MULTILINE) + if not match: + print("Error: Could not parse version from Cargo.toml", file=sys.stderr) + sys.exit(1) + return int(match.group(1)), int(match.group(2)), int(match.group(3)) + + +def bump_version(current: tuple[int, int, int], bump_type: str) -> str: + """Calculate new version based on bump type. + + Args: + current: Current version as tuple + bump_type: One of 'major', 'minor', 'patch' + + Returns: + New version string + """ + major, minor, patch = current + if bump_type == "major": + return f"{major + 1}.0.0" + elif bump_type == "minor": + return f"{major}.{minor + 1}.0" + else: + return f"{major}.{minor}.{patch + 1}" + + +def update_cargo_toml(new_version: str) -> None: + """Update version in Cargo.toml. + + Args: + new_version: New version string + """ + cargo_toml = Path("Cargo.toml") + content = cargo_toml.read_text() + content = re.sub( + r'^(version\s*=\s*")[^"]+(")', + f'\\g<1>{new_version}\\2', + content, + count=1, + flags=re.MULTILINE, + ) + cargo_toml.write_text(content) + + +def main() -> None: + """Main function.""" + parser = argparse.ArgumentParser(description="Bump version in Cargo.toml") + parser.add_argument( + "bump_type", + choices=["major", "minor", "patch"], + help="Type of version bump", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Show what would be done without making changes", + ) + args = parser.parse_args() + + current = get_current_version() + current_str = f"{current[0]}.{current[1]}.{current[2]}" + new_version = bump_version(current, args.bump_type) + + print(f"Current version: {current_str}") + print(f"New version: {new_version}") + + if args.dry_run: + print("Dry run - no changes made") + else: + update_cargo_toml(new_version) + print("Updated Cargo.toml") + + +if __name__ == "__main__": + try: + main() + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) diff --git a/scripts/check_file_size.py b/scripts/check_file_size.py new file mode 100644 index 0000000..41274e8 --- /dev/null +++ b/scripts/check_file_size.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +"""Check for files exceeding the maximum allowed line count. + +Exits with error code 1 if any files exceed the limit. +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +MAX_LINES = 1000 +FILE_EXTENSIONS = [".rs"] +EXCLUDE_PATTERNS = [ + "target", + ".git", + "node_modules", +] + + +def should_exclude(path: Path, exclude_patterns: list[str]) -> bool: + """Check if a path should be excluded. + + Args: + path: Path to check + exclude_patterns: List of patterns to exclude + + Returns: + True if path should be excluded + """ + path_str = str(path) + return any(pattern in path_str for pattern in exclude_patterns) + + +def find_rust_files(directory: Path, exclude_patterns: list[str]) -> list[Path]: + """Recursively find all Rust files in a directory. + + Args: + directory: Directory to search + exclude_patterns: Patterns to exclude + + Returns: + List of file paths + """ + files = [] + for path in directory.rglob("*"): + if should_exclude(path, exclude_patterns): + continue + if path.is_file() and path.suffix in FILE_EXTENSIONS: + files.append(path) + return files + + +def count_lines(file_path: Path) -> int: + """Count lines in a file. + + Args: + file_path: Path to the file + + Returns: + Number of lines + """ + return len(file_path.read_text(encoding="utf-8").split("\n")) + + +def main() -> None: + """Main function.""" + cwd = Path.cwd() + print(f"\nChecking Rust files for maximum {MAX_LINES} lines...\n") + + files = find_rust_files(cwd, EXCLUDE_PATTERNS) + violations = [] + + for file in files: + line_count = count_lines(file) + if line_count > MAX_LINES: + violations.append({"file": file.relative_to(cwd), "lines": line_count}) + + if not violations: + print("All files are within the line limit\n") + sys.exit(0) + else: + print("Found files exceeding the line limit:\n") + for violation in violations: + print( + f" {violation['file']}: {violation['lines']} lines " + f"(exceeds {MAX_LINES})" + ) + print(f"\nPlease refactor these files to be under {MAX_LINES} lines\n") + sys.exit(1) + + +if __name__ == "__main__": + try: + main() + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) diff --git a/scripts/collect_changelog.py b/scripts/collect_changelog.py new file mode 100644 index 0000000..a60bc06 --- /dev/null +++ b/scripts/collect_changelog.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +"""Collect changelog fragments into CHANGELOG.md. + +This script collects all .md files from changelog.d/ (except README.md) +and prepends them to CHANGELOG.md, then removes the processed fragments. +""" + +from __future__ import annotations + +import re +import sys +from datetime import datetime +from pathlib import Path + + +def get_version_from_cargo() -> str: + """Extract version from Cargo.toml. + + Returns: + Version string + """ + cargo_toml = Path("Cargo.toml") + if not cargo_toml.exists(): + print("Error: Cargo.toml not found", file=sys.stderr) + sys.exit(1) + + content = cargo_toml.read_text() + match = re.search(r'^version\s*=\s*"([^"]+)"', content, re.MULTILINE) + if not match: + print("Error: Could not find version in Cargo.toml", file=sys.stderr) + sys.exit(1) + + return match.group(1) + + +def collect_fragments() -> str: + """Collect all changelog fragments. + + Returns: + Combined changelog content + """ + changelog_dir = Path("changelog.d") + if not changelog_dir.exists(): + return "" + + fragments = [] + for fragment_path in sorted(changelog_dir.glob("*.md")): + if fragment_path.name == "README.md": + continue + content = fragment_path.read_text().strip() + if content: + fragments.append(content) + + return "\n\n".join(fragments) + + +def update_changelog(version: str, fragments: str) -> None: + """Update CHANGELOG.md with collected fragments. + + Args: + version: Version number for the release + fragments: Collected fragment content + """ + changelog_path = Path("CHANGELOG.md") + insert_marker = "" + date_str = datetime.now().strftime("%Y-%m-%d") + + new_entry = f"\n## [{version}] - {date_str}\n\n{fragments}\n" + + if changelog_path.exists(): + content = changelog_path.read_text() + if insert_marker in content: + content = content.replace(insert_marker, f"{insert_marker}{new_entry}") + else: + # Insert after the header + lines = content.split("\n") + for i, line in enumerate(lines): + if line.startswith("## ["): + lines.insert(i, new_entry) + break + content = "\n".join(lines) + else: + content = f"""# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +{insert_marker} +{new_entry} +""" + + changelog_path.write_text(content) + print(f"Updated CHANGELOG.md with version {version}") + + +def remove_fragments() -> None: + """Remove processed changelog fragments.""" + changelog_dir = Path("changelog.d") + if not changelog_dir.exists(): + return + + for fragment_path in changelog_dir.glob("*.md"): + if fragment_path.name == "README.md": + continue + fragment_path.unlink() + print(f"Removed {fragment_path}") + + +def main() -> None: + """Main function.""" + version = get_version_from_cargo() + print(f"Collecting changelog fragments for version {version}") + + fragments = collect_fragments() + if not fragments: + print("No changelog fragments found") + return + + update_changelog(version, fragments) + remove_fragments() + + print("Changelog collection complete") + + +if __name__ == "__main__": + try: + main() + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) diff --git a/scripts/create_github_release.py b/scripts/create_github_release.py new file mode 100644 index 0000000..438da20 --- /dev/null +++ b/scripts/create_github_release.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +"""Create a GitHub release with changelog content. + +This script creates a GitHub release using the gh CLI. +""" + +from __future__ import annotations + +import argparse +import re +import subprocess +import sys +from pathlib import Path + + +def get_changelog_for_version(version: str) -> str: + """Extract changelog content for a specific version. + + Args: + version: Version to extract changelog for + + Returns: + Changelog content for the version + """ + changelog_path = Path("CHANGELOG.md") + if not changelog_path.exists(): + return f"Release v{version}" + + content = changelog_path.read_text() + + # Find the section for this version + pattern = rf"## \[{re.escape(version)}\].*?\n(.*?)(?=\n## \[|\Z)" + match = re.search(pattern, content, re.DOTALL) + + if match: + return match.group(1).strip() + + return f"Release v{version}" + + +def create_release(version: str, repository: str, body: str) -> None: + """Create GitHub release using gh CLI. + + Args: + version: Version for the release + repository: Repository in owner/repo format + body: Release body content + """ + tag = f"v{version}" + title = f"v{version}" + + cmd = [ + "gh", + "release", + "create", + tag, + "--repo", + repository, + "--title", + title, + "--notes", + body, + ] + + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode != 0: + if "already exists" in result.stderr: + print(f"Release {tag} already exists, skipping") + return + print(f"Error creating release: {result.stderr}", file=sys.stderr) + sys.exit(1) + + print(f"Created release {tag}") + print(result.stdout) + + +def main() -> None: + """Main function.""" + parser = argparse.ArgumentParser(description="Create GitHub release") + parser.add_argument( + "--version", + required=True, + help="Version for the release", + ) + parser.add_argument( + "--repository", + required=True, + help="Repository in owner/repo format", + ) + args = parser.parse_args() + + body = get_changelog_for_version(args.version) + create_release(args.version, args.repository, body) + + +if __name__ == "__main__": + try: + main() + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) diff --git a/scripts/version_and_commit.py b/scripts/version_and_commit.py new file mode 100644 index 0000000..5633a69 --- /dev/null +++ b/scripts/version_and_commit.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +"""Bump version in Cargo.toml and commit changes. + +This script is used by the CI/CD pipeline for releases. +""" + +from __future__ import annotations + +import argparse +import re +import subprocess +import sys +from pathlib import Path + + +def get_current_version() -> tuple[int, int, int]: + """Get current version from Cargo.toml. + + Returns: + Tuple of (major, minor, patch) + """ + cargo_toml = Path("Cargo.toml") + content = cargo_toml.read_text() + match = re.search(r'^version\s*=\s*"(\d+)\.(\d+)\.(\d+)"', content, re.MULTILINE) + if not match: + print("Error: Could not parse version from Cargo.toml", file=sys.stderr) + sys.exit(1) + return int(match.group(1)), int(match.group(2)), int(match.group(3)) + + +def bump_version(current: tuple[int, int, int], bump_type: str) -> str: + """Calculate new version based on bump type. + + Args: + current: Current version as tuple + bump_type: One of 'major', 'minor', 'patch' + + Returns: + New version string + """ + major, minor, patch = current + if bump_type == "major": + return f"{major + 1}.0.0" + elif bump_type == "minor": + return f"{major}.{minor + 1}.0" + else: + return f"{major}.{minor}.{patch + 1}" + + +def update_cargo_toml(new_version: str) -> None: + """Update version in Cargo.toml. + + Args: + new_version: New version string + """ + cargo_toml = Path("Cargo.toml") + content = cargo_toml.read_text() + content = re.sub( + r'^(version\s*=\s*")[^"]+(")', + f'\\g<1>{new_version}\\2', + content, + count=1, + flags=re.MULTILINE, + ) + cargo_toml.write_text(content) + print(f"Updated Cargo.toml to version {new_version}") + + +def check_tag_exists(version: str) -> bool: + """Check if a git tag already exists for this version. + + Args: + version: Version to check + + Returns: + True if tag exists + """ + result = subprocess.run( + ["git", "rev-parse", f"v{version}"], + capture_output=True, + text=True, + ) + return result.returncode == 0 + + +def commit_and_tag(version: str, description: str | None = None) -> None: + """Commit version changes and create tag. + + Args: + version: Version for the tag + description: Optional release description + """ + # Stage Cargo.toml and CHANGELOG.md + subprocess.run(["git", "add", "Cargo.toml", "CHANGELOG.md"], check=True) + + # Check if there are changes to commit + result = subprocess.run( + ["git", "diff", "--cached", "--quiet"], + capture_output=True, + ) + + if result.returncode != 0: + # There are changes to commit + commit_msg = f"chore: release v{version}" + if description: + commit_msg += f"\n\n{description}" + subprocess.run(["git", "commit", "-m", commit_msg], check=True) + print(f"Committed version {version}") + + # Create tag + tag_msg = f"Release v{version}" + if description: + tag_msg += f"\n\n{description}" + subprocess.run(["git", "tag", "-a", f"v{version}", "-m", tag_msg], check=True) + print(f"Created tag v{version}") + + # Push changes and tag + subprocess.run(["git", "push"], check=True) + subprocess.run(["git", "push", "--tags"], check=True) + print("Pushed changes and tags") + + +def main() -> None: + """Main function.""" + parser = argparse.ArgumentParser(description="Bump version and commit") + parser.add_argument( + "--bump-type", + choices=["major", "minor", "patch"], + required=True, + help="Type of version bump", + ) + parser.add_argument( + "--description", + default=None, + help="Release description", + ) + args = parser.parse_args() + + current = get_current_version() + new_version = bump_version(current, args.bump_type) + + # Check if this version was already released + if check_tag_exists(new_version): + print(f"Tag v{new_version} already exists") + print("::set-output name=already_released::true") + print(f"::set-output name=new_version::{new_version}") + return + + update_cargo_toml(new_version) + commit_and_tag(new_version, args.description) + + # Set GitHub Actions outputs + print(f"::set-output name=version_committed::true") + print(f"::set-output name=new_version::{new_version}") + + +if __name__ == "__main__": + try: + main() + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) From 280efc49d2ad1818f0fef56fc965833cf6200200 Mon Sep 17 00:00:00 2001 From: konard Date: Sat, 27 Dec 2025 17:37:02 +0100 Subject: [PATCH 3/3] Revert "Initial commit with task details" This reverts commit 227e354ab22b26fde5653c62c916e1f2c66cf6e6. --- CLAUDE.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 20eeb52..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,5 +0,0 @@ -Issue to solve: https://github.com/linksplatform/core-rs/issues/6 -Your prepared branch: issue-6-015a7fd0a8da -Your prepared working directory: /tmp/gh-issue-solver-1766852709674 - -Proceed. \ No newline at end of file