Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 39 additions & 0 deletions .changelogs/v3.29.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Release v3.29.0

Released: 2026-08-15

## Highlights

- **No command requests a reviewer on its own anymore.** `/do:rpr` was the last holdout — it used to summon a GitHub Copilot review whenever a PR had none. It now behaves like every other command: name a reviewer with `--review-with` (or save one with `/do:config`) and that is exactly what runs; name nothing and it simply resolves the review threads the PR already has.
- **Copilot is now a legacy opt-in.** The `copilot` slug still works everywhere it did before, but it is listed last, marked legacy, and never selected for you. Local CLI reviewers are the primary path.
- **Changelogs follow your project's convention instead of slashdo's.** `/do:push`, `/do:next`, `/do:better`, and `/do:better-swift` no longer assume a `.changelogs/NEXT.md` staging file. They read your stated convention first, otherwise imitate whatever changelog you already keep — a rolling `CHANGELOG.md`, a per-release directory, a changesets/towncrier fragment — and write nothing at all if your release notes come from commit messages.
- **`/do:release` can build release notes from your commit history.** Deriving notes from the commits since the last tag is now a first-class path rather than a fallback, including for projects that keep no changelog file at all, where the synthesized notes become the release body directly.
- **Task tracking is convention-driven too.** Completed items get marked done the way your tracker already marks them, instead of assuming a `PLAN.md` with checkboxes.

## Code review

- `/do:rpr` no longer has a built-in default reviewer. Previously it defaulted to a *conditional* Copilot review: if the PR had no review yet, rpr would request one on its own. With no `--review-with` flag and no saved default, it now requests nothing and does only its reviewer-agnostic core job — fetching and resolving the unresolved threads already on the PR, whoever wrote them. **If you relied on bare `/do:rpr` summoning a Copilot review, pass `--review-with copilot` or save it with `/do:config --review-with copilot`.**
- An `@<login>`-only `--review-with` value no longer falls back to Copilot. Because rpr can't yet request arbitrary GitHub reviewers, such a value now resolves to no reviewer rather than quietly substituting a different one.
- Copilot is demoted to a legacy opt-in throughout: it is listed last in every reviewer list and error message, marked legacy in the README reviewer table, and its review loop is labelled legacy. Nothing about the slug's behavior changed — only its prominence and the fact that nothing picks it for you.
- Two review checklists asserted that "Copilot review must complete before merge" as a blanket pipeline rule, regardless of which reviewer actually ran. Both now refer to whichever reviewers the PR requested.

## Changelogs and release notes

- `/do:push` resolves how your project logs changes rather than assuming one layout. It checks your `CLAUDE.md` / `AGENT.md` / `AGENTS.md` / `CONTRIBUTING.md` for a stated convention first, then imitates whatever changelog artifacts already exist, and otherwise skips the step entirely and lets the commit message carry the change. It will no longer create a changelog file a project never asked for.
- `/do:next` follows the same resolution when logging a shipped item, and no longer hardcodes changelog paths in its staging commands.
- `/do:release` resolves how a project produces release notes before touching anything, and now recognizes when a release-automation tool (release-please, semantic-release, changesets, towncrier, git-cliff) owns the changelog — in which case it leaves the file alone instead of hand-writing something the tool will overwrite.
- Building release notes from `git log` since the last tag is now a documented first-class path, with guidance to prefer merge/PR titles, group by feature or theme, and drop noise commits — rather than emitting a raw commit dump.
- `/do:better` and `/do:better-swift` detect the changelog target the same way. Previously each ran a bare directory check, and `/do:better` only ever looked for `.changelog/` — so it silently missed projects using `.changelogs/`.

## Task tracking

- `/do:push` marks a completed item done the way the surrounding tracker already marks things done — flipping a checkbox, striking it, deleting the line, closing the issue — instead of prescribing a `- [ ]` to `- [x]` flip on a `PLAN.md` most projects don't have.

## Internal

- slashdo's own repo dropped its `.changelogs/NEXT.md` staging area. Every push used to edit the same file, parallel `/do:next` claims conflicted on it, and the entries duplicated what the commit history already carried. Release notes are now rolled up once at release time. The release workflow reads the versioned changelog file directly, and its "archive changelog on main" job — which pushed to `main` and force-synced `main:release` from CI — was removed along with the pattern-file scheme it existed to support.
- `CONTRIBUTING.md` documents the changelog convention explicitly, which is what keeps the now-convention-driven commands from re-inferring a staging layout from the archived release files.

## Full Changelog

**Full Diff**: https://github.com/atomantic/slashdo/compare/v3.28.1...v3.29.0
55 changes: 8 additions & 47 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,26 +94,20 @@ jobs:
if: steps.npm-check.outputs.published == 'false'
run: npm publish --provenance

# /do:release rolls the commits since the last tag up into .changelogs/v{VERSION}.md
# as part of the release PR, so by the time this runs the file is already on the
# branch. The git-log fallback below only fires for a release cut by hand.
- name: Generate changelog
id: changelog
if: steps.release-check.outputs.exists == 'false'
run: |
VERSION="${{ steps.package-version.outputs.version }}"
MAJOR_MINOR=$(echo "$VERSION" | cut -d. -f1-2)

CHANGELOG_FILE_EXACT=".changelogs/v${VERSION}.md"
CHANGELOG_FILE_PATTERN=".changelogs/v${MAJOR_MINOR}.x.md"
CHANGELOG_FILE_NEXT=".changelogs/NEXT.md"

if [ -f "$CHANGELOG_FILE_EXACT" ]; then
CHANGELOG=$(cat "$CHANGELOG_FILE_EXACT")
elif [ -f "$CHANGELOG_FILE_PATTERN" ]; then
CHANGELOG=$(cat "$CHANGELOG_FILE_PATTERN")
CHANGELOG=$(printf '%s' "$CHANGELOG" | sed "s/v${MAJOR_MINOR}\.x/v${VERSION}/g" | sed "s/${MAJOR_MINOR}\.x/${VERSION}/g" | sed "s/YYYY-MM-DD/$(date +%Y-%m-%d)/g")
elif [ -f "$CHANGELOG_FILE_NEXT" ]; then
CHANGELOG=$(cat "$CHANGELOG_FILE_NEXT")
CHANGELOG=$(printf '%s' "$CHANGELOG" | sed "s/NEXT/v${VERSION}/g" | sed "s/YYYY-MM-DD/$(date +%Y-%m-%d)/g")
CHANGELOG_FILE=".changelogs/v${VERSION}.md"

if [ -f "$CHANGELOG_FILE" ]; then
CHANGELOG=$(cat "$CHANGELOG_FILE")
else
echo "No ${CHANGELOG_FILE}; falling back to a raw commit list." >&2
PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null || git rev-list --max-parents=0 HEAD)
COMMIT_LOG=$(git log "$PREV_TAG"..HEAD --pretty=format:"- %s" --no-merges | grep -v "\[skip ci\]" || true)
COMMIT_LOG=$(printf '%s\n' "$COMMIT_LOG" | head -50)
Expand All @@ -136,36 +130,3 @@ jobs:
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Archive changelog on main
if: steps.release-check.outputs.exists == 'false'
continue-on-error: true
run: |
CURRENT_VERSION=${{ steps.package-version.outputs.version }}
MAJOR=$(echo $CURRENT_VERSION | cut -d. -f1)
MINOR=$(echo $CURRENT_VERSION | cut -d. -f2)
MAJOR_MINOR="$MAJOR.$MINOR"

PATTERN_FILE=".changelogs/v${MAJOR_MINOR}.x.md"
VERSIONED_FILE=".changelogs/v${CURRENT_VERSION}.md"

git fetch origin main
git checkout -B main origin/main

if [ -f "$VERSIONED_FILE" ]; then
echo "Versioned changelog $VERSIONED_FILE already exists; skipping archive."
elif [ -f "$PATTERN_FILE" ]; then
git mv "$PATTERN_FILE" "$VERSIONED_FILE"
sed -i.bak "s/v${MAJOR_MINOR}\.x/v${CURRENT_VERSION}/g; s/${MAJOR_MINOR}\.x/${CURRENT_VERSION}/g; s/YYYY-MM-DD/$(date +%Y-%m-%d)/g" "$VERSIONED_FILE"
rm "${VERSIONED_FILE}.bak"

git add "$VERSIONED_FILE"
git commit -m "docs: archive changelog for v${CURRENT_VERSION} [skip ci]"
git push origin main

if git merge-base --is-ancestor origin/release HEAD; then
git push origin main:release
else
echo "Warning: release branch has diverged from main; skipping sync to avoid rewriting history."
fi
fi
8 changes: 8 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ Thanks for considering a contribution. slashdo is a small, actively-maintained p
- `npm test` — run the full test suite
5. If your change affects behavior covered by an existing test, update it; if it adds new behavior worth locking in, add a test under `test/`.

## Changelog

**Do not write a changelog entry in your PR.** There is no unreleased/staging changelog file in this repo — no `NEXT.md`, no `## Unreleased` section. **The commit history is the changelog.**

Release notes are rolled up once, at release time: `/do:release` reads `git log {last_tag}..HEAD`, synthesizes it into feature-grouped notes, and writes `.changelogs/v{version}.md` as part of the release PR. `.github/workflows/release.yml` then publishes that file as the GitHub Release body. The `.changelogs/v*.md` files are the archive of past releases — read them, don't hand-edit them.

This is why commit subjects matter (below): a vague subject becomes a vague release note, and nothing downstream can recover the intent.

## Commit and PR conventions

- Commit subjects are specific sentences, not vague tags — `fix: guard empty array expansion in review-loop bash 3.2 path`, not `fix bug`.
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ All commands live under the `do:` namespace:

| Command | What it does |
|:---|:---|
| `/do:push` | Commit and push all work with changelog |
| `/do:push` | Commit and push all work, logging it per the project's own changelog convention |
| `/do:pr` | Commit, push, and open a PR (GitHub `gh`) or merge request (GitLab `glab`) with self-review. External reviewers run only when you list them ([Review loop](#review-loop)); `--merge` auto-merges once reviews and CI pass ([Auto-merge](#auto-merge-dopr---merge)) |
| `/do:pr-better` | Run a full do:better audit on the current branch, commit fixes directly, then open a single PR |
| `/do:fpr` | Fork PR — push to fork, PR against upstream |
Expand Down Expand Up @@ -208,25 +208,25 @@ Requirement IDs are stable across `--refresh` runs — unchanged requirements ke

## Review loop

`/do:pr`, `/do:release`, `/do:pr-better`, `/do:review`, `/do:better`, `/do:better-swift`, `/do:simplify`, `/do:depfree`, and `/do:rpr` share one review system: you pick the reviewer(s) with `--review-with`, and a set of companion flags controls how the loop runs. **No reviewer is ever hardcoded** — omit the flag and no external review runs (each command still runs its own unconditional self-review gate). The one exception is `/do:rpr`, whose conditional default is [documented below](#command-specific-behavior).
`/do:pr`, `/do:release`, `/do:pr-better`, `/do:review`, `/do:better`, `/do:better-swift`, `/do:simplify`, `/do:depfree`, and `/do:rpr` share one review system: you pick the reviewer(s) with `--review-with`, and a set of companion flags controls how the loop runs. **No reviewer is ever hardcoded, in any command** — omit the flag and no external review runs (each command still runs its own unconditional self-review gate).

### Reviewers

| Slug | What runs | Model pinnable? |
|:---|:---|:---|
| `copilot` | GitHub's cloud Copilot review on the PR (GitHub only) | no |
| `codex` | The Codex CLI in headless mode, reviewing locally | yes |
| `claude` | The Claude Code CLI in headless mode | yes |
| `agy` | The Antigravity CLI (`agy` binary; aliases: `gemini`, `antigravity`) | yes |
| `grok` | The Grok CLI in headless mode, reviewing locally | yes |
| `ollama` | A local Ollama model — review-only (non-agentic). Bare `ollama` auto-selects your most capable installed coding model | yes |
| `@<login>` | Any GitHub user or App/bot (e.g. `@octocat`, `@some-app[bot]`): slashdo requests their review on the PR, waits for it, and fixes what it surfaces. GitHub only; slashdo never posts an approval itself | no |
| `copilot` | **Legacy.** GitHub's cloud Copilot review on the PR (GitHub only). Still fully supported when you name it, but no command selects it for you | no |

Reviewers run **in the order listed**, and whatever you list is exactly what runs — `--review-with codex` runs codex only; copilot is never added implicitly.
Reviewers run **in the order listed**, and whatever you list is exactly what runs — `--review-with codex` runs codex only; nothing is ever added implicitly.

```
/do:pr --review-with codex # one local reviewer
/do:pr --review-with codex,agy,copilot # codex, then Antigravity, then Copilot — each sees the prior's fixes
/do:pr --review-with codex,agy # codex, then Antigravity — each sees the prior's fixes
/do:pr --review-with claude[claude-opus-4-8],codex[o3] # pin the model per reviewer
/do:pr --review-with ollama[qwen2.5-coder:32b] # pin a specific installed Ollama model
/do:pr --review-with codex,@org-review-bot # codex, then request a review from a GitHub bot
Expand Down Expand Up @@ -264,7 +264,7 @@ By default the orchestrator that opened the PR applies every reviewer's fixes it

- **`/do:review`** — the listed agents run *after* the host CLI's own multi-agent self-review; the list names *additional* reviewers.
- **`/do:better` / `/do:better-swift` / `/do:simplify` / `/do:depfree`** — the chosen reviewers run as the post-PR review loop (per PR, in parallel for the multi-PR better commands). **Omitting `--review-with` skips the review loop and the auto-merge** — PRs are left open for manual review.
- **`/do:rpr`** — resolves review threads from any author (Copilot, human, or bot). Its `--review-with` default is a *conditional* `copilot`: it requests a Copilot review only when the PR has no review yet, or when Copilot is already the reviewer in play. It accepts only `--review-with` and `--reviewer-applies` (not `--review-iterations`, `--review-mode`, or the stop-mode flags), and it doesn't support `@<login>` entries — it drops them with a notice and falls back to its conditional copilot default.
- **`/do:rpr`** — resolves review threads from any author (Copilot, human, or bot). Like every other command it has **no default reviewer**: omit `--review-with` (and set no saved default) and rpr requests nothing — it just fetches and resolves the unresolved threads the PR already carries. Name a reviewer and rpr requests it, then loops review → fix → re-review. It accepts only `--review-with` and `--reviewer-applies` (not `--review-iterations`, `--review-mode`, or the stop-mode flags), and it doesn't support `@<login>` entries — it drops them with a notice.

## Auto-merge (`/do:pr --merge`)

Expand Down
Loading