Skip to content

Commit 1d6c164

Browse files
authored
chore: refactor release automation and optimize commit extension (#3)
* docs(readme): update installation instructions and extension table * chore: add changelog extraction script and auto-release workflow * docs(changelog): document auto-release workflow and command updates * docs(changelog): document sandbox runtime toggling and commit extension overhaul * docs(changelog): add release notes summary * refactor(commit): optimize token usage and consolidate ai calls * docs: update changelog
1 parent 8b893ef commit 1d6c164

7 files changed

Lines changed: 700 additions & 254 deletions

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/env python3
2+
"""Extract release notes from CHANGELOG.md.
3+
4+
Tries in order:
5+
1. Body under "## Unreleased"
6+
2. Body under a heading matching the given version (e.g. ## 1.2.0, ## [1.2.0](...))
7+
3. Git commit log since the last tag
8+
9+
Usage:
10+
python extract-changelog.py <version> <output-file>
11+
"""
12+
13+
import re
14+
import subprocess
15+
import sys
16+
from pathlib import Path
17+
18+
19+
def extract_section(changelog: str, heading_match: str) -> str:
20+
"""Return the body text between a ## heading matching `heading_match` and the next ## heading."""
21+
lines = changelog.splitlines()
22+
capturing = False
23+
body: list[str] = []
24+
25+
for line in lines:
26+
if line.startswith("## "):
27+
if capturing:
28+
break
29+
# Strip markdown link syntax: ## [1.2.0](url) - date -> 1.2.0
30+
title = re.sub(r"^## \[?", "", line)
31+
title = re.sub(r"[\]( -].*", "", title).strip()
32+
if title == heading_match:
33+
capturing = True
34+
continue
35+
elif capturing:
36+
body.append(line)
37+
38+
return "\n".join(body).strip()
39+
40+
41+
def git_log_fallback() -> str:
42+
"""Return commit messages since the last tag, or all commits if no tags exist."""
43+
try:
44+
base = subprocess.check_output(
45+
["git", "describe", "--tags", "--abbrev=0"],
46+
stderr=subprocess.DEVNULL,
47+
text=True,
48+
).strip()
49+
except subprocess.CalledProcessError:
50+
base = subprocess.check_output(
51+
["git", "rev-list", "--max-parents=0", "HEAD"],
52+
text=True,
53+
).strip()
54+
55+
log = subprocess.check_output(
56+
["git", "log", f"{base}..HEAD", "--pretty=format:* %s"],
57+
text=True,
58+
).strip()
59+
return log
60+
61+
62+
def main() -> None:
63+
if len(sys.argv) != 3:
64+
print(f"Usage: {sys.argv[0]} <version> <output-file>", file=sys.stderr)
65+
sys.exit(1)
66+
67+
version = sys.argv[1]
68+
output_path = Path(sys.argv[2])
69+
70+
changelog_path = Path("CHANGELOG.md")
71+
if not changelog_path.exists():
72+
print("CHANGELOG.md not found, falling back to git log", file=sys.stderr)
73+
output_path.write_text(git_log_fallback())
74+
return
75+
76+
changelog = changelog_path.read_text()
77+
78+
# 1. Try "Unreleased"
79+
notes = extract_section(changelog, "Unreleased")
80+
if notes:
81+
print(f"Extracted release notes from '## Unreleased' ({len(notes)} chars)")
82+
output_path.write_text(notes)
83+
return
84+
85+
# 2. Try version match
86+
notes = extract_section(changelog, version)
87+
if notes:
88+
print(f"Extracted release notes from '## {version}' ({len(notes)} chars)")
89+
output_path.write_text(notes)
90+
return
91+
92+
# 3. Fallback to git log
93+
print("No changelog section found, falling back to git log", file=sys.stderr)
94+
output_path.write_text(git_log_fallback())
95+
96+
97+
if __name__ == "__main__":
98+
main()

.github/workflows/npm-publish.yml

Lines changed: 0 additions & 32 deletions
This file was deleted.

.github/workflows/release.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Auto Release
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
release:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v4
17+
with:
18+
fetch-depth: 0
19+
20+
- name: Read version from package.json
21+
id: version
22+
run: |
23+
VERSION=$(node -p "require('./package.json').version")
24+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
25+
echo "tag=v$VERSION" >> "$GITHUB_OUTPUT"
26+
27+
- name: Check if tag already exists
28+
id: check
29+
run: |
30+
if git rev-parse "v${{ steps.version.outputs.version }}" >/dev/null 2>&1; then
31+
echo "exists=true" >> "$GITHUB_OUTPUT"
32+
else
33+
echo "exists=false" >> "$GITHUB_OUTPUT"
34+
fi
35+
36+
- name: Extract changelog
37+
if: steps.check.outputs.exists == 'false'
38+
run: python3 .github/scripts/extract-changelog.py "${{ steps.version.outputs.version }}" /tmp/release-notes.md
39+
40+
- name: Create tag and GitHub release
41+
if: steps.check.outputs.exists == 'false'
42+
env:
43+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44+
run: |
45+
TAG="${{ steps.version.outputs.tag }}"
46+
git tag "$TAG"
47+
git push origin "$TAG"
48+
gh release create "$TAG" \
49+
--title "$TAG" \
50+
--notes-file /tmp/release-notes.md

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
All notable changes to agent-stuff are documented here.
44

5+
56
## git-rebase-master
67

78
Added `/git-rebase-master` command that fetches the latest `main` or `master`
@@ -16,6 +17,10 @@ new `autoBranch` option on `performCommit`.
1617
`/commit-push-pr` now creates PRs in ready mode by default instead of draft
1718
mode, streamlining the publish workflow for most use cases.
1819

20+
## docs/update-readme-and-sandbox
21+
22+
The commit extension was overhauled to dramatically reduce token consumption by gathering changelog context deterministically through git/gh commands and calling the model once for summary generation, with the new `/merge-pr` command streamlining the full pre-merge workflow including changelog updates, incremental PR description refresh, and squash-merge with cleanup. The sandbox extension now supports runtime toggling via `/sandbox on` and `/sandbox off` commands with a visual status indicator, eliminating the need to restart. An auto-release workflow now replaces the npm-publish pipeline, automatically creating GitHub tags and releases on main merges when `package.json` version changes, with release notes extracted from `CHANGELOG.md` via a new Python script that falls back to the git commit log. (#3)
23+
1924
## Unreleased
2025

2126
* Added the `/plan` command for read-only planning mode with interactive brainstorming via the `ask_question` tool. Supports Shift+Tab mode toggle and an always-visible mode status indicator.

README.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,7 @@ Extensions, skills, and themes for [Pi](https://buildwithpi.ai/), the coding age
66
77
## Installation
88

9-
Install as a Pi package (extensions, skills, and themes are all discovered automatically):
10-
11-
```bash
12-
pi install npm:kostyay-agent-stuff
13-
```
14-
15-
Or install from git:
9+
Install from git (extensions, skills, and themes are all discovered automatically):
1610

1711
```bash
1812
pi install git:github.com/kostyay/agent-stuff
@@ -32,6 +26,7 @@ All extensions live in [`pi-extensions/`](pi-extensions). Each file is a self-co
3226
|-----------|-------------|
3327
| [`answer.ts`](pi-extensions/answer.ts) | Extracts questions from assistant responses and presents an interactive TUI for answering them one by one |
3428
| [`clear.ts`](pi-extensions/clear.ts) | `/clear` command — starts a new session (alias for `/new`) |
29+
| [`commit.ts`](pi-extensions/commit.ts) | `/commit` command — stages all changes, generates a Conventional Commits message via LLM, creates a side branch if on the default branch |
3530
| [`context.ts`](pi-extensions/context.ts) | `/context` command — shows loaded extensions, skills, AGENTS.md/CLAUDE.md, and token usage |
3631
| [`control.ts`](pi-extensions/control.ts) | Session control via Unix domain sockets for inter-session communication |
3732
| [`files.ts`](pi-extensions/files.ts) | `/files` command — file browser merging git status with session-referenced files, plus diff/edit actions |
@@ -42,10 +37,10 @@ All extensions live in [`pi-extensions/`](pi-extensions). Each file is a self-co
4237
| [`prompt-editor.ts`](pi-extensions/prompt-editor.ts) | Prompt mode selector (default/fast/precise) with per-mode model & thinking persistence |
4338
| [`review.ts`](pi-extensions/review.ts) | `/review` command — code review for uncommitted changes, PRs, or specific commits with optional auto-fix loop |
4439
| [`session-breakdown.ts`](pi-extensions/session-breakdown.ts) | `/session-breakdown` command — analyzes session usage (cost by model) with a GitHub-style activity graph |
40+
| [`simplify.ts`](pi-extensions/simplify.ts) | `/simplify` command — detects the dominant language of uncommitted changes and runs the matching code-simplifier skill |
4541
| [`status-bar.ts`](pi-extensions/status-bar.ts) | Rich two-line footer with model, context meter, token counts, cost, git status, and tool tally |
4642
| [`todos.ts`](pi-extensions/todos.ts) | File-backed todo manager with a TUI for listing and editing todos |
4743
| [`whimsical.ts`](pi-extensions/whimsical.ts) | Replaces "Thinking..." with random phrases like "Reticulating splines..." and "Consulting the void..." |
48-
| [`commit.ts`](pi-extensions/commit.ts) | `/commit` command — stages all changes, generates a Conventional Commits message via LLM, creates a side branch if on the default branch |
4944

5045
## Skills
5146

@@ -82,6 +77,7 @@ Custom themes live in [`pi-themes/`](pi-themes).
8277
├── skills/ # Agent skills (SKILL.md per skill)
8378
├── pi-themes/ # Custom themes
8479
├── plumbing-commands/ # Release automation templates
80+
├── Makefile # Release and changelog targets
8581
├── AGENTS.md # Agent-facing coding conventions
8682
├── CHANGELOG.md # Release history
8783
└── package.json # Pi package manifest

0 commit comments

Comments
 (0)