Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
cf93c70
Improve CLI release, identity, org switching
simonsmallchua Apr 4, 2026
cc81b43
Fix errcheck lint on Sscanf return
simonsmallchua Apr 4, 2026
bf0efd7
Fix version preview picking up cli tags
simonsmallchua Apr 4, 2026
4e5288f
Add CLI version to release preview
simonsmallchua Apr 4, 2026
6b33ba6
Clean up release preview comment
simonsmallchua Apr 4, 2026
635537d
Remove tip line from release preview
simonsmallchua Apr 4, 2026
36fce10
Add changelog to release versions comment
simonsmallchua Apr 4, 2026
eb439bd
Read changelog from file not env var
simonsmallchua Apr 4, 2026
0709c7f
Clean up release preview, fix changelog
simonsmallchua Apr 4, 2026
a590733
Address CodeRabbit review feedback
simonsmallchua Apr 4, 2026
0d2b7a5
Trigger changelog check on all PRs
simonsmallchua Apr 4, 2026
405d9f5
Only trigger on CHANGELOG.md not all md
simonsmallchua Apr 4, 2026
6946144
Fix operator precedence in bot finder
simonsmallchua Apr 4, 2026
58dd188
Filter CLI tags strictly, fix tag SHA comparison
simonsmallchua Apr 4, 2026
06b44a9
Fix changelog content extraction
simonsmallchua Apr 4, 2026
95c5b65
Reuse shell changelog extraction in JS step
simonsmallchua Apr 4, 2026
fdf5ad9
Use GITHUB_OUTPUT heredoc for changelog
simonsmallchua Apr 4, 2026
3e7d3d7
Harden CI workflows and centralise changelog
simonsmallchua Apr 4, 2026
9ca5f5c
Clean up settings, remove risky perms
simonsmallchua Apr 4, 2026
1aa3e98
Remove broad permission auto-allows
simonsmallchua Apr 4, 2026
3f2f0bb
Sync permissions across Claude and OpenCode
simonsmallchua Apr 4, 2026
804b4f1
Address CodeRabbit review findings
simonsmallchua Apr 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 13 additions & 19 deletions .claude/settings.local.json → .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@
"Bash(pgrep:*)",
"Bash(timeout:*)",
"Bash(gtimeout:*)",
"Bash(source:*)",
"Bash(unalias:*)",
"Bash(open:*)",
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git status:*)",
Expand All @@ -40,7 +38,6 @@
"Bash(git fetch:*)",
"Bash(git pull:*)",
"Bash(git checkout:*)",
"Bash(git rm:*)",
"Bash(git tag:*)",
"Bash(git stash:*)",
"Bash(git worktree:*)",
Expand Down Expand Up @@ -69,22 +66,9 @@
"Bash(npm run test:*)",
"Bash(npm run dev:*)",
"Bash(npm test)",
"Bash(npx:*)",
"Bash(claude --version)",
"Bash(claude mcp:*)",
"Bash(./scripts/test-db.sh:*)",
"WebSearch",
"WebFetch(domain:docs.anthropic.com)",
"WebFetch(domain:github.com)",
"WebFetch(domain:supabase.com)",
"WebFetch(domain:goo.gle)",
"WebFetch(domain:docs.developers.webflow.com)",
"WebFetch(domain:developers.webflow.com)",
"WebFetch(domain:loops.so)",
"WebFetch(domain:knock.app)",
"WebFetch(domain:www.emailtooltester.com)",
"WebFetch(domain:www.implicator.ai)",
"WebFetch(domain:codingforseo.com)",
"Edit",
"Write",
"MultiEdit",
Expand All @@ -99,7 +83,6 @@
"mcp__supabase__get_logs",
"mcp__supabase__list_projects",
"mcp__supabase__list_tables",
"mcp__supabase__execute_sql",
"mcp__plugin_serena_serena__initial_instructions",
"mcp__plugin_serena_serena__list_dir",
"mcp__plugin_serena_serena__get_symbols_overview",
Expand All @@ -119,9 +102,20 @@
"Bash(bash scripts/pr-status-check.sh)",
"Bash(bash scripts/pr-comment-reply.sh:*)",
"Bash(bash scripts/pr-comment-reply.sh)",
"Bash(npm audit:*)"
"Bash(npm audit:*)",
"Bash(git show:*)",
"Bash(npx prettier --write:*)",
"Bash(npx eslint --fix:*)"
],
"deny": []
"deny": [
"Bash(git push --force:*)",
"Bash(git reset --hard:*)",
"Bash(rm -rf:*)",
"Bash(npm publish:*)"
]
},
"env": {
"PATH": "/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
},
"enableAllProjectMcpServers": false
}
96 changes: 21 additions & 75 deletions .github/workflows/auto-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,80 +70,19 @@ jobs:

