diff --git a/README.md b/README.md index 06f4222..9c6526d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # ragmesh [![CI](https://github.com/BaliDataMan/ragmesh/actions/workflows/ci.yml/badge.svg)](https://github.com/BaliDataMan/ragmesh/actions/workflows/ci.yml) +[![PyPI](https://img.shields.io/pypi/v/ragmesh.svg)](https://pypi.org/project/ragmesh/) ![Python 3.12](https://img.shields.io/badge/python-3.12-blue) ![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-informational) @@ -42,6 +43,12 @@ curl -X POST localhost:8080/chat \ That's the only secret required — retrieval (embeddings + FAISS) needs no API key at all (see `project-docs/adr/0003`). +Also published on [PyPI](https://pypi.org/project/ragmesh/) (`pip install ragmesh`) +— note that installing the package alone only gives you the `ragmesh` CLI and +library code; it still needs a running MCP retrieval server (`RAGMESH_MCP_SERVER_URL`) +to talk to, since retrieval is a real network service, not a bundled dependency. +`docker compose up` above is the fastest way to get both pieces running together. + ## Local development ```bash diff --git a/project-docs/architecture-rationale.md b/project-docs/architecture-rationale.md index 908c89b..f68a301 100644 --- a/project-docs/architecture-rationale.md +++ b/project-docs/architecture-rationale.md @@ -176,13 +176,27 @@ Each entry follows the same shape: **Decision → Why → Alternatives rejected --- -## 13. Explicitly deferred beyond v0.1 (and why) +## 13. Packaging and release: PyPI via tag-triggered Trusted Publishing + +**Decision:** Releases publish to PyPI (`pypi.org/project/ragmesh`) via a dedicated GitHub Actions workflow (`.github/workflows/publish.yml`), triggered only by pushing a `v*` git tag, authenticating to PyPI through OIDC Trusted Publishing rather than a long-lived API token stored as a repo secret. + +**Why:** `ci.yml` runs on every push/PR and must never have publish credentials in scope — a separate, tag-gated workflow keeps "run tests" and "ship a release" as distinct, deliberate actions. Trusted Publishing removes the need to generate, store, and eventually rotate a `PYPI_API_TOKEN` secret; GitHub mints a short-lived credential per run instead. The workflow also hard-fails if the tag's version doesn't match `pyproject.toml`, so a release can't accidentally publish the wrong version. + +**Alternatives rejected:** Publishing on every push to `main` (no meaningful "release" boundary, and would immediately fail on the second push since PyPI rejects re-uploading an existing version); a long-lived API token secret (works, but is a standing credential to leak or rotate — Trusted Publishing is now PyPI's own recommended approach for exactly this reason). + +**Trade-off accepted:** Releasing requires a manual, explicit sequence (bump version, commit, tag, push tag) rather than happening automatically — this is intentional friction, not an oversight; see `developer-guide.md`'s "Releasing a new version" section for the exact steps. + +**Revisit when:** N/A for this repo's scale — reconsider only if release cadence becomes frequent enough that the manual version-bump step becomes the actual bottleneck. + +--- + +## 14. Explicitly deferred beyond v0.1 (and why) Multi-agent supervision (v0.2), eval-gated CI with a golden-question regression gate (v0.3), and one-command AWS deploy via Terraform/CDK (v1.0) are all out of scope for this pass, even though they're part of the longer-term playbook. **Why defer:** The single biggest risk called out in the source planning docs is scoping too big and abandoning it before anything ships — the same pattern the user's own Substack has shown twice. Landing a working, tested, documented v0.1 and pinning it is worth more than an ambitious-but-unfinished v0.3. Each later milestone can be scoped and planned fresh once v0.1 is actually running, rather than speculatively designed now against assumptions that may not hold once real code exists. -**Revisit when:** v0.1 is shipped, tagged, and pinned. Not before. +**Revisit when:** v0.1 is shipped, tagged, and pinned — **done**: tagged `v0.1.0`, published to PyPI. v0.2 can now be scoped for real. --- diff --git a/project-docs/developer-guide.md b/project-docs/developer-guide.md index bd37abb..904c664 100644 --- a/project-docs/developer-guide.md +++ b/project-docs/developer-guide.md @@ -16,6 +16,7 @@ individual concepts (pydantic-settings, MCP transports, LangGraph, etc.), see - [Editing the sample corpus](#editing-the-sample-corpus) - [5. Run the CLI directly (no API server)](#5-run-the-cli-directly-no-api-server) - [6. The opt-in integration test](#6-the-opt-in-integration-test) +- [7. Releasing a new version](#7-releasing-a-new-version) - [Project layout](#project-layout) - [How a request flows end to end](#how-a-request-flows-end-to-end) - [Troubleshooting](#troubleshooting) @@ -148,6 +149,63 @@ then tears the stack down. It needs Docker and a real LLM key in `.env` — it i **not** run in CI (see `project-docs/architecture-rationale.md` #11) and should be run manually before tagging a release. +## 7. Releasing a new version + +Releases publish to [PyPI](https://pypi.org/project/ragmesh/) via a tag-triggered +GitHub Actions workflow (`.github/workflows/publish.yml`), authenticated with PyPI +Trusted Publishing (OIDC) — no API token secret involved. See +`architecture-rationale.md` #13 for why it's shaped this way. + +**One-time setup (already done for this repo, documented here for reference or a +fork):** +1. On [pypi.org's Trusted Publishing settings](https://pypi.org/manage/project/ragmesh/settings/publishing/) + for this project, add a trusted publisher: owner `BaliDataMan`, repository + `ragmesh`, workflow filename `publish.yml`, environment name `pypi`. +2. On GitHub, under repo **Settings → Environments**, create an environment named + `pypi` (matches the workflow's `environment: pypi`). Adding a required reviewer + here makes every publish need a manual approval click before it runs — a good + safety net, since a bad publish to PyPI can never be undone or overwritten. + +**Every release, in order:** + +```bash +# 1. Bump the version — pick the next real semver, e.g.: +# sed -i '' 's/^version = ".*"/version = "0.2.0"/' pyproject.toml +# (or edit pyproject.toml's `version` field directly) + +# 2. Commit the bump (through the normal branch -> PR -> merge flow, same as any +# other change — do not push a version bump straight to main) +git checkout -b chore/release-0.2.0 +git add pyproject.toml +git commit -m "Bump version to 0.2.0 for release" +git push -u origin chore/release-0.2.0 +# open a PR, merge it, then sync local main: +git checkout main +git fetch origin +git merge --ff-only origin/main + +# 3. Tag the merged commit and push the tag — this is what actually triggers +# the publish workflow +git tag v0.2.0 +git push origin v0.2.0 + +# 4. If the `pypi` environment has a required reviewer, approve the run under the +# repo's Actions tab (Review deployments -> pypi -> Approve). + +# 5. Verify: check https://pypi.org/project/ragmesh/#history for the new version. +``` + +**Non-negotiable rule:** the git tag (`vX.Y.Z`) must exactly match `pyproject.toml`'s +`version` field (without the `v` prefix) — the workflow checks this and fails the +run otherwise, on purpose, so a mismatched release can't silently ship. + +**Also update before/alongside each release:** +- This developer guide and `architecture-rationale.md` if the release changes + anything they describe. +- `README.md`'s badges/version references if applicable. +- Any deferred-scope note (see `architecture-rationale.md` #14) whose "revisit when" + condition the release just satisfied. + ## Project layout ```