Files
promptkit/docs/release.md

8.3 KiB

Release Procedure

Release Model

Promptkit publishes a Go library through source commits and semantic Go module tags. It does not publish runnable binaries or binary packages and does not currently use hosted CI. The release maintainer performs and records the required validation.

v0.1.0 is the initial published release. Later releases use semantic vMAJOR.MINOR.PATCH tags. Before v1, minor releases may change the public API and patch releases preserve compatibility within their minor line. Every pre-v1 release note must summarize compatibility, identify public API changes, and state any action required of consumers.

Promptkit releases are source-only. The annotated tag message is the release note; there is no separate hosted release or binary packaging step.

Establish The Candidate

Choose a version that has not been published and export it as RELEASE_VERSION. Run every command in this procedure from the Promptkit repository root in the same POSIX shell. Do not reuse v0.1.0 or another existing version.

The following guard derives the release commit from HEAD and stops on a missing or malformed version, a checkout other than synchronized main, uncommitted changes, an active Go workspace, a module replacement, a vendor tree, or an existing local or remote tag:

set -eu

: "${RELEASE_VERSION:?export an unpublished vMAJOR.MINOR.PATCH version}"
if ! printf '%s\n' "$RELEASE_VERSION" |
    grep -Eq '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
then
    printf '%s\n' "invalid release version: $RELEASE_VERSION" >&2
    exit 1
fi

RELEASE_COMMIT=$(git rev-parse --verify 'HEAD^{commit}')
export RELEASE_COMMIT

check_release_candidate() {
    test "$(git branch --show-current)" = main
    test -z "$(git status --porcelain)"

    gowork_value=$(go env GOWORK)
    case "$gowork_value" in
        ''|off) ;;
        *)
            printf '%s\n' "active Go workspace: $gowork_value" >&2
            return 1
            ;;
    esac

    test -z "$(git ls-files go.work go.work.sum)"
    test ! -e vendor
    if grep -Eq '^[[:space:]]*replace([[:space:]]|\()' go.mod
    then
        printf '%s\n' 'go.mod contains a replacement' >&2
        return 1
    fi

    git fetch origin main --tags
    test "$RELEASE_COMMIT" = \
        "$(git rev-parse --verify 'refs/remotes/origin/main^{commit}')"

    if git show-ref --verify --quiet "refs/tags/$RELEASE_VERSION"
    then
        printf '%s\n' "local tag already exists: $RELEASE_VERSION" >&2
        return 1
    fi
    if test -n "$(
        git ls-remote --tags origin \
            "refs/tags/$RELEASE_VERSION" \
            "refs/tags/$RELEASE_VERSION^{}"
    )"
    then
        printf '%s\n' "remote tag already exists: $RELEASE_VERSION" >&2
        return 1
    fi
}

check_release_candidate

Do not continue unless the guard completes successfully. In particular, push the intended commit through the normal main branch workflow before release; the tag procedure is not a substitute for publishing the source commit.

Validate The Candidate

Confirm the module and root package metadata:

go list -m -f '{{.Path}} {{.GoVersion}}'
go list -f '{{.Name}} {{.ImportPath}}' .

The output must be:

gitea.maximumdirect.net/eric/promptkit 1.25.5
promptkit gitea.maximumdirect.net/eric/promptkit

Run the complete maintainer validation required by the development guide:

go test ./...
go test -race ./...
go vet ./...
go build ./...
go run ./examples/go-library/prepare

Check every tracked Go file. This command must produce no output:

unformatted=$(
    git ls-files '*.go' |
        while IFS= read -r go_file
        do
            gofmt -l "$go_file"
        done
)
test -z "$unformatted"

Follow every maintained Markdown link and confirm that its local or published target exists. Review the repository for generated binaries, test or coverage output, credentials, template residue, downloaded assets, and other files that do not belong in source control.

Recheck module and repository hygiene, whitespace, and the clean checkout:

test -z "$(git ls-files go.work go.work.sum)"
test ! -e vendor
if grep -Eq '^[[:space:]]*replace([[:space:]]|\()' go.mod
then
    printf '%s\n' 'go.mod contains a replacement' >&2
    exit 1
