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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# CodeLens v8.2 — AI-Native Code Intelligence
# CodeLens — AI-Native Code Intelligence

> **Before an AI writes a new class/id/function, CodeLens must be checked. This is not optional.**

Expand Down
2 changes: 1 addition & 1 deletion SKILL-QUICK.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# CodeLens v8.2 — Quick Reference
# CodeLens — Quick Reference

**MUST activate before writing/editing/deleting any class, id, or function.**

Expand Down
2 changes: 1 addition & 1 deletion SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description: >
For version history, see CHANGELOG.md.
---

# CodeLens v8.1
# CodeLens

Before an AI writes a new class/id/function, CodeLens must be checked. This is not optional.

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "codelens"
version = "7.2.0"
version = "8.2.0"
description = "Live Codebase Reference Intelligence — 45 commands for AI-powered code analysis, security auditing, and quality scoring"
readme = "README.md"
license = {text = "MIT"}
Expand Down
2 changes: 1 addition & 1 deletion scripts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ def _identify_signature(sig: bytes) -> Optional[str]:

# ─── Version ────────────────────────────────────────────────

CODELENS_VERSION = "8.0.0"
CODELENS_VERSION = "8.2.0"


# ─── Generated File Detection ───────────────────────────────
Expand Down
2 changes: 1 addition & 1 deletion skill.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codelens",
"version": "7.2.0",
"version": "8.2.0",
"description": "Live Codebase Reference Intelligence. 45 commands for AI-powered code analysis, security auditing, quality scoring, and pre-write safety checks. Supports 28+ languages with regex+AST hybrid parsing. Must activate before writing/editing/deleting any class, id, or function.",
"author": "codelens",
"command_categories": {
Expand Down
169 changes: 169 additions & 0 deletions tests/test_version_consistency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
"""
Tests for version consistency across CodeLens metadata files.

Issue #37 reported version drift across 6 files: pyproject.toml,
skill.json, scripts/utils.py, README.md, SKILL.md, SKILL-QUICK.md.
This test file is the CI guard that prevents future drift.

Contract:
- ``scripts/utils.py:CODELENS_VERSION`` is the single source of truth.
- ``pyproject.toml`` ``version`` field must equal ``CODELENS_VERSION``.
- ``skill.json`` ``version`` field must equal ``CODELENS_VERSION``.
- Documentation H1 headings (README.md, SKILL.md, SKILL-QUICK.md)
must NOT embed a ``vX.Y`` literal — readers should run
``codelens --version`` for the live version.

Run:
python3 -m pytest tests/test_version_consistency.py -v
"""

import json
import os
import re
import sys
from pathlib import Path

import pytest

# Make scripts/ importable so we can read the canonical CODELENS_VERSION
# directly. Same convention as tests/test_cli.py.
SCRIPT_DIR = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "scripts"
)
sys.path.insert(0, SCRIPT_DIR)

from utils import CODELENS_VERSION # noqa: E402

REPO_ROOT = Path(__file__).resolve().parent.parent
PYPROJECT_PATH = REPO_ROOT / "pyproject.toml"
SKILL_JSON_PATH = REPO_ROOT / "skill.json"
DOC_PATHS = [
REPO_ROOT / "README.md",
REPO_ROOT / "SKILL.md",
REPO_ROOT / "SKILL-QUICK.md",
]

# A "vX.Y" or "vX.Y.Z" literal embedded in a doc H1. We reject any
# version-like prefix on the title line because the live version lives
# in CODELENS_VERSION, not in markdown.
_DOC_VERSION_PATTERN = re.compile(r"v\d+\.\d+")


def _read_pyproject_version() -> str:
"""Return the ``version`` value from ``[project]`` in pyproject.toml.

Prefers ``tomllib`` (stdlib in Python 3.11+) for a clean parse.
Falls back to a regex on the ``version = "..."`` line for older
Python (3.8-3.10) where ``tomllib`` is not available and we don't
want to add ``tomli`` as a test-only dependency.

Returns:
The version string declared in pyproject.toml.

Raises:
AssertionError: if the version field cannot be located.
"""
try:
import tomllib # type: ignore[import-not-found]
except ModuleNotFoundError:
# Python 3.8-3.10 fallback: regex the version line. Safe here
# because pyproject.toml has exactly one top-level
# ``version = "..."`` line (verified by the test below).
text = PYPROJECT_PATH.read_text(encoding="utf-8")
match = re.search(r'^version\s*=\s*"([^"]+)"', text, re.MULTILINE)
assert match, (
f"Could not find `version = \"...\"` in {PYPROJECT_PATH}. "
f"Is the [project] table still present?"
)
return match.group(1)

