Pre-PR Check: LMCache Coding Standard Compliance
Check the developer's local changes against the coding standards defined in
docs/coding_standards.md and modify the code to fix compliance issues, so the
PR is clean before it is opened.
This skill is meant to be run before /create-pr.
Arguments
--scope (default: branch)
uncommitted -- check only unstaged + staged changes vs HEAD
staged -- check only staged changes
branch -- check all commits on the current branch not yet in dev (the
full PR diff). This is the default since that matches what a reviewer sees.
--fix (default) -- automatically fix issues that can be fixed mechanically
--check-only -- only report issues, do not modify files
Workflow
Step 0 -- Load the standard
Read docs/coding_standards.md at the repo root. This is the authoritative reference.
Step 1 -- Detect the scope of changes
Run git status --short and git branch --show-current to orient yourself.
Then, based on --scope, compute the diff:
| Scope |
Command |
uncommitted |
git diff HEAD |
staged |
git diff --cached |
branch (default) |
git diff dev...HEAD |
Also list changed files:
| Scope |
Command |
uncommitted |
git diff --name-only HEAD |
staged |
git diff --cached --name-only |
branch |
git diff --name-only dev...HEAD |
If there are no changes in the selected scope, report that and stop.
Step 2 -- Read the changed files
For each changed file, read the current content (not just the diff) so you can
make accurate edits. Focus on:
.py files -- full coding-standard review
.md, .rst -- documentation review (brief)
.cpp, .cu, .rs -- light review (style only; clang-format / cargo fmt handle most of it)
For each changed file, also identify the changed public symbols (functions,
methods, classes, fields whose signature, return type, raised exceptions, or
behavior contract changed). You will use these in Step 4 to find and inspect callers.
For large changes, delegate to the Explore agent to summarize per-file
responsibilities before reviewing.
Step 3 -- Run the checks
For each changed Python file, check the following (from docs/coding_standards.md):
A. Mechanical issues (fix automatically unless --check-only)
These are straightforward to fix:
- License header (Section 1.3): Missing
# SPDX-License-Identifier: Apache-2.0 on line 1 of new Python files.
assert used for runtime validation (Section 2.2): Replace with if <not cond>: raise ValueError(<clear msg>).
Optional usage (Section 2.1): Where a value is always initialized, remove the Optional wrapper and initialize directly. Where None is genuinely possible, rewrite as X | None.
- Bare generics (Section 2.1): Replace bare
list, dict, tuple, set in annotations with parameterized forms (list[T], dict[K, V]).
- Lazy imports (Section 7.2): Move imports to the top of the file, under the correct section heading. Preserve
torch-before-native-C-extension ordering.
- Bare
logging.getLogger(__name__) (Section 7.4): Replace with init_logger(__name__) from lmcache.logging.
- Operational logs at
INFO level (Section 7.4): If a log is about store/retrieve/task progress, change logger.info(...) to logger.debug(...).
- Inappropriate
WARNING (Section 7.4): For log messages about benign concurrent conditions (e.g., "key not found, this should not happen" in a code path documented as not holding the global lock), downgrade to debug and fix the misleading message.
B. Docstring issues (fix when content is clear; ask when content is unclear)
- Missing docstrings on public/modified functions (Section 3.1-3.2):
- If the function is simple and its purpose is obvious from the code, draft a full docstring (summary, args, returns, raises).
- If behavior is non-obvious, draft a docstring with clear sections but add a
TODO: confirm note where you are uncertain, and flag it in the report.
- Docstring accuracy (Section 3.3): If you added a parameter but the docstring still describes the old signature, update it. If a parameter is currently a no-op (accepted but ignored), the docstring must say so explicitly.
- Missing type hints on function args or return values (Section 2.1): Add them based on the function's body.
C. Design issues (flag; do NOT silently refactor)
These are judgment calls and should be reported, not auto-fixed:
- Cross-class private member access (Section 4.4): Accessing
other._private outside the defining class.
- Ambiguous return values (Section 4.3): Function returns
None for multiple distinct meanings.
- Boolean parameters on public APIs (Section 4.3).
- New features without tests (Section 5.2).
- Bug fixes without regression tests (Section 5.2).
- Unbounded collection growth (Section 7.6): A set/dict that is appended to without cleanup.
- Error paths that leave state inconsistent (Section 7.5): Exceptions that skip lock release, FD close, or pool free.
- Missing design doc for non-trivial new features (Section 5.1).
- PR scope too large (Section 1.1): If the diff touches many unrelated modules, suggest how to split.
- Lock protocol violations (Section 8): Debug methods not holding
self._lock; shared state accessed without synchronization.
Step 4 -- Apply fixes
For category A and B issues (unless --check-only):
- Use
Edit to make precise, minimal changes.
- Preserve existing comments and code style.
- After fixes, re-read the file to confirm the edit applied cleanly.
For category C issues: do NOT auto-fix. Report them for the developer to address.
Step 5 -- Caller Impact Analysis (Section 6.2 of the standard)
Even when the diff itself looks clean, a change can silently break unchanged
callers. Before declaring the changes ready for PR, verify the global view for
each changed public symbol identified in Step 2.
Triggers -- run this step if the diff does any of:
- Adds, removes, renames, or reorders parameters of a public function/method
- Changes parameter types, defaults, or accepted domains (e.g., adds
None to accepted values, narrows a type)
- Changes the return type or the meaning of sentinel returns (e.g., what
None means)
- Adds, removes, or changes raised exceptions
- Changes side effects, ordering, blocking behavior, or thread-safety guarantees
- Modifies a method on a base class or protocol (subclasses are callers)
- Changes an invariant of a class field accessed by public API
If none of the above applies, skip this step.
How to run it:
- For each changed public symbol,
Grep its name across the whole repo
(production code, tests/, benchmarks/, lmcache/integration/, docs/).
Do not limit to files in the diff.
- For commonly-named symbols with many false matches, use
Grep -A 2 -B 2
for context, or delegate to the Agent (Explore) tool with a focused prompt
(e.g., "find all callers of LMCacheMPWorkerAdapter.submit_store_request
and summarize the arguments each caller passes").
- For base-class / protocol changes, also grep for subclasses
(
class \w+\(<BaseName>).
For each caller, decide:
Broken and must be fixed in this PR (category A-like, fix it):
- Removed / renamed parameter that the caller passes by keyword
- Reordered positional parameters the caller relies on
- Return type change that the caller destructures (e.g., tuple shape change)
- Newly raised exception the caller doesn't handle, when the caller is on the normal success path
For these, update the caller in the same pass as the main change. Apply the
edit using Edit, then re-read to confirm. If the caller is in an unrelated
module and the fix is non-trivial, flag it for manual review instead.
Behaviorally risky (category C-like, flag for the developer):
- Signature is still compatible but semantics changed (e.g., a return value
that used to always be non-empty can now be empty)
- Caller passes through to another function whose contract may now be violated
- Base-class change where a subclass may need updating even though the
signature is backwards-compatible
Do NOT silently edit these -- the developer needs to confirm the intent. Flag
them in the output with path:line of the caller and a one-line explanation.
Safe (no action):
- Backwards-compatible signature extension the caller does not use
- Change in an internal detail not observable at the call site
Track the caller-impact findings under the existing output sections:
- Auto-fixed callers go under "Auto-fixed issues" with a new category "Caller updates"
- Flagged risky callers go under "Manual review required" with severity
error if the caller is clearly broken and warning otherwise
Step 6 -- Final verification
Run these checks and report the outcome. Do NOT auto-run them; print the command the developer should run (respecting the user's preference to run these themselves):
pre-commit run --all-files
pytest -xvs tests/v1/distributed/ # if distributed/ changed
pytest -xvs tests/v1/mp_observability/ # if mp_observability/ changed
Output Format
Summary
- Scope:
<uncommitted|staged|branch>
- Files changed:
<N>
- Issues found:
<total> (auto-fixed: <N>, manual: <N>)
Auto-fixed issues
Grouped by category, with file:line references. E.g.:
Typing (3 fixes)
lmcache/v1/foo.py:42 -- replaced Optional[int] with int | None
lmcache/v1/foo.py:87 -- added type hint for limit: int and return type -> list[str]
lmcache/v1/bar.py:120 -- replaced dict with dict[str, int]
Runtime validation (1 fix)
lmcache/v1/config.py:548 -- replaced assert config.url is not None with if config.url is None: raise ValueError("url is required")
Docstrings (2 fixes)
lmcache/v1/baz.py:15 -- added full docstring for public method submit_request
lmcache/v1/baz.py:60 -- updated docstring for lookup to reflect new cache_salt parameter
Manual review required (needs developer judgment)
Grouped by severity:
error (must fix before PR):
lmcache/v1/foo.py:230 -- new feature BatchProcessor.process_async has no tests in tests/v1/. Add unit tests that exercise the public process_async API.
lmcache/v1/bar.py:15 -- BarAdapter.do_thing accesses self._cache._internal_map directly; use a public accessor or expose a method on Cache.
warning (should fix):
lmcache/v1/baz.py:42 -- register(name: str, force: bool) uses a boolean parameter. Consider splitting into register(name) and register_overwrite(name), or using an enum RegisterMode.
info (suggestion):
lmcache/v1/baz.py:78 -- filter parameter is named filter; consider key_eligible_filter for clarity.
Commands to run next
pre-commit run --all-files
# then run relevant test suites
Summary table
| Category |
Auto-fixed |
Manual |
Total |
| Typing |
3 |
0 |
3 |
| Runtime validation |
1 |
0 |
1 |
| Docstrings |
2 |
0 |
2 |
| Design |
0 |
2 |
2 |
| Tests |
0 |
1 |
1 |
| Total |
6 |
3 |
9 |
Rules
- Only touch files in the selected scope, except when Step 5 identifies an
unchanged caller that is clearly broken by a signature change in this PR. In
that case, update the caller in the same pass. Do not otherwise "improve"
unchanged files.
- Never add error handling, fallbacks, or validation that the task does not require.
- Never introduce new abstractions, helpers, or dependencies in fixes.
- When a fix requires judgment (e.g., "what should this docstring say?" or "is
this caller's assumption still valid?"), prefer to flag it for manual review
rather than guess.
- Never run
git add, git commit, or git push. The developer handles git operations themselves.
- Never run
pre-commit or the test suite automatically; print the command for the developer to run.
- If the scope is
branch and the branch has no upstream or is far behind dev, warn the developer but proceed.
- If
docs/coding_standards.md is missing, stop and ask the developer to check it out or pull the latest dev.
1---2name: pre-pr-check3description: Check local changes against the LMCache coding standards and fix issues before creating a PR4---56# Pre-PR Check: LMCache Coding Standard Compliance78Check the developer's local changes against the coding standards defined in9`docs/coding_standards.md` and modify the code to fix compliance issues, so the10PR is clean before it is opened.1112This skill is meant to be run **before** `/create-pr`.1314## Arguments1516- `--scope` (default: `branch`)17 - `uncommitted` -- check only unstaged + staged changes vs HEAD18 - `staged` -- check only staged changes19 - `branch` -- check all commits on the current branch not yet in `dev` (the20 full PR diff). This is the default since that matches what a reviewer sees.21- `--fix` (default) -- automatically fix issues that can be fixed mechanically22- `--check-only` -- only report issues, do not modify files2324## Workflow2526### Step 0 -- Load the standard2728Read `docs/coding_standards.md` at the repo root. This is the authoritative reference.2930### Step 1 -- Detect the scope of changes3132Run `git status --short` and `git branch --show-current` to orient yourself.3334Then, based on `--scope`, compute the diff:3536| Scope | Command |37|-------|---------|38| `uncommitted` | `git diff HEAD` |39| `staged` | `git diff --cached` |40| `branch` (default) | `git diff dev...HEAD` |4142Also list changed files:4344| Scope | Command |45|-------|---------|46| `uncommitted` | `git diff --name-only HEAD` |47| `staged` | `git diff --cached --name-only` |48| `branch` | `git diff --name-only dev...HEAD` |4950If there are no changes in the selected scope, report that and stop.5152### Step 2 -- Read the changed files5354For each changed file, read the current content (not just the diff) so you can55make accurate edits. Focus on:56- `.py` files -- full coding-standard review57- `.md`, `.rst` -- documentation review (brief)58- `.cpp`, `.cu`, `.rs` -- light review (style only; clang-format / cargo fmt handle most of it)5960For each changed file, also identify the **changed public symbols** (functions,61methods, classes, fields whose signature, return type, raised exceptions, or62behavior contract changed). You will use these in Step 4 to find and inspect callers.6364For large changes, delegate to the Explore agent to summarize per-file65responsibilities before reviewing.6667### Step 3 -- Run the checks6869For each changed Python file, check the following (from `docs/coding_standards.md`):7071#### A. Mechanical issues (fix automatically unless `--check-only`)7273These are straightforward to fix:74751. **License header** (Section 1.3): Missing `# SPDX-License-Identifier: Apache-2.0` on line 1 of new Python files.762. **`assert` used for runtime validation** (Section 2.2): Replace with `if <not cond>: raise ValueError(<clear msg>)`.773. **`Optional` usage** (Section 2.1): Where a value is always initialized, remove the `Optional` wrapper and initialize directly. Where None is genuinely possible, rewrite as `X | None`.784. **Bare generics** (Section 2.1): Replace bare `list`, `dict`, `tuple`, `set` in annotations with parameterized forms (`list[T]`, `dict[K, V]`).795. **Lazy imports** (Section 7.2): Move imports to the top of the file, under the correct section heading. Preserve `torch`-before-native-C-extension ordering.806. **Bare `logging.getLogger(__name__)`** (Section 7.4): Replace with `init_logger(__name__)` from `lmcache.logging`.817. **Operational logs at `INFO` level** (Section 7.4): If a log is about store/retrieve/task progress, change `logger.info(...)` to `logger.debug(...)`.828. **Inappropriate `WARNING`** (Section 7.4): For log messages about benign concurrent conditions (e.g., "key not found, this should not happen" in a code path documented as not holding the global lock), downgrade to `debug` and fix the misleading message.8384#### B. Docstring issues (fix when content is clear; ask when content is unclear)85869. **Missing docstrings** on public/modified functions (Section 3.1-3.2):87 - If the function is simple and its purpose is obvious from the code, draft a full docstring (summary, args, returns, raises).88 - If behavior is non-obvious, draft a docstring with clear sections but add a `TODO: confirm` note where you are uncertain, and flag it in the report.8910. **Docstring accuracy** (Section 3.3): If you added a parameter but the docstring still describes the old signature, update it. If a parameter is currently a no-op (accepted but ignored), the docstring must say so explicitly.9011. **Missing type hints** on function args or return values (Section 2.1): Add them based on the function's body.9192#### C. Design issues (flag; do NOT silently refactor)9394These are judgment calls and should be reported, not auto-fixed:959612. **Cross-class private member access** (Section 4.4): Accessing `other._private` outside the defining class.9713. **Ambiguous return values** (Section 4.3): Function returns `None` for multiple distinct meanings.9814. **Boolean parameters on public APIs** (Section 4.3).9915. **New features without tests** (Section 5.2).10016. **Bug fixes without regression tests** (Section 5.2).10117. **Unbounded collection growth** (Section 7.6): A set/dict that is appended to without cleanup.10218. **Error paths that leave state inconsistent** (Section 7.5): Exceptions that skip lock release, FD close, or pool free.10319. **Missing design doc** for non-trivial new features (Section 5.1).10420. **PR scope too large** (Section 1.1): If the diff touches many unrelated modules, suggest how to split.10521. **Lock protocol violations** (Section 8): Debug methods not holding `self._lock`; shared state accessed without synchronization.106107### Step 4 -- Apply fixes108109For category A and B issues (unless `--check-only`):110- Use `Edit` to make precise, minimal changes.111- Preserve existing comments and code style.112- After fixes, re-read the file to confirm the edit applied cleanly.113114For category C issues: do NOT auto-fix. Report them for the developer to address.115116### Step 5 -- Caller Impact Analysis (Section 6.2 of the standard)117118Even when the diff itself looks clean, a change can silently break unchanged119callers. Before declaring the changes ready for PR, verify the global view for120each changed public symbol identified in Step 2.121122**Triggers -- run this step if the diff does any of:**123- Adds, removes, renames, or reorders parameters of a public function/method124- Changes parameter types, defaults, or accepted domains (e.g., adds `None` to accepted values, narrows a type)125- Changes the return type or the meaning of sentinel returns (e.g., what `None` means)126- Adds, removes, or changes raised exceptions127- Changes side effects, ordering, blocking behavior, or thread-safety guarantees128- Modifies a method on a base class or protocol (subclasses are callers)129- Changes an invariant of a class field accessed by public API130131If none of the above applies, skip this step.132133**How to run it:**1341351. For each changed public symbol, `Grep` its name across the whole repo136 (production code, `tests/`, `benchmarks/`, `lmcache/integration/`, `docs/`).137 Do not limit to files in the diff.1382. For commonly-named symbols with many false matches, use `Grep -A 2 -B 2`139 for context, or delegate to the `Agent` (Explore) tool with a focused prompt140 (e.g., "find all callers of `LMCacheMPWorkerAdapter.submit_store_request`141 and summarize the arguments each caller passes").1423. For base-class / protocol changes, also grep for subclasses143 (`class \w+\(<BaseName>`).144145**For each caller, decide:**146147- **Broken and must be fixed in this PR** (category A-like, fix it):148 - Removed / renamed parameter that the caller passes by keyword149 - Reordered positional parameters the caller relies on150 - Return type change that the caller destructures (e.g., tuple shape change)151 - Newly raised exception the caller doesn't handle, when the caller is on the normal success path152153 For these, update the caller in the same pass as the main change. Apply the154 edit using `Edit`, then re-read to confirm. If the caller is in an unrelated155 module and the fix is non-trivial, flag it for manual review instead.156157- **Behaviorally risky** (category C-like, flag for the developer):158 - Signature is still compatible but semantics changed (e.g., a return value159 that used to always be non-empty can now be empty)160 - Caller passes through to another function whose contract may now be violated161 - Base-class change where a subclass may need updating even though the162 signature is backwards-compatible163164 Do NOT silently edit these -- the developer needs to confirm the intent. Flag165 them in the output with `path:line` of the caller and a one-line explanation.166167- **Safe** (no action):168 - Backwards-compatible signature extension the caller does not use169 - Change in an internal detail not observable at the call site170171Track the caller-impact findings under the existing output sections:172- Auto-fixed callers go under "Auto-fixed issues" with a new category "Caller updates"173- Flagged risky callers go under "Manual review required" with severity174 `error` if the caller is clearly broken and `warning` otherwise175176### Step 6 -- Final verification177178Run these checks and report the outcome. Do NOT auto-run them; print the command the developer should run (respecting the user's preference to run these themselves):179180```bash181pre-commit run --all-files182pytest -xvs tests/v1/distributed/ # if distributed/ changed183pytest -xvs tests/v1/mp_observability/ # if mp_observability/ changed184```185186## Output Format187188### Summary189- Scope: `<uncommitted|staged|branch>`190- Files changed: `<N>`191- Issues found: `<total>` (auto-fixed: `<N>`, manual: `<N>`)192193### Auto-fixed issues194Grouped by category, with file:line references. E.g.:195196**Typing (3 fixes)**197- `lmcache/v1/foo.py:42` -- replaced `Optional[int]` with `int | None`198- `lmcache/v1/foo.py:87` -- added type hint for `limit: int` and return type `-> list[str]`199- `lmcache/v1/bar.py:120` -- replaced `dict` with `dict[str, int]`200201**Runtime validation (1 fix)**202- `lmcache/v1/config.py:548` -- replaced `assert config.url is not None` with `if config.url is None: raise ValueError("url is required")`203204**Docstrings (2 fixes)**205- `lmcache/v1/baz.py:15` -- added full docstring for public method `submit_request`206- `lmcache/v1/baz.py:60` -- updated docstring for `lookup` to reflect new `cache_salt` parameter207208### Manual review required (needs developer judgment)209210Grouped by severity:211212**error** (must fix before PR):213- `lmcache/v1/foo.py:230` -- new feature `BatchProcessor.process_async` has no tests in `tests/v1/`. Add unit tests that exercise the public `process_async` API.214- `lmcache/v1/bar.py:15` -- `BarAdapter.do_thing` accesses `self._cache._internal_map` directly; use a public accessor or expose a method on `Cache`.215216**warning** (should fix):217- `lmcache/v1/baz.py:42` -- `register(name: str, force: bool)` uses a boolean parameter. Consider splitting into `register(name)` and `register_overwrite(name)`, or using an enum `RegisterMode`.218219**info** (suggestion):220- `lmcache/v1/baz.py:78` -- filter parameter is named `filter`; consider `key_eligible_filter` for clarity.221222### Commands to run next223224```bash225pre-commit run --all-files226# then run relevant test suites227```228229### Summary table230231| Category | Auto-fixed | Manual | Total |232|----------|-----------|--------|-------|233| Typing | 3 | 0 | 3 |234| Runtime validation | 1 | 0 | 1 |235| Docstrings | 2 | 0 | 2 |236| Design | 0 | 2 | 2 |237| Tests | 0 | 1 | 1 |238| **Total** | **6** | **3** | **9** |239240## Rules241242- Only touch files in the selected scope, **except** when Step 5 identifies an243 unchanged caller that is clearly broken by a signature change in this PR. In244 that case, update the caller in the same pass. Do not otherwise "improve"245 unchanged files.246- Never add error handling, fallbacks, or validation that the task does not require.247- Never introduce new abstractions, helpers, or dependencies in fixes.248- When a fix requires judgment (e.g., "what should this docstring say?" or "is249 this caller's assumption still valid?"), prefer to flag it for manual review250 rather than guess.251- Never run `git add`, `git commit`, or `git push`. The developer handles git operations themselves.252- Never run `pre-commit` or the test suite automatically; print the command for the developer to run.253- If the scope is `branch` and the branch has no upstream or is far behind `dev`, warn the developer but proceed.254- If `docs/coding_standards.md` is missing, stop and ask the developer to check it out or pull the latest `dev`.