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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- **CLAUDE.md outdated-hint: one version source + stderr** (GH #161).
`check_outdated` compared the injected version against
`importlib.metadata` (stale under editable installs — dist-info is baked
at install time) while the hint print used module `__version__`, so a
freshly injected CLAUDE.md triggered a self-contradictory
"v0.35.1 vs v0.35.1, run update" hint on every CLI invocation. The check
now uses the module's existing `__version__` resolver (source pyproject
first, the same one `--version` uses), and the startup hint prints to
stderr so `--output json` consumers can `json.loads` stdout without
tripping over it (this once made 21 CLI JSON tests fail red on a stale
venv).

- **post-commit hook no longer overwrites scan-all's hierarchical READMEs**
(GH #160). The hook re-rendered each affected directory via a per-dir
`codeindex scan` subprocess, which hardcodes `level="detailed"` and
Expand Down
2 changes: 1 addition & 1 deletion src/codeindex/README_AI.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!-- codeindex navigation index — agent: drill into source via Read/Grep for precise mechanism; do not treat this as final word. -->
<!-- Generated by codeindex (navigation) at 2026-08-15T02:15:38.441490 -->
<!-- Generated by codeindex (navigation) at 2026-08-15T02:25:33.783737 -->
<!-- enrichment: ok -->

# codeindex
Expand Down
19 changes: 12 additions & 7 deletions src/codeindex/claude_md.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
<!-- codeindex:end -->
"""

import importlib.metadata
import logging
import re
from pathlib import Path
Expand Down Expand Up @@ -64,12 +63,18 @@ def detect_locale(content: str) -> str:


def _get_current_version() -> str:
"""Get current codeindex package version."""
try:
return importlib.metadata.version("ai-codeindex")
except importlib.metadata.PackageNotFoundError:
from . import __version__
return __version__
"""Get current codeindex package version.

Uses the module's ``__version__`` resolver (source pyproject first,
installed metadata as fallback). GH #161: this previously did its own
importlib-first lookup, which goes stale under editable installs
(dist-info baked at install time) — a freshly injected CLAUDE.md then
triggered a self-contradictory "v0.35.1 vs v0.35.1, run update" hint
on every CLI invocation.
"""
from . import __version__

return __version__


def _load_template(version: str, lang: str = "en") -> str:
Expand Down
5 changes: 4 additions & 1 deletion src/codeindex/cli_claude_md.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from .claude_md import check_outdated, extract_version, inject

console = Console()
stderr_console = Console(stderr=True)


@click.group("claude-md")
Expand Down Expand Up @@ -90,7 +91,9 @@ def print_outdated_warning():
"""Print a one-line warning if CLAUDE.md is outdated. Called on CLI startup."""
outdated_version = check_outdated()
if outdated_version:
console.print(
# stderr: stdout must stay clean for machine-readable output
# (`--output json` consumers json.loads the whole stream, GH #161).
stderr_console.print(
f"[dim yellow]hint: CLAUDE.md has codeindex v{outdated_version}, "
f"current is v{__version__}. "
f"Run `codeindex claude-md update` to refresh.[/dim yellow]"
Expand Down
4 changes: 2 additions & 2 deletions tests/README_AI.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!-- codeindex navigation index — agent: drill into source via Read/Grep for precise mechanism; do not treat this as final word. -->
<!-- Generated by codeindex (navigation) at 2026-08-15T02:15:38.648408 -->
<!-- Generated by codeindex (navigation) at 2026-08-15T02:25:33.987150 -->
<!-- enrichment: ok -->

# tests
Expand All @@ -8,7 +8,7 @@
## Overview

- **Files**: 149
- **Symbols**: 2438
- **Symbols**: 2443
- **Subdirectories**: 5

## Subdirectories
Expand Down
55 changes: 55 additions & 0 deletions tests/test_claude_md.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,61 @@ def test_returns_old_version_if_outdated(self, tmp_path):
assert check_outdated(tmp_path) == "0.22.0"


class TestVersionSourceConsistency:
"""GH #161: one version source everywhere.

_get_current_version previously did its own importlib-first lookup while
the hint print used module __version__ — under an editable install with
stale dist-info, a fresh CLAUDE.md triggered a self-contradictory
"v0.35.1 vs v0.35.1, run update" hint.
"""

def test_get_current_version_matches_module_version(self):
from codeindex import __version__
from codeindex.claude_md import _get_current_version

assert _get_current_version() == __version__

def test_stale_dist_info_does_not_leak(self):
"""Even with importlib metadata disagreeing (editable install with
stale dist-info), the check must follow the module resolver."""
import importlib.metadata as _m
from unittest.mock import patch as _patch

from codeindex import __version__
from codeindex.claude_md import _get_current_version

with _patch.object(_m, "version", return_value="0.0.1"):
assert _get_current_version() == __version__

def test_fresh_claude_md_not_flagged_despite_stale_dist_info(self, tmp_path):
"""End-to-end property: CLAUDE.md at the current version must NOT be
flagged, whatever dist-info claims (the original #161 symptom)."""
import importlib.metadata as _m
from unittest.mock import patch as _patch

from codeindex import __version__

claude_md = tmp_path / "CLAUDE.md"
claude_md.write_text(f"<!-- codeindex:start v{__version__} -->\n<!-- codeindex:end -->\n")

with _patch.object(_m, "version", return_value="0.0.1"):
assert check_outdated(tmp_path) is None

def test_print_outdated_warning_goes_to_stderr(self, capsys):
"""The startup hint must not pollute stdout (breaks --output json)."""
from unittest.mock import patch as _patch

from codeindex.cli_claude_md import print_outdated_warning

with _patch("codeindex.cli_claude_md.check_outdated", return_value="0.1.0"):
print_outdated_warning()

captured = capsys.readouterr()
assert "hint:" in captured.err
assert captured.out == ""


class TestMarkerPattern:
"""Tests for marker regex pattern."""

Expand Down
Loading