core.setOutput('should_skip', shouldSkip ? 'true' : 'false');

- name: Check changelog for release marker
- name: Check changelog and calculate version
id: check
run: |
# Check if CHANGELOG.md has [Unreleased:*] and non-empty content under it
if grep -q "^## \[Unreleased" CHANGELOG.md; then
# Extract the release type from changelog
CHANGELOG_HEADER=$(grep "^## \[Unreleased" CHANGELOG.md | head -1)

if echo "$CHANGELOG_HEADER" | grep -qi "\[Unreleased:major\]"; then
RELEASE_TYPE="major"
elif echo "$CHANGELOG_HEADER" | grep -qi "\[Unreleased:minor\]"; then
RELEASE_TYPE="minor"
else
# Default to patch for [Unreleased] or [Unreleased:patch]
RELEASE_TYPE="patch"
fi

# Extract content between [Unreleased] and next version header
UNRELEASED_CONTENT=$(awk '/^## \[Unreleased/ {flag=1; next} /^## \[[0-9]/ {flag=0} flag' CHANGELOG.md)

# Only create release when there is actual content (non-blank)
if [ -z "$(echo "$UNRELEASED_CONTENT" | grep -v '^[[:space:]]*$')" ]; then
echo "should_release=false" >> $GITHUB_OUTPUT
echo "Unreleased section is empty - skipping release"
else
echo "should_release=true" >> $GITHUB_OUTPUT
echo "release_type=$RELEASE_TYPE" >> $GITHUB_OUTPUT
echo "Detected release type: $RELEASE_TYPE"
fi
else
echo "should_release=false" >> $GITHUB_OUTPUT
echo "No [Unreleased] section found - skipping release"
fi

