redis_queued_locks 1.19.0 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/project-overview.md +96 -23
- data/.claude/rules/acquirer.md +39 -20
- data/.claude/rules/arguments.md +3 -2
- data/.claude/rules/git.md +3 -2
- data/.claude/rules/logic.md +32 -8
- data/.claude/rules/swarm.md +14 -9
- data/.claude/rules/tests.md +12 -3
- data/.claude/rules/type-checking.md +2 -1
- data/.claude/rules/visitors.md +10 -11
- data/.claude/skills/rql-code-review/SKILL.md +386 -0
- data/.claude/skills/rql-code-review/templates/report.en.md +31 -0
- data/.claude/skills/rql-code-review/templates/report.ru.md +31 -0
- data/CHANGELOG.md +194 -0
- data/CLAUDE.md +6 -3
- data/README.md +502 -92
- data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +1 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +42 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +45 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +1 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +695 -128
- data/lib/redis_queued_locks/acquirer/acquire_lock/with_acq_timeout.rb +2 -2
- data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire/log_visitor.rb +1 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire.rb +8 -6
- data/lib/redis_queued_locks/acquirer/acquire_lock.rb +134 -26
- data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +229 -24
- data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +189 -18
- data/lib/redis_queued_locks/acquirer/is_locked.rb +0 -1
- data/lib/redis_queued_locks/acquirer/is_queued.rb +0 -1
- data/lib/redis_queued_locks/acquirer/keys.rb +151 -7
- data/lib/redis_queued_locks/acquirer/lock_info.rb +39 -41
- data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +3 -2
- data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +5 -4
- data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +79 -20
- data/lib/redis_queued_locks/acquirer/locks.rb +212 -83
- data/lib/redis_queued_locks/acquirer/queue_info.rb +11 -18
- data/lib/redis_queued_locks/acquirer/queues.rb +123 -50
- data/lib/redis_queued_locks/acquirer/reindex_locks.rb +253 -0
- data/lib/redis_queued_locks/acquirer/release_all_locks.rb +177 -61
- data/lib/redis_queued_locks/acquirer/release_lock.rb +84 -25
- data/lib/redis_queued_locks/acquirer/release_locks_of.rb +274 -70
- data/lib/redis_queued_locks/acquirer/release_read_lock.rb +22 -3
- data/lib/redis_queued_locks/acquirer.rb +1 -0
- data/lib/redis_queued_locks/client.rb +164 -36
- data/lib/redis_queued_locks/config.rb +16 -13
- data/lib/redis_queued_locks/errors.rb +34 -4
- data/lib/redis_queued_locks/instrument/sampler.rb +4 -7
- data/lib/redis_queued_locks/logging/sampler.rb +4 -8
- data/lib/redis_queued_locks/resource.rb +113 -6
- data/lib/redis_queued_locks/swarm/flush_zombies.rb +333 -61
- data/lib/redis_queued_locks/swarm/redis_client_builder.rb +8 -4
- data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +3 -2
- data/lib/redis_queued_locks/swarm/zombie_info.rb +203 -53
- data/lib/redis_queued_locks/swarm.rb +97 -5
- data/lib/redis_queued_locks/utilities.rb +1 -1
- data/lib/redis_queued_locks/version.rb +2 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +14 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +13 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +107 -1
- data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +3 -3
- data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +3 -1
- data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +19 -1
- data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +20 -4
- data/sig/redis_queued_locks/acquirer/keys.rbs +11 -1
- data/sig/redis_queued_locks/acquirer/locks.rbs +15 -6
- data/sig/redis_queued_locks/acquirer/queues.rbs +9 -2
- data/sig/redis_queued_locks/acquirer/reindex_locks.rbs +52 -0
- data/sig/redis_queued_locks/acquirer/release_all_locks.rbs +8 -1
- data/sig/redis_queued_locks/acquirer/release_lock.rbs +6 -3
- data/sig/redis_queued_locks/acquirer/release_locks_of.rbs +20 -1
- data/sig/redis_queued_locks/acquirer/release_read_lock.rbs +1 -1
- data/sig/redis_queued_locks/client.rbs +21 -2
- data/sig/redis_queued_locks/errors.rbs +5 -2
- data/sig/redis_queued_locks/instrument/sampler.rbs +0 -2
- data/sig/redis_queued_locks/logging/sampler.rbs +0 -2
- data/sig/redis_queued_locks/resource.rbs +7 -1
- data/sig/redis_queued_locks/swarm/flush_zombies.rbs +51 -1
- data/sig/redis_queued_locks/swarm/zombie_info.rbs +58 -7
- data/sig/vendor/redis_client.rbs +2 -0
- metadata +6 -1
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rql-code-review
|
|
3
|
+
description: Code review of redis_queued_locks changes against the project's own invariants and conventions (WATCH/MULTI lock safety, read/write key families, the lock index superset invariant in both lookup modes, one-pass iteration, operation-module independence, long explicit argument lists, log/instrumentation visitors, RBS/Steep, docs sync). Use when the user asks to review / code-review (ревью, код-ревью, проверь изменения) a branch, diff, commit range, PR, staged changes or files of this repository. Also run it automatically on the uncommitted changes when a task in this repository is done and ready to commit, right before asking about the commit (never earlier). Spec files (*_spec.rb) are exempt from style/principle checks until the suite rework; only the coverage of the changed library behavior is checked.
|
|
4
|
+
argument-hint: "[uncommitted | base..head | <commit> | pr <N> | staged | <paths...>]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# redis_queued_locks code review
|
|
8
|
+
|
|
9
|
+
Review a change of this gem the way its maintainer does: lock safety first, then the project invariants
|
|
10
|
+
(read/write locks, lock index, iteration), then architecture, types, docs and style. The rules live in
|
|
11
|
+
`CLAUDE.md`, `.claude/project-overview.md` and `.claude/rules/*.md`; this skill says what to check, where the
|
|
12
|
+
details are, and what must not be reported.
|
|
13
|
+
|
|
14
|
+
- Report defects, with a concrete failure scenario and the rule they break. Every rule-based finding cites its
|
|
15
|
+
source (`acquirer.md#12` = rule 12 of the "Claude rules" list in `.claude/rules/acquirer.md`, `overview "Lock index"`).
|
|
16
|
+
- The review is read-only: don't edit, stage or commit anything. After a review on request, offer to fix the
|
|
17
|
+
findings; the automatic pre-commit run acts on them itself (see "When to run").
|
|
18
|
+
- Write the report from the template that matches the Claude Code `language` setting, English when that language
|
|
19
|
+
has no template (§7); code identifiers, rule names and quotes stay as they are.
|
|
20
|
+
|
|
21
|
+
## When to run
|
|
22
|
+
|
|
23
|
+
- On request: the user asks for a review (any scope of §1).
|
|
24
|
+
- Automatically, as the pre-commit review (`git.md#7`): when a task in this repository (a feature, a fix, a prompt,
|
|
25
|
+
a docs or `.claude` change) is done and committable, at the moment the commit question would be asked or a commit
|
|
26
|
+
authorized in advance would be made (`git.md#8`). Never earlier: not during the implementation, not after
|
|
27
|
+
intermediate edits, not for incomplete or red states (`git.md#9`), not for an explicit commit request.
|
|
28
|
+
- Scope `uncommitted`: the changes of the step (earlier steps are reviewed before their own commits).
|
|
29
|
+
- The completion checks have just run: report their results, don't re-run them. A step without code changes (docs,
|
|
30
|
+
`.claude` files) gets a docs review only: accuracy against the code, the overview and the rules, cross-file
|
|
31
|
+
consistency (§4 J).
|
|
32
|
+
- Blocker/Major findings in the step's own changes mean the step is not complete: fix them as part of the task (the
|
|
33
|
+
review itself stays read-only), re-run the affected checks and review the fixed hunks once more; findings that
|
|
34
|
+
need the user's decision, or remain after that pass, are reported instead of the commit question (`git.md#9`).
|
|
35
|
+
- The report is followed by the commit question (`git.md#7`) with the verdict and the remaining Minor/Nit findings.
|
|
36
|
+
|
|
37
|
+
## 1. Scope
|
|
38
|
+
|
|
39
|
+
| Argument | Diff to review |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `uncommitted` | the changes of the current step: `git diff HEAD` (staged + unstaged) plus untracked files (`git status --porcelain`); the scope of the automatic pre-commit run |
|
|
42
|
+
| none | the branch against `master`: `git merge-base master HEAD`, then `git diff <base>` (committed + staged + unstaged) plus untracked files (`git status --porcelain`). On `master` with a clean tree there is nothing to review: ask for a target |
|
|
43
|
+
| `base..head` / `<commit>` | `git diff base..head` / `git show <commit>` |
|
|
44
|
+
| `staged` | `git diff --cached` |
|
|
45
|
+
| `pr <N>` | `gh pr diff <N>` when `gh` is installed, otherwise `git fetch origin pull/<N>/head` + `git diff master...FETCH_HEAD` (no local branch) |
|
|
46
|
+
| `<paths...>` | the whole files: report real defects and rule violations, but pre-existing known debt (§5) is not a finding |
|
|
47
|
+
|
|
48
|
+
- Start with `git diff --name-status` and `git diff --stat`, record the base sha.
|
|
49
|
+
- Environment artifacts are never part of a change (`git.md#10`): `rbs_collection.lock.yaml` rewritten by
|
|
50
|
+
`rbs collection install`, `coverage/`, `.gem_rbs_collection/`, `.claude/settings.local.json`, `.DS_Store`.
|
|
51
|
+
Flag them only when they are committed or about to be.
|
|
52
|
+
- Big diffs: learn the intent first (commit subjects `git log --format=%s <base>..HEAD`, the CHANGELOG
|
|
53
|
+
`[Unreleased]` and overview hunks), then review in dependency order: `resource.rb`/`config.rb` →
|
|
54
|
+
`acquire_lock/*` → other `acquirer/*` → `client.rb` → `swarm/*` → `sig/` → docs; specs last, for coverage only.
|
|
55
|
+
|
|
56
|
+
## 2. Context to load before judging
|
|
57
|
+
|
|
58
|
+
Read the rule files of the touched paths and the overview sections of the touched topics (lock flow, "Read/Write
|
|
59
|
+
locks", key layout, "Lock index", lock lookup modes, events, errors, conventions):
|
|
60
|
+
|
|
61
|
+
| Changed path | Rule files |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `lib/**` | `logic.md`, `type-checking.md` |
|
|
64
|
+
| `lib/redis_queued_locks/acquirer/**` | + `acquirer.md`, `arguments.md`, `visitors.md` |
|
|
65
|
+
| `client.rb`, `config.rb`, `swarm.rb` | + `arguments.md` |
|
|
66
|
+
| `swarm/**`, `sig/redis_queued_locks/swarm/**` | + `swarm.md` |
|
|
67
|
+
| `logging/**`, `instrument/**` | + `visitors.md` |
|
|
68
|
+
| `sig/**`, `Steepfile`, `rbs_collection.yaml` | `type-checking.md` |
|
|
69
|
+
| `spec/**` | `tests.md` (coverage expectations only, see §3) |
|
|
70
|
+
| commits / messages | `git.md` |
|
|
71
|
+
|
|
72
|
+
- Read every changed method in full, its callers, its RBS signature and every copy of duplicated logic. Copies
|
|
73
|
+
that must change together:
|
|
74
|
+
- write lock info formatting `LockInfo.lock_info` ↔ `Locks.extract_locks_info`; reader formatting
|
|
75
|
+
`LockInfo.read_lock_info` ↔ `Locks.read_lock_info`; request formatting `QueueInfo.queue_info` ↔
|
|
76
|
+
`Queues.extract_queues_info`;
|
|
77
|
+
- `each_lock_name_batch` in `Locks`, `Queues`, `Keys`, `ReleaseAllLocks`, `ReleaseLocksOf`,
|
|
78
|
+
`ClearDeadRequests` (inline iteration in `FlushZombies.flush_lock_index_zombies` and
|
|
79
|
+
`ZombieInfo.each_lock_index_zombie_lock`); `ZombieInfo` lookups mirror `FlushZombies`;
|
|
80
|
+
- `EXTEND_LOCK_PTTL` in `TryToLock` and `ExtendLockTTL`;
|
|
81
|
+
- lock state reply reading in `try_to_lock` Step 0.1 and `precheck_lock` (shared `pipeline_lock_state`,
|
|
82
|
+
position constants, `lock_obstacle`, `log_lock_obstacle`);
|
|
83
|
+
- reserved `meta` keys validation in `AcquireLock` and `LockSeriesPoC`;
|
|
84
|
+
- lock-family options in `Client#lock`, `#lock!`, `#lock_series`, `#lock_series!` and their forwarding in
|
|
85
|
+
`LockSeriesPoC.lock_series_poc`.
|
|
86
|
+
- Mirror path exceptions: `acquirer.rb` → `sig/redis_queued_locks/acquier.rbs`;
|
|
87
|
+
`acquire_lock/yield_expire/log_visitor.rb` → `sig/.../acquire_lock/yield_with_expire/log_visitor.rbs`;
|
|
88
|
+
`lock_series_poc*` has no RBS.
|
|
89
|
+
- Next version for `@since`/`@version`: the version the unreleased work already uses (`git diff <base> -- lib |
|
|
90
|
+
grep -oE '^\+.*@(since|version) [0-9.]+'`, the CHANGELOG `[Unreleased]` code), otherwise the next minor of
|
|
91
|
+
`RedisQueuedLocks::VERSION` (`version.rb`); don't take the maximum over `lib/` (old tags can be wrong).
|
|
92
|
+
A value older than `VERSION` on new code, a method behavior/signature change without the method `@version` bump, or a
|
|
93
|
+
class/module header `@version` that does not match its latest interface change (`logic.md#2`: signatures,
|
|
94
|
+
added/removed methods, `extend`/`include`/`prepend`, the superclass; behavior changes never bump the header) is a
|
|
95
|
+
Minor finding.
|
|
96
|
+
|
|
97
|
+
## 3. Spec files are exempt (until the suite rework)
|
|
98
|
+
|
|
99
|
+
`spec/**/*_spec.rb` are not ideal and are going to be rewritten entirely (header `NOTE` of
|
|
100
|
+
`spec/redis_queued_locks_spec.rb`, overview "Conventions", rspec-retry as temporary debt). Until then any coding
|
|
101
|
+
principle may be broken there. Never report the way specs are written: duplication, long or nested examples, `sleep` waits,
|
|
102
|
+
anonymous fakes, magic numbers, naming, structure, `it`/`specify`, rubocop directives, thread handling,
|
|
103
|
+
cleanup style, retry reliance, file size, the `tests.md` rules on writing specs and its recommendations.
|
|
104
|
+
|
|
105
|
+
Still in scope (the exemption covers how specs are written, not whether the library is tested):
|
|
106
|
+
1. Coverage of the changed library behavior (finding against the `lib/` change, category `test-coverage`):
|
|
107
|
+
new behavior without an example; RW changes without both modes (`tests.md#13`); lock-iterating changes without
|
|
108
|
+
the small-batch examples of both lookups and the recorded-command checks (`acquirer.md#7`); new lock index
|
|
109
|
+
writes without `describe 'lock index'` examples (`acquirer.md#16`); a changed pipeline layout without an updated
|
|
110
|
+
`lock state pipeline layout` spec (`acquirer.md#15`); a touched obtaining transaction that the spec
|
|
111
|
+
`the lock obtaining transaction indexes the lock` no longer exercises (it drops the index entry right before the
|
|
112
|
+
obtaining `MULTI`); a new log/instrumentation event without a fake logger/notifier assertion (`visitors.md#11`).
|
|
113
|
+
2. A changed spec that asserts behavior contradicting the overview/rules, or that can't fail for the behavior
|
|
114
|
+
it claims to check (vacuous expectation, expectation never reached).
|
|
115
|
+
|
|
116
|
+
`spec/spec_helper.rb` and `spec/setup_simplecov.rb` are support files: review them for correctness only (the
|
|
117
|
+
`around` hook that switches `config['lock_index_enabled']` and restores the library default).
|
|
118
|
+
|
|
119
|
+
## 4. Checklist
|
|
120
|
+
|
|
121
|
+
### A. Lock safety and Redis correctness
|
|
122
|
+
- Mutual exclusion only from the lock state checked under `WATCH`/`MULTI` (`logic.md#6`, `#14`); Lua only for the
|
|
123
|
+
existing write TTL extension/decrease, no new Lua in the RW acquisition path unless the user asked. No
|
|
124
|
+
check-then-set in Ruby without a transaction; queues, client clocks, the lock index and the pre-check never
|
|
125
|
+
decide mutual exclusion (the pre-check never obtains the lock).
|
|
126
|
+
- Asymmetric WATCH (`acquirer.md#12`): writers watch `[lock_key, lock_readers_key]`, readers `[lock_key]` only.
|
|
127
|
+
No write to a watched key outside `MULTI` after `WATCH` (aborts the own `EXEC`); never merge `WATCH` into a
|
|
128
|
+
pipeline (unsafe on reconnect).
|
|
129
|
+
- Lock state: write needs no write lock and no live readers; read needs no write lock; same-acquirer cases go
|
|
130
|
+
through `conflict_strategy`; read→write upgrade never works through (`:conflict_lock_upgrade`, waits with
|
|
131
|
+
`:wait_for_lock`).
|
|
132
|
+
- Time: reader liveness and expirations in Redis time (`TIME` → `Resource.redis_time_ms`, `PEXPIRE`); client
|
|
133
|
+
`Time.now.to_f` only for queue positions, `ts` fields, zombie probes; lock index scores in
|
|
134
|
+
`Resource.lock_index_time` (epoch ms) + grace. Readers are live only while `score > now`: never decide by
|
|
135
|
+
`ZCARD`/`EXISTS` alone.
|
|
136
|
+
- TTLs: max-TTL keys (registry, reader data, queues) get the `PEXPIRE NX` + `PEXPIRE GT` pair (`GT` alone sets
|
|
137
|
+
nothing on a key without TTL); `PTTL` sentinels handled (`-2` missing, `-1` → `Float::INFINITY` / `'+inf'`);
|
|
138
|
+
units right (ttl ms, `queue_ttl` s → `(queue_ttl * 1_000).ceil`, `zombie_ttl` ms → `/ 1_000.0`), integers for
|
|
139
|
+
`PEXPIRE`.
|
|
140
|
+
- Replies read by position (`acquirer.md#15`): `NOTE: (pipeline layout)` next to the commands, conditional
|
|
141
|
+
commands form a tail after the unconditional ones and are read under the same condition, position constants
|
|
142
|
+
(`LOCK_STATE_*`, `QUEUE_HEAD_REPLY`, `KEY_STATE_REPLIES`, `LOCK_STATE_REPLIES`) and the layout spec updated;
|
|
143
|
+
the reentrant write extension check `result.size == (lock_index_enabled ? 5 : 4)` follows its `MULTI`.
|
|
144
|
+
- Round trips of the hot path must not regress silently (overview "Lock acquisition flow"): `lock {}` = 4, a
|
|
145
|
+
failed attempt = 3, a failed retry = 1 (pre-check), `fail_fast` on an obtained lock = 1, `unlock` without
|
|
146
|
+
readers = 1. Independent reads share one `pipelined` round trip (also inside `multi(watch:)`).
|
|
147
|
+
- New destructive paths (delete/shorten a lock) check the owner, unless the change documents the trade-off as
|
|
148
|
+
accepted by the user (like the known limitations in §5).
|
|
149
|
+
- Shared mutable state behind `Utilities::Lock#synchronize` (`logic.md#9`).
|
|
150
|
+
|
|
151
|
+
### B. Read/write awareness (`logic.md#13`, `acquirer.md#12`)
|
|
152
|
+
- Every operation over locks, queues, zombies or lock info covers `rql:lock:*`, `rql:lock_queue:*`,
|
|
153
|
+
`rql:lock_readers:*`, `rql:lock_read_queue:*` and `rql:lock_reader:<name>:<acq_id>`; every attempt prunes dead
|
|
154
|
+
requests in both queues; dequeue/cleanup removes the request from the queue of its mode.
|
|
155
|
+
- Write-only result shapes stay unchanged; read keys/fields appear only when read data exists.
|
|
156
|
+
- `read_lock_key` is `nil` for write requests; reader data is a data carrier only (the registry decides existence),
|
|
157
|
+
without `acq_id`/`hst_id`; don't store derivable values; a new lock field updates every formatting copy (§2,
|
|
158
|
+
`acquirer.md#11`); `rw_mode` stays a reserved `meta` key.
|
|
159
|
+
- Backward compatibility: data written by older versions (readers with `acq_id`/`hst_id`, names without index
|
|
160
|
+
entries) is still read correctly; a key/data format change states the rolling-deploy requirement in README and
|
|
161
|
+
CHANGELOG.
|
|
162
|
+
|
|
163
|
+
### C. Lock index, both lookup modes (`acquirer.md#16`, overview "Lock index")
|
|
164
|
+
- Disabled mode (default): no lock index command at all (`LOCK_INDEX_KEY` only under `if lock_index_enabled` /
|
|
165
|
+
after `return unless lock_index_enabled`), no `ZSCAN`, queues without TTL. Enabled mode: no keyspace `SCAN`
|
|
166
|
+
except `ReindexLocks`.
|
|
167
|
+
- Superset invariant: every key-creating command is followed by `ZADD <index> GT <now + lifetime + grace> <name>`
|
|
168
|
+
in the same pipeline/transaction, after the key command; the obtaining `MULTI` indexes the lock itself; every
|
|
169
|
+
extension is followed by `ZADD XX INCR`; scores never decrease; names are removed only in the same `MULTI`
|
|
170
|
+
that deletes all their keys (`ReleaseLocksOf`, `YieldExpire` releases and `FlushZombies` never remove names).
|
|
171
|
+
- Lock index commands are conditional tails: reply positions never depend on the mode.
|
|
172
|
+
- A new key family: distinct prefix `rql:lock_<kind>:<name>` (never a suffix of the name), bounded TTL in the
|
|
173
|
+
enabled mode, covered by the index score, `Resource.lock_key_family`/`lock_name_from_key`, both lookups and
|
|
174
|
+
`ReindexLocks`.
|
|
175
|
+
- `lock_index_enabled` is client-wide (`arguments.md#9`): read from `config` inside `Client`/`Swarm` and forwarded,
|
|
176
|
+
never a keyword of a public method; the flusher Ractor gets a copy. `lock_index_grace_period` is forwarded by
|
|
177
|
+
`lock`, `lock!`, `lock_series`, `lock_series!`, `reindex_locks`. Pruning (`ZREMRANGEBYSCORE <index> -inf
|
|
178
|
+
<now - 1>`) stays in the queueing pipeline, `flush_zombies`, `clear_locks`, `clear_dead_requests`.
|
|
179
|
+
|
|
180
|
+
### D. Iteration and performance (`acquirer.md#7`, `arguments.md#7`)
|
|
181
|
+
- One pass per operation: one `SCAN` over `KEY_PATTERN`/`ALL_LOCK_QUEUES_PATTERN` classified by
|
|
182
|
+
`Resource.lock_key_family`, or one `ZSCAN` via `each_lock_name_batch` (expired scores skipped). Never `KEYS`,
|
|
183
|
+
never one round trip per key; per-key commands in pipelined batches collected across pages, plus the
|
|
184
|
+
remainder batch.
|
|
185
|
+
- A new lock-iterating operation implements both lookups side by side, chooses the lookup in one place
|
|
186
|
+
(`lock_index_enabled ? index_x : scan_x` or one `if`/`else` with `(lock index)`/`(keyspace)` step comments) and
|
|
187
|
+
keeps the shared processing in one copy.
|
|
188
|
+
- No `EXISTS` next to `ZRANGE`/`HGETALL`: Redis never keeps empty sorted sets/hashes.
|
|
189
|
+
- Hot path (`AcquireLock`, `TryToLock`, `YieldExpire`, `DequeueFromLockQueue`, visitor call sites): no wrapper
|
|
190
|
+
objects, hash merging, splats to build arguments, closures for argument plumbing, new `{}` per attempt (use
|
|
191
|
+
`NO_LOCK_DATA`); keys computed once in `acquire_lock`; log-only Redis reads gated at the call site
|
|
192
|
+
(`acquirer.md#13`); no `Set` splatted in a loop.
|
|
193
|
+
|
|
194
|
+
### E. Architecture and API design
|
|
195
|
+
- `Client` only fills defaults from `config['...']` and delegates (`logic.md#3`); an `x!` variant exists only when it
|
|
196
|
+
must raise and repeats the full keyword list.
|
|
197
|
+
- No parameter objects, `Struct`/`Data.define`/`OpenStruct`, builders, `**opts`, `options = {}`, `...` forwarding
|
|
198
|
+
(`arguments.md#1`, `#3`); a new public option goes to every `Client` variant with a `config` default
|
|
199
|
+
(`arguments.md#2`, `#10`) and is forwarded by name with shorthand; defaults live only in `Client`, internal
|
|
200
|
+
params are required (`arguments.md#4`); positional order: subject → domain data → observability tail
|
|
201
|
+
`logger, instrumenter, instrument, log_sampling_*, log_sample_this, instr_sampling_*, instr_sample_this`, lock key
|
|
202
|
+
params `lock_key, read_write_mode, lock_key_queue, read_lock_key_queue, lock_readers_key, read_lock_key, lock_name,
|
|
203
|
+
acquirer_id, host_id, ...`, TTL params `ttl, queue_ttl, lock_index_enabled, lock_index_grace_period`
|
|
204
|
+
(`arguments.md#5`); every call site and the RBS updated with the signature.
|
|
205
|
+
- Operation modules are independent (`acquirer.md#14`): no calls to another `Acquirer::*` module and no references to
|
|
206
|
+
its RBS types (exceptions: the `AcquireLock` core, `*PoC` modules); shared logic only via `Resource`/`Utilities`;
|
|
207
|
+
a duplicate keeps the original name and shape with a `NOTE` that it is duplicated on purpose; every copy changes
|
|
208
|
+
in the same change. Don't copy the `WithAcqTimeout` → `LockInfo`/`QueueInfo` debt.
|
|
209
|
+
- New operation: `acquirer/<snake_name>.rb` with one public `class << self` entry named after the file and private
|
|
210
|
+
helpers, `require_relative` in `acquirer.rb`, thin `Client` method, RBS, spec, README/CHANGELOG
|
|
211
|
+
(`acquirer.md#1`). Mutating operations follow the two-layer pattern (timing wrapper + private `fully_*`,
|
|
212
|
+
`acquirer.md#2`).
|
|
213
|
+
- Results: `{ ok:, result: }` only at the public boundary (`Client`, `Acquirer::*`, `Swarm` facade actions) with
|
|
214
|
+
Symbol statuses and abbreviated Symbol keys (`rel_*`, `*_cnt`, `*_time`); info queries return a String-keyed
|
|
215
|
+
Hash/Set or `nil`; swarm element internals return bare scalars (`swarm.md#15`). Raise only in `x!` methods, the
|
|
216
|
+
`raise_errors` path of `AcquireLock` and on invalid arguments (`RedisQueuedLocks::ArgumentError`), with the lock
|
|
217
|
+
key / acquirer id in the message.
|
|
218
|
+
- Keys only via `Resource.prepare_*`, `*_PATTERN`, `SWARM_KEY`, `LOCK_INDEX_KEY` (`logic.md#5`): flag every new
|
|
219
|
+
`'rql:...'` literal outside `resource.rb`.
|
|
220
|
+
- Config: `setting` + `validate` in `config.rb` (booleans `val == true || val == false`, Integers with ranges; per-call
|
|
221
|
+
numeric options validated with the same ranges in the operation module, `logic.md#7`), dotted
|
|
222
|
+
keys for groups, unit in a trailing `# NOTE:` comment, read as `config['key']` (`logic.md#7`). Errors:
|
|
223
|
+
`class XError < Error; end` in `errors.rb`, each under the global base of its branch (`Error`, `ArgumentError`,
|
|
224
|
+
`Exception`: super exceptional cases only, with the reason in the comment; `logic.md#15`). Runtime dependencies:
|
|
225
|
+
`redis-client` only; dev gems in `Gemfile`.
|
|
226
|
+
- `AcquireLock`: new behavior as a new step or mixin with `# Step N.x` numbering and consistent `acq_process` keys
|
|
227
|
+
(`acquirer.md#10`); `lock_series_poc.rb` is experimental: don't accept new features built on it without the
|
|
228
|
+
user's request.
|
|
229
|
+
|
|
230
|
+
### F. Swarm, Ractors, threads (`swarm.md`)
|
|
231
|
+
- Redis work in stateless class functions shared by the main loop and the manual API (`#1`); main loops build
|
|
232
|
+
their own client with `RedisClientBuilder` inside the loop thread (`#4`).
|
|
233
|
+
- Into `Ractor.new` only copied plain values (`config.slice(...)`, scalars, `lock_index_enabled`): no `rql_client`,
|
|
234
|
+
logger, Redis client; inside only shareable constants and module functions (`#4`, `#5`).
|
|
235
|
+
- Ports (`#13`): results port created in the main Ractor, commands port inside the element Ractor, `monitor`
|
|
236
|
+
registered before waiting, one reply per command, Symbol messages reserved for death notices; no
|
|
237
|
+
`Ractor.receive`/`Ractor#send`/default port; names end in `_port`, block params `r_res_p`/`r_com_p`.
|
|
238
|
+
- Threads: kill + join inside the Ractor (`#14`), `abort_on_exception = false` for main loops, no retry loops in
|
|
239
|
+
elements (`#8`); state changes and request/reply pairs inside `sync.synchronize` (`#6`); the supervisor is
|
|
240
|
+
stopped before elements are killed or re-created (`#9`).
|
|
241
|
+
- Lifecycle names and semantics identical in both bases (`#7`); a new element is registered everywhere (`#3`);
|
|
242
|
+
status shapes `{ running:, state: }` with `'non_initialized'` and their RBS (`#10`).
|
|
243
|
+
|
|
244
|
+
### G. Logging and instrumentation (`visitors.md`)
|
|
245
|
+
- Logs/events of `AcquireLock` and its mixins and of `LockSeriesPoC` go through visitor methods (`#1`); mutation
|
|
246
|
+
modules write one log line and one event inline inside `run_non_critical`, gated by `log_sampled`/`instr_sampled`
|
|
247
|
+
(`acquirer.md#9`).
|
|
248
|
+
- Observability coverage (`logic.md#16`): a new or changed logic that creates/changes/deletes keys without logs and
|
|
249
|
+
instrumentation, a mutation event/log that times an internal part instead of the whole root operation, a mutation
|
|
250
|
+
payload without `instrument`, a `Client` mutation method without the observability keywords, or instrumented lock
|
|
251
|
+
info queries are findings; the swarm elements are known debt.
|
|
252
|
+
- Visitor shape: `class << self`, method name = event name (`__` between phase and detail), same name in the
|
|
253
|
+
Log and Instr visitors, first line `return unless log_sampled` (`&& log_lock_try` for `TryToLock` steps), one
|
|
254
|
+
`logger.debug do ... end rescue nil` with `"[redis_queued_locks.<event>] "` and `key => 'value'` pairs, or
|
|
255
|
+
`instrumenter.notify('redis_queued_locks.<event>', { ... }) rescue nil` with shorthand Symbol keys (`#5`-`#9`).
|
|
256
|
+
- Parameter order: port, sampled flag, extra gates, `lock_key`, data; `rw_mode` after `access_strategy` (logs) or
|
|
257
|
+
after `lock_key` (instrumentation), user `instrument` last (`#6`). `rw_mode` is the requested mode in
|
|
258
|
+
`AcquireLock`/`TryToLock`/`DequeueFromLockQueue`, the held mode in `YieldExpire`, the series mode in `LockSeriesPoC`.
|
|
259
|
+
- Sampling decided once per operation (`Logging.should_log?` / `Instrument.should_instrument?`) and passed down (`#7`).
|
|
260
|
+
- Event names and payload keys are public API: no rename/removal without the user's request; no new event on
|
|
261
|
+
the default write success path (`#13`); new or changed log keys/payload keys go to README `## Logging` /
|
|
262
|
+
`### Instrumentation Events` and CHANGELOG (`#14`).
|
|
263
|
+
|
|
264
|
+
### H. Types (`type-checking.md`)
|
|
265
|
+
- Every changed method, param, return shape, constant or ivar is mirrored in `sig/` (`#1`): nested module blocks,
|
|
266
|
+
`use RedisQueuedLocks as RQL` / `use RedisClient as RC`, shared types (`RC::client`, `RQL::loggerObj`,
|
|
267
|
+
`RQL::instrObj`, `samplerObj`), `type xxxResult = { ok: bool, result: ... }` for public results, literal types for
|
|
268
|
+
swarm internals, `Hash[String, ...]` instead of String-keyed records (they crash Steep).
|
|
269
|
+
- `# steep:ignore` only where Steep reports a diagnostic (strict mode fails on redundant ignores); prefer
|
|
270
|
+
`# @type var x: T` / `#: T` and tuple annotations of pipeline results; a needed ignore without an honest
|
|
271
|
+
annotation carries `NOTE: (steep)`; never shape runtime code around Steep (`#5`). `Steepfile` is never loosened (`#6`).
|
|
272
|
+
- RBS must match runtime values (runtime type checks run the suite): e.g. `Integer|Float` where a Float can arrive.
|
|
273
|
+
|
|
274
|
+
### I. Ruby style of the codebase
|
|
275
|
+
- `# frozen_string_literal: true`; compact constant paths (`module RedisQueuedLocks::Acquirer::Keys`); namespace
|
|
276
|
+
files only `require_relative` (`logic.md#1`).
|
|
277
|
+
- YARD on every class, module, constant, attr and method: `@param`/`@option`/`@return` (`Format: { ... }` for hash
|
|
278
|
+
results), blank `#`, `@api public|private`, `@since <next>`; behavior/signature changes bump `@version <next>` on
|
|
279
|
+
the method; the module/class header `@version` changes only with the interface (signatures, added/removed methods,
|
|
280
|
+
`extend`/`include`/`prepend`, the superclass), never for behavior changes inside the methods (`logic.md#2`).
|
|
281
|
+
- Comments: `# NOTE:` with `# - item;` lists, `# Step N.x` in algorithms, `(RW)`/`(lock index)`/`(keyspace)`
|
|
282
|
+
tags, `NOTE: (pipeline layout)`/`(transaction layout)` for positional replies.
|
|
283
|
+
- Redis: uppercase String commands, String numeric args (`'0'`, `'-1'`, `'-inf'`, `'+inf'`), `redis.with do |rconn|`
|
|
284
|
+
for multi-command work, `multi do |transact|`, `pipelined do |pipeline|`, named reply variables (`hget_cmd_res`,
|
|
285
|
+
`lock_state`), Lua as a frozen `<<~LUA_SCRIPT.strip.tr("\n", '').freeze` constant (`acquirer.md#5`, `#6`).
|
|
286
|
+
- Normalization with `Float(...)`/`Integer(...)` inside `tap do |lock_data|`, optional fields guarded with
|
|
287
|
+
`if lock_data['x']` (`acquirer.md#11`).
|
|
288
|
+
- Idioms: `x == nil`/`x != nil` or truthiness (not `.nil?`); `raise(Klass, "message")` with `\` continuations;
|
|
289
|
+
subject-less `case` with `start_with?` instead of regexps on hot paths; shorthand `key:` forwarding;
|
|
290
|
+
`clock_gettime` (µs) for durations reported as `((finish - start) / 1_000.0).ceil(2)` ms, `Time.now.to_f` only for
|
|
291
|
+
`at:`/`ts`; names `rconn`, `transact`, `pipeline`, `*_key`, `*_queue`, `acquirer_id`/`host_id` (`acq_id`/`hst_id`
|
|
292
|
+
in data, logs and payloads), private `fully_*`, `scan_*`/`index_*` lookups, `swarmed__*` predicates.
|
|
293
|
+
- Rubocop: only specific `Metrics`/`Layout` cops disabled locally with a paired `enable`, never
|
|
294
|
+
`# rubocop:disable all` in new code (`logic.md#10`); long signatures are not "fixed" (`arguments.md#8`).
|
|
295
|
+
- Suggestions only (`logic.md` recommendations, not violations): correctly spelled new identifiers (don't copy the
|
|
296
|
+
known typos), `then` instead of `yield_self` in new code.
|
|
297
|
+
|
|
298
|
+
### J. Docs and process
|
|
299
|
+
- The same change updates `.claude/project-overview.md` and the affected `.claude/rules/*.md` for every new/changed
|
|
300
|
+
key, option, result shape, event, algorithm step or limitation (`logic.md#12`), README and CHANGELOG
|
|
301
|
+
`[Unreleased]` for user-visible behavior, and the YARD of the touched `Client` methods.
|
|
302
|
+
- Commits under review (`git.md`): subject only `[<scope>] <summary>`, lowercase, imperative, no trailing period,
|
|
303
|
+
no body except the attribution trailer, never on `master`.
|
|
304
|
+
|
|
305
|
+
## 5. Do not report
|
|
306
|
+
|
|
307
|
+
- Settled design (overview, `acquirer.md`, `swarm.md`): long explicit parameter lists and disabled
|
|
308
|
+
`Metrics/ParameterLists`; logic duplicated between operation modules (report only diverged copies);
|
|
309
|
+
read→write upgrade as an error unless `:wait_for_lock`; nested reads through `conflict_strategy`; no fairness
|
|
310
|
+
between modes with `:random`; best-effort cross-host FIFO; no Lua in the RW path; one mode per lock series;
|
|
311
|
+
`lock_index_enabled` as a config-only mode; the lock index defaulting to `false`.
|
|
312
|
+
- Unused `Resource` methods marked `# NOTE: (debug)` (manual runtime checks, `logic.md#17`); report a debug method
|
|
313
|
+
only when it is wrong for a key family it can get.
|
|
314
|
+
- Known limitations kept by the user: `YieldExpire` write release (`EXPIRE 0`) and ttl decrease without an owner
|
|
315
|
+
check; `HMGET`→`DEL` without `WATCH` in `ReleaseLocksOf`/`FlushZombies`; `unlock` dropping the data of a reader
|
|
316
|
+
re-acquired between its two round trips; `zombie_acquirers` vs `flush_zombies` acquirer sets.
|
|
317
|
+
- Known debt: `WithAcqTimeout` → `LockInfo`/`QueueInfo`; rspec-retry, disabled coverage minimum, non-blocking runtime
|
|
318
|
+
type checks; `acquier.rbs` and the `yield_with_expire` RBS path; the `swarm_element__termiante` typo;
|
|
319
|
+
`sleep(0.1)` readiness waits and missing logging in the swarm;
|
|
320
|
+
one flusher per process; `EVAL` without `SCRIPT LOAD`; `# rubocop:disable all` around lock series.
|
|
321
|
+
- "Recommendations (proposed, not yet project policy)" in the rule files: never violations; mention one only as an
|
|
322
|
+
optional suggestion for touched code.
|
|
323
|
+
- Pre-existing problems outside the diff: out of scope; mention a severe one briefly in the template's
|
|
324
|
+
"outside the diff" section.
|
|
325
|
+
- Anything in `*_spec.rb` except §3.
|
|
326
|
+
|
|
327
|
+
## 6. Verify every finding
|
|
328
|
+
|
|
329
|
+
- Confirm it in the code (call sites, copies, RBS, specs), not by pattern match: concurrency claims name the
|
|
330
|
+
interleaving (acquirer A at step X, B at step Y) and why `WATCH` doesn't catch it; layout claims count the
|
|
331
|
+
commands and positions; mode claims check both `lock_index_enabled` values and both `read_write_mode` values.
|
|
332
|
+
- Drop findings contradicted by §5, by a design `NOTE` in the code or by the overview. Mark the rest
|
|
333
|
+
`CONFIRMED` (traced end to end) or `PLAUSIBLE` (depends on an assumption: name it).
|
|
334
|
+
- Helpful greps:
|
|
335
|
+
- added key literals and scans (outside `resource.rb` every `'rql:` literal is a finding):
|
|
336
|
+
`git diff <base> -U0 -- lib | grep '^+' | grep -v '^+\s*#' | grep -n "'rql:\|\"rql:\|'KEYS'\|'SCAN'\|\.scan(\|\.zscan("`;
|
|
337
|
+
- `git grep -n LOCK_INDEX_KEY -- lib`: every use reachable only with `lock_index_enabled` (an `if`/`return unless`
|
|
338
|
+
guard or a lock index lookup branch), except `ReindexLocks` and `Resource`;
|
|
339
|
+
- cross-module calls: `git grep -n "RedisQueuedLocks::Acquirer::" -- lib/redis_queued_locks/acquirer` (only the
|
|
340
|
+
known `WithAcqTimeout` debt and `LockSeriesPoC` are allowed);
|
|
341
|
+
- `git diff --name-only <base>` against the `sig/` mirror (§2 exceptions); `git diff --check <base>`.
|
|
342
|
+
- Checks (read-only): a review on request of local changes runs `bundle exec rake rubocop` and
|
|
343
|
+
`bundle exec rbs collection install && bundle exec rake steep:check` (grep the output for `ERROR`/`FATAL`, restore a
|
|
344
|
+
rewritten `rbs_collection.lock.yaml` with `git checkout -- rbs_collection.lock.yaml`); the automatic pre-commit run
|
|
345
|
+
reports the completion checks instead of re-running them (see "When to run"). Don't run `rake rspec` without
|
|
346
|
+
asking: it needs a local Redis, `FLUSHDB`s db 0 and db 1 and takes ~6 minutes.
|
|
347
|
+
|
|
348
|
+
## 7. Report
|
|
349
|
+
|
|
350
|
+
### Template language
|
|
351
|
+
The report template is chosen by the Claude Code `language` setting:
|
|
352
|
+
1. Take the effective value: the first settings file that defines `language`, in precedence order (run from the
|
|
353
|
+
repository root; Ruby is always available here, `jq` may be not):
|
|
354
|
+
```sh
|
|
355
|
+
for f in "/Library/Application Support/ClaudeCode/managed-settings.json" /etc/claude-code/managed-settings.json \
|
|
356
|
+
.claude/settings.local.json .claude/settings.json ~/.claude/settings.json; do
|
|
357
|
+
[ -f "$f" ] && v=$(ruby -rjson -e 'print JSON.parse(File.read(ARGV[0]))["language"].to_s' "$f" 2>/dev/null) \
|
|
358
|
+
&& [ -n "$v" ] && { echo "$v"; break; }
|
|
359
|
+
done
|
|
360
|
+
```
|
|
361
|
+
Settings passed with `--settings` override these files; the session's response-language instruction (it comes
|
|
362
|
+
from the same setting) then shows the effective value.
|
|
363
|
+
2. Map the value case-insensitively to a template in `.claude/skills/rql-code-review/templates/`:
|
|
364
|
+
|
|
365
|
+
| `language` value | Template |
|
|
366
|
+
|---|---|
|
|
367
|
+
| Russian: `russian`, `русский`, `ru`, `ru-RU`, `ru_RU`, ... | `report.ru.md` |
|
|
368
|
+
| English (`english`, `en`, `en-US`, ...), any language without a template (`japanese`, `de`, ...), no value | `report.en.md` |
|
|
369
|
+
|
|
370
|
+
3. Read the chosen template and fill it in. The whole report (headings, labels, verdict, findings) is written in the
|
|
371
|
+
template language; code identifiers, rule names, quotes and the `CONFIRMED`/`PLAUSIBLE` markers stay as they
|
|
372
|
+
are. Text outside the report (the offer to fix) follows the session language.
|
|
373
|
+
|
|
374
|
+
Both templates have the same structure: change them together. A new language = a new `report.<code>.md` with the
|
|
375
|
+
same structure plus a row in the table above.
|
|
376
|
+
|
|
377
|
+
### Severity and order
|
|
378
|
+
- Blocker: breaks mutual exclusion or the lock index superset, loses or leaks locks/data, crashes, breaks the public
|
|
379
|
+
API (method, result shape, event, config key) or turns CI red.
|
|
380
|
+
- Major: a rule violation with a real consequence: a missed read key family or lookup mode, per-key round trips, a
|
|
381
|
+
hot-path regression, broken module independence, a missing RBS/docs update for a changed contract, new
|
|
382
|
+
behavior without specs.
|
|
383
|
+
- Minor: convention deviations with little impact (YARD tags, comment format, naming, idioms).
|
|
384
|
+
- Nit: optional polish.
|
|
385
|
+
- Order by severity, then by file. Empty sections are omitted; with no findings the verdict says so and the sync and
|
|
386
|
+
checks sections list what was checked.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
## Review: <scope> (<N> files, base <sha>)
|
|
2
|
+
**Verdict:** <blockers found | mergeable after fixes | no findings> — <one sentence why>
|
|
3
|
+
|
|
4
|
+
### Blockers
|
|
5
|
+
1. `<path>:<line>` — <the defect in one sentence>. [CONFIRMED | PLAUSIBLE: <assumption>]
|
|
6
|
+
Scenario: <input / state / interleaving → wrong result>.
|
|
7
|
+
Rule: <`acquirer.md#16` | overview "Lock index">.
|
|
8
|
+
Fix: <what to do>.
|
|
9
|
+
|
|
10
|
+
### Major
|
|
11
|
+
<findings in the same format>
|
|
12
|
+
|
|
13
|
+
### Minor
|
|
14
|
+
<findings in the same format>
|
|
15
|
+
|
|
16
|
+
### Nits
|
|
17
|
+
<findings in the same format>
|
|
18
|
+
|
|
19
|
+
### Docs and types sync
|
|
20
|
+
- RBS: <ok | what is missing>
|
|
21
|
+
- overview and rules: <ok | what is missing>
|
|
22
|
+
- README and CHANGELOG: <ok | what is missing>
|
|
23
|
+
- YARD (`@since`/`@version`): <ok | what is missing>
|
|
24
|
+
|
|
25
|
+
### Checks
|
|
26
|
+
- rubocop: <result>
|
|
27
|
+
- steep: <result>
|
|
28
|
+
- rspec: <not run (reason) | result>
|
|
29
|
+
|
|
30
|
+
### Outside the diff and suggestions
|
|
31
|
+
- <a severe problem outside the diff or an optional suggestion>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
## Ревью: <scope> (<N> файлов, base <sha>)
|
|
2
|
+
**Вердикт:** <есть блокеры | можно мержить после правок | замечаний нет> — <одна фраза почему>
|
|
3
|
+
|
|
4
|
+
### Блокеры
|
|
5
|
+
1. `<path>:<line>` — <дефект одним предложением>. [CONFIRMED | PLAUSIBLE: <допущение>]
|
|
6
|
+
Сценарий: <входные данные / состояние / интерливинг → неверный результат>.
|
|
7
|
+
Правило: <`acquirer.md#16` | overview "Lock index">.
|
|
8
|
+
Исправление: <что сделать>.
|
|
9
|
+
|
|
10
|
+
### Важные
|
|
11
|
+
<находки в том же формате>
|
|
12
|
+
|
|
13
|
+
### Мелкие
|
|
14
|
+
<находки в том же формате>
|
|
15
|
+
|
|
16
|
+
### Придирки
|
|
17
|
+
<находки в том же формате>
|
|
18
|
+
|
|
19
|
+
### Синхронизация документации и типов
|
|
20
|
+
- RBS: <ок | чего не хватает>
|
|
21
|
+
- overview и rules: <ок | чего не хватает>
|
|
22
|
+
- README и CHANGELOG: <ок | чего не хватает>
|
|
23
|
+
- YARD (`@since`/`@version`): <ок | чего не хватает>
|
|
24
|
+
|
|
25
|
+
### Проверки
|
|
26
|
+
- rubocop: <результат>
|
|
27
|
+
- steep: <результат>
|
|
28
|
+
- rspec: <не запускался (причина) | результат>
|
|
29
|
+
|
|
30
|
+
### Вне диффа и предложения
|
|
31
|
+
- <серьёзная проблема вне диффа или необязательное предложение>
|