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

### Fixed

- **The README version badge had read 0.8.3 for nine releases** ([#335](https://github.com/Digital-Process-Tools/claude-remember/issues/335)) — the release sweep greps for the *outgoing* version when cutting a new one, which finds every site mid-bump and none that stopped being bumped at all. Bumped to match `.claude-plugin/plugin.json` (0.18.0), and `tests/test_version_manifest.py::test_readme_version_badge_matches_code` now pins the badge to the manifest and fails loud if its own pattern stops matching, rather than passing on a badge it never found.

- **Two files cited `docs/validators.md`, a path that only exists in the sibling `claude-supertool` repo** ([#333](https://github.com/Digital-Process-Tools/claude-remember/issues/333)) — `hooks.d/before_session_start/50-git-restore.sh` and `tests/test_git_restore_hook_253.py` pointed readers at a doc this repo has never shipped. The "declining instead of guessing" three-state contract they were citing does exist here: the 0.12.0 entry above, and `_push_and_report` in `hooks.d/after_save/50-git-backup.sh`. Both citations now point there instead.

- **A cooldown marker AHEAD of now sticks the throttle ON, permanently and mutely** ([#326](https://github.com/Digital-Process-Tools/claude-remember/issues/326)) — `case "$X" in ''|*[!0-9]*) X=0 ;; esac` validates a marker's *syntax*. It does not bound its *range*. A digits-only value ahead of the current clock is accepted, makes `ELAPSED` negative, and a negative `ELAPSED` is `-lt` any cooldown — so the gate takes its `exit 0`. That exit sits **above** the line that rewrites the marker, so the self-heal the previous three releases rest on is unreachable on exactly the path that needs it. Four sites, four different outages: no session is ever saved again; `now.md` is never compressed again and grows unbounded; the git backup stops, which is [#258](https://github.com/Digital-Process-Tools/claude-remember/issues/258)'s original outage reached through a value the guard *accepts*; and the per-tool-call fork throttle refuses to fork the save that would have healed the marker, so the two throttles hold each other shut.

**This needs no corruption.** An NTP step backwards, a VM snapshot restore, a container clock jump, or a store on a share with a skewed clock all produce it. Not a timezone or DST change — epoch seconds do not move for those, which is why proceeding is the right call and costs at most one extra save rather than mis-throttling a laptop that suspended over a boundary.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![Python](https://img.shields.io/badge/python-3.9%2B-blue)](https://www.python.org/)
[![OS](https://img.shields.io/badge/tested%20on-Linux%20%7C%20macOS%20%7C%20Windows-blue)](https://github.com/Digital-Process-Tools/claude-remember/actions/workflows/tests.yml)
[![License](https://img.shields.io/badge/license-Community-brightgreen)](LICENSE)
[![Version](https://img.shields.io/badge/version-0.8.3-orange)](.claude-plugin/plugin.json)
[![Version](https://img.shields.io/badge/version-0.18.0-orange)](.claude-plugin/plugin.json)

Claude Code starts every session blank. It doesn't know what you worked on yesterday, what conventions your team follows, or what mistakes it already made. You re-explain everything, every time.

Expand Down
7 changes: 4 additions & 3 deletions hooks.d/before_session_start/50-git-restore.sh
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,10 @@ _spawn_fetch() {
}

# ── What the last fetch has to say ───────────────────────────────────────────
# Three answers, never two (docs/validators.md, "Declining instead of
# guessing"): it worked / it failed / it never came back. Silence is not a
# fourth way of saying "up to date".
# Three answers, never two ("declining instead of guessing", CHANGELOG.md
# 0.12.0, and _push_and_report in hooks.d/after_save/50-git-backup.sh for the
# worked example): it worked / it failed / it never came back. Silence is not
# a fourth way of saying "up to date".
_fetch_health() {
[ -f "$FETCH_STATE_FILE" ] || { echo "never-run"; return; }
local _s='' _f='' _rc='' _now _age
Expand Down
5 changes: 3 additions & 2 deletions tests/test_git_restore_hook_253.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,8 +460,9 @@ def test_a_repaired_store_clears_the_count(self, tmp_path):

class TestCouldNotCheckIsItsOwnState:
"""Part 1's defect, mirrored onto the read side: 'I could not reach the
remote' must never render as 'you are up to date'. `docs/validators.md`
calls this declining instead of guessing."""
remote' must never render as 'you are up to date'. CHANGELOG.md's 0.12.0
entry and `_push_and_report` in hooks.d/after_save/50-git-backup.sh call
this declining instead of guessing."""

def test_a_failed_fetch_is_not_reported_as_up_to_date(self, tmp_path):
home, remember, remote, slug_dir, project = _store(tmp_path)
Expand Down
27 changes: 27 additions & 0 deletions tests/test_version_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@
REPO_ROOT = Path(__file__).resolve().parent.parent
MANIFEST = REPO_ROOT / ".claude-plugin" / "plugin.json"
CHANGELOG = REPO_ROOT / "CHANGELOG.md"
README = REPO_ROOT / "README.md"

SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+$")
RELEASE_HEADING_RE = re.compile(r"^## \[(\d+\.\d+\.\d+)\]")
README_BADGE_RE = re.compile(
r"!\[Version\]\(https://img\.shields\.io/badge/version-(\d+\.\d+\.\d+)-orange\)"
)


def _manifest_version() -> str:
Expand Down Expand Up @@ -59,3 +63,26 @@ def test_changelog_releases_are_strictly_descending():
assert versions == sorted(versions, reverse=True), (
f"CHANGELOG.md release headings are not in descending order: {_changelog_versions()}"
)

def test_readme_version_badge_matches_code():
"""A sweep keyed on the outgoing version can never find a badge that stopped
being bumped (#335): it is not mid-transition, so it never matches the
pattern any release sweep would grep for. Pin the badge to the manifest
directly, and fail loud if the badge is not even found -- a regex that
matched nothing has not checked the badge, and must not read as a pass.
"""
text = README.read_text(encoding="utf-8")
match = README_BADGE_RE.search(text)
assert match is not None, (
"README.md has no version badge matching the expected shields.io pattern "
"(![Version](.../badge/version-X.Y.Z-orange)) -- the badge markup may have "
"changed shape. Update README_BADGE_RE rather than letting this pass silently."
)
badge_version = match.group(1)
manifest_version = _manifest_version()
assert badge_version == manifest_version, (
f"README.md's version badge reads {badge_version} but "
f".claude-plugin/plugin.json declares {manifest_version}. The badge is "
f"README.md:9 and drifts silently because release sweeps grep for the "
f"outgoing version, which a frozen badge never matches (#335)."
)