Skip to content

Commit 6a477c2

Browse files
authored
Merge pull request #9 from moonrunnerkc/maintainability-improvements
Maintainability: enforce lint/types in CI, split cli.py, measure coverage
2 parents cf14b02 + efc8668 commit 6a477c2

56 files changed

Lines changed: 729 additions & 647 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci.yml‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,14 @@ jobs:
4444
with:
4545
python-version: "3.12"
4646

47-
- name: Compile check (all modules)
48-
run: python -m compileall -q src/skillcheck
47+
- name: Install dev dependencies
48+
run: pip install -e ".[dev]"
49+
50+
- name: Ruff check
51+
run: ruff check src tests
52+
53+
- name: Mypy strict
54+
run: mypy src/skillcheck
4955

5056
package:
5157
runs-on: ubuntu-latest

‎.gitignore‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ dist/
77
build/
88
.pytest_cache/
99
.ruff_cache/
10+
.mypy_cache/
11+
.coverage
12+
.coverage.*
13+
htmlcov/
1014
*.egg
1115
CLAUDE.md
12-
copilot-instructions.md
1316
# Field test artifacts (kept locally, not in git)
1417
runs/
1518

‎CHANGELOG.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- `[tool.ruff]` and `[tool.mypy]` configuration in `pyproject.toml`. Ruff lints `src` and `tests` with an explicit `E, F, I, UP, B` selection at line length 127 (`E501` ignored for unavoidable long string literals in JSON fixtures and schema text). Mypy runs `strict` against `src/skillcheck` under `python_version = "3.10"`, with an `ignore_missing_imports` override for the untyped `tiktoken` dependency and the `tomllib` 3.11+ stdlib backport branch. The source was brought clean under both with real annotations, not blanket ignores.
13+
- Test coverage measurement via `pytest-cov` (added to the `dev` extra). `[tool.coverage.run]` and `[tool.coverage.report]` configure the run, and `addopts` in `[tool.pytest.ini_options]` adds `--cov=skillcheck --cov-report=term-missing --cov-fail-under=68`, so the floor is enforced on every local run and in CI (which runs the same `pytest`). The floor sits a few points under the ~72% measured on CPython 3.10 to absorb matrix variance: the CLI modules run in subprocesses the in-process tracer does not see, the `tomllib` vs fallback-parser branch flips between Python 3.10 and 3.11+, and a few tests skip on Windows.
14+
15+
### Changed
16+
17+
- CI `lint` job now enforces `ruff check src tests` and `mypy src/skillcheck` (strict) on every push and pull request, replacing the prior `compileall`-only check. Either tool reporting a finding fails the job.
18+
- `cli.py` split to separate argument wiring from command execution. Parser construction, `skillcheck.toml` application, mode-conflict dispatch, and `main` stay in `cli.py`; the per-mode handlers (emit prompts and graphs, `--show-history`, the default validation pipeline) and the path and ingest IO helpers move to a new `skillcheck.commands` module. `skillcheck.cli:main` and the `skillcheck` console script are unchanged. Pure refactor, no behavior change.
19+
1020
## [1.4.0] - 2026-05-27
1121

1222
### Changed

