clod-0.1.13: bin/release
#!/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 <ink@fuzz.ink>
% 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 <ink@fuzz.ink>
% 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 <ink@fuzz.ink>
% 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