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
15 changes: 8 additions & 7 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ Review code against these criteria, whether on its own or in the TDD refactor st

- **Comments**: A docstring is one line. A docstring is only more than one line if something needs explaining the code cannot. A module's docstring says what the module holds rather than repeating its main class. A docstring or comment says what this code does now. It never says where the increment is heading, and never repeats the signature. Delete a sentence that narrates or justifies an edit, that walks through the steps in order, that says what the code is not, or that restates a parameter, a return type, or a default. Delete a sentence that says what the return value is for: the summary already names it. Delete a sentence that says what a caller does, or that names a concept from a layer further out. A test's docstring says which case the test pins; the reason for the behaviour belongs in the code under test and in the README. Keep a contrast only when the reader has to act on the difference. When a signature changes, the summary usually needs one word, not a new sentence. A docstring that matches the one beside it was copied rather than checked, so read both.
- **Duplication**: Look for the same decision taken in more than one place, not for repeated lines. Count what the fix adds against what it removes before you write it: a shared version needing a parameter for everything that differs is repeated lines. A rule every module has to remember is duplication too, so state it once, where nobody can forget it. A helper that callers have to remember to call can be forgotten as well, so prefer a check that reads the code itself. In tests, turn repeated setup or a repeated assertion into a named helper.
- **Reuse**: Look for an existing type, test helper, or fixture before you write a new one. Watch for three misses: a fixture's value spelled out as a literal, a mixin's setup redone inline, and the same builder written in two modules instead of in the shared one.
- **Reuse**: Look for an existing type, test helper, or fixture before you write a new one. Watch for four misses: a fixture's value spelled out as a literal, a mixin's setup redone inline, the same builder written in two modules instead of in the shared one, and an inline expression or check that a function in the same module already computes. Grep the module for it.
- **Complexity**: A function holds one decision. Watch for nesting, for a flag parameter that makes one function do two things, and for a long parameter list. When a docstring needs several sentences for the control flow, the code does too much.
- **Missing abstractions**: Values that always travel together want a type. A sequence of calls that callers have to make in the right order wants a name. Raw strings, tuples, and dicts standing in for a domain concept are the usual smell, whether that type exists already or still has to be written.
- **Visibility**: A class, function, method, or constant without a leading underscore claims callers outside its own module or class. Check that it has them. A name made public for a call site that has since changed is how that claim goes stale.
- **Failure paths**: For every call that can fail — a request, a parse, a subprocess — check what happens when it does, and mock it so that it fails the same way. A mock that answers where the real thing raises lets a test pass without the branch it is named for. Log every exception you catch, or the failure is invisible. An exception that escapes aborts the whole run over one bad reference. A write that fails partway has to leave the file as it was.
- **Cost**: The work here is network requests, so count them. Watch for a request added inside a per-reference loop, and for a call path that bypasses a cache the old one used.
- **Readability**: Use the domain vocabulary that the code and the README establish. Use it in code, docstrings, log messages, and tests alike, and never coin a second word for a concept one of them already names. Prefer an early return to a nested conditional. Say what a test asserts in its method name. Keep implementation terms out of anything the user reads. When a behaviour is described in more than one place, change every place, not only the one you are in.
- **Prose**: Write plainly — in docstrings, comments, log messages, CLI help, the README, and your replies to me. One sentence, one claim. Put the subject first and the verb right after it. Split a sentence instead of nesting a clause in it. Name the thing when `it` or `which` could point at two things. Don't stack negatives. Don't rank; saying for example "the least," "the only," "the one thing." Give the concrete case, not the general rule. Don't skip a step in an explanation. Read each new sentence again, and rewrite it if it needs a second read.
- **Prose**: Write plainly — in docstrings, comments, log messages, CLI help, the README, and your replies to me. One sentence, one claim. Put the subject first and the verb right after it. Split a sentence instead of nesting a clause in it. Name the thing when `it` or `which` could point at two things. Don't stack negatives. Don't rank; saying for example "the least," "the only," "the one thing." Give the concrete case, not the general rule. Don't skip a step in an explanation. Read each new sentence again, in context, and rewrite it if it needs a second read.

## TDD

