feat(standards): MR-based tag strategy + repository inventory (registry://repos)
Versioning standard now documents the MR flow: bump pyproject.toml/VERSION and promote CHANGELOG in the MR (referencing the version/tag, not a commit id), merge/squash, then tag the resulting commit. Adds the agent-assisted tagging procedure (wait for the go-ahead, find the version-bump commit, tag only it, ask the user to push) to both the reference page and the releasing wiki runbook. Adds the repo inventory (src/repos.json -> mcp/repos.json), the registry://repos resource + list_repos tool, the 15_repository_inventory lib page, and two MCP prompts: compliance (remediate repos) and release (agent tagging).
This commit is contained in:
@@ -27,8 +27,9 @@ Rules:
|
||||
2. **Tag = version.** The git tag is bare SemVer (`0.0.6`), no `v` prefix, and
|
||||
it MUST match the version file byte-for-byte. The Drone guard fails the
|
||||
build otherwise — this is what stops a tag from silently publishing nothing.
|
||||
3. **A release is a tag.** Bump the version file, promote the changelog, commit,
|
||||
tag, push. Everything else is automated by Drone.
|
||||
3. **A release is a tag, created after the change is merged.** The version bump
|
||||
and the changelog promotion travel inside the merge request (MR); the tag is
|
||||
added on the merged commit and pushed. See [Tagging strategy](#tagging-strategy-mr-based).
|
||||
4. **Never reuse a tag.** If a release is wrong, cut a new patch version.
|
||||
|
||||
### Pre-1.0 (0.y.z)
|
||||
@@ -88,17 +89,54 @@ Conventions:
|
||||
|
||||
---
|
||||
|
||||
## Tagging strategy (MR-based)
|
||||
|
||||
The release version is known while the change is still a merge request, so the
|
||||
bump travels with the change and only the tag is added afterwards:
|
||||
|
||||
1. **In the MR** — set the intended release version: `version` in
|
||||
`pyproject.toml` (or the `VERSION` file) to `x.y.z`, and promote the
|
||||
changelog (below). Changelog entries and link references name the
|
||||
**version/tag**, never a commit id.
|
||||
2. **Merge or squash** the MR into `main`.
|
||||
3. **Create the tag** `x.y.z` on the resulting commit (the merge/squash commit).
|
||||
4. **Push the tag** (`git push origin x.y.z`) — Drone builds the release.
|
||||
|
||||
Because the version bump is inside the tagged commit, the guard's
|
||||
`tag == version` check can only fail if the wrong commit was tagged.
|
||||
|
||||
### Agent-assisted tagging
|
||||
|
||||
The agent neither authors nor merges the MR, and never tags before the merge.
|
||||
After the user confirms the MR is merged/squashed and says to proceed:
|
||||
|
||||
1. **Wait** for that explicit go-ahead.
|
||||
2. Find the commit that introduced the version bump:
|
||||
- packages:
|
||||
`git log -S 'version = "x.y.z"' -n 1 --format='%H %s' -- pyproject.toml`
|
||||
- services:
|
||||
`git log -S 'x.y.z' -n 1 --format='%H %s' -- VERSION`
|
||||
3. Confirm that commit's version file equals `x.y.z`, then tag **only** that
|
||||
commit, lightweight and bare: `git tag x.y.z <sha>`.
|
||||
4. **Do not push.** Ask the user to push the tag: `git push origin x.y.z`.
|
||||
|
||||
---
|
||||
|
||||
## Release checklist
|
||||
|
||||
1. Confirm the quality gate is green on `main`.
|
||||
2. Move every `[Unreleased]` entry under the new version heading with today's
|
||||
date (`## [x.y.z] - YYYY-MM-DD`); restore an empty `[Unreleased]` on top.
|
||||
3. Add the two compare-link references at the bottom of the changelog.
|
||||
4. Set the version file: `pyproject.toml` (`version = "x.y.z"`) or `VERSION`.
|
||||
5. Commit: `chore(release): x.y.z`.
|
||||
6. Tag: `git tag x.y.z` and push the tag (`git push origin x.y.z`).
|
||||
7. Watch Drone: Type A publishes the wheel/sdist, Type B builds and deploys the
|
||||
image. Both first assert `tag == version`.
|
||||
1. Confirm the quality gate is green and `main` is up to date.
|
||||
2. In the MR: move every `[Unreleased]` entry under the new version heading with
|
||||
the target date (`## [x.y.z] - YYYY-MM-DD`); restore an empty `[Unreleased]`
|
||||
on top.
|
||||
3. In the MR: add the two compare-link references at the bottom of the
|
||||
changelog, naming the version/tag (never a commit id).
|
||||
4. In the MR: set the version file — `pyproject.toml` (`version = "x.y.z"`) or
|
||||
`VERSION` — to the intended release version.
|
||||
5. Merge or squash the MR into `main`.
|
||||
6. Create the tag on the resulting commit: lightweight `git tag x.y.z`.
|
||||
7. Push the tag (`git push origin x.y.z`). Watch Drone: Type A publishes the
|
||||
wheel/sdist, Type B builds and deploys the image; both first assert
|
||||
`tag == version`.
|
||||
8. Do not reuse the tag if the run fails — fix and cut `x.y.(z+1)`.
|
||||
|
||||
---
|
||||
@@ -116,4 +154,4 @@ Drone guard turns that silent no-op into a hard failure.
|
||||
|
||||
## Related
|
||||
|
||||
- [10 – Drone Pipelines](10_drone_pipelines.md) · [01 – Build Recipe](01_build_recipe.md) · [13 – Library Registry](13_library_registry.md)
|
||||
- [10 – Drone Pipelines](10_drone_pipelines.md) · [15 – Repository Inventory](15_repository_inventory.md) · [01 – Build Recipe](01_build_recipe.md) · [13 – Library Registry](13_library_registry.md)
|
||||
|
||||
74
standards/src/lib/15_repository_inventory.md
Normal file
74
standards/src/lib/15_repository_inventory.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Repository Inventory
|
||||
|
||||
## Summary
|
||||
|
||||
The canonical list of Aetos repositories covered by the CI and versioning
|
||||
standards, with the artifact each one produces, its version source, and its
|
||||
Drone shape. Every entry maps to the contract in
|
||||
[11 – Versioning & Changelog](11_versioning_and_changelog.md) and the two CI
|
||||
files in [10 – Drone Pipelines](10_drone_pipelines.md).
|
||||
|
||||
The same data is published machine-readably as the `registry://repos` MCP
|
||||
resource (and the `list_repos` tool), sourced from `standards/src/repos.json`.
|
||||
Paths are relative to the `aetos/` workspace root.
|
||||
|
||||
---
|
||||
|
||||
## Packages (Type A)
|
||||
|
||||
A wheel + sdist published to the private index. Version source is
|
||||
`pyproject.toml`; Drone file is `python-lib.yaml` (tag-triggered).
|
||||
|
||||
| Repo | Path | Docs | MCP server |
|
||||
|------|------|------|------------|
|
||||
| `dagpipe` | `dagpipe` | lib, wiki | `dagpipe:8006` |
|
||||
| `doc-forge` | `doc-forge` | lib, wiki | `docforge:8001` |
|
||||
| `hexa` | `hexa` | lib, wiki | `hexa:8008` |
|
||||
| `mail-intake` | `mail-intake` | lib, wiki | `mail_intake:8002` |
|
||||
| `mongo-ops` | `mongo-ops` | lib, wiki | `mongo_ops:8007` |
|
||||
| `omniread` | `omniread` | lib, wiki | `omniread:8003` |
|
||||
| `openapi-first` | `openapi-first` | lib, wiki | `openapi_first:8004` |
|
||||
| `py-jwt` | `py-jwt` | lib, wiki | `jwtlib:8005` |
|
||||
|
||||
---
|
||||
|
||||
## Services & sites (Type B)
|
||||
|
||||
A container image pushed to the registry and run on the Docker host. Version
|
||||
source is the `VERSION` file; Drone file is `service-docker.yaml`.
|
||||
|
||||
| Repo | Type | Path | Trigger | Docs |
|
||||
|------|------|------|---------|------|
|
||||
| `auth-server` | service | `auth-server` | tag | api, lib, wiki |
|
||||
| `docs` | site | `docs` | push | — |
|
||||
| `mcp` | site | `mcp` | push | — |
|
||||
|
||||
The **service** variant is tag-triggered and enforces `tag == VERSION`. The
|
||||
**site** variant is push-triggered, ships `:latest`, and has no tag guard.
|
||||
|
||||
---
|
||||
|
||||
## Required files per repo
|
||||
|
||||
| Type | Version source | Changelog | Drone |
|
||||
|------|----------------|-----------|-------|
|
||||
| package | `pyproject.toml` | `CHANGELOG.md` | `python-lib.yaml` |
|
||||
| service / site | `VERSION` | `CHANGELOG.md` | `service-docker.yaml` |
|
||||
|
||||
Every covered repository must be able to satisfy `tag == version`:
|
||||
the version file (never a commit id) is bumped in the release MR, the MR is
|
||||
merged, and the tag is created on the merged commit.
|
||||
|
||||
---
|
||||
|
||||
## Reading this at runtime
|
||||
|
||||
- Resource: `registry://repos`
|
||||
- Tool: `list_repos()`
|
||||
- Humans: this page (mirror of `standards/src/repos.json`).
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- [10 – Drone Pipelines](10_drone_pipelines.md) · [11 – Versioning & Changelog](11_versioning_and_changelog.md) · [13 – Library Registry](13_library_registry.md)
|
||||
@@ -17,6 +17,8 @@ patterns, and the internal library catalog that agents consult while coding.
|
||||
- **Drone Pipelines** — the two CI shapes: a PyPI package or a Docker deployment.
|
||||
- **Versioning & Changelog** — SemVer, the tag↔version contract, and the release
|
||||
changelog.
|
||||
- **Repository Inventory** — every covered repo, its type, version source, and
|
||||
Drone shape.
|
||||
- **Genericity Matrix** — what is library-backed, template-provided, or authored.
|
||||
- **Auth Integration** — consuming the shared auth-server service (spec-driven,
|
||||
no auth code).
|
||||
|
||||
@@ -65,6 +65,7 @@ nav:
|
||||
- Infra Adapters: 09_infra_adaptors.md
|
||||
- Drone Pipelines: 10_drone_pipelines.md
|
||||
- Versioning & Changelog: 11_versioning_and_changelog.md
|
||||
- Repository Inventory: 15_repository_inventory.md
|
||||
- Genericity Matrix: 12_genericity_matrix.md
|
||||
- Auth Integration: 14_auth_integration.md
|
||||
- Library Registry: 13_library_registry.md
|
||||
117
standards/src/repos.json
Normal file
117
standards/src/repos.json
Normal file
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"project": "standards",
|
||||
"note": "Machine-readable repository inventory. Mirrors standards/src/lib/15_repository_inventory.md. Read it with the registry://repos resource or the list_repos tool.",
|
||||
"path_convention": "relative to the aetos/ workspace root",
|
||||
"repos": [
|
||||
{
|
||||
"name": "dagpipe",
|
||||
"type": "package",
|
||||
"path": "dagpipe",
|
||||
"version_source": "pyproject.toml",
|
||||
"drone": "python-lib.yaml",
|
||||
"trigger": "tag",
|
||||
"docs": ["lib", "wiki"],
|
||||
"mcp": { "server": "dagpipe", "port": 8006 }
|
||||
},
|
||||
{
|
||||
"name": "doc-forge",
|
||||
"type": "package",
|
||||
"path": "doc-forge",
|
||||
"version_source": "pyproject.toml",
|
||||
"drone": "python-lib.yaml",
|
||||
"trigger": "tag",
|
||||
"docs": ["lib", "wiki"],
|
||||
"mcp": { "server": "docforge", "port": 8001 }
|
||||
},
|
||||
{
|
||||
"name": "hexa",
|
||||
"type": "package",
|
||||
"path": "hexa",
|
||||
"version_source": "pyproject.toml",
|
||||
"drone": "python-lib.yaml",
|
||||
"trigger": "tag",
|
||||
"docs": ["lib", "wiki"],
|
||||
"mcp": { "server": "hexa", "port": 8008 }
|
||||
},
|
||||
{
|
||||
"name": "mail-intake",
|
||||
"type": "package",
|
||||
"path": "mail-intake",
|
||||
"version_source": "pyproject.toml",
|
||||
"drone": "python-lib.yaml",
|
||||
"trigger": "tag",
|
||||
"docs": ["lib", "wiki"],
|
||||
"mcp": { "server": "mail_intake", "port": 8002 }
|
||||
},
|
||||
{
|
||||
"name": "mongo-ops",
|
||||
"type": "package",
|
||||
"path": "mongo-ops",
|
||||
"version_source": "pyproject.toml",
|
||||
"drone": "python-lib.yaml",
|
||||
"trigger": "tag",
|
||||
"docs": ["lib", "wiki"],
|
||||
"mcp": { "server": "mongo_ops", "port": 8007 }
|
||||
},
|
||||
{
|
||||
"name": "omniread",
|
||||
"type": "package",
|
||||
"path": "omniread",
|
||||
"version_source": "pyproject.toml",
|
||||
"drone": "python-lib.yaml",
|
||||
"trigger": "tag",
|
||||
"docs": ["lib", "wiki"],
|
||||
"mcp": { "server": "omniread", "port": 8003 }
|
||||
},
|
||||
{
|
||||
"name": "openapi-first",
|
||||
"type": "package",
|
||||
"path": "openapi-first",
|
||||
"version_source": "pyproject.toml",
|
||||
"drone": "python-lib.yaml",
|
||||
"trigger": "tag",
|
||||
"docs": ["lib", "wiki"],
|
||||
"mcp": { "server": "openapi_first", "port": 8004 }
|
||||
},
|
||||
{
|
||||
"name": "py-jwt",
|
||||
"type": "package",
|
||||
"path": "py-jwt",
|
||||
"version_source": "pyproject.toml",
|
||||
"drone": "python-lib.yaml",
|
||||
"trigger": "tag",
|
||||
"docs": ["lib", "wiki"],
|
||||
"mcp": { "server": "jwtlib", "port": 8005 }
|
||||
},
|
||||
{
|
||||
"name": "auth-server",
|
||||
"type": "service",
|
||||
"path": "auth-server",
|
||||
"version_source": "VERSION",
|
||||
"drone": "service-docker.yaml",
|
||||
"trigger": "tag",
|
||||
"docs": ["api", "lib", "wiki"],
|
||||
"mcp": null
|
||||
},
|
||||
{
|
||||
"name": "docs",
|
||||
"type": "site",
|
||||
"path": "docs",
|
||||
"version_source": "VERSION",
|
||||
"drone": "service-docker.yaml",
|
||||
"trigger": "push",
|
||||
"docs": [],
|
||||
"mcp": null
|
||||
},
|
||||
{
|
||||
"name": "mcp",
|
||||
"type": "site",
|
||||
"path": "mcp",
|
||||
"version_source": "VERSION",
|
||||
"drone": "service-docker.yaml",
|
||||
"trigger": "push",
|
||||
"docs": [],
|
||||
"mcp": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -20,16 +20,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Security
|
||||
|
||||
<!--
|
||||
On release, promote the entries above into a new heading with today's date and
|
||||
leave a fresh, empty [Unreleased] section:
|
||||
In the release merge request, promote the entries above into a new heading and
|
||||
leave a fresh, empty [Unreleased] section. Name the version/tag, never a commit
|
||||
id:
|
||||
|
||||
## [0.1.0] - YYYY-MM-DD
|
||||
|
||||
### Added
|
||||
- Initial release.
|
||||
|
||||
Then update the compare links below. See the standard:
|
||||
Versioning & Changelog (registry://release).
|
||||
Then update the compare links below. The tag is created on the merged commit —
|
||||
see the standard: Versioning & Changelog (registry://release).
|
||||
-->
|
||||
|
||||
[Unreleased]: <repo-url>/compare/0.1.0...HEAD
|
||||
|
||||
@@ -5,14 +5,16 @@ page [Versioning & Changelog](../lib/11_versioning_and_changelog/); this is the
|
||||
runbook you follow.
|
||||
|
||||
Every repository is one of two Drone shapes — a **PyPI package** or a **Docker
|
||||
deployment** ([Drone Pipelines](../lib/10_drone_pipelines/)). Both are
|
||||
released the same way: bump the version file, promote the changelog, tag.
|
||||
deployment** ([Drone Pipelines](../lib/10_drone_pipelines/)). Both release the
|
||||
same way: the version bump and changelog promotion go **in the merge request**,
|
||||
and the tag is created on the merged commit.
|
||||
|
||||
---
|
||||
|
||||
## 🧾 Before you start
|
||||
|
||||
- `main` is green (the Drone quality gate passed on the last run).
|
||||
- You have an open **merge request** holding the change, and `main` is green
|
||||
(the Drone quality gate passed on the last run).
|
||||
- You know whether this is a **breaking**, **feature**, or **fix** release.
|
||||
- The version file exists:
|
||||
- package → `version` in `pyproject.toml`
|
||||
@@ -33,36 +35,46 @@ line (see the reference page); once at `1.0.0`, normal SemVer applies.
|
||||
|
||||
---
|
||||
|
||||
## ✍️ Promote the changelog
|
||||
## 🌿 Release in the MR
|
||||
|
||||
In `CHANGELOG.md`:
|
||||
The version is known while the change is still a merge request, so the bump
|
||||
rides along with it. Do both edits **in the MR**:
|
||||
|
||||
1. Rename the content under `[Unreleased]` into a new
|
||||
`## [x.y.z] - YYYY-MM-DD` heading (keep the sections that apply).
|
||||
2. Leave a fresh, empty `## [Unreleased]` on top.
|
||||
3. Add the compare links at the bottom:
|
||||
- **Promote the changelog** in `CHANGELOG.md`: move the `[Unreleased]` content
|
||||
under a new `## [x.y.z] - YYYY-MM-DD` heading, leave a fresh empty
|
||||
`[Unreleased]` on top, and add the compare links — naming the **version/tag**,
|
||||
never a commit id:
|
||||
|
||||
```markdown
|
||||
[Unreleased]: https://git.aetoskia.com/aetos/<repo>/compare/x.y.z...HEAD
|
||||
[x.y.z]: https://git.aetoskia.com/aetos/<repo>/compare/x.y.(z-1)...x.y.z
|
||||
```
|
||||
```markdown
|
||||
[Unreleased]: https://git.aetoskia.com/aetos/<repo>/compare/x.y.z...HEAD
|
||||
[x.y.z]: https://git.aetoskia.com/aetos/<repo>/compare/x.y.(z-1)...x.y.z
|
||||
```
|
||||
|
||||
- **Bump the version file** to the intended release version: `version = "x.y.z"`
|
||||
in `pyproject.toml` (packages) or the `VERSION` file (services/sites).
|
||||
|
||||
---
|
||||
|
||||
## 🔢 Bump the version file
|
||||
## 🏷️ Tag the merged commit
|
||||
|
||||
- **Package:** edit `version = "x.y.z"` in `pyproject.toml`.
|
||||
- **Service / site:** edit the `VERSION` file.
|
||||
1. **Merge or squash** the MR into `main`.
|
||||
2. **Create the tag** on the resulting commit: `git tag x.y.z` (lightweight).
|
||||
3. **Push the tag** (`git push origin x.y.z`) — Drone builds the release.
|
||||
|
||||
Then commit and tag:
|
||||
### 🤖 Agent-assisted tagging
|
||||
|
||||
```bash
|
||||
git add CHANGELOG.md pyproject.toml # or VERSION
|
||||
git commit -m "chore(release): x.y.z"
|
||||
git tag x.y.z
|
||||
git push origin HEAD
|
||||
git push origin x.y.z
|
||||
```
|
||||
The agent does not author or merge the MR and never tags before the merge. After
|
||||
you confirm the MR is merged/squashed and tell it to proceed, the agent:
|
||||
|
||||
1. Waits for that explicit go-ahead.
|
||||
2. Finds the commit that introduced the bump:
|
||||
- package:
|
||||
`git log -S 'version = "x.y.z"' -n 1 --format='%H %s' -- pyproject.toml`
|
||||
- service / site:
|
||||
`git log -S 'x.y.z' -n 1 --format='%H %s' -- VERSION`
|
||||
3. Verifies that commit's version file equals `x.y.z`, then tags **only** that
|
||||
commit: `git tag x.y.z <sha>`.
|
||||
4. Does **not** push — it asks you to run `git push origin x.y.z`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user