fi
git diff --check
test -z "$(git status --porcelain)"

Write The Release Note

Prepare a plain-text annotated-tag message outside the repository and export its path as RELEASE_NOTES_FILE. Use this form, replacing each summary with release-specific text; write None. when there are no public API changes or consumer actions:

Promptkit vMAJOR.MINOR.PATCH

Validated commit: full commit ID
Compatibility: compatibility summary
Public API changes: changes or None.
Consumer action: required action or None.

After writing it, require all release-note fields, the selected version, and the validated commit to be present:

: "${RELEASE_NOTES_FILE:?export the path to the release-note file}"
test -f "$RELEASE_NOTES_FILE"
test -s "$RELEASE_NOTES_FILE"
grep -F "Promptkit $RELEASE_VERSION" "$RELEASE_NOTES_FILE"
grep -F "Validated commit: $RELEASE_COMMIT" "$RELEASE_NOTES_FILE"
grep -F 'Compatibility:' "$RELEASE_NOTES_FILE"
grep -F 'Public API changes:' "$RELEASE_NOTES_FILE"
grep -F 'Consumer action:' "$RELEASE_NOTES_FILE"

Inspect the complete message and confirm that it accurately records the compatibility impact, public API changes, and required consumer action.

Create And Inspect The Tag

Run the candidate guard again immediately before tag creation. This ensures that validation or release-note preparation did not change the checkout and that the commit is still published and untagged:

check_release_candidate

Create the annotated tag from the prepared release note and bind it explicitly to the validated commit:

git tag --annotate "$RELEASE_VERSION" \
    --file "$RELEASE_NOTES_FILE" \
    "$RELEASE_COMMIT"

Inspect both the tag message and its source commit before publication:

test "$(git cat-file -t "refs/tags/$RELEASE_VERSION")" = tag
git show --no-patch --decorate "refs/tags/$RELEASE_VERSION"
test "$(
    git rev-parse --verify "refs/tags/$RELEASE_VERSION^{commit}"
)" = "$RELEASE_COMMIT"

If inspection finds an error, delete the unpublished local tag, correct the release note or candidate, and repeat the guards. Never move or recreate a tag that has been published.

Publish The Selected Tag

Push only the selected tag ref. Do not use git push --tags:

git push origin \
    "refs/tags/$RELEASE_VERSION:refs/tags/$RELEASE_VERSION"

Verify Publication

Compare the remote annotated-tag object with the local object, then compare the remote peeled source commit with the validated commit:

remote_tag=$(
    git ls-remote --tags origin "refs/tags/$RELEASE_VERSION" |
        awk 'NR == 1 { print $1 }'
)
remote_commit=$(
    git ls-remote --tags origin "refs/tags/$RELEASE_VERSION^{}" |
        awk 'NR == 1 { print $1 }'
)
test -n "$remote_tag"
test "$remote_tag" = \
    "$(git rev-parse --verify "refs/tags/$RELEASE_VERSION")"
test "$remote_commit" = "$RELEASE_COMMIT"

Finally, resolve the version as an ordinary Go module in a temporary module outside this repository and without a workspace or replacement:

resolution_dir=$(mktemp -d)
(
    trap 'rm -rf "$resolution_dir"' 0 1 2 15
    cd "$resolution_dir"
    GOWORK=off go mod init example.com/promptkit-release-check
    GOWORK=off go mod download \
        "gitea.maximumdirect.net/eric/promptkit@$RELEASE_VERSION"
    resolved_version=$(
        GOWORK=off go list -m -f '{{.Version}}' \
            "gitea.maximumdirect.net/eric/promptkit@$RELEASE_VERSION"
    )
    test "$resolved_version" = "$RELEASE_VERSION"
)

Promptkit must publish and verify the required version before Scriptorium or another consumer publishes a release that depends on it. This ordering does not replace the consumer project's own release procedure. Released consumers must select the published Promptkit tag through ordinary module resolution, without a workspace, replacement, vendored Promptkit source, or unpublished revision.

Policy Changes

Document and approve a durable policy change before introducing hosted automation, binary artifacts, or different release governance. Update this procedure in the same change so maintainers do not rely on hidden release requirements.