‎pyproject.toml‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ classifiers = [
3333
dev = [
3434
"mypy>=1.10",
3535
"pytest>=8.0",
36+
"pytest-cov>=5.0",
3637
"ruff>=0.6",
3738
"types-PyYAML>=6.0",
3839
]
@@ -48,3 +49,41 @@ packages = ["src/skillcheck"]
4849

4950
[tool.pytest.ini_options]
5051
testpaths = ["tests"]
52+
# Coverage floor sits a few points under the ~72% measured on CPython 3.10 so it
53+
# survives matrix variance: the CLI modules (cli.py, commands.py) run in
54+
# subprocesses the in-process tracer does not see, the tomllib vs fallback-parser
55+
# branch flips between 3.10 and 3.11+, and a few tests skip on Windows.
56+
addopts = "--cov=skillcheck --cov-report=term-missing --cov-fail-under=68"
57+
58+
[tool.ruff]
59+
target-version = "py310"
60+
line-length = 127
61+
62+
[tool.ruff.lint]
63+
select = ["E", "F", "I", "UP", "B"]
64+
ignore = ["E501"] # line-length in unchangeable string literals (JSON fixtures, base64, schema text)
65+
66+
[tool.mypy]
67+
strict = true
68+
python_version = "3.10"
69+
files = ["src/skillcheck"]
70+
71+
[[tool.mypy.overrides]]
72+
# tiktoken ships no type stubs; tomllib has no typeshed stubs under the pinned
73+
# python_version (3.10), where it is the optional 3.11+ stdlib backport branch.
74+
module = ["tiktoken", "tomllib"]
75+
ignore_missing_imports = true
76+
77+
[tool.coverage.run]
78+
source = ["skillcheck"]
79+
# __main__.py is the `python -m skillcheck` shim; it only runs in a subprocess,
80+
# so the in-process test run never executes it.
81+
omit = ["*/__main__.py"]
82+
83+
[tool.coverage.report]
84+
show_missing = true
85+
exclude_lines = [
86+
"pragma: no cover",
87+
"if __name__ == .__main__.:",
88+
"raise NotImplementedError",
89+
]

‎src/skillcheck/agents/codex.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818

1919
from skillcheck.agents.base import (
2020
SCHEMA_VERSION,
21+
SelfCritiquePrompt,
2122
schema_reference,
2223
worked_example,
2324
)
24-
from skillcheck.agents.base import SelfCritiquePrompt
2525
from skillcheck.parser import ParsedSkill
2626

2727

‎src/skillcheck/agents/cursor.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626

2727
from skillcheck.agents.base import (
2828
SCHEMA_VERSION,
29+
SelfCritiquePrompt,
2930
compact_schema_signature,
3031
)
31-
from skillcheck.agents.base import SelfCritiquePrompt
3232
from skillcheck.parser import ParsedSkill
3333

3434

‎src/skillcheck/agents/graph_parser.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from __future__ import annotations
1212

1313
import json
14+
from collections.abc import Mapping
1415
from typing import Literal
1516

1617
from skillcheck.agents._response_text import strip_response_noise
@@ -102,7 +103,7 @@ class GraphValueError(GraphParseError):
102103

103104

104105
def _require_field(
105-
obj: dict,
106+
obj: dict[str, object],
106107
key: str,
107108
expected_type: type | tuple[type, ...],
108109
context: str = "",
@@ -140,7 +141,7 @@ def _require_field(
140141
raise GraphSchemaError(
141142
f"Field '{full_key}' must be int or null, got bool: {value!r}"
142143
)
143-
if not isinstance(value, expected_type): # type: ignore[arg-type]
144+
if not isinstance(value, expected_type):
144145
if isinstance(expected_type, tuple):
145146
type_name = " or ".join(
146147
"null" if t is type(None) else t.__name__ for t in expected_type
@@ -154,7 +155,7 @@ def _require_field(
154155
return value
155156

156157

157-
def _check_no_extra_fields(obj: dict, allowed: dict, context: str) -> None:
158+
def _check_no_extra_fields(obj: Mapping[str, object], allowed: Mapping[str, object], context: str) -> None:
158159
extra = set(obj) - set(allowed)
159160
if extra:
160161
raise GraphSchemaError(

‎src/skillcheck/agents/parser.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class CritiqueValueError(CritiqueParseError):
7373
"""
7474

7575

76-
def _require_field(obj: dict, key: str, expected_type: type | tuple[type, ...], context: str = "") -> object:
76+
def _require_field(obj: dict[str, object], key: str, expected_type: type | tuple[type, ...], context: str = "") -> object:
7777
"""Extract a required field with type checking.
7878
7979
Args:
@@ -98,7 +98,7 @@ def _require_field(obj: dict, key: str, expected_type: type | tuple[type, ...],
9898
raise CritiqueSchemaError(
9999
f"Field '{full_key}' must be int, got bool: {value!r}"
100100
)
101-
if not isinstance(value, expected_type): # type: ignore[arg-type]
101+
if not isinstance(value, expected_type):
102102
type_name = (
103103
expected_type.__name__
104104
if isinstance(expected_type, type)

0 commit comments

Comments
 (0)