with PYPROJECT_PATH.open("rb") as fh:
data = tomllib.load(fh)
project_table = data.get("project", {})
version = project_table.get("version")
assert version, (
f"pyproject.toml [project] table has no `version` key. "
f"Either hardcode it or wire up dynamic = ['version']."

Check warning on line 86 in tests/test_version_consistency.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add replacement fields or use a normal string instead of an f-string.

See more on https://sonarcloud.io/project/issues?id=Wolfvin_CodeLens&issues=AZ8O5S8TlDUuF1_p3RgK&open=AZ8O5S8TlDUuF1_p3RgK&pullRequest=77
)
# If dynamic versioning is ever enabled, `version` will be None and
# the assert above will fire — surfacing the regression explicitly
# rather than silently passing the test.
return str(version)


def test_pyproject_version_matches_utils() -> None:
"""``pyproject.toml`` version must equal ``utils.CODELENS_VERSION``.

This is the CI guard for the PyPI/packaging metadata side of the
drift reported in issue #37. If a maintainer bumps
``CODELENS_VERSION`` without also bumping ``pyproject.toml``, this
test fails.

The single source of truth is ``utils.CODELENS_VERSION`` — when
the two disagree, this test treats the constant as canonical and
reports the pyproject value as the one that needs updating.
"""
pyproject_version = _read_pyproject_version()
assert pyproject_version == CODELENS_VERSION, (
f"Version drift: pyproject.toml declares version="
f"{pyproject_version!r} but scripts/utils.py:CODELENS_VERSION="
f"{CODELENS_VERSION!r}. Bump pyproject.toml to match "
f"(CODELENS_VERSION is the single source of truth)."
)


def test_skill_json_version_matches_utils() -> None:
"""``skill.json`` version must equal ``utils.CODELENS_VERSION``.

``skill.json`` is the MCP server's externally-advertised manifest
(and what AI hosts read to decide whether to activate the skill).
A stale ``version`` field here is the most user-visible symptom of
drift — it tells AI agents the wrong capability level.
"""
with SKILL_JSON_PATH.open("r", encoding="utf-8") as fh:
skill_data = json.load(fh)
skill_version = skill_data.get("version")
assert skill_version is not None, (
f"skill.json has no top-level `version` field. "
f"Add one matching CODELENS_VERSION={CODELENS_VERSION!r}."
)
assert skill_version == CODELENS_VERSION, (
f"Version drift: skill.json declares version={skill_version!r} "
f"but scripts/utils.py:CODELENS_VERSION={CODELENS_VERSION!r}. "
f"Bump skill.json to match (CODELENS_VERSION is the single "
f"source of truth)."
)


def test_no_version_in_doc_headings() -> None:
"""Doc H1 headings must NOT embed a ``vX.Y`` literal.

The live version lives in ``CODELENS_VERSION`` and is queried via
``codelens --version``. Hardcoding ``v8.2`` (or any other literal)
in the README/SKILL/SKILL-QUICK H1 headings creates a third source
of truth that drifts the moment a maintainer bumps
``CODELENS_VERSION`` without remembering to edit markdown — which
is exactly how issue #37's drift happened.

This test scans the first line of each doc and fails if a
``v<digit>.<digit>`` pattern appears anywhere on it.
"""
missing = [p for p in DOC_PATHS if not p.exists()]
assert not missing, f"Doc file(s) missing: {missing}"

drifted: list[str] = []
for path in DOC_PATHS:
first_line = path.read_text(encoding="utf-8").splitlines()[0]
match = _DOC_VERSION_PATTERN.search(first_line)
if match is not None:
drifted.append(
f"{path.name}:1 — H1 contains version literal "
f"{match.group(0)!r} in line: {first_line!r}"
)

assert not drifted, (
f"Doc H1 headings must not embed version literals — "
f"CODELENS_VERSION={CODELENS_VERSION!r} is the single source "
f"of truth. Readers should run `codelens --version` for the "
f"live version. Drift found in:\n - " + "\n - ".join(drifted)
)
Loading