Expand Down Expand Up @@ -87,7 +87,7 @@ A few rules that keep the cycle honest:
2. Build a behaviour before its off-switch. Don't test an opt-out, a flag, or any other suppression until the thing it suppresses exists.
3. Assert what happens and what doesn't. An assertion runs on every run; a claim in a docstring is checked by nobody.
- A test that asserts nothing was found also passes when nothing was examined, so assert that something was.
- Neither a test's name nor a green run is evidence of what the test guards. Settle that with `just mutate`, for a duplicate you would fold or delete as much as for anything else. When it says a case guards nothing its neighbours don't, delete it, and reshape the test that leaves behind. Register the mutation with `@kills` only where that test is what kills it, and leave it unregistered where the suite kills it anyway.
- Neither a test's name nor a green run is evidence of what the test guards. Settle that with `just mutate`: for a duplicate you would fold or delete, and for a candidate you propose to drop from the list, as much as for anything else. When it says a case guards nothing its neighbours don't, delete it, and reshape the test that leaves behind. Register the mutation with `@kills` only where that test is what kills it, and leave it unregistered where the suite kills it anyway.
- Call a stub that varies its answer by an argument with more than one value of that argument, or the test shows something other than what its name claims. The same holds for a fixture whose docstring names a case it does not create.
- Pick the mutation from the regression the guard defends against, not from the nearest line to mutate. One that leaves the guard green says nothing about it. One that fails a dozen other tests says little more: it shows the suite reacting, not that guard.
- When a stub quotes more than a handful of lines, look for a shorter form that isolates the same regression.
Expand All @@ -110,27 +110,28 @@ A few rules that keep the cycle honest:
- When only CI can decide, try each option locally and say what stays unverified.
- Don't announce a comparison and then not run it.
- A probe is evidence only when it would fail if the answer were the other way. A rule that matched nothing, or a probe whose signal the code under test swallows, passes exactly like one that holds.
- A sample settles a decision only when it holds the case the decision turns on. Look for that case before you recommend.
9. Check a claim against the code or the tools before you state it. One run or a look at a sibling module usually settles one, such as "nothing covers this yet."
- A review finding is a claim, and the most plausible findings are the ones to check hardest. Mutate the code the finding describes and say what that showed, or don't report it.
- A review finding is a claim, and the most plausible findings are the ones to check hardest. Mutate the code the finding describes and say what that showed, or don't report it. A reviewer's count or estimate is a claim too: measure it before you put it to me.
- Anything an issue says is a claim too, whether you carry out its instruction or copy its sentence into the README. A spec says what was intended, not what got built.
- So is the reason you give for an option you put to me, because I choose on that reason.
- Measure a challenged claim. Don't argue it.
10. Add a missing candidate test to the list as soon as you find one, at whatever step you are in.
11. A refactor that changes behaviour is not a refactor, however unreachable the changed case looks. A change that only *adds* behaviour counts as well: a decorator that fills in methods the class was missing changes what calling them does. Say so before you make the change, not after, and let me decide whether a test has to drive it first.
12. A problem you hit and fixed yourself needs no narration, at whatever step it happens: a probe that misfired, a rewrite that overreached, a formatter that undid an edit. That holds for the cycle's report and for the session's edits as much as for the work itself. Note it, and bring it to the session's evaluation if it still matters.
13. An answer of mine may admit more than one reading. Name the readings you see and ask, rather than implementing the one you would pick: a message costs less than the cycle that undoes a guess. When I push back on one passage twice, we are working from different assumptions. Name yours and ask for mine, rather than rewriting the passage again.
13. An answer of mine may admit more than one reading. Name the readings you see and ask, rather than implementing the one you would pick: a message costs less than the cycle that undoes a guess. When I push back on one passage twice, we are working from different assumptions. Name yours and ask for mine, rather than rewriting the passage again. A question I leave unanswered next to your recommendation is answered by the recommendation.

When every candidate test passes, propose an increment review before you propose the self-improvement session. The increment is the one an issue lists, or the whole session where no issue lists one.

Review everything the increment changed, against the same criteria, and report it as its own numbered list. Read the files it touched end to end rather than its diff: a finding can span cycles and show up in no single one, such as a helper the second cycle duplicated, a module head grown long with constants, or a name that restates the line beside it.

A bug fix or a small diff gets that one review. An increment of several cycles gets a second review with fresh context, from subagents you give the criteria and the diff but not the reasoning that produced the code, because a review in the context that wrote the code misses what a fresh one catches. Run each in a git worktree of its own, so the mutations they run rewrite neither your tree nor each other's. Tell the subagents to check each finding against the code before reporting it, and judge what they report against the criteria yourself before it reaches my list.
A bug fix or a small diff gets that one review. An increment of several cycles gets a second review with fresh context, from subagents you give the criteria and the diff but not the reasoning that produced the code, because a review in the context that wrote the code misses what a fresh one catches. Run each in a git worktree of its own, so the mutations they run rewrite neither your tree nor each other's. A worktree starts at the default branch, so tell each subagent to `git checkout --detach` the branch head first. Remove the worktrees and their branches once they report. Tell the subagents to check each finding against the code before reporting it, and judge what they report against the criteria yourself before it reaches my list.

Merge both reviews into one numbered list. A finding only one review reached belongs in it as much as one they both reached, which you state once. Every finding sits in that list, so each can be referred to by its number, and the text around the list holds none. Group the list, the findings that change behaviour first and the rest by file or subject.

## Documentation

- README.md is generated. Edit `docs/README.md.in` and regenerate with `just readme`. An edit to README.md itself is lost on the next run, without a word. Its per-type headings are questions, so keep each section's sentences answering its own question.
- README.md is generated. Edit `docs/README.md.in` and regenerate with `just readme`. An edit to README.md itself is lost on the next run, without a word. Its per-type headings are questions, so keep each section's sentences answering its own question. After a cycle adds to a section, reread the whole section as someone asking its question, and rewrite it when the additions no longer answer that question in order.
- A change to what Update-time does gets a changelog entry under `[Unreleased]`: one line naming the behaviour and linking the issue. A change to the documentation alone gets none.
- The detail belongs in the README, not in the changelog. The README names the behaviour and shows the message it produces. It leaves out a worked example of what that message already shows.
- When a change lands over several cycles, update the README and the changelog once the behaviour has settled, and before you hand the increment back. Neither may document a state the code is not in. A command-line option is one such state: the cycle that adds it does everything its help promises, rather than accepting a value it then ignores.
Expand Down
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

## [Unreleased]

No changes yet.
### Added

- Show what changed in the new version of each Maven dependency that Maven updated. Closes [#374](https://github.com/ICTU/update-time/issues/374).

### Changed

- Check a Maven dependency for archival when its pom names its GitHub repository in the project's own `<url>` rather than in `<scm>`, or leaves it to the parent pom. Closes [#374](https://github.com/ICTU/update-time/issues/374).

## 0.0.39 - 2026-09-23

Expand Down
Loading
Loading