mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-08-21 15:03:46 +00:00
188 lines
7.2 KiB
Bash
Executable file
188 lines
7.2 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
# Generate release notes with an agent that explores the actual repo history.
|
|
#
|
|
# Engine: headless Claude Code (`claude -p`, uses your Claude subscription —
|
|
# no API key), with Codex CLI (`codex exec`, OpenAI subscription) as fallback.
|
|
# The agent runs read-only git/gh commands itself instead of being fed
|
|
# pre-chewed diff fragments, so nothing user-visible is missed by grep luck.
|
|
#
|
|
# Usage: ./scripts/generate-release-notes.sh <version> [previous-tag]
|
|
#
|
|
# Contract: the release notes markdown is written to STDOUT (trigger-release.sh
|
|
# captures it); all progress/diagnostics go to STDERR. SAVE_TO_FILE=1 also
|
|
# writes release-notes-v<version>.md.
|
|
#
|
|
# Env overrides:
|
|
# RELEASE_NOTES_ENGINE=claude|codex force an engine (default: claude, codex fallback)
|
|
# RELEASE_NOTES_MODEL=<model> model for the claude engine (default: sonnet)
|
|
|
|
set -euo pipefail
|
|
|
|
VERSION=${1:-}
|
|
PREVIOUS_TAG=${2:-}
|
|
|
|
if [ -z "$VERSION" ]; then
|
|
echo "Usage: $0 <version> [previous-tag]" >&2
|
|
echo "Example: $0 6.1.0 v6.0.6" >&2
|
|
exit 1
|
|
fi
|
|
|
|
cd "$(git rev-parse --show-toplevel)"
|
|
|
|
if [ -z "$PREVIOUS_TAG" ]; then
|
|
PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
|
if [ -z "$PREVIOUS_TAG" ]; then
|
|
echo "No previous tag found, cannot generate diff-based release notes" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if ! git rev-parse -q --verify "${PREVIOUS_TAG}^{commit}" >/dev/null; then
|
|
echo "Previous tag '${PREVIOUS_TAG}' does not exist" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Generating release notes for v${VERSION} (changes since ${PREVIOUS_TAG})..." >&2
|
|
|
|
read -r -d '' PROMPT <<EOF || true
|
|
You are generating the release notes for Pulse v${VERSION}.
|
|
|
|
Pulse is a self-hosted monitoring dashboard for Proxmox VE, PBS, Docker, and
|
|
Kubernetes, used mostly by homelab and small-ops users.
|
|
|
|
The repo is checked out at the commit that will become v${VERSION}. The
|
|
previous release tag is ${PREVIOUS_TAG}. Investigate the changes yourself —
|
|
start with \`git log ${PREVIOUS_TAG}..HEAD --oneline\` and
|
|
\`git diff ${PREVIOUS_TAG}..HEAD --stat\`, then use targeted \`git diff\` /
|
|
\`git show\` and read source files where the diff alone is unclear. For
|
|
commits that reference GitHub issues (#1234), you may use \`gh issue view\` to
|
|
understand the user-facing symptom. Only describe changes that exist in the
|
|
final code state; verify anything you are unsure about before writing it.
|
|
|
|
Focus on USER-VISIBLE changes only: features, fixes, and behavior users will
|
|
notice. Ignore internal refactors, test changes, CI/tooling, and docs.
|
|
|
|
Write the release notes in exactly this format:
|
|
|
|
## v${VERSION}
|
|
|
|
### Highlights
|
|
[2-3 short bullets covering only what a typical user would notice and care
|
|
about. This is the entire overview shown inside Pulse after an update. Use one
|
|
plain-text sentence per bullet, no more than 140 characters. Say what improved
|
|
and why it matters in everyday words; avoid component names, acronyms,
|
|
implementation details, links, issue numbers, and Markdown formatting.
|
|
IMPORTANT: if this release has nothing a typical user would notice (only
|
|
internal fixes or minor patches), OMIT this entire section — that deliberately
|
|
keeps the in-app banner silent for maintenance releases. Keep the heading at
|
|
level 3 (###).]
|
|
|
|
### Added
|
|
[Genuinely new user-facing capabilities. Name the page, workflow, integration,
|
|
or platform where users will find each one, then say what they can now do.]
|
|
|
|
### Improved
|
|
[Meaningful changes to existing behavior. Name the affected experience and the
|
|
observable improvement; do not summarize several unrelated changes together.]
|
|
|
|
### Fixed
|
|
[Problems users would have encountered. State the visible symptom that no
|
|
longer happens, not the internal cause. Include issue refs like (#1234) only
|
|
when the fix verifiably addresses that issue.]
|
|
|
|
Guidelines:
|
|
- Plain, factual, understated. No marketing language, no emojis.
|
|
- Omit any section that has no items.
|
|
- Every Added, Improved, and Fixed bullet must stand on its own as a concrete
|
|
changelog entry. A reader should understand where they would notice the
|
|
change and what is different without knowing Pulse's implementation.
|
|
- Avoid internal release and architecture vocabulary such as canonical,
|
|
governed, schema, provider transport, preflight, convergence, or runtime
|
|
boundary unless that exact term is visible to the user in the product.
|
|
- Do not use vague entries such as "improved agent handling" or "various UI
|
|
fixes". Split unrelated changes and name the behavior that changed.
|
|
- Do NOT write an Installation section or anything after Fixed — the
|
|
release pipeline appends those.
|
|
- Highlights is the ONE exception to "boring": it is shown in-app before users
|
|
update, so make it the shortest useful preview of what changed — still
|
|
factual, with no hype.
|
|
|
|
Your reply must be ONLY the release-notes markdown, starting with
|
|
"## v${VERSION}" — no preamble, no code fences, no commentary.
|
|
EOF
|
|
|
|
# Strip accidental markdown fences and anything before the "## v" heading.
|
|
clean_notes() {
|
|
sed -e 's/^```[a-z]*$//' -e 's/^```$//' | awk '/^## v/{found=1} found{print}'
|
|
}
|
|
|
|
# Both engines must run on the logged-in subscription (Claude Max / OpenAI
|
|
# plan), never on metered API keys. Stray env vars silently override
|
|
# subscription auth — scrub them before invoking either CLI.
|
|
scrub_env() {
|
|
env -u ANTHROPIC_API_KEY -u ANTHROPIC_AUTH_TOKEN -u ANTHROPIC_BASE_URL \
|
|
-u ANTHROPIC_PROFILE -u OPENAI_API_KEY "$@"
|
|
}
|
|
|
|
generate_with_claude() {
|
|
command -v claude >/dev/null || return 1
|
|
echo "Engine: claude (model: ${RELEASE_NOTES_MODEL:-sonnet})" >&2
|
|
scrub_env claude -p "$PROMPT" \
|
|
--model "${RELEASE_NOTES_MODEL:-sonnet}" \
|
|
--allowedTools \
|
|
"Bash(git log:*)" "Bash(git diff:*)" "Bash(git show:*)" \
|
|
"Bash(git describe:*)" "Bash(git tag:*)" "Bash(git rev-parse:*)" \
|
|
"Bash(gh issue view:*)" "Bash(gh pr view:*)" \
|
|
"Read" "Grep" "Glob" \
|
|
</dev/null
|
|
}
|
|
|
|
generate_with_codex() {
|
|
command -v codex >/dev/null || return 1
|
|
echo "Engine: codex" >&2
|
|
local out
|
|
out=$(mktemp)
|
|
# Session log goes to stderr; only the agent's final message is kept.
|
|
if ! scrub_env codex exec --sandbox read-only -o "$out" "$PROMPT" >&2 </dev/null; then
|
|
rm -f "$out"
|
|
return 1
|
|
fi
|
|
cat "$out"
|
|
rm -f "$out"
|
|
}
|
|
|
|
RELEASE_NOTES=""
|
|
case "${RELEASE_NOTES_ENGINE:-claude}" in
|
|
codex)
|
|
RELEASE_NOTES=$(generate_with_codex) || {
|
|
echo "Codex generation failed" >&2
|
|
exit 1
|
|
}
|
|
;;
|
|
*)
|
|
if ! RELEASE_NOTES=$(generate_with_claude); then
|
|
echo "Claude generation failed, trying Codex fallback..." >&2
|
|
RELEASE_NOTES=$(generate_with_codex) || {
|
|
echo "Both engines failed (need 'claude' or 'codex' CLI, logged in)" >&2
|
|
exit 1
|
|
}
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
RELEASE_NOTES=$(printf '%s\n' "$RELEASE_NOTES" | clean_notes)
|
|
|
|
if [ -z "$RELEASE_NOTES" ]; then
|
|
echo "Error: release notes generation returned no '## v' section" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Notes to stdout only — callers capture this.
|
|
printf '%s\n' "$RELEASE_NOTES"
|
|
|
|
if [ "${SAVE_TO_FILE:-}" = "1" ]; then
|
|
OUTPUT_FILE="release-notes-v${VERSION}.md"
|
|
printf '%s\n' "$RELEASE_NOTES" > "$OUTPUT_FILE"
|
|
echo "Saved to ${OUTPUT_FILE}" >&2
|
|
fi
|