diff --git a/.github/workflows/publish-github-release.yml b/.github/workflows/publish-github-release.yml index 2e7b9fe..29ea17f 100644 --- a/.github/workflows/publish-github-release.yml +++ b/.github/workflows/publish-github-release.yml @@ -69,13 +69,12 @@ jobs: set -euo pipefail NOTES=$(mktemp) - { - echo "Automated release for ${RELEASE_TAG}." - echo - echo "Published packages:" - echo "- npm: @cafitac/hermit-agent@${VERSION}" - echo "- PyPI: cafitac-hermit-agent==${VERSION}" - } > "$NOTES" + python3 scripts/render_release_notes.py \ + --tag "$RELEASE_TAG" \ + --version "$VERSION" \ + --repo "$REPOSITORY" \ + --reason "tag-triggered release publication" \ + --out "$NOTES" if gh release view "$RELEASE_TAG" --repo "$REPOSITORY" >/dev/null 2>&1; then gh release edit "$RELEASE_TAG" \ @@ -90,7 +89,6 @@ jobs: --repo "$REPOSITORY" \ --verify-tag \ --title "$RELEASE_TAG" \ - --notes-file "$NOTES" \ - --generate-notes + --notes-file "$NOTES" echo "Created GitHub Release ${RELEASE_TAG}." \ No newline at end of file diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 005a1f4..4c7a9a5 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -308,21 +308,23 @@ jobs: RELEASE_TAG: ${{ needs.determine_version.outputs.release_tag }} RELEASE_REASON: ${{ needs.classify_release.outputs.reason }} steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Create or update GitHub Release shell: bash run: | set -euo pipefail NOTES=$(mktemp) - { - echo "Automated release for ${RELEASE_TAG}." - echo - echo "Release classifier: ${RELEASE_REASON}" - echo - echo "Published packages:" - echo "- npm: @cafitac/hermit-agent@${NEXT}" - echo "- PyPI: cafitac-hermit-agent==${NEXT}" - } > "$NOTES" + python3 scripts/render_release_notes.py \ + --tag "$RELEASE_TAG" \ + --version "$NEXT" \ + --repo "${{ github.repository }}" \ + --reason "$RELEASE_REASON" \ + --out "$NOTES" if gh release view "$RELEASE_TAG" --repo "${{ github.repository }}" >/dev/null 2>&1; then gh release edit "$RELEASE_TAG" \ @@ -337,8 +339,7 @@ jobs: --repo "${{ github.repository }}" \ --verify-tag \ --title "$RELEASE_TAG" \ - --notes-file "$NOTES" \ - --generate-notes + --notes-file "$NOTES" echo "Created GitHub Release ${RELEASE_TAG}." diff --git a/CHANGELOG.md b/CHANGELOG.md index b66c013..20d4d90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,12 @@ - Refreshed the README opening section to position Hermit as a distinct open-source executor layer for Claude Code and Codex rather than a generic terminal UI. - Added a comparison section that explains why teams would pair Hermit with Claude Code or Codex instead of treating it as a replacement orchestrator. - Added explicit "who Hermit is for / not for" guidance so the landing page qualifies the intended audience instead of only describing features. -- Added a reusable `docs/open-source-positioning.md` copy deck for repository descriptions, social preview messaging, release framing, audience-fit guidance, and topic candidates. +- Added a reusable `docs/open-source-positioning.md` copy deck for repository descriptions, social preview messaging, release framing, audience-fit guidance, topic candidates, and hero-asset references. - Added a final social-preview asset set: editable SVG, ready-to-upload PNG export, and a local review page under `docs/assets/` for GitHub/social-preview iteration. +- Added a dedicated `docs/assets/hermit-readme-hero.svg` so the README explains the planner/executor split with a reusable visual instead of terminal ASCII. - Added `docs/social-preview-ops.md` so maintainers have a concrete review/export/upload checklist for the GitHub social preview image. - Added `docs/release-notes-template.md` so release blurbs and GitHub Releases can reuse the same planner/executor positioning without improvising each time. -- Reordered the README badge row around release health and package availability, and tightened the hero copy so the landing page reads closer to the final social-preview message. +- Reordered the README badge row around release health and package availability, tightened the hero copy, and swapped the ASCII diagram for a polished SVG hero that matches the social-preview tone. - Updated package and repository-facing descriptions to emphasize the MCP executor + cheaper execution lane story instead of the outdated Codex-first fallback wording. - Tightened the public metadata around predictable local / flat-rate defaults so the repository pitch matches the current install and routing policy. @@ -23,6 +24,7 @@ - Added a dedicated `Publish GitHub Release` workflow on `v*` tags for manual or external tag pushes, while the main release workflow also creates the GitHub Release directly so auto-published releases do not depend on cross-workflow tag triggers. - Added release-workflow concurrency plus idempotent npm publish, tag-push, and GitHub Release checks so reruns do not accidentally create duplicate artifacts. - Fixed release write-back to use the configured push token correctly, and kept the protected-`main` fallback that opens a sync PR when direct write-back is rejected. +- GitHub Release notes are now rendered from `scripts/render_release_notes.py` so the auto-publish path and the tag/manual fallback both follow the same release-note template and planner/executor framing. ### Install and model-selection UX - Switched the primary onboarding flow to `npm install -g @cafitac/hermit-agent` followed by `hermit`, with guided setup offered from startup when Claude Code or Codex integration is incomplete. diff --git a/README.md b/README.md index 8fecc88..608e59a 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,7 @@ > Hermit adds a dedicated MCP execution lane under Claude Code or Codex: keep the premium orchestrator for judgment, and route the repetitive repo work to predictable local or flat-rate models. -``` -┌──────────────┐ -│ Claude Code │──┐ -│ (planner) │ │ ┌──────────────┐ any OpenAI-compatible ┌───────┐ -└──────────────┘ ├───▶│ HermitAgent │ ────────────────────────▶ │ LLM │ - │ │ (executor) │ └───────┘ -┌──────────────┐ │ └──────────────┘ -│ Codex │──┘ local / flat-rate by default -│ (planner) │ -└──────────────┘ -``` +![Hermit README hero](docs/assets/hermit-readme-hero.svg) Claude Code or Codex stays in charge of planning, interviewing, and review. Hermit takes the mechanical path: file edits, test runs, refactors, commits, and MCP-executed follow-through on predictable local or flat-rate execution models. The switch is one word in a slash command: `/foo` → `/foo-hermit`. @@ -164,6 +154,7 @@ MIT — see [LICENSE](LICENSE). - [docs/open-source-positioning.md](docs/open-source-positioning.md) — short public-facing copy for descriptions, releases, and future social previews - [docs/release-notes-template.md](docs/release-notes-template.md) — reusable release-note framing that matches Hermit's planner/executor positioning - [docs/social-preview-ops.md](docs/social-preview-ops.md) — how to review, export, and upload the GitHub social-preview image +- [docs/assets/hermit-readme-hero.svg](docs/assets/hermit-readme-hero.svg) — README hero graphic for the planner/executor split - [docs/assets/hermit-social-preview.svg](docs/assets/hermit-social-preview.svg) — final editable social-preview asset for repo cards and launch posts - [docs/assets/hermit-social-preview.png](docs/assets/hermit-social-preview.png) — ready-to-upload GitHub social-preview export - [docs/assets/hermit-social-preview-review.html](docs/assets/hermit-social-preview-review.html) — local review page for checking the social-preview composition before export diff --git a/docs/assets/hermit-readme-hero.svg b/docs/assets/hermit-readme-hero.svg new file mode 100644 index 0000000..05b0a3c --- /dev/null +++ b/docs/assets/hermit-readme-hero.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + MCP EXECUTION LANE FOR CLAUDE CODE OR CODEX + Keep Claude or Codex for judgment. + Let Hermit carry the repo labor. + MCP execution for edits, tests, commits, and releases on predictable + local or flat-rate models. + + + Claude Code / Codex + + Hermit MCP + + Local / flat-rate LLMs + + + + Planner + + Hermit + + + Planner: interviews, scope, review + Hermit: edits, tests, commits, releases + + One execution lane for many orchestrators. + + + diff --git a/docs/open-source-positioning.md b/docs/open-source-positioning.md index 8d4ba07..a1e2361 100644 --- a/docs/open-source-positioning.md +++ b/docs/open-source-positioning.md @@ -62,6 +62,15 @@ Hermit ships the mechanical follow-through. One MCP executor layer. Cheaper execution across multiple orchestrators. +## Homepage / hero copy variants + +### Hero visual assets +- README hero: `docs/assets/hermit-readme-hero.svg` +- Social preview card: `docs/assets/hermit-social-preview.svg` +- Social preview export: `docs/assets/hermit-social-preview.png` + +Use the README hero for in-page explanation of the planner/executor split, and use the social-preview card for repository thumbnails, launch posts, and Open Graph-style previews. + ## Social preview asset - Final editable asset: `docs/assets/hermit-social-preview.svg` diff --git a/scripts/render_release_notes.py b/scripts/render_release_notes.py new file mode 100644 index 0000000..3b6287d --- /dev/null +++ b/scripts/render_release_notes.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import os +import re +import subprocess +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +TEMPLATE = ROOT / "docs/release-notes-template.md" + + +def git(*args: str) -> str: + return subprocess.check_output(["git", *args], cwd=ROOT, text=True).strip() + + +def safe_git(*args: str) -> str: + try: + return git(*args) + except Exception: + return "" + + +def detect_previous_tag(current_tag: str) -> str: + tags = [t.strip() for t in safe_git("tag", "--list", "v*").splitlines() if t.strip()] + tags = [t for t in tags if t != current_tag] + def key(tag: str): + parts = tag.lstrip("v").split(".") + return tuple(int(p) if p.isdigit() else 0 for p in parts) + tags.sort(key=key) + return tags[-1] if tags else "" + + +def recent_subjects(previous_tag: str) -> list[str]: + if previous_tag: + text = safe_git("log", "--format=%s", f"{previous_tag}..HEAD") + else: + text = safe_git("log", "-n", "5", "--format=%s") + subjects = [line.strip() for line in text.splitlines() if line.strip()] + cleaned: list[str] = [] + seen: set[str] = set() + for subject in subjects: + if subject.startswith("chore: release v"): + continue + if subject in seen: + continue + seen.add(subject) + cleaned.append(subject) + return cleaned[:5] + + +def sentence_case(subject: str) -> str: + subject = re.sub(r"\s*\(#\d+\)$", "", subject).strip() + if not subject: + return subject + return subject[0].upper() + subject[1:] + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--tag", required=True) + parser.add_argument("--version", required=True) + parser.add_argument("--repo", required=True) + parser.add_argument("--reason", default="automated main-branch release") + parser.add_argument("--out", required=True) + args = parser.parse_args() + + current_tag = args.tag + version = args.version + repo = args.repo + reason = args.reason + previous_tag = detect_previous_tag(current_tag) + subjects = recent_subjects(previous_tag) + headline = subjects[0] if subjects else f"Automated release for {current_tag}" + summary = sentence_case(headline) + + template_text = TEMPLATE.read_text() + opening_lines = re.findall(r"^- (.+)$", template_text.split("## Reusable opening lines", 1)[1].split("## Reusable closing lines", 1)[0], re.M) + closing_lines = re.findall(r"^- (.+)$", template_text.split("## Reusable closing lines", 1)[1], re.M) + opening = opening_lines[0] if opening_lines else "Hermit keeps planner judgment premium while pushing repetitive repo work into a dedicated MCP executor lane." + closing = closing_lines[0] if closing_lines else "The planner stays premium; the repo mechanics stay efficient." + + bullets = [] + if subjects: + for subject in subjects[:3]: + bullets.append(f"- {sentence_case(subject)}") + else: + bullets.append(f"- Automated release for {current_tag}.") + + if previous_tag: + range_line = f"Changes since {previous_tag}." + else: + range_line = "Changes from the latest main-branch release payload." + + body = f"""## Summary +{summary} + +## What changed +{chr(10).join(bullets)} +- Published packages: npm @cafitac/hermit-agent@{version} and PyPI cafitac-hermit-agent=={version}. + +## Why it matters +{opening} This release was classified as: {reason}. {range_line} + +## Operator notes +- Release tag: {current_tag} +- If protected main blocks workflow write-back, the publish workflow opens a metadata-only sync PR automatically. +- README, changelog, and package metadata stay aligned through the same release path. + +## Assets +- GitHub Release: https://github.com/{repo}/releases/tag/{current_tag} +- npm: https://www.npmjs.com/package/@cafitac/hermit-agent +- PyPI: https://pypi.org/project/cafitac-hermit-agent/ +- README: https://github.com/{repo}#readme + +> {closing} +""" + Path(args.out).write_text(body) + + +if __name__ == "__main__": + main()