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
1 change: 1 addition & 0 deletions STANDARDS_VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.10.0
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.11.0
1.12.0
16 changes: 8 additions & 8 deletions scaffold/create-tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@


TEMPLATES_DIR = Path(__file__).parent / "templates"
VERSION_FILE = Path(__file__).parent.parent / "VERSION"
STANDARDS_VERSION_FILE = Path(__file__).parent.parent / "STANDARDS_VERSION"

LICENSE_FILES = {
"cc-by-nc-nd-4.0": "CC-BY-NC-ND-4.0",
Expand All @@ -42,23 +42,23 @@ def slugify(name: str) -> str:


def read_standards_version() -> str:
"""Read the meta-repo VERSION at generation time.
"""Read the meta-repo STANDARDS_VERSION at generation time.

New tool repos are pre-aligned with the current standards version, so the
value here is not a runtime decision. If VERSION is missing or unreadable,
fail loudly rather than silently substituting a default - a wrong version
would defeat the drift-checker invariant.
value here is not a runtime decision. If STANDARDS_VERSION is missing or
unreadable, fail loudly rather than silently substituting a default - a
wrong version would defeat the drift-checker invariant.
"""
try:
raw = VERSION_FILE.read_text(encoding="utf-8").strip()
raw = STANDARDS_VERSION_FILE.read_text(encoding="utf-8").strip()
except FileNotFoundError:
print(
f"Error: VERSION file not found at {VERSION_FILE}. "
f"Error: STANDARDS_VERSION file not found at {STANDARDS_VERSION_FILE}. "
"The scaffold must run from a working copy of Developer-Tools-Directory."
)
sys.exit(1)
except OSError as e:
print(f"Error: could not read {VERSION_FILE}: {e}")
print(f"Error: could not read {STANDARDS_VERSION_FILE}: {e}")
sys.exit(1)
if not re.fullmatch(r"\d+\.\d+\.\d+", raw):
print(
Expand Down
12 changes: 6 additions & 6 deletions scripts/drift_check/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,19 @@


def _find_repo_root() -> Path:
"""Walk up from this file to the repo that contains ``VERSION``."""
"""Walk up from this file to the repo that contains ``STANDARDS_VERSION``."""
here = Path(__file__).resolve()
for candidate in (here.parent, *here.parents):
if (candidate / "VERSION").is_file():
if (candidate / "STANDARDS_VERSION").is_file():
return candidate
return here.parents[2]


def _read_meta_version(repo_root: Path) -> Version:
raw = (repo_root / "VERSION").read_text(encoding="utf-8").strip()
raw = (repo_root / "STANDARDS_VERSION").read_text(encoding="utf-8").strip()
v = parse_version(raw)
if v is None:
raise SystemExit(f"meta-repo VERSION is not a valid semver: {raw!r}")
raise SystemExit(f"meta-repo STANDARDS_VERSION is not a valid semver: {raw!r}")
return v


Expand Down Expand Up @@ -199,7 +199,7 @@ def build_parser() -> argparse.ArgumentParser:
default=None,
help=(
"path to the meta-repo root for resolving standards/*.md references "
"(defaults to the repo that contains VERSION)"
"(defaults to the repo that contains STANDARDS_VERSION)"
),
)
p.add_argument(
Expand Down Expand Up @@ -355,7 +355,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
except SystemExit:
raise
except Exception as exc: # pragma: no cover - defensive
print(f"error: cannot read VERSION: {exc}", file=sys.stderr)
print(f"error: cannot read STANDARDS_VERSION: {exc}", file=sys.stderr)
return 2

try:
Expand Down
24 changes: 18 additions & 6 deletions standards/versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,24 @@ The release workflow auto-generates release notes grouped by commit type:

`CHANGELOG.md` is maintained manually for curated, human-readable release history. It is not auto-generated.

## What a MINOR bump means for ecosystem standards
## Two version files in the meta-repo

The meta-repo's `VERSION` file carries the ecosystem-wide standards version. It follows the same SemVer rules, but each component has a second, standards-specific meaning for tool repos that embed a `standards-version` signal in their agent files.
The meta-repo root contains two separate version files that serve distinct purposes and move independently.

- **MAJOR** (e.g., `1.x.y` → `2.0.0`) — an incompatible change to the standards themselves. New required elements, removed fields, or restructured file conventions that existing tool repos will fail to validate against without re-alignment.
- **MINOR** (e.g., `1.6.x` → `1.7.0`) — ecosystem standards changed in a way that tool repos need to re-align with. Typical triggers: new required elements in agent files, changed frontmatter schemas, new required standards references, restructured validation rules, or new checks in the drift checker that introduce findings for existing tool-repo content. A mechanical rollout session across the tool repos is typically scheduled after a MINOR bump.
- **PATCH** (e.g., `1.7.0` → `1.7.1`) — bug fixes, clarifications, or additions that do not change the standards surface. Tool repos with a PATCH-behind signal are reported as `info` by the drift checker — visible in verbose runs but not blocking CI.
### `VERSION` - meta-repo release version

The drift checker enforces this mapping via the `same-major-minor` signal policy (see `standards/drift-checker.config.json`). Tool repos whose `standards-version` differs from the meta-repo's `VERSION` in MAJOR or MINOR are reported as `error`; PATCH differences are `info`; tool values ahead of meta are `warn` (either an in-flight rollout or a missed meta-repo bump, both worth surfacing).
Tracks the release history of the meta-repo itself (registry additions, scaffold changes, doc updates, new CI workflows). Bumped by `release.yml` on every qualifying push to `main` using the same conventional-commit rules as tool repos. Registry additions (`feat:`) force a MINOR bump here. This number appears in GitHub Releases and the release changelog but has no direct meaning to tool repos.

### `STANDARDS_VERSION` - ecosystem standards surface version

Tracks the version of the standards that tool repos are expected to comply with. Tool repos embed this value in their `<!-- standards-version: X.Y.Z -->` markers in CLAUDE.md, AGENTS.md, and ROADMAP.md. The drift checker compares each tool repo's embedded marker against this file, not against `VERSION`.

`STANDARDS_VERSION` moves **only** when the standards surface actually changes in a way that requires tool repos to update:

- **MAJOR** (e.g., `1.x.y` to `2.0.0`) - an incompatible change to the standards themselves. New required elements, removed fields, or restructured file conventions that existing tool repos will fail to validate against without re-alignment.
- **MINOR** (e.g., `1.6.x` to `1.7.0`) - standards changed in a way that tool repos need to re-align with. Typical triggers: new required elements in agent files, changed frontmatter schemas, new required standards references, restructured validation rules, or new checks in the drift checker that introduce findings for existing tool-repo content. A fleet-wide re-stamp session is typically scheduled after a MINOR bump.
- **PATCH** (e.g., `1.7.0` to `1.7.1`) - clarifications or additions that do not change the standards surface. Tool repos with a PATCH-behind marker are reported as `info` by the drift checker - visible in verbose runs but not blocking CI.

Registry-only changes, scaffold improvements, docs additions, and other meta-repo work that does not change what tool repos are required to contain **do not** bump `STANDARDS_VERSION`, even if they bump `VERSION`.

The drift checker enforces this mapping via the `same-major-minor` signal policy (see `standards/drift-checker.config.json`). Tool repos whose `standards-version` marker differs from `STANDARDS_VERSION` in MAJOR or MINOR are reported as `error`; PATCH differences are `info`; tool values ahead of meta are `warn`.
4 changes: 2 additions & 2 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@

@pytest.fixture
def meta_repo(tmp_path: Path) -> Path:
"""Isolated meta-repo for CLI tests. Pins meta VERSION to 1.6.3 so that
"""Isolated meta-repo for CLI tests. Pins STANDARDS_VERSION to 1.6.3 so that
the on-disk fixtures (which still carry 1.6.3 signals) read as clean."""
root = tmp_path / "meta"
root.mkdir()
(root / "VERSION").write_text("1.6.3", encoding="utf-8")
(root / "STANDARDS_VERSION").write_text("1.6.3", encoding="utf-8")
(root / "standards").mkdir()
(root / "standards" / "required-refs.json").write_text(
'{"version": 1, "requirements": {"cursor-plugin": {}, "mcp-server": {}}}',
Expand Down
4 changes: 2 additions & 2 deletions tests/test_cli_remote_flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
def meta_repo(tmp_path: Path) -> Path:
root = tmp_path / "meta"
root.mkdir()
(root / "VERSION").write_text("1.6.3", encoding="utf-8")
(root / "STANDARDS_VERSION").write_text("1.6.3", encoding="utf-8")
(root / "standards").mkdir()
(root / "standards" / "required-refs.json").write_text(
'{"version": 1, "requirements": {"cursor-plugin": {}, "mcp-server": {}}}',
Expand Down Expand Up @@ -48,7 +48,7 @@ def test_remote_without_token_errors(capsys, meta_repo: Path, monkeypatch):
def test_all_without_registry_errors(capsys, tmp_path: Path):
bare = tmp_path / "bare-meta"
bare.mkdir()
(bare / "VERSION").write_text("1.6.3", encoding="utf-8")
(bare / "STANDARDS_VERSION").write_text("1.6.3", encoding="utf-8")
rc = cli.main([
"--all",
"--meta-repo", str(bare),
Expand Down
Loading