cihai-cli 0.32.0__tar.gz → 0.34.0__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 (115) hide show
  1. cihai_cli-0.34.0/.github/contributing.md +26 -0
  2. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/.github/workflows/docs.yml +12 -3
  3. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/.github/workflows/tests.yml +1 -1
  4. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/.gitignore +4 -0
  5. cihai_cli-0.34.0/.tool-versions +3 -0
  6. cihai_cli-0.34.0/AGENTS.md +435 -0
  7. cihai_cli-0.34.0/CHANGES +815 -0
  8. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/PKG-INFO +2 -2
  9. cihai_cli-0.34.0/conftest.py +18 -0
  10. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/cli/completion.md +6 -6
  11. cihai_cli-0.34.0/docs/cli/index.md +60 -0
  12. cihai_cli-0.34.0/docs/conf.py +50 -0
  13. cihai_cli-0.34.0/docs/index.md +76 -0
  14. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/justfile +3 -3
  15. cihai_cli-0.34.0/docs/project/code-style.md +31 -0
  16. cihai_cli-0.34.0/docs/project/contributing.md +28 -0
  17. cihai_cli-0.34.0/docs/project/index.md +36 -0
  18. cihai_cli-0.34.0/docs/project/releasing.md +29 -0
  19. cihai_cli-0.34.0/docs/redirects.txt +2 -0
  20. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/pyproject.toml +40 -37
  21. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/src/cihai_cli/__about__.py +1 -1
  22. cihai_cli-0.34.0/src/cihai_cli/__init__.py +7 -0
  23. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/src/cihai_cli/_colors.py +6 -0
  24. cihai_cli-0.34.0/uv.lock +2146 -0
  25. cihai_cli-0.32.0/.tool-versions +0 -3
  26. cihai_cli-0.32.0/AGENTS.md +0 -174
  27. cihai_cli-0.32.0/CHANGES +0 -729
  28. cihai_cli-0.32.0/docs/_ext/__init__.py +0 -3
  29. cihai_cli-0.32.0/docs/_ext/argparse_exemplar.py +0 -1278
  30. cihai_cli-0.32.0/docs/_ext/argparse_lexer.py +0 -429
  31. cihai_cli-0.32.0/docs/_ext/argparse_roles.py +0 -370
  32. cihai_cli-0.32.0/docs/_ext/cli_usage_lexer.py +0 -115
  33. cihai_cli-0.32.0/docs/_ext/conftest.py +0 -15
  34. cihai_cli-0.32.0/docs/_ext/sphinx_argparse_neo/__init__.py +0 -101
  35. cihai_cli-0.32.0/docs/_ext/sphinx_argparse_neo/compat.py +0 -271
  36. cihai_cli-0.32.0/docs/_ext/sphinx_argparse_neo/directive.py +0 -241
  37. cihai_cli-0.32.0/docs/_ext/sphinx_argparse_neo/nodes.py +0 -564
  38. cihai_cli-0.32.0/docs/_ext/sphinx_argparse_neo/parser.py +0 -627
  39. cihai_cli-0.32.0/docs/_ext/sphinx_argparse_neo/renderer.py +0 -521
  40. cihai_cli-0.32.0/docs/_ext/sphinx_argparse_neo/utils.py +0 -42
  41. cihai_cli-0.32.0/docs/_static/css/argparse-highlight.css +0 -274
  42. cihai_cli-0.32.0/docs/_static/css/custom.css +0 -20
  43. cihai_cli-0.32.0/docs/_templates/layout.html +0 -45
  44. cihai_cli-0.32.0/docs/_templates/sidebar/projects.html +0 -69
  45. cihai_cli-0.32.0/docs/cli/index.md +0 -36
  46. cihai_cli-0.32.0/docs/conf.py +0 -231
  47. cihai_cli-0.32.0/docs/index.md +0 -26
  48. cihai_cli-0.32.0/docs/redirects.txt +0 -2
  49. cihai_cli-0.32.0/src/cihai_cli/__init__.py +0 -1
  50. cihai_cli-0.32.0/tests/docs/__init__.py +0 -3
  51. cihai_cli-0.32.0/tests/docs/_ext/__init__.py +0 -3
  52. cihai_cli-0.32.0/tests/docs/_ext/conftest.py +0 -11
  53. cihai_cli-0.32.0/tests/docs/_ext/sphinx_argparse_neo/__init__.py +0 -3
  54. cihai_cli-0.32.0/tests/docs/_ext/sphinx_argparse_neo/conftest.py +0 -237
  55. cihai_cli-0.32.0/tests/docs/_ext/sphinx_argparse_neo/test_compat.py +0 -330
  56. cihai_cli-0.32.0/tests/docs/_ext/sphinx_argparse_neo/test_nodes.py +0 -259
  57. cihai_cli-0.32.0/tests/docs/_ext/sphinx_argparse_neo/test_parser.py +0 -524
  58. cihai_cli-0.32.0/tests/docs/_ext/sphinx_argparse_neo/test_renderer.py +0 -498
  59. cihai_cli-0.32.0/tests/docs/_ext/sphinx_argparse_neo/test_utils.py +0 -72
  60. cihai_cli-0.32.0/tests/docs/_ext/test_argparse_exemplar.py +0 -1020
  61. cihai_cli-0.32.0/tests/docs/_ext/test_argparse_lexer.py +0 -798
  62. cihai_cli-0.32.0/tests/docs/_ext/test_argparse_roles.py +0 -442
  63. cihai_cli-0.32.0/tests/docs/_ext/test_cli_usage_lexer.py +0 -358
  64. cihai_cli-0.32.0/uv.lock +0 -1894
  65. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/.codecov.yml +0 -0
  66. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/.cursor/rules/avoid-debug-loops.mdc +0 -0
  67. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/.cursor/rules/dev-loop.mdc +0 -0
  68. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/.cursor/rules/git-commits.mdc +0 -0
  69. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/.cursor/rules/notes-llms-txt.mdc +0 -0
  70. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/.github/dependabot.yml +0 -0
  71. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/.prettierrc +0 -0
  72. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/.python-version +0 -0
  73. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/.tmuxp.yaml +0 -0
  74. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/.vim/coc-settings.json +0 -0
  75. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/.windsurfrules +0 -0
  76. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/CLAUDE.md +0 -0
  77. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/LICENSE +0 -0
  78. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/MANIFEST.in +0 -0
  79. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/MIGRATION +0 -0
  80. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/README.md +0 -0
  81. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/_static/favicon.ico +0 -0
  82. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/_static/img/cihai.svg +0 -0
  83. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/_static/img/icons/icon-128x128.png +0 -0
  84. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/_static/img/icons/icon-144x144.png +0 -0
  85. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/_static/img/icons/icon-152x152.png +0 -0
  86. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/_static/img/icons/icon-192x192.png +0 -0
  87. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/_static/img/icons/icon-384x384.png +0 -0
  88. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/_static/img/icons/icon-512x512-centered.png +0 -0
  89. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/_static/img/icons/icon-512x512.png +0 -0
  90. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/_static/img/icons/icon-72x72.png +0 -0
  91. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/_static/img/icons/icon-96x96.png +0 -0
  92. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/api.md +0 -0
  93. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/cli/info.md +0 -0
  94. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/cli/reverse.md +0 -0
  95. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/history.md +0 -0
  96. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/manifest.json +0 -0
  97. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/migration.md +0 -0
  98. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/docs/quickstart.md +0 -0
  99. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/justfile +0 -0
  100. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/src/cihai_cli/_formatter.py +0 -0
  101. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/src/cihai_cli/cli.py +0 -0
  102. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/src/cihai_cli/py.typed +0 -0
  103. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/tests/__init__.py +0 -0
  104. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/tests/conftest.py +0 -0
  105. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/tests/fixtures/Unihan_DictionaryIndices.txt +0 -0
  106. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/tests/fixtures/Unihan_DictionaryLikeData.txt +0 -0
  107. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/tests/fixtures/Unihan_IRGSources.txt +0 -0
  108. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/tests/fixtures/Unihan_NumericValues.txt +0 -0
  109. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/tests/fixtures/Unihan_OtherMappings.txt +0 -0
  110. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/tests/fixtures/Unihan_RadicalStrokeCounts.txt +0 -0
  111. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/tests/fixtures/Unihan_Readings.txt +0 -0
  112. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/tests/fixtures/Unihan_Variants.txt +0 -0
  113. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/tests/fixtures/test_config.yml +0 -0
  114. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/tests/test_cli.py +0 -0
  115. {cihai_cli-0.32.0 → cihai_cli-0.34.0}/tox.ini +0 -0