- name: Determine version bump
if:
steps.check.outputs.should_release == 'true' &&
steps.skip.outputs.should_skip != 'true'
id: bump
run: |
# Get current version (strip 'v' prefix and any suffix)
CURRENT_TAG=$(git tag -l 'v[0-9]*' --sort=-version:refname | head -1)
CURRENT_TAG=${CURRENT_TAG:-v0.6.4}
CURRENT_VERSION=${CURRENT_TAG#v}
CURRENT_VERSION=${CURRENT_VERSION%%-*}

# Parse version components
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"

# Determine bump type from changelog
RELEASE_TYPE="${{ steps.check.outputs.release_type }}"

if [ "$RELEASE_TYPE" = "major" ]; then
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
elif [ "$RELEASE_TYPE" = "minor" ]; then
MINOR=$((MINOR + 1))
PATCH=0
else
# Default: patch bump
PATCH=$((PATCH + 1))
fi

NEW_VERSION="v${MAJOR}.${MINOR}.${PATCH}"
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "Next version: $NEW_VERSION"
# Use shared script for changelog parsing and version calculation
bash scripts/changelog-version.sh

- name: Update changelog
if:
steps.check.outputs.should_release == 'true' &&
steps.skip.outputs.should_skip != 'true'
env:
NEW_VERSION: ${{ steps.check.outputs.next_version }}
run: |
NEW_VERSION="${{ steps.bump.outputs.new_version }}"
VERSION_NO_V="${NEW_VERSION#v}"
TODAY=$(date +%Y-%m-%d)

Expand Down Expand Up @@ -175,7 +114,7 @@ jobs:
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
if [ -z "$PREV_TAG" ]; then
echo "cli_changed=true" >> $GITHUB_OUTPUT
elif git diff --name-only "$PREV_TAG"..HEAD -- cmd/hover/ | grep -q .; then
elif git diff --name-only "$PREV_TAG"..HEAD -- cmd/hover/ npm/ | grep -q .; then
echo "cli_changed=true" >> $GITHUB_OUTPUT
echo "CLI code changed since $PREV_TAG"
else
Expand All @@ -187,11 +126,13 @@ jobs:
if:
steps.check.outputs.should_release == 'true' &&
steps.skip.outputs.should_skip != 'true'
env:
NEW_VERSION: ${{ steps.check.outputs.next_version }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "${{ steps.bump.outputs.new_version }}" -m "Release ${{ steps.bump.outputs.new_version }}"
git push origin "${{ steps.bump.outputs.new_version }}"
git tag -a "$NEW_VERSION" -m "Release $NEW_VERSION"
git push origin "$NEW_VERSION"

- name: Create and push CLI tag
if:
Expand All @@ -200,7 +141,7 @@ jobs:
steps.cli.outputs.cli_changed == 'true'
run: |
# Independent CLI version — find latest cli-v* tag and bump patch
LATEST_CLI_TAG=$(git tag -l 'cli-v*' --sort=-version:refname | head -1)
LATEST_CLI_TAG=$(git tag -l 'cli-v[0-9]*.[0-9]*.[0-9]*' --sort=-version:refname | head -1)
if [ -z "$LATEST_CLI_TAG" ]; then
CLI_VERSION="cli-v0.1.0"
else
Expand All @@ -217,17 +158,18 @@ jobs:
steps.check.outputs.should_release == 'true' &&
steps.skip.outputs.should_skip != 'true'
id: changelog
env:
NEW_VERSION: ${{ steps.check.outputs.next_version }}
run: |
VERSION_NO_V="${{ steps.bump.outputs.new_version }}"
VERSION_NO_V="${VERSION_NO_V#v}"
VERSION_NO_V="${NEW_VERSION#v}"

# Extract content for this version from updated CHANGELOG.md
# Get content between this version header and the next version header
# Use awk to stop before the next version header (more reliable than sed)
CHANGELOG_CONTENT=$(awk "/^## \[$VERSION_NO_V\]/ {flag=1; next} /^## \[/ {flag=0} flag" CHANGELOG.md)

# Save to file to preserve newlines
echo "$CHANGELOG_CONTENT" > /tmp/changelog_extract.md
echo "$CHANGELOG_CONTENT" > "$RUNNER_TEMP/changelog_extract.md"

echo "Extracted changelog content for $VERSION_NO_V"

Expand All @@ -236,10 +178,14 @@ jobs:
steps.check.outputs.should_release == 'true' &&
steps.skip.outputs.should_skip != 'true'
uses: actions/github-script@v8
env:
NEW_VERSION: ${{ steps.check.outputs.next_version }}
RUNNER_TEMP: ${{ runner.temp }}
with:
script: |
const fs = require('fs');
const newVersion = '${{ steps.bump.outputs.new_version }}';
const newVersion = process.env.NEW_VERSION;
const runnerTemp = process.env.RUNNER_TEMP;
const prNumber = context.payload.pull_request && context.payload.pull_request.number;
const prTitle = context.payload.pull_request && context.payload.pull_request.title;
const commitSha = context.sha;
Expand All @@ -248,7 +194,7 @@ jobs:
// Read changelog content
let changelogContent = '';
try {
changelogContent = fs.readFileSync('/tmp/changelog_extract.md', 'utf8').trim();
changelogContent = fs.readFileSync(`${runnerTemp}/changelog_extract.md`, 'utf8').trim();
} catch (err) {
console.log('Could not read changelog extract:', err);
changelogContent = `Automated release from PR #${prNumber}: ${prTitle}`;
Expand Down
97 changes: 37 additions & 60 deletions .github/workflows/changelog-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
- main
paths-ignore:
- "**.md"
- "!CHANGELOG.md"
- "docs/**"
- "LICENSE"
- ".gitignore"
Expand All @@ -31,8 +32,10 @@ jobs:

- name: Check for changelog updates
id: check
env:
COMPARE_REF: origin/main
run: |
# Check if CHANGELOG.md has content under [Unreleased]
# Fail with helpful message if [Unreleased] section is missing
if ! grep -q "^## \[Unreleased" CHANGELOG.md; then
echo "❌ ERROR: No [Unreleased] section found in CHANGELOG.md"
echo ""
Expand All @@ -43,57 +46,35 @@ jobs:
exit 1
fi

# Extract content between [Unreleased] and next version header
UNRELEASED_CONTENT=$(sed -n '/^## \[Unreleased/,/^## \[0-9/p' CHANGELOG.md | sed '$d' | tail -n +2)
# Use shared script for changelog parsing and version calculation
bash scripts/changelog-version.sh

# Check if there's actual content (not just whitespace)
if [ -z "$(echo "$UNRELEASED_CONTENT" | grep -v '^[[:space:]]*$')" ]; then
echo "⚠️ [Unreleased] section is empty - release will have blank changelog"
else
# Report status
if grep -q 'should_release=true' "$GITHUB_OUTPUT"; then
echo "✅ Changelog has been updated with changes"
fi

# Determine release type from changelog header
CHANGELOG_HEADER=$(grep "^## \[Unreleased" CHANGELOG.md | head -1)

if echo "$CHANGELOG_HEADER" | grep -qi "\[Unreleased:major\]"; then
RELEASE_TYPE="major"
elif echo "$CHANGELOG_HEADER" | grep -qi "\[Unreleased:minor\]"; then
RELEASE_TYPE="minor"
else
RELEASE_TYPE="patch"
fi

echo "release_type=$RELEASE_TYPE" >> $GITHUB_OUTPUT

# Calculate next version
CURRENT_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.6.4")
CURRENT_VERSION=${CURRENT_TAG#v}
CURRENT_VERSION=${CURRENT_VERSION%%-*}

IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"

if [ "$RELEASE_TYPE" = "major" ]; then
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
elif [ "$RELEASE_TYPE" = "minor" ]; then
MINOR=$((MINOR + 1))
PATCH=0
else
PATCH=$((PATCH + 1))
echo "⚠️ [Unreleased] section is empty — release will have blank changelog"
fi

NEW_VERSION="v${MAJOR}.${MINOR}.${PATCH}"
echo "next_version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "Next version will be: $NEW_VERSION"

- name: Comment on PR with version info
uses: actions/github-script@v8
env:
RELEASE_TYPE: ${{ steps.check.outputs.release_type }}
CURRENT_VERSION: ${{ steps.check.outputs.current_version }}
NEXT_VERSION: ${{ steps.check.outputs.next_version }}
CLI_CHANGED: ${{ steps.check.outputs.cli_changed }}
CLI_CURRENT: ${{ steps.check.outputs.cli_current }}
CLI_NEXT: ${{ steps.check.outputs.cli_next }}
CHANGELOG_CONTENT: ${{ steps.check.outputs.changelog_content }}
with:
script: |
const releaseType = '${{ steps.check.outputs.release_type }}';
const nextVersion = '${{ steps.check.outputs.next_version }}';
const releaseType = process.env.RELEASE_TYPE;
const currentVersion = process.env.CURRENT_VERSION;
const nextVersion = process.env.NEXT_VERSION;
const cliChanged = process.env.CLI_CHANGED === 'true';
const cliCurrent = process.env.CLI_CURRENT;
const cliNext = process.env.CLI_NEXT;
const changelogContent = (process.env.CHANGELOG_CONTENT || '').trim();

const isThrottleError = (error) => {
const status = error?.status;
Expand All @@ -119,22 +100,18 @@ jobs:
}
};

const body = `## 🏷️ Release Preview

When this PR is merged, it will automatically create:

- **Version:** \`${nextVersion}\` (${releaseType} release)
- **Tag:** \`${nextVersion}\`
- **GitHub Release:** Pre-release with changelog content

The changelog will be updated and committed automatically.

---

💡 **Tip:** Update the changelog heading to control release type:
- \`## [Unreleased]\` or \`## [Unreleased:patch]\` → patch release (default)
- \`## [Unreleased:minor]\` → minor release
- \`## [Unreleased:major]\` → major release`;
const lines = [
`# Release Versions`,
``,
`**App** ${releaseType}: \`${currentVersion}\` → \`${nextVersion}\``,
];
if (cliChanged) {
lines.push(`**CLI** patch: \`${cliCurrent}\` → \`${cliNext}\``);
}
if (changelogContent) {
lines.push(``, `# Changelog`, ``, changelogContent);
}
const body = lines.join('\n');

try {
// Find existing comment
Expand All @@ -146,7 +123,7 @@ jobs:

const botComment = comments.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('🏷️ Release Preview')
(comment.body.includes('Release Versions') || comment.body.includes('Release Preview'))
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

if (botComment) {
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/cleanup-orphaned-apps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
FLY_API_TOKEN: op://Good Native/hover-fly/FLY_API_TOKEN

- name: Setup Fly.io CLI
uses: superfly/flyctl-actions/setup-flyctl@master
uses: superfly/flyctl-actions/setup-flyctl@63da3ecc5e2793b98a3f2519b3d75d4f4c11cec2 # pinned

- name: Find and destroy orphaned preview apps
env:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/fly-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@ jobs:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
FLY_API_TOKEN: op://Good Native/hover-fly/FLY_API_TOKEN

- uses: superfly/flyctl-actions/setup-flyctl@master
- uses: superfly/flyctl-actions/setup-flyctl@63da3ecc5e2793b98a3f2519b3d75d4f4c11cec2 # pinned
- run: flyctl deploy --remote-only
Loading
Loading