From b83765d0c92366a8b8a682e5ef3a7bceeb858f93 Mon Sep 17 00:00:00 2001 From: MK Date: Thu, 11 Jun 2026 13:55:14 -0400 Subject: [PATCH] feat(release): publish forge-core, forge-skills, forge-plugins as Go libraries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit External consumers (e.g. the Initializ platform) need to embed the agent runtime as a library, not shell out to the CLI: go get github.com/initializ/forge/forge-core@v0.15.0 Today that fails. The library modules cross-reference each other via `replace ../forge-skills` (and `v0.0.0` placeholder require lines) which is correct for workspace-mode local dev but unusable for a Go module proxy fetch. The Initializ build sees `replace ../forge-skills`, can't find the local path, and fails. Path-prefixed Git tags are the standard Go multi-module convention for this — `forge-skills/vX.Y.Z`, `forge-core/vX.Y.Z`, `forge-plugins/vX.Y.Z` per release. But pointing those tags directly at the binary-release commit would publish the broken go.mod files verbatim. Need a tagged tree where `replace` is dropped and `require` references real sibling versions. This commit adds the "ephemeral release commit" pattern: 1. scripts/release/tag-libraries.sh — runs `go mod edit` on a detached worktree of the binary-release tag to drop `replace` directives and bump `require` lines to the new version. Commits the result as a throwaway commit (one parent = the binary tag, never merged to main). Tags forge-core/vX.Y.Z and forge-plugins/vX.Y.Z at that ephemeral commit; tags forge-skills/vX.Y.Z at the binary commit directly (leaf module, no rewrite needed). Pushes all three. 2. .github/workflows/release.yaml — new `library-tags` job that runs the script after goreleaser succeeds. Uses GITHUB_TOKEN and the workflow's `contents: write` permission to push tags. 3. docs/reference/library-modules.md — full reference covering the three importable modules + their roles, the path-prefixed tag scheme, the ephemeral-commit mechanism with an ASCII diagram, the versioning policy (unified during v0.x), the currently-stable public API surface, and what workspace-mode dev still looks like (unchanged — `replace` stays on main). 4. README.md — Library Modules row in the Operations doc table. 5. CONTRIBUTING.md — Project Structure table gains a "Published as library?" column and a sentence pointing at the new doc. Main is untouched by every release. The workspace-mode `replace` directives stay; main never has go.mod files with cross-module `require vX.Y.Z` lines that would break local dev (workspace mode can't satisfy `require X v0.15.0` from a local source tree). Script supports --dry-run (verified) and --no-push for safe manual testing. Refuses to overwrite existing path-prefixed tags so a hotfix re-release can't accidentally clobber a published version. Verified locally: - bash -n syntax check passes - --dry-run output matches expectations against v0.14.1 - `go mod edit` rewrites produce clean go.mod files (replace dropped, require bumped to v0.14.1) Next release (v0.15.0) will be the first one that publishes library tags. Initializ can then `go get github.com/initializ/forge/ forge-core@v0.15.0` and import the runtime directly. --- .github/workflows/release.yaml | 34 +++++ CONTRIBUTING.md | 15 ++- README.md | 1 + docs/reference/library-modules.md | 165 +++++++++++++++++++++++ scripts/release/tag-libraries.sh | 211 ++++++++++++++++++++++++++++++ 5 files changed, 420 insertions(+), 6 deletions(-) create mode 100644 docs/reference/library-modules.md create mode 100755 scripts/release/tag-libraries.sh diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index c0c5b64..18d158a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -30,6 +30,40 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} + # Path-prefixed Git tags so external consumers (e.g. the Initializ + # platform) can `go get github.com/initializ/forge/forge-{skills,core, + # plugins}@`. See scripts/release/tag-libraries.sh and + # docs/contributing/release-process.md for the rationale + mechanism. + library-tags: + name: Library module tags + runs-on: ubuntu-latest + needs: release + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + # checkout@v4 fetches with a token that only has read access by + # default for tag pushes; the workflow's `permissions: + # contents: write` block above gives the implicit + # GITHUB_TOKEN write scope, but the checkout step still needs + # to be configured to use it for `git push`. + token: ${{ secrets.GITHUB_TOKEN }} + persist-credentials: true + + - uses: actions/setup-go@v5 + with: + go-version: "1.25" + + - name: Tag library modules + run: ./scripts/release/tag-libraries.sh "${GITHUB_REF_NAME}" + env: + # The script runs `git push`; ensure the bot identity is set + # so the ephemeral release commit has a sensible author. + GIT_AUTHOR_NAME: forge release bot + GIT_AUTHOR_EMAIL: release-bot@initializ.ai + GIT_COMMITTER_NAME: forge release bot + GIT_COMMITTER_EMAIL: release-bot@initializ.ai + docker: name: Docker Image runs-on: ubuntu-latest diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 31712d1..15ef1be 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,12 +41,15 @@ cd forge-skills && go test ./... && cd .. Forge is a multi-module Go workspace: -| Module | Purpose | -|--------|---------| -| `forge-core/` | Core library — registry, tools, security, channels, LLM | -| `forge-cli/` | CLI commands, TUI wizard, runtime | -| `forge-plugins/` | Channel plugins (Telegram, Slack), markdown converter | -| `forge-skills/` | Skill parser, compiler, analyzer, trust, embedded skills | +| Module | Purpose | Published as library? | +|--------|---------|-----------------------| +| `forge-core/` | Core library — registry, tools, security, channels, LLM | yes — `github.com/initializ/forge/forge-core` | +| `forge-cli/` | CLI commands, TUI wizard, runtime | no — CLI binary only | +| `forge-plugins/` | Channel plugins (Telegram, Slack), markdown converter | yes — `github.com/initializ/forge/forge-plugins` | +| `forge-skills/` | Skill parser, compiler, analyzer, trust, embedded skills | yes — `github.com/initializ/forge/forge-skills` | +| `forge-ui/` | Web UI (Skill Builder, dashboard) | no — UI binary only | + +The three "yes" modules ship as path-prefixed Git tags (`forge-core/vX.Y.Z` etc.) on every Forge release so host platforms can embed the runtime as a library. See [Library Modules](docs/reference/library-modules.md) for the release pipeline, tag scheme, and how the workspace `replace` directives are stripped at tag time. Skills live in two locations: diff --git a/README.md b/README.md index 2ee80ed..d27bb02 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ You write a `SKILL.md`. Forge compiles it into a secure, runnable agent with egr | [Dashboard](docs/reference/web-dashboard.md) | Web UI features and architecture | | [Deployment](docs/deployment/kubernetes.md) | Container packaging, Kubernetes, air-gap | | [Hooks](docs/core-concepts/hooks.md) | Agent loop hook system | +| [Library Modules](docs/reference/library-modules.md) | Import `forge-core`, `forge-skills`, `forge-plugins` as Go libraries — tag scheme, release pipeline, embedder API | | [Plugins](docs/reference/framework-plugins.md) | Framework plugin system | | [Command Integration](docs/reference/command-integration.md) | Initializ Command platform guide | diff --git a/docs/reference/library-modules.md b/docs/reference/library-modules.md new file mode 100644 index 0000000..f788292 --- /dev/null +++ b/docs/reference/library-modules.md @@ -0,0 +1,165 @@ +--- +title: "Library Modules" +description: "Import forge-core, forge-skills, and forge-plugins as Go library packages — for embedding Forge in a host application." +order: 12 +--- + +## Library Modules + +Forge ships as a single CLI binary for most users (`forge build`, `forge run`, `forge package`), but the underlying Go modules are also published independently so a host platform can embed Forge's agent runtime as a library — `go get github.com/initializ/forge/forge-core@` instead of shelling out to `forge run`. + +This page documents: +- the three importable modules and their roles +- the path-prefixed Git tag scheme used to release them +- the release pipeline that produces those tags +- the cross-module dependency graph and what's in `go.mod` at a tagged version vs in the monorepo workspace + +For the CLI workflow, see [CLI Reference](cli-reference.md). For embedding patterns, see the host platform's own integration docs. + +## Importable modules + +| Module | Import path | Role | Internal deps | +|---|---|---|---| +| **`forge-skills`** | `github.com/initializ/forge/forge-skills` | SKILL.md parser, skill registry, bin classifier, security policy analyzer. The library piece any skill-aware tool needs. | none | +| **`forge-core`** | `github.com/initializ/forge/forge-core` | Runtime engine (LLM loop, guardrails, audit, hooks), tool registry, MCP client, channel interfaces, A2A types, security subsystem (auth chain, egress enforcer, platform policy), memory, scheduler, secrets, validation. | `forge-skills` | +| **`forge-plugins`** | `github.com/initializ/forge/forge-plugins` | Channel adapters (Slack, Telegram, MS Teams), Markdown converter. Optional — only needed if you want Forge's bundled channel adapters in your host. | `forge-core` | + +`forge-cli` and `forge-ui` are CLI / web-UI consumers and are **not** published as library modules. Their internals are subject to change without notice. + +## Releasing — path-prefixed tags + +Each library module is published as its own Go module, tagged separately, on every Forge release. The tag format follows the [Go multi-module repo convention](https://go.dev/ref/mod#vcs-version): + +``` +forge-skills/vX.Y.Z +forge-core/vX.Y.Z +forge-plugins/vX.Y.Z +``` + +The CLI binary continues to use the flat `vX.Y.Z` tag (consumed by goreleaser). All three library tags share the same version as the CLI for a given release — see [Versioning](#versioning). + +Consumer side, this means: + +```sh +# In the host platform's go.mod: +go get github.com/initializ/forge/forge-core@v0.15.0 + +# Equivalent to: +require github.com/initializ/forge/forge-core v0.15.0 +``` + +Go's module proxy resolves `forge-core@v0.15.0` to the tag `forge-core/v0.15.0`. The flat `v0.15.0` tag is used by `forge-cli`'s release artifacts but never by `go get` against the library modules. + +## How the release pipeline produces those tags + +The library tags can't simply point at the same commit as the binary tag, because: + +- The `go.mod` files in the **monorepo workspace** use `replace github.com/initializ/forge/forge-skills => ../forge-skills` and `require github.com/initializ/forge/forge-skills v0.0.0` (a placeholder). This is correct for `go.work`-based local development — workspace mode resolves the cross-module references to the local source tree. +- A **published Go module** can't rely on local `replace` paths. An external consumer fetching `forge-core@v0.15.0` would see `replace ../forge-skills` and the build would fail with "path does not exist." + +The fix is an **ephemeral release commit**: a single commit, never merged to `main`, whose tree contains `go.mod` files with the `replace` directives dropped and the `require` lines bumped to point at real sibling versions. The library tags reference this commit. + +``` +main ◀── workspace-mode go.mod files + │ + ├─ ... (regular commits) + │ + └─ tagged: v0.15.0 ◀── flat binary-release tag (consumed by goreleaser) + │ + │ (ephemeral commit, only referenced by tags) + └── release-libs(go.mod rewrites) ◀── forge-core/v0.15.0 + forge-plugins/v0.15.0 + +main itself is unchanged after the release. The release commit lives only +as a tag target and never enters the working history. +``` + +`forge-skills/v0.15.0` points at the binary-release commit directly — `forge-skills` has no internal deps so no `go.mod` rewrite is needed. + +The rewrite + tagging is automated by [`scripts/release/tag-libraries.sh`](https://github.com/initializ/forge/blob/main/scripts/release/tag-libraries.sh), called from the `library-tags` job in [`.github/workflows/release.yaml`](https://github.com/initializ/forge/blob/main/.github/workflows/release.yaml). + +### What the script does + +1. Verifies the binary-release tag (`vX.Y.Z`) exists locally. +2. Creates a temporary git worktree at that tag (detached HEAD). +3. Runs `go mod edit` to: + - Drop `replace github.com/initializ/forge/forge-skills` from `forge-core/go.mod` + - Bump `require github.com/initializ/forge/forge-skills` to the new version + - Drop `replace github.com/initializ/forge/forge-core` from `forge-plugins/go.mod` + - Bump `require github.com/initializ/forge/forge-core` to the new version +4. Commits the rewritten `go.mod` files (authored by the `forge release bot` identity). +5. Tags `forge-skills/vX.Y.Z` at the original binary-release commit. +6. Tags `forge-core/vX.Y.Z` and `forge-plugins/vX.Y.Z` at the ephemeral commit. +7. Pushes all three tags. + +The script supports `--dry-run` (print the actions that would happen) and `--no-push` (tag locally without pushing). See the script header for the full usage. + +### Running it manually + +If a release tag was pushed without the library tags (e.g. a hotfix where the workflow was skipped), run the script directly from a checkout with push access: + +```sh +git fetch --tags +./scripts/release/tag-libraries.sh v0.15.0 +``` + +Refusing to run if any library tag for the version already exists is intentional — re-running on a duplicate is a hard error. Delete the offending tag first (`git tag -d ` locally + `git push origin :refs/tags/` remotely) before re-running. + +## Versioning + +Forge follows **unified versioning** during the v0.x line: every Forge release ships all three library modules at the same version, even when only one of them changed. This keeps the operational model simple and matches how the workspace is developed (changes routinely span modules). + +Semantic versioning applies the way you'd expect: + +- **v0.x.y** — no API stability promise. Public symbols may change between minor versions. Breaking changes are noted in `CHANGELOG.md`. +- **v1.0 onward** (when adopted) — semver applies to every package not under an `internal/` directory. Breaking changes require a major-version bump per Go module conventions (the import path changes to `github.com/initializ/forge/forge-core/v2`). + +If you depend on the library modules from a host platform, **pin to an exact version** in your `go.mod` (`v0.15.0`, not `v0`) until the v1.0 promise is in effect. + +## Public API surface + +The convention used inside the library modules: + +- Anything under a directory named `internal/` is private — Go's tooling enforces that only paths sharing a common ancestor with the `internal/` directory can import from it. The host platform cannot import from `internal/` at all. +- Anything else is considered public and follows the versioning rules above. + +Today the library modules **do not yet have `internal/` markers** for the implementation details that aren't intended to be embedder-facing. Tightening that is on the roadmap (issue link TBD). Until then, treat `forge-core`'s subpackages as having an unstable surface unless the [`docs/`](https://github.com/initializ/forge/tree/main/docs) explicitly call them out as supported. + +The currently-stable public entry points for embedders are: + +- `forge-core/runtime` — `NewLLMExecutor`, `LLMExecutorConfig`, `Execute`, `Hooks` +- `forge-core/llm` — `Client`, `ChatRequest`, `ChatResponse`, the provider abstractions +- `forge-core/tools` — `Registry`, `Tool` interfaces +- `forge-core/a2a` — wire types for the A2A 0.3.0 protocol +- `forge-core/types` — `ForgeConfig` (the `forge.yaml` shape) +- `forge-skills/contract` — `SkillEntry`, `SkillRequirements`, the SKILL.md shape +- `forge-skills/parser` — `ParseFileWithMetadata` +- `forge-skills/registry` — `Default` (the embedded bin registry) +- `forge-skills/analyzer` — `SecurityPolicy`, `AnalyzeSkill*`, `CheckPolicy` + +For everything else, look for usage in `forge-cli` (which exercises the same surface as a reference embedder) before depending on it. + +## Workspace-mode dev still works the same way + +The monorepo continues to use `go.work` with the five-module `use (...)` block. Local development is unchanged: + +- `go.mod` files keep their `replace ../forge-skills` and `replace ../forge-core` directives. +- `go build` from any module uses the workspace overrides, not whatever's in `require`. +- The release pipeline only rewrites `go.mod` for the ephemeral release commit; main never sees the rewrite. + +What this means in practice: **don't bump the `v0.0.0` placeholder require lines on main**. The release script does that surgically, in the ephemeral commit, only at tag time. Editing them on main breaks workspace-mode dev because the workspace can't resolve `require X v0.15.0` when X is supposed to come from a local path. + +If you do need to bump the placeholder (e.g. to test a downstream consumer against an unreleased state), use a feature branch and revert before merging. + +## Roadmap + +- **API stability markers**: introduce `internal/` directories under `forge-core` and `forge-skills` for the implementation details that are not intended for embedders. Track in a follow-up issue. +- **Smoke-test CI**: a job that, after the release tags are pushed, scaffolds a tiny external module and runs `go get github.com/initializ/forge/forge-core@` + a "compile a minimal agent" check. Catches `replace`-leak / unresolved-version bugs the same day they happen, not when a consumer hits them. +- **Independent versioning**: opt-in once one of the libraries justifies a release cadence different from the CLI. Today unified versioning is simpler. + +## Source files + +- `scripts/release/tag-libraries.sh` — the rewrite-and-tag script +- `.github/workflows/release.yaml` — the `library-tags` job that runs the script +- `forge-skills/go.mod`, `forge-core/go.mod`, `forge-plugins/go.mod` — module declarations (with workspace-mode `replace` directives) +- `go.work` — the monorepo workspace declaration diff --git a/scripts/release/tag-libraries.sh b/scripts/release/tag-libraries.sh new file mode 100755 index 0000000..ee27683 --- /dev/null +++ b/scripts/release/tag-libraries.sh @@ -0,0 +1,211 @@ +#!/usr/bin/env bash +# +# tag-libraries.sh — push path-prefixed Git tags for the three library +# modules (`forge-skills`, `forge-core`, `forge-plugins`) so external +# consumers (e.g. the Initializ platform) can `go get +# github.com/initializ/forge/@`. +# +# Why this exists: in development, the library modules cross-reference +# each other via `replace ../` directives in their go.mod files +# (and `v0.0.0` placeholder require lines). That's how a `go.work` +# workspace handles the multi-module repo locally. When an external +# consumer fetches a tagged version via the Go module proxy, the +# `replace` directive points at a non-existent local path and the +# `v0.0.0` require can't resolve. The dependency graph (forge-skills <- +# forge-core <- forge-plugins) needs to be expressed with real version +# constraints in the published `go.mod` files. +# +# Strategy: leave main alone. For each release, build an *ephemeral* +# git commit on top of the binary-release tag (`vX.Y.Z`) whose tree +# carries `go.mod` files rewritten with `replace` directives dropped +# and `require` lines bumped to the new version. Path-prefixed tags +# (`forge-core/vX.Y.Z`, `forge-plugins/vX.Y.Z`) point at this +# ephemeral commit; `forge-skills/vX.Y.Z` points at the original +# binary-tag commit (it has no internal deps so no rewrite needed). +# Main never sees the rewritten go.mod files — workspace-mode dev keeps +# working unchanged. +# +# Usage: +# scripts/release/tag-libraries.sh v0.15.0 +# scripts/release/tag-libraries.sh v0.15.0 --dry-run +# scripts/release/tag-libraries.sh v0.15.0 --no-push # tag locally, don't push +# +# Assumptions: +# - The script is invoked from the repo root. +# - The binary-release tag `vX.Y.Z` already exists locally (annotated +# or lightweight). The release.yaml workflow ensures this — it's +# the trigger. +# - `go` is on PATH (used for `go mod edit`). +# - For push mode: the caller has `git push` credentials. + +set -euo pipefail + +usage() { + cat < [--dry-run] [--no-push] + +Arguments: + The release version, e.g. v0.15.0. Must match a tag that + already exists in this repo. + +Flags: + --dry-run Show what would happen without creating any commits, tags, + or pushes. + --no-push Create the tags locally but don't push them to origin. + +Library modules tagged (in dependency order): + forge-skills/ no internal deps; tags at the binary-release commit + forge-core/ depends on forge-skills; tags at an ephemeral commit + forge-plugins/ depends on forge-core; tags at an ephemeral commit +EOF + exit 1 +} + +# ─── arg parsing ─────────────────────────────────────────────────── +if [[ $# -lt 1 ]]; then + usage +fi + +VERSION="$1" +shift + +DRY_RUN=0 +PUSH=1 +for arg in "$@"; do + case "$arg" in + --dry-run) DRY_RUN=1 ;; + --no-push) PUSH=0 ;; + *) echo "unknown flag: $arg" >&2; usage ;; + esac +done + +if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then + echo "error: version must match vMAJOR.MINOR.PATCH (with optional -prerelease); got: $VERSION" >&2 + exit 1 +fi + +# ─── helpers ─────────────────────────────────────────────────────── +log() { printf "[tag-libraries] %s\n" "$*"; } +run() { + if [[ $DRY_RUN -eq 1 ]]; then + printf "[tag-libraries dry-run] %s\n" "$*" + else + eval "$@" + fi +} + +# ─── preflight ───────────────────────────────────────────────────── +REPO_ROOT="$(git rev-parse --show-toplevel)" +cd "$REPO_ROOT" + +if ! git rev-parse --verify "refs/tags/$VERSION" >/dev/null 2>&1; then + echo "error: tag $VERSION does not exist locally. Fetch it first or push the binary release tag." >&2 + exit 1 +fi + +BINARY_TAG_SHA="$(git rev-parse "refs/tags/$VERSION^{commit}")" +log "binary-release tag $VERSION → commit $BINARY_TAG_SHA" + +for prefix in forge-skills forge-core forge-plugins; do + if git rev-parse --verify "refs/tags/$prefix/$VERSION" >/dev/null 2>&1; then + echo "error: tag $prefix/$VERSION already exists. Delete it before re-running, or pick a different version." >&2 + exit 1 + fi +done + +# Make sure we're not on a feature branch with dirty state that would +# get caught up in the worktree we create. +if ! git diff-index --quiet HEAD --; then + echo "error: working tree has uncommitted changes; commit, stash, or clean before tagging." >&2 + exit 1 +fi + +# ─── build the ephemeral commit ──────────────────────────────────── +# Use a temporary git worktree so we can edit go.mod without touching +# the active checkout (avoids confusing the human running this). +TMP_WORKTREE="$(mktemp -d -t forge-libtag-XXXXXX)" +trap "git worktree remove --force '$TMP_WORKTREE' >/dev/null 2>&1 || true" EXIT + +log "preparing ephemeral commit in $TMP_WORKTREE" +run "git worktree add --detach '$TMP_WORKTREE' '$BINARY_TAG_SHA'" + +# Rewrite forge-core/go.mod: drop the local-path replace for +# forge-skills, replace the v0.0.0 placeholder require with the real +# version about to be tagged. +log "rewriting forge-core/go.mod (drop replace, require forge-skills@$VERSION)" +run "(cd '$TMP_WORKTREE/forge-core' && \ + go mod edit -dropreplace=github.com/initializ/forge/forge-skills && \ + go mod edit -require=github.com/initializ/forge/forge-skills@$VERSION)" + +# Same surgery on forge-plugins/go.mod for forge-core. +log "rewriting forge-plugins/go.mod (drop replace, require forge-core@$VERSION)" +run "(cd '$TMP_WORKTREE/forge-plugins' && \ + go mod edit -dropreplace=github.com/initializ/forge/forge-core && \ + go mod edit -require=github.com/initializ/forge/forge-core@$VERSION)" + +# Commit the rewrites in the detached worktree. The commit's parent is +# the binary-release tag so consumers fetching by tag see the full +# history. +log "creating ephemeral release commit" +run "(cd '$TMP_WORKTREE' && \ + git add forge-core/go.mod forge-plugins/go.mod && \ + git -c user.name='forge release bot' -c user.email='release-bot@initializ.ai' \ + commit -m 'release(libs): pin cross-module require lines for $VERSION + +Tags forge-core/$VERSION and forge-plugins/$VERSION point at this +commit. forge-skills/$VERSION points at the parent (the $VERSION +binary-release commit). Main is unaffected; the workspace-mode +go.mod files there continue to use replace directives. + +Generated by scripts/release/tag-libraries.sh.')" + +if [[ $DRY_RUN -eq 1 ]]; then + log "dry-run: would tag forge-skills/$VERSION at $BINARY_TAG_SHA" + log "dry-run: would tag forge-core/$VERSION and forge-plugins/$VERSION at the ephemeral commit" + [[ $PUSH -eq 1 ]] && log "dry-run: would push all three tags to origin" + exit 0 +fi + +EPHEMERAL_SHA="$(git -C "$TMP_WORKTREE" rev-parse HEAD)" +log "ephemeral commit SHA: $EPHEMERAL_SHA" + +# ─── create the tags ─────────────────────────────────────────────── +# forge-skills tags at the binary-release commit — no internal deps, +# no rewrite needed. +log "tagging forge-skills/$VERSION at $BINARY_TAG_SHA" +git tag -a "forge-skills/$VERSION" "$BINARY_TAG_SHA" \ + -m "forge-skills $VERSION + +Released as part of forge $VERSION. See CHANGELOG.md for details." + +# forge-core + forge-plugins tag at the ephemeral commit. +log "tagging forge-core/$VERSION at $EPHEMERAL_SHA" +git tag -a "forge-core/$VERSION" "$EPHEMERAL_SHA" \ + -m "forge-core $VERSION + +Released as part of forge $VERSION. See CHANGELOG.md for details. +Cross-module require pinned to forge-skills@$VERSION." + +log "tagging forge-plugins/$VERSION at $EPHEMERAL_SHA" +git tag -a "forge-plugins/$VERSION" "$EPHEMERAL_SHA" \ + -m "forge-plugins $VERSION + +Released as part of forge $VERSION. See CHANGELOG.md for details. +Cross-module require pinned to forge-core@$VERSION." + +# ─── push ────────────────────────────────────────────────────────── +if [[ $PUSH -eq 1 ]]; then + log "pushing library tags to origin" + git push origin \ + "refs/tags/forge-skills/$VERSION" \ + "refs/tags/forge-core/$VERSION" \ + "refs/tags/forge-plugins/$VERSION" +else + log "skipping push (--no-push). Push manually with:" + log " git push origin refs/tags/forge-skills/$VERSION refs/tags/forge-core/$VERSION refs/tags/forge-plugins/$VERSION" +fi + +log "done. External consumers can now:" +log " go get github.com/initializ/forge/forge-skills@$VERSION" +log " go get github.com/initializ/forge/forge-core@$VERSION" +log " go get github.com/initializ/forge/forge-plugins@$VERSION"