@@ -0,0 +1,26 @@
1
+ # Contributing
2
+
3
+ When contributing to this repository, please first discuss the change you wish to make via issue,
4
+ email, or any other method with the maintainers of this repository before making a change.
5
+
6
+ See [AGENTS.md](../AGENTS.md) for coding standards and development workflow.
7
+
8
+ ## Pull Request Process
9
+
10
+ 1. **Format and lint**: `uv run ruff format .` then `uv run ruff check . --fix --show-fixes`
11
+ 2. **Type check**: `uv run mypy`
12
+ 3. **Test**: `uv run pytest` — all tests must pass before submitting
13
+ 4. **Document**: Update docs if your change affects the public interface
14
+ 5. You may merge the Pull Request once you have the sign-off of one other developer. If you
15
+ do not have permission to do that, you may request a reviewer to merge it for you.
16
+
17
+ ## Decorum
18
+
19
+ - Participants will be tolerant of opposing views.
20
+ - Participants must ensure that their language and actions are free of personal
21
+ attacks and disparaging personal remarks.
22
+ - When interpreting the words and actions of others, participants should always
23
+ assume good intentions.
24
+ - Behaviour which can be reasonably considered harassment will not be tolerated.
25
+
26
+ Based on [Ruby's Community Conduct Guideline](https://www.ruby-lang.org/en/conduct/)
@@ -20,7 +20,7 @@ jobs:
20
20
  - uses: actions/checkout@v6
21
21
 
22
22
  - name: Filter changed file paths to outputs
23
- uses: dorny/paths-filter@v3.0.2
23
+ uses: dorny/paths-filter@v4.0.1
24
24
  id: changes
25
25
  with:
26
26
  filters: |
@@ -51,7 +51,7 @@ jobs:
51
51
  run: uv sync --all-extras --dev
52
52
 
53
53
  - name: Install just
54
- uses: extractions/setup-just@v3
54
+ uses: extractions/setup-just@v4
55
55
 
56
56
  - name: Print python versions
57
57
  if: env.PUBLISH == 'true'
@@ -59,6 +59,15 @@ jobs:
59
59
  python -V
60
60
  uv run python -V
61
61
 
62
+ - name: Cache sphinx fonts
63
+ if: env.PUBLISH == 'true'
64
+ uses: actions/cache@v5
65
+ with:
66
+ path: ~/.cache/sphinx-fonts
67
+ key: sphinx-fonts-${{ hashFiles('docs/conf.py') }}
68
+ restore-keys: |
69
+ sphinx-fonts-
70
+
62
71
  - name: Build documentation
63
72
  if: env.PUBLISH == 'true'
64
73
  run: |
@@ -66,7 +75,7 @@ jobs:
66
75
 
67
76
  - name: Configure AWS Credentials
68
77
  if: env.PUBLISH == 'true'
69
- uses: aws-actions/configure-aws-credentials@v5
78
+ uses: aws-actions/configure-aws-credentials@v6
70
79
  with:
71
80
  role-to-assume: ${{ secrets.CIHAI_CLI_DOCS_ROLE_ARN }}
72
81
  aws-region: us-east-1
@@ -41,7 +41,7 @@ jobs:
41
41
  - name: Test with pytest
42
42
  run: uv run py.test --cov=./ --cov-report=xml
43
43
 
44
- - uses: codecov/codecov-action@v5
44
+ - uses: codecov/codecov-action@v6
45
45
  with:
46
46
  token: ${{ secrets.CODECOV_TOKEN }}
47
47
 
@@ -56,3 +56,7 @@ monkeytype.sqlite3
56
56
 
57
57
  # Test data
58
58
  .cihai_cache/
59
+
60
+ # Generated by sphinx_fonts extension (downloaded at build time)
61
+ docs/_static/fonts/
62
+ docs/_static/css/fonts.css
@@ -0,0 +1,3 @@
1
+ just 1.54
2
+ uv 0.11.25
3
+ python 3.14 3.13 3.12 3.11 3.10
@@ -0,0 +1,435 @@
1
+ # AGENTS.md
2
+
3
+ Guidance for AI agents (Cursor, Claude Code, Copilot, etc.) working in this repository.
4
+
5
+ ## CRITICAL REQUIREMENTS
6
+
7
+ ### Test Success
8
+ - ALL tests must pass (unit, doctest, lint, type checks) before declaring work complete.
9
+ - Do not describe code as "working" if any test fails.
10
+ - Fix regressions rather than disabling or skipping tests unless explicitly approved.
11
+
12
+ ## Project Overview
13
+
14
+ gp-libs is the shared tooling stack used across the git-pull ecosystem. This repository, `cihai-cli`, is a command-line interface built on top of the `cihai` library to explore the Unihan (CJK) character database. Key abilities:
15
+ - Lookup CJK characters with `cihai info <char>` and YAML-formatted output.
16
+ - Reverse search definitions with `cihai reverse <term>`.
17
+ - Bootstraps and queries the Unihan dataset via `cihai` / `unihan-etl`.
18
+ - Provides a small, typed argparse-based CLI (`src/cihai_cli/cli.py`) exposed as the `cihai` entry point.
19
+
20
+ ## Development Environment
21
+
22
+ This project uses:
23
+ - Python 3.10+
24
+ - [uv](https://github.com/astral-sh/uv) for dependency and task execution
25
+ - [ruff](https://github.com/astral-sh/ruff) for linting/formatting
26
+ - [mypy](https://github.com/python/mypy) with strict settings
27
+ - [pytest](https://docs.pytest.org/) (+ doctests) for testing
28
+ - [gp-libs](https://github.com/gp-libs/gp-libs) for shared docs/testing helpers
29
+ - Sphinx (Furo) for documentation
30
+
31
+ ## Common Commands
32
+
33
+ ### Setup
34
+ ```bash
35
+ # Install dependencies (editable)
36
+ uv pip install --editable .
37
+ uv pip sync
38
+
39
+ # Install with dev extras
40
+ uv pip install --editable . -G dev
41
+ ```
42
+
43
+ ### Tests
44
+ ```bash
45
+ just test # or: uv run pytest
46
+ uv run pytest tests/test_cli.py # single file
47
+ uv run pytest tests/test_cli.py::test_info_command # single test
48
+
49
+ just start # run tests then watch with pytest-watcher
50
+ uv run ptw . # standalone watcher (doctests enabled by default)
51
+ ```
52
+
53
+ ### Linting & Types
54
+ ```bash
55
+ just ruff # uv run ruff check .
56
+ just ruff-format # uv run ruff format .
57
+ uv run ruff check . --fix --show-fixes
58
+
59
+ just mypy # strict type checking
60
+ ```
61
+
62
+ ### Documentation
63
+ ```bash
64
+ just build-docs # build Sphinx HTML in docs/_build
65
+ just start-docs # autobuild + livereload
66
+ just design-docs # update CSS/JS assets
67
+ ```
68
+
69
+ ### Workflow (recommended)
70
+ 1) `uv run ruff format .`
71
+ 2) `uv run pytest`
72
+ 3) `uv run ruff check . --fix --show-fixes`
73
+ 4) `uv run mypy`
74
+ 5) `uv run pytest` (verify clean)
75
+
76
+ ## Code Architecture (quick map)
77
+ - `src/cihai_cli/cli.py`: argparse entrypoint, implements `info` and `reverse` commands, logging setup.
78
+ - `src/cihai_cli/__about__.py`: package metadata (`__version__`).
79
+ - Tests: `tests/` (unit) plus doctests in `src/` and `docs/`.
80
+ - Docs: `docs/` Sphinx project (Furo theme).
81
+
82
+ ## Testing Strategy
83
+ - Pytest with doctests enabled (`addopts` in `pyproject.toml`).
84
+ - Prefer real `cihai` / `unihan_etl` integration over heavy mocking; reuse fixtures where present.
85
+ - Watch mode: `uv run ptw .` (used in `just start`).
86
+ - Coverage via `pytest-cov`; configuration in `pyproject.toml`.
87
+ - Prefer fixtures over mocks (`server`, `session`, etc. when available); use `tmp_path` over `tempfile`, `monkeypatch` over `unittest.mock`.
88
+
89
+ ## Coding Standards
90
+ - `from __future__ import annotations` required; enforced by ruff.
91
+ - Namespace imports for stdlib/typing (`import typing as t`); third-party packages may use `from X import Y`.
92
+ - Docstrings follow NumPy style (see `tool.ruff.lint.pydocstyle`).
93
+ - Python target version: 3.10 (`tool.ruff.target-version`).
94
+ - Keep CLI output human-friendly YAML; avoid breaking existing flags/args.
95
+ - Doctests: keep concise, narrative Examples blocks; move complex flows to `tests/examples/`.
96
+
97
+ ## Logging Standards
98
+
99
+ These rules guide future logging changes; existing code may not yet conform.
100
+
101
+ ### Logger setup
102
+
103
+ - Use `logging.getLogger(__name__)` in every module
104
+ - Add `NullHandler` in library `__init__.py` files
105
+ - Never configure handlers, levels, or formatters in library code — that's the application's job
106
+
107
+ ### Structured context via `extra`
108
+
109
+ Pass structured data on every log call where useful for filtering, searching, or test assertions.
110
+
111
+ **Core keys** (stable, scalar, safe at any log level):
112
+
113
+ | Key | Type | Context |
114
+ |-----|------|---------|
115
+ | `unihan_field` | `str` | UNIHAN field name |
116
+ | `unihan_source_file` | `str` | source data file path |
117
+ | `unihan_record_count` | `int` | records processed |
118
+ | `cihai_command` | `str` | CLI command name |
119
+
120
+ **Heavy/optional keys** (DEBUG only, potentially large):
121
+
122
+ | Key | Type | Context |
123
+ |-----|------|---------|
124
+ | `unihan_stdout` | `list[str]` | subprocess stdout lines (truncate or cap; `%(unihan_stdout)s` produces repr) |
125
+ | `unihan_stderr` | `list[str]` | subprocess stderr lines (same caveats) |
126
+
127
+ Treat established keys as compatibility-sensitive — downstream users may build dashboards and alerts on them. Change deliberately.
128
+
129
+ ### Key naming rules
130
+
131
+ - `snake_case`, not dotted; `unihan_` prefix
132
+ - Prefer stable scalars; avoid ad-hoc objects
133
+ - Heavy keys (`unihan_stdout`, `unihan_stderr`) are DEBUG-only; consider companion `unihan_stdout_len` fields or hard truncation (e.g. `stdout[:100]`)
134
+
135
+ ### Lazy formatting
136
+
137
+ `logger.debug("msg %s", val)` not f-strings. Two rationales:
138
+ - Deferred string interpolation: skipped entirely when level is filtered
139
+ - Aggregator message template grouping: `"Running %s"` is one signature grouped ×10,000; f-strings make each line unique
140
+
141
+ When computing `val` itself is expensive, guard with `if logger.isEnabledFor(logging.DEBUG)`.
142
+
143
+ ### stacklevel for wrappers
144
+
145
+ Increment for each wrapper layer so `%(filename)s:%(lineno)d` and OTel `code.filepath` point to the real caller. Verify whenever call depth changes.
146
+
147
+ ### LoggerAdapter for persistent context
148
+
149
+ For objects with stable identity (Dataset, Reader, Exporter), use `LoggerAdapter` to avoid repeating the same `extra` on every call. Lead with the portable pattern (override `process()` to merge); `merge_extra=True` simplifies this on Python 3.13+.
150
+
151
+ ### Log levels
152
+
153
+ | Level | Use for | Examples |
154
+ |-------|---------|----------|
155
+ | `DEBUG` | Internal mechanics, data I/O | Field parsing, record transformation steps |
156
+ | `INFO` | Data lifecycle, user-visible operations | Download completed, export finished, database bootstrapped |
157
+ | `WARNING` | Recoverable issues, deprecation, user-actionable config | Missing optional field, deprecated data format |
158
+ | `ERROR` | Failures that stop an operation | Download failed, parse error, database write failed |
159
+
160
+ Config discovery noise belongs in `DEBUG`; only surprising/user-actionable config issues → `WARNING`.
161
+
162
+ ### Message style
163
+
164
+ - Lowercase, past tense for events: `"download completed"`, `"parse error"`
165
+ - No trailing punctuation
166
+ - Keep messages short; put details in `extra`, not the message string
167
+
168
+ ### Exception logging
169
+
170
+ - Use `logger.exception()` only inside `except` blocks when you are **not** re-raising
171
+ - Use `logger.error(..., exc_info=True)` when you need the traceback outside an `except` block
172
+ - Avoid `logger.exception()` followed by `raise` — this duplicates the traceback. Either add context via `extra` that would otherwise be lost, or let the exception propagate
173
+
174
+ ### Testing logs
175
+
176
+ Assert on `caplog.records` attributes, not string matching on `caplog.text`:
177
+ - Scope capture: `caplog.at_level(logging.DEBUG, logger="cihai_cli.cli")`
178
+ - Filter records rather than index by position: `[r for r in caplog.records if hasattr(r, "unihan_field")]`
179
+ - Assert on schema: `record.unihan_record_count == 100` not `"100 records" in caplog.text`
180
+ - `caplog.record_tuples` cannot access extra fields — always use `caplog.records`
181
+
182
+ ### Avoid
183
+
184
+ - f-strings/`.format()` in log calls
185
+ - Unguarded logging in hot loops (guard with `isEnabledFor()`)
186
+ - Catch-log-reraise without adding new context
187
+ - `print()` for diagnostics
188
+ - Logging secret env var values (log key names only)
189
+ - Non-scalar ad-hoc objects in `extra`
190
+ - Requiring custom `extra` fields in format strings without safe defaults (missing keys raise `KeyError`)
191
+
192
+ ## Documentation Standards
193
+
194
+ ### Code Blocks in Documentation
195
+
196
+ When writing documentation (README, CHANGES, docs/), follow these rules for code blocks:
197
+
198
+ **One command per code block.** This makes commands individually copyable. For sequential commands, either use separate code blocks or chain them with `&&` or `;` and `\` continuations (keeping it one logical command).
199
+
200
+ **Put explanations outside the code block**, not as comments inside.
201
+
202
+ Good:
203
+
204
+ Run the tests:
205
+
206
+ ```console
207
+ $ uv run pytest
208
+ ```
209
+
210
+ Run with coverage:
211
+
212
+ ```console
213
+ $ uv run pytest --cov
214
+ ```
215
+
216
+ Bad:
217
+
218
+ ```console
219
+ # Run the tests
220
+ $ uv run pytest
221
+
222
+ # Run with coverage
223
+ $ uv run pytest --cov
224
+ ```
225
+
226
+ ### Shell Command Formatting
227
+
228
+ These rules apply to shell commands in documentation (README, CHANGES, docs/), **not** to Python doctests.
229
+
230
+ **Use `console` language tag with `$ ` prefix.** This distinguishes interactive commands from scripts and enables prompt-aware copy in many terminals.
231
+
232
+ Good:
233
+
234
+ ```console
235
+ $ uv run pytest
236
+ ```
237
+
238
+ Bad:
239
+
240
+ ```bash
241
+ uv run pytest
242
+ ```
243
+
244
+ **Split long commands with `\` for readability.** Each flag or flag+value pair gets its own continuation line, indented. Positional parameters go on the final line.
245
+
246
+ Good:
247
+
248
+ ```console
249
+ $ pipx install \
250
+ --suffix=@next \
251
+ --pip-args '\--pre' \
252
+ --force \
253
+ 'cihai-cli'
254
+ ```
255
+
256
+ Bad:
257
+
258
+ ```console
259
+ $ pipx install --suffix=@next --pip-args '\--pre' --force 'cihai-cli'
260
+ ```
261
+
262
+ ### Changelog Conventions
263
+
264
+ These rules apply when authoring entries in `CHANGES`, which is rendered as the Sphinx changelog page. Modeled on Django's release-notes shape — deliverables get titles and prose, not bullets.
265
+
266
+ **Release entry boilerplate.** Every release header is `## cihai-cli X.Y.Z (YYYY-MM-DD)`. The file opens with a `## cihai-cli X.Y.Z (unreleased)` placeholder block fenced by `<!-- KEEP THIS PLACEHOLDER ... -->` and `<!-- END PLACEHOLDER ... -->` HTML comments — new release entries land immediately below the END marker, never above it.
267
+
268
+ **Open with a multi-sentence lead paragraph.** Plain prose, no italic. Open with the version as sentence subject (*"cihai-cli X.Y.Z ships …"*) so the lead is self-contained when excerpted. Two to four sentences telling the reader what shipped and who cares — user-visible takeaways, not internal mechanism. Cross-reference detail docs with `{ref}` to keep the lead compact.
269
+
270
+ **Each deliverable is a section, not a bullet.** Inside `### What's new`, every distinct deliverable gets a `#### Deliverable title (#NN)` heading naming it in user vocabulary, followed by 1-3 prose paragraphs explaining what shipped. Don't wrap a paragraph in `- ` — bullets are for enumerable lists, not paragraph containers. Cross-link detail docs (`See {ref}\`foo\` for details.`) so prose stays focused.
271
+
272
+ **The deliverable test.** Before writing an entry, ask: "What's the deliverable, in user vocabulary?" If you can't answer in one sentence, the entry isn't ready. Mechanism (helper internals, byte counters, schema-validation locations) belongs in PR descriptions and code comments, not the changelog.
273
+
274
+ **Fixed subheadings**, in this order when present: `### Breaking changes`, `### Dependencies`, `### What's new`, `### Fixes`, `### Documentation`, `### Development`. Dev tooling (helper scripts, internal automation) lives under `### Development`. For breaking changes, show the migration path with concrete inline code (e.g. a `# Before` / `# After` fenced code block). Dependency floor bumps use the form ``Minimum `pkg>=X.Y.Z` (was `>=X.Y.W`)``.
275
+
276
+ **PR refs `(#NN)`** sit in each deliverable's `####` heading.
277
+
278
+ **When bullets are appropriate.** Catch-all sections (`### Fixes`, occasionally `### Documentation`) with 3+ genuinely small items use bullets — one line each, never paragraphs. If a bullet swells past two lines, promote it to a `#### Title (#NN)` heading with prose body.
279
+
280
+ **Anti-patterns.**
281
+
282
+ - Fragile metrics: token ceilings, third-party version pins, percent benchmarks, exact byte counts. Describe the *capability*, not the math.
283
+ - Internal jargon: private symbols (leading-underscore identifiers), algorithm names exposed for the first time, backend scaffolding.
284
+ - Walls of text dressed up as bullets.
285
+ - Buried breaking changes — they get their own subheading at the top of the entry.
286
+
287
+ **Always link autodoc'd APIs.** Any class, method, function, exception, or attribute that has its own rendered page must be cited via the appropriate role (`{class}`, `{meth}`, `{func}`, `{exc}`, `{attr}`) — never with plain backticks. Doc pages without explicit ref labels use `{doc}`. Plain backticks are correct for code syntax, env vars, parameter names, and file paths that aren't doc pages — anything without an autodoc destination.
288
+
289
+ **MyST roles.** Class references use `{class}`, methods use `{meth}`, functions use `{func}`, exceptions use `{exc}`, attributes use `{attr}`, internal anchors use `{ref}`, doc-path links use `{doc}`.
290
+
291
+ **Summarization style.** When a user asks "what changed in the latest version?" or similar, lead with the entry's lead paragraph (paraphrased if needed), followed by each `####` deliverable heading under `### What's new` with a one-sentence summary. Cite `(#NN)` only if the user asks for source links. Don't invent versions, dates, or numbers not present in `CHANGES`. Don't quote line numbers or file offsets — those shift as the file evolves.
292
+
293
+ ## Debugging Tips
294
+ - Lean on `pytest -k <pattern> -vv` for focused failures.
295
+ - For CLI behavior, run `uv run cihai info 好` or `uv run cihai reverse library`.
296
+ - If Unihan DB is missing, CLI bootstraps automatically; avoid altering that flow unless required.
297
+ - Stuck in loops? Pause, minimize to a minimal repro, document exact errors, and restate the hypothesis before another attempt.
298
+
299
+ ## Git Commit Standards
300
+
301
+ Commit subjects: `Scope(type[detail]): concise description`
302
+
303
+ Body template:
304
+ ```
305
+ why: Reason or impact.
306
+
307
+ what:
308
+ - Key technical changes
309
+ - Single topic only
310
+ ```
311
+
312
+ Guidelines:
313
+ - Subject ≤50 chars; body lines ≤72 chars; imperative mood.
314
+ - One topic per commit; separate subject and body with a blank line.
315
+ - Mark breaking changes with `BREAKING:` and include related issue refs when relevant.
316
+
317
+ Common commit types:
318
+ - **feat**: New features or enhancements
319
+ - **fix**: Bug fixes
320
+ - **refactor**: Code restructuring without functional change
321
+ - **docs**: Documentation updates
322
+ - **chore**: Maintenance (dependencies, tooling, config)
323
+ - **test**: Test-related updates
324
+ - **style**: Code style and formatting
325
+ - **py(deps)**: Dependencies
326
+ - **py(deps[dev])**: Dev dependencies
327
+ - **ai(rules[AGENTS])**: AI rule updates
328
+ - **ai(claude[rules])**: Claude Code rules (CLAUDE.md)
329
+ - **ai(claude[command])**: Claude Code command changes
330
+
331
+ #### Release commits
332
+
333
+ Never create tags. Never push tags. The user handles tagging and tag
334
+ pushes (tags trigger the CI publish workflow).
335
+
336
+ Release commit subjects are plain and short: `Tag v<version>`. Put
337
+ the detailed why/what in the commit body. Don't use the
338
+ `Scope(type[detail]):` format for releases — don't bury the lede.
339
+
340
+ ## Notes & Docs Authoring
341
+ - For `notes/**/*.md`, keep content concise and well-structured (headings, bullets, code fences).
342
+ - Use clear link text `[Title](mdc:URL)` and avoid redundancy; follow llms.txt style when possible.
343
+
344
+ ## References
345
+ - Project docs: https://cihai-cli.git-pull.com
346
+ - Library docs: https://cihai.git-pull.com
347
+ - Unihan dataset: https://www.unicode.org/charts/unihan.html
348
+ - Shared tooling: https://github.com/gp-libs/gp-libs
349
+
350
+ ## AI Slop Prevention
351
+
352
+ Treat AI slop as **review-hostile noise**, not as proof that text or
353
+ code is wrong. The goal is to maximize information density by removing
354
+ artifacts that make the repository harder to trust or navigate.
355
+
356
+ ### The Anti-Slop Rubric
357
+
358
+ Before committing, audit all AI-assisted changes for these noise
359
+ patterns:
360
+
361
+ - **AI Signatures:** Remove "Generated by", footers, conversational
362
+ filler ("Certainly!", "Here is..."), unexplained emojis (🤖, ✨), and
363
+ AI-tool metadata.
364
+ - **Brittle References:** Avoid hard-coded line numbers, fragile
365
+ file/test counts, dated "as of" claims, bare SHAs, and local
366
+ absolute paths unless they are strict evidentiary artifacts (e.g.,
367
+ benchmark logs).
368
+ - **Diff Narration:** Do not restate what moved, was renamed, or was
369
+ removed in artifacts the downstream reader holds: code, docstrings,
370
+ README, CHANGES, PR descriptions, or release notes. The diff and
371
+ commit message already carry this history.
372
+ - **Branch-Internal Narrative:** Do not mention intermediate branch
373
+ states, abandoned approaches, or "no longer" behavior unless users
374
+ of a published release actually experienced the old state (**The
375
+ Published-Release Test**).
376
+ - **Low-Value Scaffolding:** Remove ownerless TODOs (`TODO: revisit`),
377
+ unused future-proofing, debug artifacts, and defensive wrappers that
378
+ do not protect a currently reachable failure mode.
379
+ - **Prose Inflation:** Replace generic AI "tells" like *comprehensive,
380
+ robust, seamless, production-ready, leverage, delve, tapestry,* and
381
+ *best practices* with concrete descriptions of behavior,
382
+ constraints, or trade-offs.
383
+
384
+ ### Preservation & Context
385
+
386
+ **When unsure, leave the text in place and ask.** Subjective cleanup
387
+ must never be a reason to remove load-bearing rationale.
388
+
389
+ - **Preserve the "Why":** You MUST NOT delete comments that document
390
+ invariants, protocol constraints, platform quirks, security
391
+ boundaries, and upstream workarounds.
392
+ - **Evidence is Immune:** Preserve exact counts, dates, and SHAs when
393
+ they serve as evidence in benchmark results, release notes, stack
394
+ traces, or lockfiles.
395
+ - **Behavior Over Inventory:** A useful description explains what
396
+ changed for the *system or user*; it does not provide an inventory
397
+ of files or functions the diff already shows.
398
+
399
+ ### The Published-Release Test
400
+
401
+ Long-running branches accumulate tactical decisions — renames,
402
+ refactors, attempts-then-reverts. When deciding what counts as
403
+ branch-internal, use trunk or the parent branch as the baseline — not
404
+ intermediate states inside the current branch. Ask:
405
+
406
+ > Did users of the most recently published release ever experience
407
+ > this old name, old behavior, or bug?
408
+
409
+ If the answer is **no**, it is branch-internal narrative. Move it to
410
+ the commit message and describe only the final state in the artifact.
411
+
412
+ **Keep in shipped artifacts:**
413
+ - Deprecations and migration guides for symbols that actually shipped.
414
+ - `### Fixes` entries for bugs that affected users of a published
415
+ release.
416
+ - Comments explaining *why the current code looks this way*
417
+ (invariants, platform quirks) that make sense to a reader who never
418
+ saw the previous version.
419
+
420
+ ### Cleanup in Hindsight
421
+
422
+ When applying these rules retroactively from inside a feature branch,
423
+ first establish scope by diffing against the parent branch (or trunk)
424
+ to identify which commits this branch actually introduced. Then:
425
+
426
+ - **In-branch commits:** Prompt the user with two options: `fixup!`
427
+ commits with `git rebase --autosquash` to address each causal commit
428
+ at its source, or a single cleanup commit at branch tip.
429
+ - **Trunk/Parent commits:** Default to leaving them alone. Act only on
430
+ explicit user instruction. If the user opts in, fold the cleanup
431
+ into a single commit at branch tip; do not rewrite shared history.
432
+ - **Scope guard:** If cleaning prior slop would touch a colleague's
433
+ work or expand the branch beyond its stated goal, stay in lane:
434
+ protect the current goal and leave prior slop alone.
435
+