#!/bin/bash # Comprehensive release script for clod # Combines man page generation, Hackage release, and Homebrew formula update set -e # Exit on any error cd "$(dirname "$0")/.." PROJECT_ROOT=$(pwd) # Extract current version from cabal file CURRENT_VERSION=$(grep "^version:" clod.cabal | sed 's/version: *//') echo "Current version: $CURRENT_VERSION" # Calculate default new version (increment last number) if [[ $CURRENT_VERSION =~ ([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then MAJOR="${BASH_REMATCH[1]}" MINOR="${BASH_REMATCH[2]}" PATCH="${BASH_REMATCH[3]}" DEFAULT_VERSION="$MAJOR.$MINOR.$((PATCH + 1))" else # Fall back to simple increment if version format is unexpected DEFAULT_VERSION="$CURRENT_VERSION+1" fi # Ask user for new version read -p "Enter new version [$DEFAULT_VERSION]: " VERSION VERSION=${VERSION:-$DEFAULT_VERSION} echo "=== Starting comprehensive release process for clod $VERSION ===" # Update version in cabal file echo "Updating version in clod.cabal..." sed -i '' "s/^version:[ ]*$CURRENT_VERSION/version: $VERSION/" clod.cabal # Update CHANGELOG.md with new version entry echo "Updating CHANGELOG.md..." TODAY=$(date +"%Y-%m-%d") # Check if a changelog entry for this version already exists if ! grep -q "^## \[$VERSION\]" CHANGELOG.md; then # Insert new version entry at the top of the changelog (after the header) sed -i '' "1,/^# Changelog/ s/^# Changelog/# Changelog\n\n## [$VERSION] - $TODAY\n\n- Add your changes here\n\nThis release was created through human-AI pair programming, with Claude as the primary code author and Fuzz Leonard providing guidance, architectural decisions, and final review.\n/" CHANGELOG.md # Open CHANGELOG.md for editing echo "Opening CHANGELOG.md for editing. Please add your release notes." echo "Press Enter to continue once you've saved the changes." ${EDITOR:-nano} CHANGELOG.md read -p "Press Enter to continue with the release process..." fi # Step 1: Generate man pages echo "=== Generating man pages ===" # Inline implementation of generate-man-pages.sh # Ensure pandoc is available if ! command -v pandoc &> /dev/null; then echo "Error: pandoc is required to generate man pages" echo "Please install pandoc: https://pandoc.org/installing.html" exit 1 fi # Make sure the source man directory exists mkdir -p "$PROJECT_ROOT/man" # Generate clod(1) - Command reference echo "Generating clod(1).md source file..." cat > "$PROJECT_ROOT/man/clod.1.md" << EOF % CLOD(1) Clod $VERSION % Fuzz Leonard & Claude % March 2025 # NAME clod - Claude Loader for preparing files for Claude AI's Project Knowledge # SYNOPSIS **clod** [*OPTIONS*] # DESCRIPTION Clod is a utility for preparing and uploading files to Claude AI's Project Knowledge feature. It tracks file changes, respects .gitignore and .clodignore patterns, and optimizes filenames for Claude's UI. # OPTIONS **--all**, **-a** : Process all files (respecting .gitignore and .clodignore) **--test**, **-t** : Run in test mode (no prompts, useful for CI) **--staging-dir** *DIR*, **-d** *DIR* : Specify a directory for test mode (only used with --test) **--verbose**, **-v** : Enable verbose output **--flush**, **-f** : Remove missing entries from the database **--last**, **-l** : Reuse the previous staging directory **--help** : Show help information **--version** : Show version information # EXAMPLES Run clod (first run processes all files, subsequent runs process only modified files): clod Force processing of all files: clod --all Run in test mode with an optional test directory: clod --test --staging-dir /path/to/test/dir Reuse the previous staging directory: clod --last Remove missing entries from the database: clod --flush # ENVIRONMENT VARIABLES **CLOD_DIR** : Override the default .clod directory name **CLODIGNORE** : Override the default .clodignore filename # FILES **.clodignore** : Pattern file similar to .gitignore for excluding files **.clod/database.dhall** : Database of file checksums and metadata # SEE ALSO **clod(7)** for information about project instructions and safeguards. **clod(8)** for a complete workflow guide to using clod with Claude AI. EOF # Generate clod(7) - Project instructions and safeguards if [ -f "$PROJECT_ROOT/project-instructions.md" ] && [ -f "$PROJECT_ROOT/guardrails.md" ]; then echo "Generating clod(7).md source file..." # Create the header section cat > "$PROJECT_ROOT/man/clod.7.md" << EOF % CLOD(7) Clod $VERSION % Fuzz Leonard & Claude % March 2025 # NAME clod - project instructions and safeguards for Claude AI integration # DESCRIPTION This man page contains guidance on how to structure project instructions for Claude AI and implement safeguards when using clod with Claude AI's Project Knowledge feature. # PROJECT INSTRUCTIONS EOF # Append project-instructions.md content cat "$PROJECT_ROOT/project-instructions.md" >> "$PROJECT_ROOT/man/clod.7.md" # Append guardrails.md content echo "" >> "$PROJECT_ROOT/man/clod.7.md" echo "# SAFEGUARDS" >> "$PROJECT_ROOT/man/clod.7.md" cat "$PROJECT_ROOT/guardrails.md" >> "$PROJECT_ROOT/man/clod.7.md" else echo "Warning: Cannot generate clod(7).md, source files missing" fi # Generate clod(8) - Complete workflow guide if [ -f "$PROJECT_ROOT/HUMAN.md" ]; then echo "Generating clod(8).md source file..." # Create the header section cat > "$PROJECT_ROOT/man/clod.8.md" << EOF % CLOD(8) Clod $VERSION % Fuzz Leonard & Claude % March 2025 # NAME clod - complete workflow guide for using clod with Claude AI # DESCRIPTION This man page contains a comprehensive guide to using clod with Claude AI, including best practices, workflow details, and integration tips. # ABOUT CLAUDE'S INVOLVEMENT This project represents a novel approach to software development, where Claude (the AI assistant from Anthropic) served as the primary programmer, implementing nearly 100% of the codebase based on guidance from Fuzz Leonard. This human-AI collaboration model leverages each collaborator's strengths, with the human providing vision, requirements and architectural decisions, and the AI handling implementation details, testing, and most documentation. EOF # Append HUMAN.md content cat "$PROJECT_ROOT/HUMAN.md" >> "$PROJECT_ROOT/man/clod.8.md" else echo "Warning: Cannot generate clod(8).md, HUMAN.md missing" fi echo "Man page markdown generation completed" # Commit version bump changes and man pages together echo "=== Committing version bump and man page changes ===" echo "Do you want to commit these changes? [y/N]" read -r version_response if [[ "$version_response" =~ ^[Yy] ]]; then git add clod.cabal CHANGELOG.md man/ git commit -m "Bump version to $VERSION and update man pages" echo "Do you want to push the commit to origin? [y/N]" read -r push_version_response if [[ "$push_version_response" =~ ^[Yy] ]]; then git push origin fi fi # Step 2: Verify tests pass echo "=== Running tests ===" cabal test # Step 3: Generate documentation echo "=== Building documentation for Hackage ===" cabal haddock --haddock-for-hackage # Step 4: Create source distribution echo "=== Creating source distribution ===" cabal sdist # Step 5: Check package echo "=== Checking package ===" cabal check # Step 6: Test build echo "=== Testing build ===" cabal build --disable-documentation # Step 7: Build man pages for verification echo "=== Building man pages for verification ===" "$PROJECT_ROOT/bin/install-man-pages.sh" /tmp/clod-man-test # Step 8: Verify man pages were generated correctly (no commit needed, already done earlier) echo "=== Verifying man pages ===" ls -la "$PROJECT_ROOT/man/" | grep -E "\.md$" # Step 9: Create tag echo "=== Creating Git tag ===" echo "Do you want to create git tag v$VERSION? [y/N]" read -r response if [[ "$response" =~ ^[Yy] ]]; then # Check if tag already exists if git rev-parse "v$VERSION" >/dev/null 2>&1; then echo "Tag v$VERSION already exists!" echo "Do you want to force update it? [y/N]" read -r force_tag if [[ "$force_tag" =~ ^[Yy] ]]; then git tag -fa "v$VERSION" -m "Release version $VERSION" echo "Tag v$VERSION updated." else echo "Skipping tag creation." fi else git tag -a "v$VERSION" -m "Release version $VERSION" echo "Tag v$VERSION created." fi echo "Do you want to push the tag to origin? [y/N]" read -r push_response if [[ "$push_response" =~ ^[Yy] ]]; then git push origin "v$VERSION" fi fi # Step 10: Generate documentation package for Hackage echo "=== Generating Hackage documentation package ===" echo "Generating documentation package for Hackage..." cabal haddock --haddock-for-hackage --enable-documentation # Step 11: Upload to Hackage (manual step) echo "=== Ready to upload to Hackage ===" echo "The following commands will upload the package and documentation to Hackage:" echo echo " cabal upload --publish dist-newstyle/sdist/clod-$VERSION.tar.gz" echo " curl --http1.1 for documentation (using HTTP 1.1 to work around Hackage networking issues)" echo echo "Do you want to upload to Hackage now? [y/N]" read -r upload_response if [[ "$upload_response" =~ ^[Yy] ]]; then echo "Uploading package to Hackage..." cabal upload --publish "dist-newstyle/sdist/clod-$VERSION.tar.gz" echo "Please enter your Hackage username for documentation upload:" read -r username echo "Uploading documentation for clod-$VERSION" echo "You will be prompted for your Hackage password." # Must force HTTP 1.1 to get around networking issues curl --http1.1 -X PUT --data-binary "@dist-newstyle/clod-$VERSION-docs.tar.gz" \ -H 'Content-Type: application/x-tar' \ -H 'Content-Encoding: gzip' \ --user "$username" \ "https://hackage.haskell.org/package/clod-$VERSION/docs" else echo "Skipping Hackage upload. Run the commands manually when ready." exit 0 fi # Step 12: Wait for Hackage to process the package echo "=== Waiting for Hackage to process the package ===" echo "Waiting for Hackage to process the package (10 seconds)..." sleep 10 # Step 13: Calculate SHA256 for Homebrew formula echo "=== Updating Homebrew formula ===" echo "Calculating SHA256 for Hackage package..." HACKAGE_URL="https://hackage.haskell.org/package/clod-$VERSION/clod-$VERSION.tar.gz" # Try to download and calculate SHA up to 3 times with increasing delays MAX_ATTEMPTS=3 ATTEMPT=1 SHA256="" while [ $ATTEMPT -le $MAX_ATTEMPTS ] && [ -z "$SHA256" ]; do echo "Attempt $ATTEMPT of $MAX_ATTEMPTS to calculate SHA256..." # Download the package and calculate SHA256 SHA256=$(curl -sL "$HACKAGE_URL" | shasum -a 256 | cut -d ' ' -f 1) if [ -z "$SHA256" ] || [ "$SHA256" = "da39a3ee5e6b4b0d3255bfef95601890afd80709" ]; then # Empty SHA or SHA of empty file (package not available yet) WAIT_TIME=$((ATTEMPT * 10)) echo "Package not available yet. Waiting $WAIT_TIME seconds before retry..." sleep $WAIT_TIME ATTEMPT=$((ATTEMPT + 1)) else # Validate that the SHA256 looks legitimate (64 hex chars) if [[ "$SHA256" =~ ^[0-9a-f]{64}$ ]]; then echo "Valid SHA256 obtained: $SHA256" break else echo "Invalid SHA256 obtained. Retrying..." SHA256="" sleep 5 ATTEMPT=$((ATTEMPT + 1)) fi fi done if [ -z "$SHA256" ]; then echo "Error: Failed to calculate SHA256 after $MAX_ATTEMPTS attempts." echo "The package might not be available on Hackage yet." echo "You will need to update the formula manually with:" echo " curl -sL $HACKAGE_URL | shasum -a 256" exit 1 fi # Verify the package is accessible with expected size PACKAGE_SIZE=$(curl -sI "$HACKAGE_URL" | grep -i "Content-Length" | awk '{print $2}' | tr -d '\r') if [ -z "$PACKAGE_SIZE" ] || [ "$PACKAGE_SIZE" -lt 1000 ]; then echo "Warning: Package seems too small or inaccessible ($PACKAGE_SIZE bytes)." echo "SHA256 might be incorrect. Proceed with caution." echo "Press Enter to continue or Ctrl+C to abort and fix manually." read -r fi # Step 14: Update Homebrew formula FORMULA_PATH="../homebrew-tap/Formula/clod.rb" if [ ! -f "$FORMULA_PATH" ]; then echo "Error: Homebrew formula not found at $FORMULA_PATH" exit 1 fi echo "Updating Homebrew formula with new version $VERSION and SHA256 $SHA256..." # Update version and SHA in the formula file # First update the version in both the URL and the comment sed -i '' "s|clod-[0-9][0-9.]*\/clod-[0-9][0-9.]*\.tar\.gz|clod-$VERSION\/clod-$VERSION.tar.gz|g" "$FORMULA_PATH" # Then update the SHA256 sed -i '' "s|sha256 \"[a-f0-9]\+\"|sha256 \"$SHA256\"|g" "$FORMULA_PATH" # Verify the URL format is correct (making sure it has .tar.gz extension) if ! grep -q "\.tar\.gz\"" "$FORMULA_PATH"; then echo "ERROR: Formula URL format issue detected. Please check $FORMULA_PATH manually." cat "$FORMULA_PATH" | grep -A 2 "url" exit 1 fi # Step 15: Commit and push Homebrew formula update echo "=== Committing Homebrew formula update ===" echo "Do you want to commit and push the updated Homebrew formula? [y/N]" read -r brew_response if [[ "$brew_response" =~ ^[Yy] ]]; then ( cd "../homebrew-tap" git add "Formula/clod.rb" git commit -m "Update clod formula to version $VERSION" echo "Do you want to push the formula update to origin? [y/N]" read -r push_brew_response if [[ "$push_brew_response" =~ ^[Yy] ]]; then git push origin fi ) fi echo "=== Release process complete! ===" echo "Version $VERSION has been released to Hackage and the Homebrew formula has been updated." echo "Remember to test the Homebrew installation with: brew install --build-from-source ./homebrew-tap/Formula/clod.rb" # Alternative Haskell implementation note: # This script could be rewritten in Haskell using libraries like: # - process: For executing external commands # - turtle: A shell scripting library # - optparse-applicative: For command line option parsing # - filepath/directory: For file path manipulation # - bytestring/text: For text processing # # Benefits would include: # - Type safety # - Better error handling # - Integration with the rest of the Haskell codebase # - Potential for more modular design