publish
SkillDev toolspublish
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the publish skill
What this skill tells your AI
The instructions your AI receives, as published by markmhendrickson/neotoma in .claude/skills/publish/SKILL.md and read by ahel’s review.
name: publish description: Publish workflow per foundation publish command. triggers:
- publish
- /publish
publish
Merge dev commits into main, create a release (planned or incremental), and deploy to production.
PARAMETER MODES:
- No parameter (default): Publish main repository
- Submodule name parameter (e.g.,
/publish foundation): Publish that specific submodule only
Release Numbering Strategy
Planned Releases (Milestones):
- Use semantic versioning:
vX.Y.0(major.minor.0) - Examples:
v1.0.0,v1.1.0,v2.0.0 - Created via
/create_releasecommand with full workflow - Control major and minor version numbers
Incremental Releases (Continuous Deployments):
- Use semantic versioning as patches:
vX.Y.Zwhere Z increments - Examples: If last planned release is
v1.1.0, incremental releases arev1.1.1,v1.1.2 - Auto-generated via
/publishcommand - Only patch version increments (never major or minor)
Relationship:
- Independent purposes: Planned releases are milestones, incremental releases are continuous deployments
- Example sequence:
v1.0.0(planned) →v1.0.1,v1.0.2(incremental) →v1.1.0(planned) →v1.1.1(incremental)
Parameter Detection and Routing
STEP 1: Detect parameter and route to appropriate workflow:
# Check if parameter provided
if [ -n "$1" ]; then
PARAM="$1"
# Submodule mode: publish that submodule only
echo "📦 SUBMODULE MODE: Publishing submodule '$PARAM'"
# Verify submodule exists
if ! git submodule status "$PARAM" >/dev/null 2>&1; then
echo "❌ Submodule not found: $PARAM"
exit 1
fi
# Proceed to submodule publish workflow
else
# Main repo mode: publish main repository
echo "📦 MAIN REPO MODE: Publishing main repository"
# Proceed to main repository publish workflow
fi
Submodule Publish Workflow
When a specific submodule name is provided (e.g., /publish foundation):
-
Change to submodule directory:
cd "$PARAM" || { echo "❌ Failed to change to submodule directory: $PARAM" exit 1 } -
Validate prerequisites:
- Check current branch: Must be on
dev(or configured integration branch) - Check for uncommitted changes
- Run test suite (if submodule has tests)
- Pull latest dev
- Check current branch: Must be on
-
Merge dev into main:
git checkout main git pull origin main git merge --no-ff dev -m "Merge dev into main for release" -
Detect planned release and determine version (see "Planned Release Detection" section)
-
Bump version in submodule:
- If planned release detected: Use planned release version
- If incremental: Increment patch from last planned release
- Update version file (package.json or equivalent)
-
Handle release document:
- If planned: Update existing release document status to
deployed - If incremental: Create new incremental release document
- If planned: Update existing release document status to
-
Commit and tag:
git add package.json docs/releases/$RELEASE_VERSION/ git commit -m "Release $RELEASE_VERSION: [Planned/Incremental] release" git tag -a "$RELEASE_VERSION" -m "Release $RELEASE_VERSION" git push origin main git push origin "$RELEASE_VERSION" -
Deploy (if configured):
- Check if submodule has deployment configuration
- Run deployment steps if applicable
-
EXIT - Do NOT proceed with main repository publish
Main Repository Publish Workflow
When no parameter is provided (default):
Step 1: Validate Prerequisites
Check that we're ready to publish:
# Check current branch
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [ "$CURRENT_BRANCH" != "dev" ]; then
echo "❌ Error: Must be on dev branch to publish"
echo "Current branch: $CURRENT_BRANCH"
exit 1
fi
# Check for uncommitted changes
if git status --porcelain | grep -q .; then
echo "❌ Error: Uncommitted changes detected. Commit or stash before publishing."
git status --short
exit 1
fi
# Pull latest dev
echo "📥 Pulling latest dev..."
git pull origin dev
Step 2: Run Test Suite
echo "🧪 Running test suite..."
npm run test
if [ $? -ne 0 ]; then
echo "❌ Error: Tests failed. Fix tests before publishing."
exit 1
fi
Step 3: Merge Dev into Main
echo "🔀 Merging dev into main..."
git checkout main
git pull origin main
# Merge with no fast-forward to preserve merge commit
git merge --no-ff dev -m "Merge dev into main for release"
if [ $? -ne 0 ]; then
echo "❌ Error: Merge conflicts detected. Resolve conflicts manually."
echo "After resolving: git add . && git merge --continue"
git merge --abort
exit 1
fi
Step 4: Detect Planned Release
Method 1: Check for release status changes
# Get commits in the merge (since last main commit)
COMMITS=$(git log main~1..main --oneline)
# Check for release status.md changes
STATUS_CHANGES=$(git diff main~1..main --name-only | grep -E "docs/releases/.*/status\.md")
if [ -n "$STATUS_CHANGES" ]; then
# Extract release version from path: docs/releases/v1.1.0/status.md -> v1.1.0
PLANNED_RELEASE=$(echo "$STATUS_CHANGES" | head -1 | sed 's|docs/releases/\(v[0-9]*\.[0-9]*\.[0-9]*\)/status\.md|\1|')
# Check if status changed to 'deployed' or 'ready_for_deployment'
STATUS_CONTENT=$(git show main:"$STATUS_CHANGES" 2>/dev/null || echo "")
if echo "$STATUS_CONTENT" | grep -qE "(status:.*deployed|status:.*ready_for_deployment)"; then
echo "✓ Planned release detected: $PLANNED_RELEASE"
RELEASE_TYPE="planned"
RELEASE_VERSION="$PLANNED_RELEASE"
fi
fi
Method 2: Check for new release directories
# Check for release directory creation
NEW_RELEASE_DIRS=$(git diff main~1..main --name-only --diff-filter=A | grep -E "docs/releases/v[0-9]*\.[0-9]*\.[0-9]*/")
if [ -z "$RELEASE_TYPE" ] && [ -n "$NEW_RELEASE_DIRS" ]; then
# Extract release version from first new directory
PLANNED_RELEASE=$(echo "$NEW_RELEASE_DIRS" | head -1 | sed 's|docs/releases/\(v[0-9]*\.[0-9]*\.[0-9]*\)/.*|\1|')
# Verify it's a planned release (has manifest.yaml, release_plan.md)
if git show main:"docs/releases/$PLANNED_RELEASE/manifest.yaml" >/dev/null 2>&1; then
echo "✓ Planned release detected: $PLANNED_RELEASE"
RELEASE_TYPE="planned"
RELEASE_VERSION="$PLANNED_RELEASE"
fi
fi
Method 3: Check commit messages
# Check commit messages for release references
if [ -z "$RELEASE_TYPE" ]; then
RELEASE_IN_COMMITS=$(echo "$COMMITS" | grep -iE "(release v[0-9]+\.[0-9]+\.[0-9]+|execute v[0-9]+\.[0-9]+\.[0-9]+)")
if [ -n "$RELEASE_IN_COMMITS" ]; then
# Extract version from commit message
PLANNED_RELEASE=$(echo "$RELEASE_IN_COMMITS" | head -1 | grep -oE "v[0-9]+\.[0-9]+\.[0-9]+" | head -1)
# Verify release directory exists
if [ -d "docs/releases/$PLANNED_RELEASE" ]; then
echo "✓ Planned release detected in commits: $PLANNED_RELEASE"
RELEASE_TYPE="planned"
RELEASE_VERSION="$PLANNED_RELEASE"
fi
fi
fi
Validation if planned release detected:
if [ "$RELEASE_TYPE" = "planned" ]; then
# Verify version format (should be vX.Y.0)
if ! echo "$RELEASE_VERSION" | grep -qE "^v[0-9]+\.[0-9]+\.[0-9]+$"; then
echo "⚠️ Warning: Planned release version format invalid: $RELEASE_VERSION"
echo "Falling back to incremental release..."
RELEASE_TYPE="incremental"
fi
# Verify patch is 0 (planned releases should be vX.Y.0)
PATCH=$(echo "$RELEASE_VERSION" | sed 's/v[0-9]*\.[0-9]*\.\([0-9]*\)/\1/')
if [ "$PATCH" != "0" ]; then
echo "⚠️ Warning: Planned release should have patch=0, but found: $RELEASE_VERSION"
echo "Falling back to incremental release..."
RELEASE_TYPE="incremental"
fi
fi
Step 5: Determine Version (if incremental)
If no planned release detected, determine incremental version:
if [ "$RELEASE_TYPE" != "planned" ]; then
RELEASE_TYPE="incremental"
# Find last planned release (vX.Y.0 format)
LAST_PLANNED_RELEASE=$(git tag -l "v*.*.0" | sort -V | tail -1)
# If no planned release found, start at v0.1.0
if [ -z "$LAST_PLANNED_RELEASE" ]; then
LAST_PLANNED_RELEASE="v0.0.0"
echo "⚠️ No planned release found. Starting from v0.1.0"
fi
# Extract major.minor from last planned release
MAJOR=$(echo "$LAST_PLANNED_RELEASE" | sed 's/v\([0-9]*\)\.[0-9]*\.[0-9]*/\1/')
MINOR=$(echo "$LAST_PLANNED_RELEASE" | sed 's/v[0-9]*\.\([0-9]*\)\.[0-9]*/\1/')
# Find highest patch version for this major.minor
HIGHEST_PATCH=$(git tag -l "v${MAJOR}.${MINOR}.*" | sed 's/v[0-9]*\.[0-9]*\.\([0-9]*\)/\1/' | sort -n | tail -1)
# If no patch versions exist, start at 1
if [ -z "$HIGHEST_PATCH" ]; then
NEW_PATCH=1
else
NEW_PATCH=$((HIGHEST_PATCH + 1))
fi
# New version: v{MAJOR}.{MINOR}.{NEW_PATCH}
RELEASE_VERSION="v${MAJOR}.${MINOR}.${NEW_PATCH}"
echo "✓ Incremental release version: $RELEASE_VERSION (from planned release $LAST_PLANNED_RELEASE)"
fi
Step 6: Bump package.json Version
echo "📝 Bumping version to $RELEASE_VERSION..."
# Remove 'v' prefix for npm version
NPM_VERSION=${RELEASE_VERSION#v}
# Set exact version
npm version $NPM_VERSION --no-git-tag
if [ $? -ne 0 ]; then
echo "❌ Error: Failed to bump version in package.json"
exit 1
fi
Step 7: Handle Release Document
If planned release:
if [ "$RELEASE_TYPE" = "planned" ]; then
# Update existing release document status to deployed
STATUS_FILE="docs/releases/$RELEASE_VERSION/status.md"
if [ -f "$STATUS_FILE" ]; then
# Update status to deployed
sed -i.bak 's/status: ready_for_deployment/status: deployed/' "$STATUS_FILE"
sed -i.bak 's/status: in_testing/status: deployed/' "$STATUS_FILE"
rm -f "${STATUS_FILE}.bak"
# Add deployment date
echo "" >> "$STATUS_FILE"
echo "**Deployment Date:** $(date -Iseconds)" >> "$STATUS_FILE"
echo "**Deployment Commit:** $(git rev-parse HEAD)" >> "$STATUS_FILE"
else
echo "⚠️ Warning: Release status file not found: $STATUS_FILE"
fi
fi
If incremental release:
if [ "$RELEASE_TYPE" = "incremental" ]; then
# Create new incremental release document
RELEASE_DIR="docs/releases/$RELEASE_VERSION"
mkdir -p "$RELEASE_DIR"
# Create release_plan.md
cat > "$RELEASE_DIR/release_plan.md" << EOF
## Release $RELEASE_VERSION — Incremental Release
### Release Type
- **Type**: Incremental (continuous deployment)
- **Last Planned Release**: v${MAJOR}.${MINOR}.0
- **Purpose**: Continuous deployment of commits from dev branch
### Summary
Incremental release bundling commits from dev branch merge.
### Commits Included
$(git log main~1..main --oneline)
### Feature Units
$(git log main~1..main --oneline | grep -oE "FU-[0-9]{4}-[0-9]{2}-[0-9]{3}" | sort -u)
### Changes
$(git diff main~1..main --name-status | head -20)
EOF
# Create status.md
cat > "$RELEASE_DIR/status.md" << EOF
# Release $RELEASE_VERSION Status
**Status**: deployed
**Release Type**: Incremental
**Deployment Date**: $(date -Iseconds)
**Deployment Commit**: $(git rev-parse HEAD)
**Base Planned Release**: v${MAJOR}.${MINOR}.0
EOF
fi
Step 8: Commit and Tag
echo "💾 Committing release..."
# Stage changes
git add package.json package-lock.json
if [ "$RELEASE_TYPE" = "planned" ]; then
git add "docs/releases/$RELEASE_VERSION/status.md"
git commit -m "Deploy planned release $RELEASE_VERSION
- Release: $RELEASE_VERSION (planned release)
- Status: deployed
- Deployment date: $(date -Iseconds)"
else
git add "docs/releases/$RELEASE_VERSION/"
COMMIT_COUNT=$(git log main~1..main --oneline | wc -l | tr -d ' ')
git commit -m "Release $RELEASE_VERSION: Incremental release from dev merge
- Version: $RELEASE_VERSION (incremental, patch increment from v${MAJOR}.${MINOR}.0)
- Commits included: $COMMIT_COUNT
- Deployment date: $(date -Iseconds)"
fi
# Create tag
echo "🏷️ Creating tag $RELEASE_VERSION..."
git tag -a "$RELEASE_VERSION" -m "Release $RELEASE_VERSION"
# Push to origin
echo "📤 Pushing to origin..."
git push origin main
git push origin "$RELEASE_VERSION"
Step 9: Deploy to Production
echo "🚀 Deploying to production..."
# Check if deployment is enabled
if [ -f "fly.toml" ]; then
# Deploy to Fly.io
echo "Deploying to Fly.io..."
flyctl deploy --remote-only
if [ $? -ne 0 ]; then
echo "❌ Error: Deployment failed"
echo "Rolling back..."
git tag -d "$RELEASE_VERSION"
git push origin :refs/tags/"$RELEASE_VERSION"
git reset --hard HEAD~1
git push origin main --force-with-lease
exit 1
fi
else
echo "⚠️ Warning: No fly.toml found. Skipping deployment."
fi
# Run database migrations
if [ -f "scripts/run_migrations.js" ]; then
echo "Running database migrations..."
npm run migrate
if [ $? -ne 0 ]; then
echo "⚠️ Warning: Database migrations failed"
# Don't rollback, but warn
fi
fi
# Run smoke tests
echo "Running smoke tests..."
# Add smoke test commands here if available
Step 10: Update Release Status
echo "✅ Release $RELEASE_VERSION deployed successfully!"
echo ""
echo "Release Type: $RELEASE_TYPE"
echo "Version: $RELEASE_VERSION"
echo "Commits: $COMMIT_COUNT"
echo ""
echo "Next steps:"
if [ "$RELEASE_TYPE" = "planned" ]; then
echo "- Verify deployment at production URL"
echo "- Run manual tests from release_report.md"
echo "- Monitor metrics for first hour"
else
echo "- Verify deployment at production URL"
echo "- Monitor for errors"
fi
Configuration
Configure publish behavior in foundation-config.yaml:
development:
publish:
enabled: true
integration_branch: "dev"
main_branch: "main"
create_release_doc: true
release_type: "not_marketed"
deployment:
enabled: true
provider: "fly"
run_migrations: true
smoke_tests: true
health_check_url: "https://app.neotoma.app/health"
versioning:
planned_release_pattern: "v*.*.0"
incremental_release_patch_only: true
initial_version: "v0.1.0"
detection:
check_status_changes: true
check_new_directories: true
check_commit_messages: true
submodules:
foundation:
deployment:
enabled: false
version_file: "package.json"
Error Handling
Rollback on Failure:
- If deployment fails, revert main branch to previous commit
- Delete release tag if created
- If planned release status was updated, revert status change
Common Issues:
- Merge conflicts: Resolve manually, then retry
- Test failures: Fix tests, then retry
- Deployment failures: Check logs, rollback if needed
- Version conflicts: Increment further if version exists
Summary
Publish Modes:
/publish- Publish main repository (detect planned vs incremental)/publish <submodule>- Publish specific submodule only
Release Types:
- Planned (vX.Y.0): Detected from commits, uses existing release document
- Incremental (vX.Y.Z): Auto-generated, creates new release document
Workflow: Validate → Merge → Detect Release Type → Version → Document → Commit → Tag → Deploy
Signals
- GitHub stars
- 32
- Forks
- 3
- Last commit
- Sep 2026
- Hacker News mentions
- 20
Advanced
- Catalog kind
- skill
- Gateway key
publish-markmhendrickson- Source
- github.com/markmhendrickson/neotoma