lepisma 0.1.1__tar.gz

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.
Files changed (177) hide show
  1. lepisma-0.1.1/.claude/skills/prose-rewrite/SKILL.md +19 -0
  2. lepisma-0.1.1/.github/dependabot.yml +16 -0
  3. lepisma-0.1.1/.github/workflows/ci.yml +17 -0
  4. lepisma-0.1.1/.github/workflows/release.yml +58 -0
  5. lepisma-0.1.1/.gitignore +18 -0
  6. lepisma-0.1.1/CLAUDE.md +127 -0
  7. lepisma-0.1.1/LICENSE +21 -0
  8. lepisma-0.1.1/PKG-INFO +308 -0
  9. lepisma-0.1.1/README.md +290 -0
  10. lepisma-0.1.1/corpus/README.md +7 -0
  11. lepisma-0.1.1/corpus/bad/adr-event-sourcing.md +46 -0
  12. lepisma-0.1.1/corpus/bad/architecture-overview.md +50 -0
  13. lepisma-0.1.1/corpus/bad/contributing.md +46 -0
  14. lepisma-0.1.1/corpus/bad/deploy-runbook.md +46 -0
  15. lepisma-0.1.1/corpus/bad/design-doc-notification-service.md +50 -0
  16. lepisma-0.1.1/corpus/bad/migration-guide-v3.md +50 -0
  17. lepisma-0.1.1/corpus/bad/onboarding-guide.md +44 -0
  18. lepisma-0.1.1/corpus/bad/readme-data-sync-tool.md +55 -0
  19. lepisma-0.1.1/corpus/bad/rest-api-guide.md +44 -0
  20. lepisma-0.1.1/corpus/bad/troubleshooting-guide.md +36 -0
  21. lepisma-0.1.1/corpus/good/adr-use-postgres-queue.md +53 -0
  22. lepisma-0.1.1/corpus/good/api-quickstart.md +43 -0
  23. lepisma-0.1.1/corpus/good/deploy-runbook.md +51 -0
  24. lepisma-0.1.1/corpus/good/onboarding-first-week.md +46 -0
  25. lepisma-0.1.1/corpus/good/readme-log-tail.md +57 -0
  26. lepisma-0.1.1/corpus/good/troubleshooting-slow-builds.md +47 -0
  27. lepisma-0.1.1/corpus/toolbox/biston/README.md +279 -0
  28. lepisma-0.1.1/corpus/toolbox/biston/docs/src/SUMMARY.md +16 -0
  29. lepisma-0.1.1/corpus/toolbox/biston/docs/src/acceptance.md +172 -0
  30. lepisma-0.1.1/corpus/toolbox/biston/docs/src/commit-hooks.md +80 -0
  31. lepisma-0.1.1/corpus/toolbox/biston/docs/src/containment.md +152 -0
  32. lepisma-0.1.1/corpus/toolbox/biston/docs/src/guide/setup.md +60 -0
  33. lepisma-0.1.1/corpus/toolbox/biston/docs/src/guide/triage.md +42 -0
  34. lepisma-0.1.1/corpus/toolbox/biston/docs/src/guide/tune.md +56 -0
  35. lepisma-0.1.1/corpus/toolbox/biston/docs/src/how-it-works.md +193 -0
  36. lepisma-0.1.1/corpus/toolbox/biston/docs/src/index.md +36 -0
  37. lepisma-0.1.1/corpus/toolbox/coati/README.md +97 -0
  38. lepisma-0.1.1/corpus/toolbox/coati/docs/guide/analyze.md +453 -0
  39. lepisma-0.1.1/corpus/toolbox/coati/docs/guide/remediate.md +141 -0
  40. lepisma-0.1.1/corpus/toolbox/coati/docs/guide/setup.md +146 -0
  41. lepisma-0.1.1/corpus/toolbox/gerenuk/README.md +268 -0
  42. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0001-two-impure-seams.md +28 -0
  43. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0002-refs-queries-by-position.md +42 -0
  44. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0003-batch-the-frontier.md +32 -0
  45. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0004-phase1-schema-carries-positions.md +37 -0
  46. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0005-why-chain-excludes-endpoints.md +25 -0
  47. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0006-file-list-from-git.md +27 -0
  48. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0007-module-level-selects-importers.md +29 -0
  49. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0008-module-ids-have-no-colon.md +32 -0
  50. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0009-run-all-is-a-success.md +33 -0
  51. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0010-a-replayed-report-is-parsed-strictly.md +37 -0
  52. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0011-a-third-seam-that-only-execs.md +67 -0
  53. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0012-a-decorator-is-a-reference.md +67 -0
  54. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0013-a-renaming-import-is-followed.md +54 -0
  55. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0014-run-all-delegates-to-a-fallback.md +77 -0
  56. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0015-the-guide-is-the-docs.md +85 -0
  57. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0016-a-framework-hook-on-a-subclass-is-a-reference.md +50 -0
  58. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0017-a-test-helper-is-a-step.md +41 -0
  59. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0018-a-registrar-is-resolved-by-position.md +50 -0
  60. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0019-a-declared-suite-time-skips-the-walk.md +44 -0
  61. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/0020-pytest-does-not-inherit-the-hooks-git.md +63 -0
  62. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/adr/README.md +31 -0
  63. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/dev/ARCHITECTURE.md +258 -0
  64. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/shared/claude-snippet.md +6 -0
  65. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/src/SUMMARY.md +20 -0
  66. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/src/commands/audit.md +153 -0
  67. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/src/commands/changed-symbols.md +190 -0
  68. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/src/commands/doctor.md +26 -0
  69. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/src/commands/guide.md +54 -0
  70. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/src/commands/impacted-tests.md +298 -0
  71. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/src/commands/overview.md +54 -0
  72. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/src/commands/run.md +564 -0
  73. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/src/guide/setup.md +55 -0
  74. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/src/guide/triage.md +60 -0
  75. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/src/guide/tune.md +60 -0
  76. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/src/how-it-works.md +130 -0
  77. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/src/index.md +68 -0
  78. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/src/setup.md +79 -0
  79. lepisma-0.1.1/corpus/toolbox/gerenuk/docs/src/troubleshooting.md +93 -0
  80. lepisma-0.1.1/corpus/toolbox/madoqua/README.md +123 -0
  81. lepisma-0.1.1/corpus/toolbox/madoqua/docs/adr/0001-exit-codes-are-a-contract.md +35 -0
  82. lepisma-0.1.1/corpus/toolbox/madoqua/docs/adr/0002-the-seams-the-hook-needs.md +61 -0
  83. lepisma-0.1.1/corpus/toolbox/madoqua/docs/adr/0003-timestamps-are-utc-and-dependency-free.md +43 -0
  84. lepisma-0.1.1/corpus/toolbox/madoqua/docs/adr/0004-the-guide-is-the-docs.md +92 -0
  85. lepisma-0.1.1/corpus/toolbox/madoqua/docs/adr/0005-git-paths-come-from-git.md +86 -0
  86. lepisma-0.1.1/corpus/toolbox/madoqua/docs/adr/README.md +19 -0
  87. lepisma-0.1.1/corpus/toolbox/madoqua/docs/dev/ARCHITECTURE.md +64 -0
  88. lepisma-0.1.1/corpus/toolbox/madoqua/docs/shared/claude-snippet.md +24 -0
  89. lepisma-0.1.1/corpus/toolbox/madoqua/docs/src/SUMMARY.md +19 -0
  90. lepisma-0.1.1/corpus/toolbox/madoqua/docs/src/commands/guide.md +56 -0
  91. lepisma-0.1.1/corpus/toolbox/madoqua/docs/src/commands/install.md +42 -0
  92. lepisma-0.1.1/corpus/toolbox/madoqua/docs/src/commands/overview.md +15 -0
  93. lepisma-0.1.1/corpus/toolbox/madoqua/docs/src/commands/run.md +64 -0
  94. lepisma-0.1.1/corpus/toolbox/madoqua/docs/src/commands/stats.md +82 -0
  95. lepisma-0.1.1/corpus/toolbox/madoqua/docs/src/configuration.md +104 -0
  96. lepisma-0.1.1/corpus/toolbox/madoqua/docs/src/guide/setup.md +60 -0
  97. lepisma-0.1.1/corpus/toolbox/madoqua/docs/src/guide/triage.md +43 -0
  98. lepisma-0.1.1/corpus/toolbox/madoqua/docs/src/guide/tune.md +60 -0
  99. lepisma-0.1.1/corpus/toolbox/madoqua/docs/src/how-it-works.md +58 -0
  100. lepisma-0.1.1/corpus/toolbox/madoqua/docs/src/index.md +36 -0
  101. lepisma-0.1.1/corpus/toolbox/madoqua/docs/src/setup.md +77 -0
  102. lepisma-0.1.1/corpus/toolbox/madoqua/docs/src/troubleshooting.md +122 -0
  103. lepisma-0.1.1/corpus/toolbox/ty-find/README.md +250 -0
  104. lepisma-0.1.1/corpus/toolbox/ty-find/docs/dev/ARCHITECTURE.md +68 -0
  105. lepisma-0.1.1/corpus/toolbox/ty-find/docs/dev/COVERAGE.md +131 -0
  106. lepisma-0.1.1/corpus/toolbox/ty-find/docs/dev/RELEASING.md +17 -0
  107. lepisma-0.1.1/corpus/toolbox/ty-find/docs/dev/TY_VERSIONS.md +222 -0
  108. lepisma-0.1.1/corpus/toolbox/ty-find/docs/dev/call-hierarchy-spike.md +225 -0
  109. lepisma-0.1.1/corpus/toolbox/ty-find/docs/guide/setup.md +50 -0
  110. lepisma-0.1.1/corpus/toolbox/ty-find/docs/guide/use.md +50 -0
  111. lepisma-0.1.1/corpus/toolbox/ty-find/docs/shared/claude-snippet.md +16 -0
  112. lepisma-0.1.1/corpus/toolbox/ty-find/docs/src/SUMMARY.md +31 -0
  113. lepisma-0.1.1/corpus/toolbox/ty-find/docs/src/commands/calls.md +173 -0
  114. lepisma-0.1.1/corpus/toolbox/ty-find/docs/src/commands/daemon.md +43 -0
  115. lepisma-0.1.1/corpus/toolbox/ty-find/docs/src/commands/find.md +71 -0
  116. lepisma-0.1.1/corpus/toolbox/ty-find/docs/src/commands/guide.md +32 -0
  117. lepisma-0.1.1/corpus/toolbox/ty-find/docs/src/commands/list.md +28 -0
  118. lepisma-0.1.1/corpus/toolbox/ty-find/docs/src/commands/mcp.md +35 -0
  119. lepisma-0.1.1/corpus/toolbox/ty-find/docs/src/commands/members.md +97 -0
  120. lepisma-0.1.1/corpus/toolbox/ty-find/docs/src/commands/overview.md +67 -0
  121. lepisma-0.1.1/corpus/toolbox/ty-find/docs/src/commands/refs.md +86 -0
  122. lepisma-0.1.1/corpus/toolbox/ty-find/docs/src/commands/show.md +102 -0
  123. lepisma-0.1.1/corpus/toolbox/ty-find/docs/src/how-it-works.md +327 -0
  124. lepisma-0.1.1/corpus/toolbox/ty-find/docs/src/index.md +59 -0
  125. lepisma-0.1.1/corpus/toolbox/ty-find/docs/src/mcp.md +146 -0
  126. lepisma-0.1.1/corpus/toolbox/ty-find/docs/src/performance.md +21 -0
  127. lepisma-0.1.1/corpus/toolbox/ty-find/docs/src/relationship-to-ty.md +86 -0
  128. lepisma-0.1.1/corpus/toolbox/ty-find/docs/src/setup.md +85 -0
  129. lepisma-0.1.1/corpus/toolbox/ty-find/docs/src/troubleshooting.md +94 -0
  130. lepisma-0.1.1/corpus/toolbox/zorilla/README.md +338 -0
  131. lepisma-0.1.1/corpus/toolbox/zorilla/docs/guide/setup.md +60 -0
  132. lepisma-0.1.1/corpus/toolbox/zorilla/docs/guide/triage.md +60 -0
  133. lepisma-0.1.1/corpus/toolbox/zorilla/docs/guide/tune.md +59 -0
  134. lepisma-0.1.1/corpus/toolbox/zorilla/docs/rules/ZR001.md +144 -0
  135. lepisma-0.1.1/corpus/toolbox/zorilla/docs/rules/ZR002.md +66 -0
  136. lepisma-0.1.1/corpus/toolbox/zorilla/docs/rules/ZR003.md +115 -0
  137. lepisma-0.1.1/corpus/toolbox/zorilla/docs/rules/ZR004.md +159 -0
  138. lepisma-0.1.1/corpus/toolbox/zorilla/docs/rules/ZR005.md +150 -0
  139. lepisma-0.1.1/corpus/toolbox/zorilla/docs/rules/ZR006.md +65 -0
  140. lepisma-0.1.1/corpus/toolbox/zorilla/docs/rules/ZR007.md +64 -0
  141. lepisma-0.1.1/corpus/toolbox/zorilla/docs/rules/ZR008.md +100 -0
  142. lepisma-0.1.1/hooks/pre-commit +4 -0
  143. lepisma-0.1.1/lepisma.toml +36 -0
  144. lepisma-0.1.1/pyproject.toml +191 -0
  145. lepisma-0.1.1/scripts/sync-toolbox-corpus.sh +19 -0
  146. lepisma-0.1.1/src/lepisma/__init__.py +23 -0
  147. lepisma-0.1.1/src/lepisma/__main__.py +5 -0
  148. lepisma-0.1.1/src/lepisma/cli.py +291 -0
  149. lepisma-0.1.1/src/lepisma/client.py +210 -0
  150. lepisma-0.1.1/src/lepisma/config.py +261 -0
  151. lepisma-0.1.1/src/lepisma/engine.py +96 -0
  152. lepisma-0.1.1/src/lepisma/guide/setup.md +68 -0
  153. lepisma-0.1.1/src/lepisma/guide/triage.md +51 -0
  154. lepisma-0.1.1/src/lepisma/guide/tune.md +76 -0
  155. lepisma-0.1.1/src/lepisma/guide.py +43 -0
  156. lepisma-0.1.1/src/lepisma/markdown.py +325 -0
  157. lepisma-0.1.1/src/lepisma/measure.py +348 -0
  158. lepisma-0.1.1/src/lepisma/measure_report.py +343 -0
  159. lepisma-0.1.1/src/lepisma/models.py +70 -0
  160. lepisma-0.1.1/src/lepisma/py.typed +0 -0
  161. lepisma-0.1.1/src/lepisma/report.py +80 -0
  162. lepisma-0.1.1/src/lepisma/rules.py +505 -0
  163. lepisma-0.1.1/src/lepisma/server.py +125 -0
  164. lepisma-0.1.1/src/lepisma/spacy_model.py +103 -0
  165. lepisma-0.1.1/tests/__init__.py +0 -0
  166. lepisma-0.1.1/tests/conftest.py +17 -0
  167. lepisma-0.1.1/tests/test_cli.py +50 -0
  168. lepisma-0.1.1/tests/test_client.py +118 -0
  169. lepisma-0.1.1/tests/test_config.py +70 -0
  170. lepisma-0.1.1/tests/test_corpus.py +36 -0
  171. lepisma-0.1.1/tests/test_guide.py +72 -0
  172. lepisma-0.1.1/tests/test_markdown.py +103 -0
  173. lepisma-0.1.1/tests/test_measure.py +106 -0
  174. lepisma-0.1.1/tests/test_rules.py +289 -0
  175. lepisma-0.1.1/tests/test_server.py +38 -0
  176. lepisma-0.1.1/tests/test_spacy_model.py +84 -0
  177. lepisma-0.1.1/uv.lock +2349 -0
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: prose-rewrite
3
+ description: Rewrite a Markdown doc so it is easier to read, driven by lepisma warnings. Use when the user asks to clean up, simplify or de-Claude a doc, or when a commit prints lepisma warnings.
4
+ ---
5
+
6
+ # Rewrite a doc with lepisma
7
+
8
+ 1. Run `lepisma check --format json <file>` and note `summary.per_1000_words`.
9
+ 2. Rewrite only the flagged prose. Keep the meaning, facts, headings, code and links.
10
+ - R1 long sentence: split it. One idea per sentence.
11
+ - R2 noun stack: unpack it with a verb or a preposition
12
+ ("error handling for config validation", or better, "when validation fails, ...").
13
+ - R3 habit word: use the suggestion, or delete the word if the sentence still holds.
14
+ - R4 nominalization: use the hidden verb ("performs validation of X" → "validates X").
15
+ 3. Run the check again. Repeat until warnings stop dropping. Zero is not the goal.
16
+ 4. Where a warning is wrong, leave the text and add
17
+ `<!-- lepisma: disable-next Rn -->` above the block. Tell the user why.
18
+ 5. Report: density before → after, and any rule that looked noisy. Noisy rules are
19
+ input for tuning `lepisma.toml`.
@@ -0,0 +1,16 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ day: "monday"
8
+ open-pull-requests-limit: 10
9
+ labels:
10
+ - "dependencies"
11
+ - "python"
12
+ groups:
13
+ minor-and-patch:
14
+ update-types:
15
+ - "minor"
16
+ - "patch"
@@ -0,0 +1,17 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ check:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v7
13
+ - uses: astral-sh/setup-uv@v10.3.3
14
+ with:
15
+ enable-cache: true
16
+ - run: uv sync
17
+ - run: uv run poe check
@@ -0,0 +1,58 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ build:
14
+ name: Build distribution
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v7
18
+ - uses: astral-sh/setup-uv@v10.3.3
19
+ - run: uv build
20
+ - run: uvx twine check dist/*
21
+ - uses: actions/upload-artifact@v7
22
+ with:
23
+ name: dist
24
+ path: dist/
25
+
26
+ publish:
27
+ name: Publish to PyPI
28
+ needs: build
29
+ runs-on: ubuntu-latest
30
+ if: startsWith(github.ref, 'refs/tags/')
31
+ environment:
32
+ name: pypi
33
+ url: https://pypi.org/p/lepisma
34
+ permissions:
35
+ id-token: write
36
+ steps:
37
+ - uses: actions/download-artifact@v8
38
+ with:
39
+ name: dist
40
+ path: dist/
41
+ - uses: pypa/gh-action-pypi-publish@release/v1
42
+
43
+ github-release:
44
+ name: Create GitHub Release
45
+ needs: publish
46
+ runs-on: ubuntu-latest
47
+ if: startsWith(github.ref, 'refs/tags/')
48
+ permissions:
49
+ contents: write
50
+ steps:
51
+ - uses: actions/download-artifact@v8
52
+ with:
53
+ name: dist
54
+ path: dist/
55
+ - uses: softprops/action-gh-release@v3
56
+ with:
57
+ files: dist/*
58
+ generate_release_notes: true
@@ -0,0 +1,18 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .eggs/
8
+ *.egg
9
+ .venv/
10
+ venv/
11
+ .env
12
+ *.so
13
+ .coverage
14
+ htmlcov/
15
+ .pytest_cache/
16
+ .ruff_cache/
17
+ .mypy_cache/
18
+ stats/
@@ -0,0 +1,127 @@
1
+ # lepisma
2
+
3
+ Soft prose warnings for Markdown written by Claude. This is the spaCy prototype. See
4
+ README.md for the design, rules and roadmap. Run `uv run lepisma guide` before
5
+ using the CLI.
6
+
7
+ ## Layout
8
+
9
+ - `markdown.py` turns Markdown into prose blocks and maps each character back to its
10
+ line and column. It drops code, tables, front matter and HTML. Inline code becomes
11
+ the `CODE` placeholder.
12
+ - `rules.py` holds R1 to R4. Each is `(block, doc, cfg) -> Iterator[Finding]`.
13
+ - `engine.py` runs spaCy and the rules. Only it, `server.py` and `measure.py` import
14
+ spaCy.
15
+ - `client.py` is the commit hook client. **Stdlib only**; it never raises and always
16
+ exits 0. Keep spaCy and FastAPI out of it, and out of `config.py`, `models.py` and
17
+ `report.py`, which it imports.
18
+ - `measure.py` computes readability stats for whole docs as Polars tables; it is
19
+ the library API (`Measurer`). `measure_report.py` builds the scores and
20
+ `report.md`. Both need the `measure` extra (TextDescriptives, Polars), so keep
21
+ them off the lint and hook path.
22
+ - `spacy_model.py` finds the spaCy model. An installed `en_core_web_sm` wins (the
23
+ dev group has it); otherwise it downloads the hash-pinned wheel once into
24
+ `~/.cache/lepisma/models/`. The model is not a runtime dependency because PyPI
25
+ rejects URL deps. Bump `VERSION`, `WHEEL_SHA256` and the dev-group URL together
26
+ with the spaCy minor.
27
+ - `server.py` is the FastAPI server on 127.0.0.1:8737, with an LRU cache and idle exit.
28
+ - `corpus/` is the baseline test set. `tests/test_corpus.py` checks that bad docs score
29
+ far worse than good ones.
30
+ - `guide.py` and `guide/*.md` are `lepisma guide`, the agent instructions. Pages
31
+ take `{{section.key}}` placeholders filled from `Config` defaults. Update the
32
+ pages when a command, rule or config key changes.
33
+
34
+ ## Tuning a rule
35
+
36
+ Run `uv run lepisma stats corpus/bad corpus/good corpus/toolbox` before and after
37
+ a change to a rule or threshold. A good change keeps bad docs high, and good and
38
+ toolbox docs low. Add a test to
39
+ `tests/test_rules.py` for each false positive you fix.
40
+
41
+ ## Commands
42
+
43
+ - `uv run poe check` — run lint (incl. ruff `S` security rules), typecheck, vulns, test-lint, clones, then tests
44
+ - `uv run poe fix` — auto-format and fix lint issues
45
+ - `uv run poe test` — run tests only
46
+ - `uv run poe check-all` — run all checks including dead-code and unused-deps
47
+
48
+ ## Releasing
49
+
50
+ Bump `version` in `pyproject.toml`, commit, then `git tag vX.Y.Z` and
51
+ `git push origin vX.Y.Z`. `.github/workflows/release.yml` builds, runs
52
+ `twine check`, and publishes to PyPI (trusted publishing, environment `pypi`).
53
+
54
+ ## Stack
55
+
56
+ uv, ruff (lint/format), ty (type check), pytest, poethepoet (task runner)
57
+
58
+ ## Toolbox
59
+
60
+ The [aesop toolbox](https://mojzis.github.io/aesop/llms.txt) is wired in here. Every
61
+ tool explains itself — run `uv run <tool> guide` before reaching for flags, and
62
+ `uv run <tool> guide tune` before changing a threshold. Call them as `uv run <tool>`;
63
+ ty-find's binary is `tyf`.
64
+
65
+ ### On every commit
66
+
67
+ **madoqua** is the only commit hook (`hooks/pre-commit`, `core.hooksPath=hooks`). It
68
+ acts on staged `.py`/`.pyi` files only — a commit without one is a silent no-op, not a
69
+ verified hook.
70
+
71
+ | phase | steps |
72
+ | --- | --- |
73
+ | fix (rewrites and re-stages) | `ruff check --fix`, `ruff format` |
74
+ | check (blocks) | `ruff check`, `ty check`, `biston scan --focus-args`, `zorilla check`, `gerenuk run` |
75
+
76
+ Whole hook runs in ~0.3 s on this repo (gerenuk ~0.27 s of that; `uv run madoqua stats`
77
+ for current numbers). Don't commit with `--no-verify` or `MADOQUA_SKIP` to get past a
78
+ check that is telling the truth.
79
+
80
+ Two changes from the hook this replaced:
81
+
82
+ - `ty check` was repo-wide, it is now **staged-only**. Repo-wide diagnostics no longer
83
+ block a clean commit, but a file with existing diagnostics blocks the first commit
84
+ that touches it. `uv run poe typecheck` still checks everything.
85
+ - **bandit is gone.** Security checks are ruff's flake8-bandit `S` rules, so they now run
86
+ on commit via `ruff check`; `tests/**` ignores S101/S105/S106 in `per-file-ignores`.
87
+
88
+ ### Every now and then
89
+
90
+ - `uv run pycoati . --format pretty` — scores every test for suspicion and prints a
91
+ remediation ladder. Run it before a test-cleanup session. Never put it in a hook or
92
+ CI: it runs the suite three times. It needs pytest-cov or coverage is silently null.
93
+
94
+ ### On demand — instead of grep
95
+
96
+ - `uv run tyf find <symbol>` / `tyf refs` / `tyf list <file>` / `tyf calls` — type-aware
97
+ symbol lookup. **Use these instead of grep** for definitions and references; grep
98
+ cannot tell a definition from a string that happens to match.
99
+ - `uv run gerenuk audit <file>` — lists symbols in a module that nothing references.
100
+ - `uv run gerenuk impacted-tests` — which tests a working-tree diff reaches, and why.
101
+
102
+ ### Fresh clone
103
+
104
+ ```
105
+ uv sync
106
+ uv run madoqua install # once; core.hooksPath is local config, not tracked
107
+ ```
108
+
109
+ ### Refreshing the toolbox
110
+
111
+ ```
112
+ uv lock --refresh --upgrade-package madoqua --upgrade-package gerenuk --upgrade-package biston --upgrade-package zorilla --upgrade-package pycoati --upgrade-package ty-find && uv sync
113
+ ```
114
+
115
+ `--refresh` matters: without it uv may serve a cached index and miss a release
116
+ published minutes ago.
117
+
118
+ ## Notes
119
+
120
+ - ty is in beta — may produce false positives. Prefer `# ty: ignore[rule]` over blanket suppression.
121
+ - The commit hook auto-fixes and restages files. Only blocks on unfixable issues.
122
+ - gerenuk diffs the **working tree** against `origin/main`, so unstaged edits count, and
123
+ any commit touching `pyproject.toml` or `uv.lock` runs the full suite. `--base HEAD`
124
+ scopes it to the commit itself.
125
+ - ty-find's index goes stale after the hook's `ruff format` rewrites a file. The tell is
126
+ `tyf refs <sym>` listing only the definition, or gerenuk reporting `selected` with an
127
+ empty list. `uv run tyf daemon restart` fixes it.
lepisma-0.1.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mojzis <mojzisek@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
lepisma-0.1.1/PKG-INFO ADDED
@@ -0,0 +1,308 @@
1
+ Metadata-Version: 2.5
2
+ Name: lepisma
3
+ Version: 0.1.1
4
+ Summary: Soft, fast prose warnings for Claude-written Markdown docs
5
+ Project-URL: Homepage, https://github.com/mojzis/lepisma
6
+ Project-URL: Issues, https://github.com/mojzis/lepisma/issues
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.11
10
+ Requires-Dist: fastapi>=0.115
11
+ Requires-Dist: pydantic>=2
12
+ Requires-Dist: spacy<3.9,>=3.8
13
+ Requires-Dist: uvicorn>=0.32
14
+ Provides-Extra: measure
15
+ Requires-Dist: polars>=2.1.0; extra == 'measure'
16
+ Requires-Dist: textdescriptives>=2.8.2; extra == 'measure'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # lepisma
20
+
21
+ Fast, soft feedback on Markdown docs that are hard to read. It targets the habits
22
+ of text written by Claude. Warnings go back to Claude so it can rewrite the text.
23
+ **Warnings never block a commit.**
24
+
25
+ The name: *Lepisma saccharina* is the silverfish, which eats paper and chews
26
+ through books.
27
+
28
+ Agents: start with the built-in guide. It picks setup or triage from the repo state.
29
+
30
+ ```
31
+ uvx lepisma@latest guide
32
+ ```
33
+
34
+ Sample output:
35
+
36
+ ```
37
+ docs/setup.md:12:1 R1 Sentence has 31 words; consider splitting.
38
+ docs/setup.md:14:22 R2 Stack of 4 nouns ("pipeline configuration validation error"); rephrase with a verb or preposition.
39
+ docs/setup.md:15:9 R3 Habit word "leverage"; try: use.
40
+ docs/setup.md:18:5 R4 "performs validation" hides the verb; try "validate".
41
+ ```
42
+
43
+ ## How it works
44
+
45
+ - **Local spaCy server.** A small FastAPI process keeps `en_core_web_sm` loaded on
46
+ `127.0.0.1:8737`. The hook starts it on first use and reuses it after that. It
47
+ exits after 4 hours without requests.
48
+ - **Commit hook client.** It sends only the staged Markdown files to the server and
49
+ prints the warnings. It uses only the standard library and starts in under 0.1 s.
50
+ - **Fail open.** If the server is down, the hook starts it and skips this commit. If
51
+ the server takes longer than 300 ms, the hook skips. Either way it prints nothing.
52
+ - **Output for Claude.** One warning per line as `file:line:col rule-id message`, or
53
+ JSON with `--format json`.
54
+
55
+ ## Install
56
+
57
+ Add it to the repo's dev dependencies, so the hook finds it in `.venv`:
58
+
59
+ ```
60
+ uv add --dev lepisma
61
+ cp /path/to/lepisma/lepisma.toml . # optional; every key has a default
62
+ ```
63
+
64
+ ### The spaCy model
65
+
66
+ spaCy models are not on PyPI, so `en_core_web_sm` 3.8.0 is not a dependency. On
67
+ first use, `check`, `stats`, `measure` and the server download the wheel once
68
+ (~13 MB) and check its SHA-256. They unpack it to
69
+ `~/.cache/lepisma/models/`, outside any virtualenv, so every `uvx` run and
70
+ venv shares one copy. This needs no pip. The hook client never downloads; it
71
+ skips commits while the server starts.
72
+
73
+ Offline, install the model into the environment instead:
74
+
75
+ ```
76
+ uv pip install https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl
77
+ ```
78
+
79
+ Or set `LEPISMA_MODEL_WHEEL` to a local copy (or mirror URL) of that wheel.
80
+
81
+ ### With madoqua (the main path)
82
+
83
+ Add a check step that wakes on staged Markdown. madoqua appends the matched files:
84
+
85
+ ```toml
86
+ [tool.madoqua]
87
+ extend_check = [
88
+ { name = "lepisma", cmd = "lepisma hook", on = ["*.md", "*.markdown"], timeout_s = 5 },
89
+ ]
90
+ ```
91
+
92
+ **Gap:** madoqua prints a step's output only when the step fails, and
93
+ `lepisma hook` always exits 0. So the warnings stay hidden until madoqua gets
94
+ a warn-only step mode that prints output without blocking
95
+ ([madoqua#19](https://github.com/mojzis/madoqua/issues/19)).
96
+
97
+ ### Without madoqua
98
+
99
+ ```
100
+ lepisma install-hook # adds one warn-only line to the pre-commit hook
101
+ ```
102
+
103
+ `install-hook` follows `core.hooksPath`. It puts its line right after the shebang, so
104
+ it runs even when the rest of the hook ends in `exec`.
105
+
106
+ ## Commands
107
+
108
+ | Command | What it does |
109
+ | --- | --- |
110
+ | `lepisma guide [setup\|triage\|tune]` | Print instructions for an agent. With no topic it picks one from the current directory. |
111
+ | `lepisma check PATH...` | Lint files or folders in-process, no server. `--format json`, `--strict` exits 1 on warnings. |
112
+ | `lepisma stats PATH...` | Warnings per 1,000 words per file and rule, one table per path. |
113
+ | `lepisma measure PATH...` | Readability stats for whole docs. Writes Parquet tables and `report.md` to `--out` (default `stats/`). Needs the `measure` extra. |
114
+ | `lepisma hook [FILE...]` | What the git hook runs. Lints the given files, or asks git for the staged docs. `--wait 10` waits for the server instead of skipping. |
115
+ | `lepisma serve` | Run the server in the foreground. `--idle-timeout 0` keeps it up. |
116
+ | `lepisma stop` | Stop the server, for example after an upgrade. |
117
+
118
+ The hook replaces a server whose code differs from its own. It compares a hash of the
119
+ package source, so upgrades and local edits take effect. The commit that finds the
120
+ mismatch is skipped.
121
+
122
+ Environment variables:
123
+
124
+ - `LEPISMA_VERBOSE=1` prints why the hook skipped.
125
+ - `LEPISMA_DISABLE=1` turns the hook off.
126
+ - `LEPISMA_PORT` and `LEPISMA_TIMEOUT_MS` override the config.
127
+ - `LEPISMA_MODEL_WHEEL` is a path or URL to fetch the spaCy model wheel from.
128
+
129
+ The server log is in `~/.cache/lepisma/server.log`.
130
+
131
+ ## Rules
132
+
133
+ | ID | Name | Warns when | Strong when |
134
+ | --- | --- | --- | --- |
135
+ | R1 | `long-sentence` | a sentence has more than 25 words | more than 35 |
136
+ | R2 | `noun-stack` | 4 or more nouns in a row modify each other | 5 or more |
137
+ | R3 | `habit-word` | a word from the habit list appears | more than 2 in one paragraph |
138
+ | R4 | `nominalization` | a weak verb takes an abstract noun ("performs validation of") | the sentence also has 4 or more abstract nouns |
139
+
140
+ Details:
141
+
142
+ - **All rules** skip code blocks, inline code, tables, front matter, HTML and link
143
+ targets. List items that hold only an identifier, like `- max_retries`, are skipped
144
+ too.
145
+ - **Headings** get R3 only.
146
+ - **R2** ignores runs made only of proper nouns, like "GitHub Actions CI". It also
147
+ ignores runs with more than one proper noun (`max_proper_nouns`), like "Claude Code
148
+ conversation logs". Inline code breaks a run, and a hyphenated word like
149
+ "event-store" counts as one noun. The threshold is 4, not 3: at 3 it flagged plain
150
+ technical terms like "Unix domain socket" in real docs. At 5 it would miss every true
151
+ stack the rollout found. The tagger often calls a verb a noun, so R2 drops these from
152
+ a run before it counts:
153
+ - a verb that opens a list item, sentence or labelled line: "Test line number
154
+ accuracy", "Purpose: Validates crossword grid structure";
155
+ - a plural before another noun, which ends a subject: "a header toggle pins
156
+ light/dark", "Claude Code loads ancestor files";
157
+ - a plural verb that the parser made the root of a verbless sentence, like "resets"
158
+ in "The progress bar resets after a logout".
159
+
160
+ The verb test uses the WordNet verb list in spaCy's lemmatizer.
161
+ <!-- lepisma: disable-next R3 -->
162
+ - **R3** matches inflections, so "leveraging" counts as "leverage". The default list:
163
+ comprehensive, robust, seamless, seamlessly, leverage, utilize, delve, crucial,
164
+ streamline, ensure, facilitate, "in order to", "it is important to note",
165
+ successfully, "solid foundation". "successfully" counts only right before a verb:
166
+ "successfully implemented", not "runs successfully". Words in `ok_at_item_start`
167
+ (default: ensure) are fine as the first word of a list item or checklist line, as in
168
+ "- Ensure the build passes". "essential" is off the list. In real docs it mostly
169
+ meant just that ("essential information").
170
+ - **R4** counts nouns that end in -tion, -sion, -ment, -ance, -ence or -ity. A stoplist
171
+ removes concrete words like "function", "document" and "instance". A weak verb is
172
+ flagged only when its noun comes from a verb, as "placement" comes from "place".
173
+ "fraction" and "flexibility" have no verb, so nothing is hidden. For "be" and "have",
174
+ the noun must also take "of" and not be counted. So "is the implementation of" is
175
+ flagged, but "is one placement of" is not. "provide", "give" and "offer" are
176
+ skipped when they hand something over. That means a recipient ("provides guidance to
177
+ Claude Code") or a named deliverable ("provide image descriptions").
178
+
179
+ ## Measuring whole docs
180
+
181
+ `lepisma measure` reports how hard a set of docs is to read, and where the
182
+ hardest parts are. It never edits a doc.
183
+
184
+ ```
185
+ uv add --dev "lepisma[measure]"
186
+ uv run lepisma measure docs/ --out stats/
187
+ ```
188
+
189
+ It writes four files:
190
+
191
+ - `sentences.parquet`: one row per sentence, with file, line, column, section and
192
+ source text. It counts words, passives, commas, parentheses, dashes,
193
+ nominalizations and habit words. It also gives mean dependency distance, main
194
+ verb position and the longest noun run.
195
+ - `sections.parquet` and `files.parquet`: sentence length as mean, median, p90 and
196
+ max, and the share of sentences over 25 and 35 words. Readability as Flesch
197
+ reading ease, Flesch-Kincaid grade, Gunning Fog and LIX. Part-of-speech shares and
198
+ the noun-to-verb ratio. Nominalizations per 100 words, repeated 5-grams, and habit
199
+ words per 1,000 words.
200
+ - `report.md`: medians and p90s, the 15 hardest files and sections, the 30 hardest
201
+ sentences, and which metrics spread enough to make good lint thresholds.
202
+
203
+ It uses the same parser as the linter, and the R2 to R4 definitions of noun runs,
204
+ nominalizations and habit words. Headings only name sections. List items under 4
205
+ words are skipped. TextDescriptives computes readability, dependency distance,
206
+ part-of-speech shares and repetition.
207
+
208
+ ### As a library
209
+
210
+ A docs server can load the model once and measure pages on request:
211
+
212
+ ```python
213
+ from lepisma.measure import Measurer
214
+ from lepisma.measure_report import render
215
+
216
+ measurer = Measurer() # loads spaCy once; reuse it
217
+ result = measurer.measure({"guide.md": text}) # {path: markdown}
218
+ result.files.row(0, named=True) # dict of file metrics
219
+ result.sentences.sort("words", descending=True).head(5)
220
+ render(result, min_words=100) # the report as Markdown
221
+ ```
222
+
223
+ `result.sentences`, `.sections` and `.files` are Polars data frames.
224
+
225
+ ## Configuration
226
+
227
+ Put one `lepisma.toml` at the repo root. The [sample in this repo](lepisma.toml)
228
+ lists every key with its default.
229
+
230
+ To turn rules off inside a document:
231
+
232
+ ```markdown
233
+ <!-- lepisma: disable R2 --> off until enable or end of file
234
+ <!-- lepisma: enable R2 -->
235
+ <!-- lepisma: disable-next R1 R3 --> off for the next block only
236
+ <!-- lepisma: disable --> all rules
237
+ ```
238
+
239
+ Rules can be named by ID (`R2`) or by name (`noun-stack`).
240
+
241
+ ## Feedback loop with Claude
242
+
243
+ Add this to the `CLAUDE.md` of a repo that uses the hook:
244
+
245
+ > When a commit prints `lepisma` warnings, rewrite the flagged prose and commit
246
+ > again. Split long sentences, turn noun stacks and nominalizations into verbs, and
247
+ > replace habit words with plain ones. Use `<!-- lepisma: disable-next Rn -->` only
248
+ > where a warning is wrong, and say why.
249
+
250
+ For a whole document, run `lepisma check --format json doc.md`, give Claude the
251
+ result, and ask for a rewrite. The `prose-rewrite` skill in `.claude/skills/` does this
252
+ loop. Then check the result with people, not just with the linter.
253
+
254
+ ## Measuring success
255
+
256
+ - **Density.** Warnings per 1,000 words before and after a rewrite:
257
+ `lepisma stats before/ after/`.
258
+ - **Human rating.** A quick 1 to 5 readability score on the test set.
259
+ - **Noise.** How often people disable each rule. Remove the noisy ones.
260
+
261
+ The baseline test set lives in [`corpus/`](corpus/):
262
+
263
+ - `bad/` and `good/` hold synthetic docs that read badly and well.
264
+ `tests/test_corpus.py` checks that bad docs score far worse than good ones.
265
+ - `toolbox/` holds the real docs of the aesop tools (gerenuk, madoqua, biston,
266
+ zorilla, coati, ty-find). They are unlabeled. The test checks that R2 to R4 stay
267
+ under 1 per 1,000 words there, which catches rules that start flagging normal
268
+ prose. `scripts/sync-toolbox-corpus.sh` refreshes the copy.
269
+
270
+ Swap in 10 to 20 real hard-to-read docs from your team for `bad/` before tuning
271
+ further.
272
+
273
+ Current numbers (warnings per 1,000 words):
274
+
275
+ | Set | Words | R1 | R2 | R3 | R4 | Total |
276
+ | --- | --- | --- | --- | --- | --- | --- |
277
+ | bad | 2,698 | 7.4 | 3.3 | 41.9 | 8.5 | 61.2 |
278
+ | good | 1,235 | 0 | 0 | 0.8 | 0 | 0.8 |
279
+ | toolbox | 51,529 | 9.0 | 0.1 | 0.0 | 0.2 | 9.3 |
280
+
281
+ ## Roadmap
282
+
283
+ 1. ~~Baseline test set~~ (placeholders; swap in real docs)
284
+ 2. ~~Four starter rules~~
285
+ 3. ~~Commit hook in warn-only mode~~
286
+ 4. Feedback loop: rewrite with Claude, then ask people whether the result reads better.
287
+ 5. Expand: try passive voice per paragraph, late main verb, deep nesting, too many
288
+ parentheses, undefined acronyms, repeated openings, summary filler and Flesch score.
289
+ Add an optional LLM judge in CI.
290
+ 6. Port the useful rules to Rust (harper-core and Weir) if speed or distribution
291
+ becomes a problem.
292
+
293
+ ## Development
294
+
295
+ ```
296
+ uv sync
297
+ uv run madoqua install
298
+ uv run poe check
299
+ ```
300
+
301
+ ### Releasing
302
+
303
+ Pushing a `vX.Y.Z` tag runs `.github/workflows/release.yml`. It builds, runs
304
+ `twine check`, publishes to PyPI by trusted publishing and creates a GitHub
305
+ release.
306
+
307
+ 1. Bump `version` in `pyproject.toml` and commit.
308
+ 2. `git tag vX.Y.Z` (the same version) and `git push origin vX.Y.Z`.