Repository navigation
Conversation
A pure move: no message, exit code, signature or file format changes, and no test asserts anything new. `anchor_file`, `finding_keys` and `snoozed_anchors` read the findings contract, not the index file and not the CLI, so neither module they sat beside was their home. Preparation for habit-hooks#163: the lapse rule is expressed in exactly these terms and is about to be built here, and the move keeps snooze.py clear of the 200-line gate for what follows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The on-disk shape changes: an entry may now be {"key": …, "anchors": {…}}
as well as a bare key string, and an entry recording nothing is still
written bare, so an index written before this loads and rewrites
unchanged. `Entry` and `Anchors` name the two halves, so a signature says
which string is a key, which is a file and which is a digest.
Nothing records into `anchors` yet, so no command does anything new. What
does change is the refusal a malformed file earns, in four ways:
"expected a JSON list of string keys" becomes "…of snoozed entries"; a
new message names the shape an entry may take; a key written twice is
refused when the two disagree, since loading into a mapping keeps the
last and the order in the file would otherwise decide which recording
survives; and a JSON name written twice inside one object is refused
too, which `json` resolves last-wins in silence — an anchor pasted twice
with two digests would otherwise load as one of them. Entries that agree
still load, as they always did. The corrupt-index spec case moves with
the first of those.
Preparation for the fix in the next commit (habit-hooks#163).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A snooze lapsed when git said the anchor file had changed, and nothing a re-run of --snooze could write made that answer differ: the entry was a bare path, so the second write was byte-identical. Splitting the file or excluding it permanently were the only ways out, and the second is the exemption list the ratchet replaced. An entry now records each anchor's content, and a snooze lapses only when the file changed *and* no longer holds what was recorded. Re-affirming therefore clears the finding until the next edit, while a branch that never touched the file is still left alone by the git half — which `### Work landed on the base ref afterwards lapses nothing` guards. Recording opens an anchor as a path, so `anchor_file` now reads a `details.file` that is not a non-empty string as absent, the way `sensors.finding_paths` already reads it. That stage lets such an issue through rather than failing its sensor, so one reaches here. Only an anchor git called changed is hashed: the other half decides the rest without it, and asking them all would read every snoozed file in the project on a run where git flagged none. Closes habit-hooks#163. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An entry records one content per anchor file, and those go stale inside an entry that is still live: a key reported through two files keeps both recorded after one of them is deleted, and pruning by key alone would leave the dead one there forever. --prune reaps an anchor the same way it reaps a key, so it asks `anchors_by_key` rather than the bare key list; `finding_keys` goes with its last caller. The contract is unchanged — --prune must still be fed a snooze-free whole-project run — and its refusal to empty a populated index on a run that measured nothing is untouched. Follows habit-hooks#163. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The spec harness runs each case inside this checkout, so a case that shells out to git without a ceiling is answered about habit-hooks itself, and one whose `git init` fails commits into the reader's own repository. Both git-backed sections in habit-snooze.spec.md get the step, at section level so their subsections inherit it. CLAUDE.md's list of the cases that still lack one shrinks to the two in habit-sensors.spec.md. Noticed while working on habit-hooks#163, not part of that fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Collaborator
|
Sorry for the slow response. The main reason was that the PR was rather large, and that made it hard to review. When I did get to it I noticed some underlying design issues that I wanted to fix instead, and make this fix a by product of fixing that deeper issue. My alternative solution is now in main. Commit is here: I referenced you on that commit as co-author. Thanks for the contribution! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #163. No new option —
--snoozeitself is fixed, as decided on the issue.An entry was only a path, so a second
--snoozewrote the same file again and nothing changed.An entry now also stores the content of each file it covers:
[{"key": "requests", "anchors": {"src/a.py": "sha256:…", "src/b.py": "sha256:…"}}]A snooze now stops working only if both of these are true:
So running
--snoozeagain clears the finding, until the next edit.Both conditions are needed. The spec already promises that "work someone else lands on the base
ref afterwards never lapses a snooze you did not touch", and the case
### Work landed on the base ref afterwards lapses nothingchecks it. If we compared only content, that case would fail.Compatibility and details
An entry that stores nothing stays a plain string. An old index still loads, and is written back
unchanged. A project moves to the new format one
--snoozeat a time.--listprints plain keysin both cases.
An entry stores one hash per file, not one per key, because a key can stand for several files
(
deptryuses module names,knipuses export names). A hash for the key alone would cover a filethat nobody looked at.
--prunenow also drops a file that the run no longer reports, not only a key. Its other rules areunchanged.
Hashes ignore the difference between
\r\nand\n. This is slightly more forgiving thangit diff, and it means a hash written on Windows still matches on Linux. I did not usegit hash-object, because--snoozehas to work without a git repository.Two things worth knowing before merging
1.5.0 cannot read an index that stores content. It raises
SnoozeErrorand exits 2. This isalso true for an index that is only half migrated. So the upgrade works forwards but not backwards:
if one person on a team upgrades, the runs of everyone else fail, and CI fails too, until they
upgrade as well. The failure is loud and names the file, which is the right way to fail, but it is
still a breaking change in a minor version.
It also affects people who do not use this feature.
--snoozestores content even without--until-changed. A project that uses plainsnoozegets the same break.--snoozere-affirms everything you give it. If you runhabit-sensors --all | habit-snooze --snoozeto add one key, it also re-affirms every other lapsedentry in the pipe. It even stores a file that has only just started to report a key you snoozed
earlier. Nothing is printed, and
--listlooks the same before and after. Only the index fileshows it.
This follows directly from fixing
--snoozeinstead of adding a flag. It is the best remainingargument for the
--reaffirmflag that was rejected. The spec case states it.Three smaller limits, all known: the index is written on one line, so a re-affirmation shows up as
one changed line; two
--snoozeruns at the same time can still lose an update, as before; and asmudge filter such as
identrewrites the file on commit, so the stored hash never matches again.Tests
docs/habit-snooze.spec.md:--snoozere-affirms a lapsed entry, and thenext edit brings the finding back. It fails on
main.--prunekeepsand reaps, and every shape the index now refuses.
uv run pytest tests/ docs/→ 821 passed, 5 skipped, on Python 3.11, the version CI pins.ruff checkandhabit-hooks --allare both clean.Changelog
I did not touch
CHANGELOG.md. One sentence, if it is useful: