pyclifer 0.4.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 (200) hide show
  1. pyclifer-0.4.1/.claude/CLAUDE.md +243 -0
  2. pyclifer-0.4.1/.claude/specs/archived/2026-06-01-deps-upgrade-fr.md +279 -0
  3. pyclifer-0.4.1/.claude/specs/archived/core-simplification.md +333 -0
  4. pyclifer-0.4.1/.claude/specs/archived/demo-taskman.md +716 -0
  5. pyclifer-0.4.1/.claude/specs/archived/error_handling.md +548 -0
  6. pyclifer-0.4.1/.claude/specs/archived/exit_codes.md +298 -0
  7. pyclifer-0.4.1/.claude/specs/archived/flat-commands.md +154 -0
  8. pyclifer-0.4.1/.claude/specs/archived/howto-guides.md +25 -0
  9. pyclifer-0.4.1/.claude/specs/archived/improvements.md +484 -0
  10. pyclifer-0.4.1/.claude/specs/archived/models.md +213 -0
  11. pyclifer-0.4.1/.claude/specs/archived/output_formats.md +183 -0
  12. pyclifer-0.4.1/.claude/specs/archived/scaffolding-add-command-nested.md +110 -0
  13. pyclifer-0.4.1/.claude/specs/archived/scaffolding-add-group.md +224 -0
  14. pyclifer-0.4.1/.claude/specs/decorators-make-context-refactor.md +158 -0
  15. pyclifer-0.4.1/.github/workflows/ci.yml +40 -0
  16. pyclifer-0.4.1/.github/workflows/docs.yml +50 -0
  17. pyclifer-0.4.1/.github/workflows/release.yml +71 -0
  18. pyclifer-0.4.1/.gitignore +210 -0
  19. pyclifer-0.4.1/.pre-commit-config.yaml +16 -0
  20. pyclifer-0.4.1/CHANGELOG.md +315 -0
  21. pyclifer-0.4.1/CLAUDE.md +208 -0
  22. pyclifer-0.4.1/LICENSE +21 -0
  23. pyclifer-0.4.1/PKG-INFO +202 -0
  24. pyclifer-0.4.1/README.md +156 -0
  25. pyclifer-0.4.1/Taskfile.yml +75 -0
  26. pyclifer-0.4.1/cliff.toml +47 -0
  27. pyclifer-0.4.1/docs/api/classes.md +38 -0
  28. pyclifer-0.4.1/docs/api/context.md +9 -0
  29. pyclifer-0.4.1/docs/api/decorators.md +63 -0
  30. pyclifer-0.4.1/docs/api/interfaces.md +135 -0
  31. pyclifer-0.4.1/docs/api/logging.md +60 -0
  32. pyclifer-0.4.1/docs/api/mixins.md +37 -0
  33. pyclifer-0.4.1/docs/api/models.md +8 -0
  34. pyclifer-0.4.1/docs/api/output.md +156 -0
  35. pyclifer-0.4.1/docs/assets/favicon-96x96.png +0 -0
  36. pyclifer-0.4.1/docs/assets/logo.png +0 -0
  37. pyclifer-0.4.1/docs/configuration.md +307 -0
  38. pyclifer-0.4.1/docs/development.md +238 -0
  39. pyclifer-0.4.1/docs/error-handling.md +214 -0
  40. pyclifer-0.4.1/docs/examples.md +296 -0
  41. pyclifer-0.4.1/docs/getting-started.md +196 -0
  42. pyclifer-0.4.1/docs/how-to/error-handling.md +169 -0
  43. pyclifer-0.4.1/docs/how-to/index.md +37 -0
  44. pyclifer-0.4.1/docs/how-to/multi-integration-commands.md +162 -0
  45. pyclifer-0.4.1/docs/how-to/output-format.md +207 -0
  46. pyclifer-0.4.1/docs/how-to/response-patterns.md +217 -0
  47. pyclifer-0.4.1/docs/how-to/rich-progressive-output.md +203 -0
  48. pyclifer-0.4.1/docs/index.md +100 -0
  49. pyclifer-0.4.1/docs/logging.md +245 -0
  50. pyclifer-0.4.1/docs/output-formatting.md +545 -0
  51. pyclifer-0.4.1/docs/scaffolding.md +420 -0
  52. pyclifer-0.4.1/docs/versions.json +1 -0
  53. pyclifer-0.4.1/mkdocs.yml +103 -0
  54. pyclifer-0.4.1/pyproject.toml +177 -0
  55. pyclifer-0.4.1/src/pyclifer/__init__.py +159 -0
  56. pyclifer-0.4.1/src/pyclifer/apps/__init__.py +8 -0
  57. pyclifer-0.4.1/src/pyclifer/apps/demo/__init__.py +25 -0
  58. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/__init__.py +0 -0
  59. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/__init__.py +19 -0
  60. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/commands/__init__.py +10 -0
  61. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/commands/add.py +27 -0
  62. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/commands/complete.py +12 -0
  63. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/commands/delete.py +15 -0
  64. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/commands/list.py +38 -0
  65. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/commands/show.py +12 -0
  66. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/commands/sync.py +16 -0
  67. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/interfaces.py +208 -0
  68. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/models.py +61 -0
  69. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/renderers.py +152 -0
  70. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/tables.py +5 -0
  71. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/users/__init__.py +19 -0
  72. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/users/commands/__init__.py +6 -0
  73. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/users/commands/list.py +12 -0
  74. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/users/commands/whoami.py +11 -0
  75. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/users/interfaces.py +127 -0
  76. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/users/models.py +18 -0
  77. pyclifer-0.4.1/src/pyclifer/apps/demo/apps/users/tables.py +5 -0
  78. pyclifer-0.4.1/src/pyclifer/apps/demo/commands/__init__.py +3 -0
  79. pyclifer-0.4.1/src/pyclifer/apps/demo/core/__init__.py +1 -0
  80. pyclifer-0.4.1/src/pyclifer/apps/demo/core/constants.py +9 -0
  81. pyclifer-0.4.1/src/pyclifer/apps/demo/core/context.py +28 -0
  82. pyclifer-0.4.1/src/pyclifer/apps/demo/core/options.py +12 -0
  83. pyclifer-0.4.1/src/pyclifer/apps/demo/core/storage.py +156 -0
  84. pyclifer-0.4.1/src/pyclifer/apps/demo/interfaces.py +24 -0
  85. pyclifer-0.4.1/src/pyclifer/apps/demo/models.py +12 -0
  86. pyclifer-0.4.1/src/pyclifer/apps/demo/tables.py +5 -0
  87. pyclifer-0.4.1/src/pyclifer/apps/project/__init__.py +14 -0
  88. pyclifer-0.4.1/src/pyclifer/apps/project/commands/__init__.py +9 -0
  89. pyclifer-0.4.1/src/pyclifer/apps/project/commands/add/__init__.py +19 -0
  90. pyclifer-0.4.1/src/pyclifer/apps/project/commands/add/app.py +27 -0
  91. pyclifer-0.4.1/src/pyclifer/apps/project/commands/add/command.py +19 -0
  92. pyclifer-0.4.1/src/pyclifer/apps/project/commands/add/group_cmd.py +14 -0
  93. pyclifer-0.4.1/src/pyclifer/apps/project/commands/add/integration.py +16 -0
  94. pyclifer-0.4.1/src/pyclifer/apps/project/commands/init.py +36 -0
  95. pyclifer-0.4.1/src/pyclifer/apps/project/interfaces.py +633 -0
  96. pyclifer-0.4.1/src/pyclifer/apps/project/renderers.py +121 -0
  97. pyclifer-0.4.1/src/pyclifer/apps/project/tables.py +57 -0
  98. pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_commands_init.py.jinja2 +3 -0
  99. pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_core_constants.py.jinja2 +1 -0
  100. pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_core_context.py.jinja2 +10 -0
  101. pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_core_init.py.jinja2 +1 -0
  102. pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_core_options.py.jinja2 +1 -0
  103. pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_init.py.jinja2 +19 -0
  104. pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_init_flat.py.jinja2 +3 -0
  105. pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_init_with_core.py.jinja2 +21 -0
  106. pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_interfaces.py.jinja2 +26 -0
  107. pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_interfaces_with_core.py.jinja2 +29 -0
  108. pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_models.py.jinja2 +12 -0
  109. pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_tables.py.jinja2 +5 -0
  110. pyclifer-0.4.1/src/pyclifer/apps/project/templates/command.py.jinja2 +11 -0
  111. pyclifer-0.4.1/src/pyclifer/apps/project/templates/gitignore.jinja2 +19 -0
  112. pyclifer-0.4.1/src/pyclifer/apps/project/templates/integration_package_client.py.jinja2 +8 -0
  113. pyclifer-0.4.1/src/pyclifer/apps/project/templates/integration_package_helpers.py.jinja2 +1 -0
  114. pyclifer-0.4.1/src/pyclifer/apps/project/templates/integration_package_init.py.jinja2 +10 -0
  115. pyclifer-0.4.1/src/pyclifer/apps/project/templates/integration_package_models.py.jinja2 +1 -0
  116. pyclifer-0.4.1/src/pyclifer/apps/project/templates/integration_simple.py.jinja2 +8 -0
  117. pyclifer-0.4.1/src/pyclifer/apps/project/templates/project_apps_init.py.jinja2 +3 -0
  118. pyclifer-0.4.1/src/pyclifer/apps/project/templates/project_cli.py.jinja2 +16 -0
  119. pyclifer-0.4.1/src/pyclifer/apps/project/templates/project_constants.py.jinja2 +1 -0
  120. pyclifer-0.4.1/src/pyclifer/apps/project/templates/project_context.py.jinja2 +11 -0
  121. pyclifer-0.4.1/src/pyclifer/apps/project/templates/project_integrations_init.py.jinja2 +1 -0
  122. pyclifer-0.4.1/src/pyclifer/apps/project/templates/project_options.py.jinja2 +1 -0
  123. pyclifer-0.4.1/src/pyclifer/apps/project/templates/project_package_init.py.jinja2 +1 -0
  124. pyclifer-0.4.1/src/pyclifer/apps/project/templates/pyproject_poetry.toml.jinja2 +48 -0
  125. pyclifer-0.4.1/src/pyclifer/apps/project/templates/pyproject_uv.toml.jinja2 +51 -0
  126. pyclifer-0.4.1/src/pyclifer/apps/project/templates/readme.md.jinja2 +41 -0
  127. pyclifer-0.4.1/src/pyclifer/apps/project/templates/tests_conftest.py.jinja2 +18 -0
  128. pyclifer-0.4.1/src/pyclifer/apps/project/templates/tests_init.py.jinja2 +0 -0
  129. pyclifer-0.4.1/src/pyclifer/cli.py +18 -0
  130. pyclifer-0.4.1/src/pyclifer/core/__init__.py +23 -0
  131. pyclifer-0.4.1/src/pyclifer/core/callbacks.py +42 -0
  132. pyclifer-0.4.1/src/pyclifer/core/classes.py +270 -0
  133. pyclifer-0.4.1/src/pyclifer/core/context.py +34 -0
  134. pyclifer-0.4.1/src/pyclifer/core/decorators.py +735 -0
  135. pyclifer-0.4.1/src/pyclifer/core/interfaces/__init__.py +5 -0
  136. pyclifer-0.4.1/src/pyclifer/core/interfaces/base.py +88 -0
  137. pyclifer-0.4.1/src/pyclifer/core/log/__init__.py +44 -0
  138. pyclifer-0.4.1/src/pyclifer/core/log/config.py +319 -0
  139. pyclifer-0.4.1/src/pyclifer/core/log/filters.py +169 -0
  140. pyclifer-0.4.1/src/pyclifer/core/log/formatters.py +45 -0
  141. pyclifer-0.4.1/src/pyclifer/core/log/handlers.py +71 -0
  142. pyclifer-0.4.1/src/pyclifer/core/log/levels.py +59 -0
  143. pyclifer-0.4.1/src/pyclifer/core/mixins/__init__.py +14 -0
  144. pyclifer-0.4.1/src/pyclifer/core/mixins/cli.py +70 -0
  145. pyclifer-0.4.1/src/pyclifer/core/mixins/output.py +284 -0
  146. pyclifer-0.4.1/src/pyclifer/core/mixins/response.py +150 -0
  147. pyclifer-0.4.1/src/pyclifer/core/mixins/rich.py +105 -0
  148. pyclifer-0.4.1/src/pyclifer/core/models.py +49 -0
  149. pyclifer-0.4.1/src/pyclifer/core/output/__init__.py +18 -0
  150. pyclifer-0.4.1/src/pyclifer/core/output/exit_codes.py +59 -0
  151. pyclifer-0.4.1/src/pyclifer/core/output/renderer.py +328 -0
  152. pyclifer-0.4.1/src/pyclifer/core/output/responses.py +229 -0
  153. pyclifer-0.4.1/src/pyclifer/core/output/tables.py +200 -0
  154. pyclifer-0.4.1/src/pyclifer/core/rich_help_config.py +119 -0
  155. pyclifer-0.4.1/tests/__init__.py +0 -0
  156. pyclifer-0.4.1/tests/apps/__init__.py +0 -0
  157. pyclifer-0.4.1/tests/apps/demo/__init__.py +0 -0
  158. pyclifer-0.4.1/tests/apps/demo/apps/__init__.py +0 -0
  159. pyclifer-0.4.1/tests/apps/demo/apps/tasks/__init__.py +0 -0
  160. pyclifer-0.4.1/tests/apps/demo/apps/tasks/test_commands.py +193 -0
  161. pyclifer-0.4.1/tests/apps/demo/apps/tasks/test_interfaces.py +167 -0
  162. pyclifer-0.4.1/tests/apps/demo/apps/tasks/test_models.py +45 -0
  163. pyclifer-0.4.1/tests/apps/demo/apps/tasks/test_renderers.py +147 -0
  164. pyclifer-0.4.1/tests/apps/demo/apps/users/__init__.py +0 -0
  165. pyclifer-0.4.1/tests/apps/demo/apps/users/test_commands.py +81 -0
  166. pyclifer-0.4.1/tests/apps/demo/apps/users/test_interfaces.py +90 -0
  167. pyclifer-0.4.1/tests/apps/demo/apps/users/test_models.py +24 -0
  168. pyclifer-0.4.1/tests/apps/demo/conftest.py +29 -0
  169. pyclifer-0.4.1/tests/apps/demo/core/__init__.py +0 -0
  170. pyclifer-0.4.1/tests/apps/demo/core/test_storage.py +112 -0
  171. pyclifer-0.4.1/tests/apps/project/__init__.py +0 -0
  172. pyclifer-0.4.1/tests/apps/project/test_interfaces.py +540 -0
  173. pyclifer-0.4.1/tests/apps/project/test_tables.py +67 -0
  174. pyclifer-0.4.1/tests/conftest.py +48 -0
  175. pyclifer-0.4.1/tests/core/__init__.py +0 -0
  176. pyclifer-0.4.1/tests/core/log/__init__.py +0 -0
  177. pyclifer-0.4.1/tests/core/log/test_file_logging.py +171 -0
  178. pyclifer-0.4.1/tests/core/log/test_rich_logging.py +695 -0
  179. pyclifer-0.4.1/tests/core/log/test_verbosity_default.py +278 -0
  180. pyclifer-0.4.1/tests/core/mixins/__init__.py +0 -0
  181. pyclifer-0.4.1/tests/core/mixins/test_output.py +451 -0
  182. pyclifer-0.4.1/tests/core/mixins/test_response.py +90 -0
  183. pyclifer-0.4.1/tests/core/mixins/test_rich.py +105 -0
  184. pyclifer-0.4.1/tests/core/options/__init__.py +0 -0
  185. pyclifer-0.4.1/tests/core/options/test_custom_config_option.py +345 -0
  186. pyclifer-0.4.1/tests/core/options/test_global_options.py +151 -0
  187. pyclifer-0.4.1/tests/core/output/__init__.py +0 -0
  188. pyclifer-0.4.1/tests/core/output/test_exit_codes.py +116 -0
  189. pyclifer-0.4.1/tests/core/output/test_responses.py +385 -0
  190. pyclifer-0.4.1/tests/core/output/test_tables.py +208 -0
  191. pyclifer-0.4.1/tests/core/test_callbacks.py +81 -0
  192. pyclifer-0.4.1/tests/core/test_context.py +59 -0
  193. pyclifer-0.4.1/tests/core/test_decorators.py +440 -0
  194. pyclifer-0.4.1/tests/core/test_interfaces.py +278 -0
  195. pyclifer-0.4.1/tests/core/test_models.py +132 -0
  196. pyclifer-0.4.1/tests/core/test_renderer.py +343 -0
  197. pyclifer-0.4.1/tests/core/test_returns_response.py +579 -0
  198. pyclifer-0.4.1/tests/core/test_rich_help_formatting.py +182 -0
  199. pyclifer-0.4.1/tests/core/test_timer_option.py +94 -0
  200. pyclifer-0.4.1/uv.lock +1523 -0
@@ -0,0 +1,243 @@
1
+ # Code Style Rules
2
+
3
+ Rules for Claude Code when writing or modifying code in this repository.
4
+
5
+ ## Type Hints
6
+
7
+ Use **PEP 585 built-in generics** (Python 3.10+) — do NOT import from `typing` for these:
8
+
9
+ ```python
10
+ # Correct
11
+ def foo(items: list[str], mapping: dict[str, Any]) -> str | None: ...
12
+
13
+
14
+ # Wrong
15
+ from typing import List, Dict, Optional
16
+
17
+
18
+ def foo(items: List[str], mapping: Dict[str, Any]) -> Optional[str]: ...
19
+ ```
20
+
21
+ Always annotate return types. Use `Any` from `typing` when genuinely dynamic.
22
+ `Callable`, `Union` (when needed), and `TYPE_CHECKING` still come from `typing`.
23
+
24
+ ## Docstrings
25
+
26
+ **Google style**, required on all public classes, methods, functions, and nested functions.
27
+ Private helpers (`_name`) use a single-line docstring only if the purpose is not obvious from the name.
28
+
29
+ ```python
30
+ def my_function(value: str, count: int = 1) -> list[str]:
31
+ """Short imperative description (no period at the end for one-liners).
32
+
33
+ Longer explanation if needed. Omit if the one-liner is enough.
34
+
35
+ Args:
36
+ value: Description of value.
37
+ count: Description of count.
38
+
39
+ Returns:
40
+ Description of the return value.
41
+
42
+ Raises:
43
+ ValueError: When the value is empty.
44
+ """
45
+ ```
46
+
47
+ - First line: imperative mood, no trailing period, fits on one line.
48
+ - Blank line between summary and `Args:` / `Returns:` / `Raises:` sections.
49
+ - Do NOT include type information in `Args:` / `Returns:` — it is already in the signature.
50
+ - No `Methods:` section in class docstrings — method docstrings are self-contained.
51
+
52
+ ### Markup: plain text only
53
+
54
+ Docstrings must use **plain prose** — no reStructuredText or Sphinx markup.
55
+
56
+ ```python
57
+ # Wrong — reStructuredText syntax
58
+ """Do something with ctx.meta.
59
+
60
+ :param name: The name.
61
+ :type name: str
62
+ :returns: The result.
63
+ :rtype: str
64
+ """
65
+
66
+ # Wrong — double-backtick / cross-reference markup
67
+ """Store value in ``ctx.meta``. See :class:`~pyclifer.Response`."""
68
+
69
+ # Wrong — directive blocks
70
+ """Example:
71
+
72
+ .. code-block:: bash
73
+
74
+ myapp hello
75
+ """
76
+
77
+ # Correct — plain text, inline code with single backticks only in narrative prose
78
+ """Store value in ctx.meta."""
79
+ ```
80
+
81
+ Allowed: single backticks `` ` `` inside narrative prose when referring to code names.
82
+ Forbidden: `` `` `` (double backtick), `:param:`, `:type:`, `:rtype:`, `:class:`, `:func:`,
83
+ `:meth:`, `.. directive::`.
84
+
85
+ ## Imports
86
+
87
+ PEP 8 order with a blank line between groups:
88
+
89
+ ```python
90
+ # 1. Standard library
91
+ import logging
92
+ from dataclasses import dataclass
93
+ from typing import Any, Callable, TYPE_CHECKING
94
+
95
+ # 2. Third-party
96
+ import click_extra
97
+ from rich.console import Console
98
+
99
+ # 3. Local (relative preferred inside the package)
100
+ from .mixins import OutputFormatMixin
101
+ from pyclifer.core.output import Response
102
+ ```
103
+
104
+ - Prefer `from x import y` over `import x.y`.
105
+ - **Prefer `click_extra` over `click` directly** — `click_extra` re-exports the full Click API
106
+ (`Context`, `Parameter`, `get_current_context`, etc.) and is the declared project dependency.
107
+ Never `import click` when `click_extra` provides the same symbol.
108
+ - Use **lazy imports** (inside the function body) only to break circular dependencies — always
109
+ add a comment explaining why.
110
+
111
+ ## Naming
112
+
113
+ | Kind | Convention | Example |
114
+ |-----------------------|----------------------|----------------------------|
115
+ | Module / file | `snake_case` | `rich_help_config.py` |
116
+ | Class | `PascalCase` | `HandleResponseMixin` |
117
+ | Function / method | `snake_case` | `configure_rich_logging()` |
118
+ | Private method / attr | `_single_underscore` | `_apply_click_group()` |
119
+ | Constant | `UPPER_SNAKE` | `PYCLIFER_LOG_LEVELS` |
120
+
121
+ Never use double-underscore (`__dunder`) for private names — reserve those for Python protocol
122
+ methods.
123
+
124
+ ## Classes
125
+
126
+ - **Mixin inheritance order**: mixins first, concrete base class last.
127
+
128
+ ```python
129
+ class PycliferRichGroup(HandleResponseMixin, GlobalOptionsMixin, RichGroup):
130
+ ```
131
+
132
+ - Use `@dataclass` for configuration / data-holding objects (`GroupConfig`, `Response`).
133
+ - Avoid no-op `__post_init__` bodies.
134
+
135
+ ## Decorator Ordering on Commands
136
+
137
+ Top to bottom: registration decorator → framework/option decorators → `@pass_cli_context`
138
+ or `@click.pass_context` → function.
139
+
140
+ ```python
141
+ @app.command() # registers with group
142
+ @output_filter_option() # custom pyclifer decorators
143
+ @returns_response # closest to function
144
+ @option("--name", default="world") # click options
145
+ @pass_cli_context # always last before def (or @click.pass_context in cli.py)
146
+ def hello(ctx, name): ...
147
+ ```
148
+
149
+ Note: `@click.pass_context` is used only in `cli.py` to build `ctx.obj`. Everywhere else,
150
+ use `pass_cli_context` (generated by `make_pass_decorator`) for typed context access.
151
+
152
+ ## Exception Handling
153
+
154
+ - Catch specific exception types; avoid bare `except Exception` unless you log and re-raise
155
+ or have a documented reason.
156
+ - When catching multiple types, list them as a tuple with a comment explaining each.
157
+ - Raise `RuntimeError` for programming errors (missing callbacks, invalid state).
158
+ - Raise `ValueError` for bad input.
159
+
160
+ ## Code quality
161
+
162
+ Every change must pass `ruff check` and `ruff format` before being committed. Run both on the
163
+ affected files before declaring a task done:
164
+
165
+ ```bash
166
+ ruff check src/ tests/
167
+ ruff format src/ tests/
168
+ ```
169
+
170
+ Fix all reported issues — do not suppress warnings with `# noqa` unless the rule is genuinely
171
+ inapplicable and the reason is documented on the same line.
172
+
173
+ ## Tests
174
+
175
+ - One test file per source module, mirroring the `src/` layout under `tests/`.
176
+ - Test classes: `Test<Subject>` (e.g., `TestOutputFilterOption`).
177
+ - Test methods: `test_<what_is_being_tested>` — descriptive, no abbreviations.
178
+ - Use `pytest.raises(ExcType, match="…")` — always include a `match` pattern.
179
+ - Prefer `MagicMock` over `Mock`; use `@patch` for module-level targets.
180
+ - Do not assert on mock call count unless the count itself is the behavior under test.
181
+ - Target 100% branch coverage for every module touched by a change. If a branch is genuinely
182
+ untestable (e.g., a defensive guard for an impossible state), mark it with `# pragma: no cover`
183
+ and add a comment explaining why.
184
+
185
+ ## File placement
186
+
187
+ Code must go in the file that matches its kind — never mix concerns across modules:
188
+
189
+ | Kind | File |
190
+ |-----------------------------------------------|---------------------------|
191
+ | Decorators (`@app_group`, `@command`, …) | `core/decorators.py` |
192
+ | Click subclasses, config dataclasses | `core/classes.py` |
193
+ | Reusable mixin classes | `core/mixins/` |
194
+ | `BaseContext` and context helpers | `core/context.py` |
195
+ | `Response`, `OperationResult`, output helpers | `core/output/` |
196
+ | `BaseRenderer`, `ResponseRenderer` Protocol | `core/output/renderer.py` |
197
+ | `BaseInterface` and `respond()` machinery | `core/interfaces/base.py` |
198
+ | Logging helpers, `get_logger` | `core/log/` |
199
+
200
+ When adding a new class or function, pick the file whose existing contents are the closest
201
+ match. If none fits, create a new module rather than placing it in a nearby but wrong one.
202
+
203
+ The API docs in `docs/api/` mirror this structure — a symbol documented in the wrong page
204
+ (e.g., a class from `classes.py` appearing in `decorators.md`) is a bug:
205
+
206
+ | Doc page | What belongs there |
207
+ |---------------------|-----------------------------------------------------------------------------------------------------|
208
+ | `api/decorators.md` | The four decorators + `returns_response` + `output_filter_option` |
209
+ | `api/classes.md` | `PycliferOption`, `PycliferGroup`, `CustomConfigOption`, `PycliferTimerOption` and any new Click subclass |
210
+ | `api/mixins.md` | All mixin classes from `core/mixins/` |
211
+ | `api/context.md` | `BaseContext` and context helpers |
212
+ | `api/output.md` | `Response`, `OperationResult`, tables, `BaseRenderer`, `ResponseRenderer` |
213
+ | `api/interfaces.md` | `BaseInterface` and `respond()` machinery |
214
+ | `api/logging.md` | Logging helpers and `get_logger` |
215
+
216
+ ## Documentation
217
+
218
+ - Every new `docs/*.md` page must be added to the `nav` section of `mkdocs.yml` in the same
219
+ commit. Never create a documentation page without registering it.
220
+
221
+ ## Git Workflow
222
+
223
+ - Always create a feature branch (`feat/<name>`) before starting implementation of a spec or
224
+ any non-trivial change. Do this before touching the first file.
225
+ - After merging into `main`, delete the feature branch immediately.
226
+ - Never push or merge without the user's explicit validation.
227
+
228
+ ### Commit message format
229
+
230
+ Every commit must follow this exact structure:
231
+
232
+ ```
233
+ <gitmoji> <type>(<scope>): <imperative summary>
234
+
235
+ - bullet describing why / what changed
236
+ - bullet for each meaningful change
237
+ ```
238
+
239
+ - **gitmoji**: coherent with the type (`✨` feat, `🐛` fix, `♻️` refactor, `📝` docs, `✅` test, `🔧` chore, etc.)
240
+ - **type**: `feat`, `fix`, `refactor`, `perf`, `docs`, `style`, `test`, `chore`, `ci`, `build`
241
+ - **scope**: optional, use the module name (e.g. `tables`, `decorators`, `log`)
242
+ - **summary**: imperative mood, ≤50 chars, no trailing period
243
+ - **body**: bullet list, each item explains *why* not just *what*; omit if summary is self-explanatory
@@ -0,0 +1,279 @@
1
+ # Mise à jour des dépendances : click-extra, rich-click, rich
2
+
3
+ > **Pour les agents :** SUB-SKILL REQUIS : Utiliser superpowers:subagent-driven-development (recommandé) ou superpowers:executing-plans pour exécuter ce plan tâche par tâche. Les étapes utilisent la syntaxe checkbox (`- [ ]`) pour le suivi.
4
+
5
+ **Objectif :** Monter `click-extra` 7.16.1→7.18.0, `rich` 13.9.4→15.0.0, `rich-click` 1.9.7→1.9.8 sans régression.
6
+
7
+ **Architecture :** Simple montée de version — aucun changement de code source attendu. Les trois mises à jour sont couplées : click-extra 7.17.0+ tire Click 8.4.1 (contre 8.3.3 actuellement), et rich-click 1.9.8 a été publié précisément pour corriger la compatibilité avec Click 8.4.x. Les trois doivent arriver ensemble. La contrainte `rich>=13.0` autorise déjà la 15.x ; seules les bornes minimales changent.
8
+
9
+ **Stack :** uv, pytest, ruff, click-extra, rich, rich-click, click (transitif)
10
+
11
+ ---
12
+
13
+ ## Audit des breaking changes (pré-recherche, ne pas sauter)
14
+
15
+ ### rich 13.9.4 → 15.0.0
16
+
17
+ | Version | Breaking change | Impact sur pyclifer |
18
+ |---------|----------------|-----------------|
19
+ | 14.0.0 | `NO_COLOR=` (vide) ne désactive plus les couleurs ; avant, toute présence de la variable suffisait | Aucun test ni CI ne définit `NO_COLOR=` — confirmé par grep |
20
+ | 14.0.0 | `FORCE_COLOR=` (vide) — même règle | Idem — aucun impact |
21
+ | 15.0.0 | Python 3.8 abandonné | pyclifer requiert ≥3.10 — aucun impact |
22
+
23
+ Aucune API supprimée ou renommée utilisée par pyclifer (`Console`, `Panel`, `Table`, `RichHandler`, `Live`, `Status`, `Prompt`, `Rule`, `Syntax`, `Text`, `box` — tous inchangés).
24
+
25
+ Effets visuels (pas des breaking changes, mais observables) :
26
+ - Fond du titre des `Panel` corrigé (14.1.0) — les panneaux s'afficheront plus correctement
27
+ - L'imbrication de `Live` est maintenant autorisée (14.1.0)
28
+
29
+ ### click-extra 7.16.1 → 7.18.0
30
+
31
+ | Symbole supprimé | Utilisé dans pyclifer ? | Verdict |
32
+ |-----------------|----------------------|---------|
33
+ | Module `click_extra.themes` | Non | Sûr |
34
+ | Constantes `DARK`, `DRACULA`, `LIGHT`, `MONOKAI`, `NORD`, `SOLARIZED_DARK` | Non | Sûr |
35
+ | Attribut `default_theme` | Non — pyclifer utilise déjà `get_default_theme()` dans `core/log/formatters.py:5` | Sûr |
36
+ | Constantes pré-rendues `OK`, `KO` | Non | Sûr |
37
+ | Attribut `ctx.telemetry` | Non | Sûr |
38
+ | Callback `disable_colors` sur `ColorOption` | Non | Sûr |
39
+ | **Chemins de sous-modules** `click_extra.jobs`, `click_extra.timer` supprimés | `TimerOption` est utilisé (`classes.py:11`, `decorators.py:113`) mais importé via `from click_extra import TimerOption` (racine) — pas via le chemin de sous-module supprimé. | Sûr |
40
+ | `ValueError` au chargement de `--config` → maintenant `SystemExit` | Aucun test n'exerce ce chemin d'erreur | Sûr |
41
+
42
+ Upgrade transitif : Click 8.3.3 → 8.4.1 (requis par click-extra 7.17.0+). Aucune suppression d'API Click 8.4.x n'affecte pyclifer.
43
+
44
+ ### rich-click 1.9.7 → 1.9.8
45
+
46
+ Uniquement un correctif : répare le mécanisme de patching de rich-click cassé par Click 8.4.0. **Doit être mis à jour en même temps que click-extra.**
47
+
48
+ ---
49
+
50
+ ## Fichiers à modifier
51
+
52
+ | Fichier | Changement |
53
+ |---------|-----------|
54
+ | `pyproject.toml` | Monter trois bornes minimales de dépendances |
55
+ | `uv.lock` | Régénéré automatiquement par `uv sync` |
56
+
57
+ Aucun changement de fichier source ou de test prévu. Si un test échoue après la mise à jour, une tâche de correction est ajoutée à ce moment-là.
58
+
59
+ ---
60
+
61
+ ## Tâche 1 : Établir la baseline (les tests passent sur les versions actuelles)
62
+
63
+ **Fichiers :** (lecture seule)
64
+
65
+ - [ ] **Étape 1 : Lancer la suite de tests complète sur les versions actuelles**
66
+
67
+ ```bash
68
+ python -m pytest tests/ -v
69
+ ```
70
+
71
+ Attendu : tous les tests passent. Si des tests échouent avant la mise à jour, stop — les corriger séparément d'abord.
72
+
73
+ - [ ] **Étape 2 : Enregistrer les versions installées pour le message de commit**
74
+
75
+ ```bash
76
+ pip show click-extra rich rich-click | grep -E "Name:|Version:"
77
+ ```
78
+
79
+ Sortie attendue (actuelle) :
80
+ ```
81
+ Name: click-extra
82
+ Version: 7.16.1
83
+ Name: rich
84
+ Version: 13.9.4
85
+ Name: rich-click
86
+ Version: 1.9.7
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Tâche 2 : Mettre à jour les contraintes de version dans pyproject.toml
92
+
93
+ **Fichiers :**
94
+ - Modifier : `pyproject.toml` lignes 12–14
95
+
96
+ Mise à jour des bornes minimales vers les versions testées, pour que `uv sync --resolution lowest` reste sur des versions connues et stables.
97
+
98
+ - [ ] **Étape 1 : Modifier les trois lignes de dépendances**
99
+
100
+ Dans `pyproject.toml`, remplacer :
101
+
102
+ ```toml
103
+ "click-extra>=7.16.1,<8.0.0",
104
+ "rich>=13.0",
105
+ "rich-click>=1.9.7,<2.0.0",
106
+ ```
107
+
108
+ par :
109
+
110
+ ```toml
111
+ "click-extra>=7.18.0,<8.0.0",
112
+ "rich>=15.0.0",
113
+ "rich-click>=1.9.8,<2.0.0",
114
+ ```
115
+
116
+ - [ ] **Étape 2 : Vérifier la modification**
117
+
118
+ ```bash
119
+ grep -E "click-extra|rich-click|rich" pyproject.toml
120
+ ```
121
+
122
+ Attendu :
123
+ ```
124
+ "click-extra>=7.18.0,<8.0.0",
125
+ "rich>=15.0.0",
126
+ "rich-click>=1.9.8,<2.0.0",
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Tâche 3 : Installer les dépendances mises à jour
132
+
133
+ **Fichiers :** `uv.lock` (régénéré automatiquement)
134
+
135
+ - [ ] **Étape 1 : Synchroniser avec les nouvelles contraintes**
136
+
137
+ ```bash
138
+ uv sync --extra dev,docs
139
+ ```
140
+
141
+ Attendu : uv résout et installe click-extra 7.18.0, rich 15.0.0, rich-click 1.9.8, et click 8.4.1 (transitif). Aucune erreur.
142
+
143
+ - [ ] **Étape 2 : Confirmer les versions installées**
144
+
145
+ ```bash
146
+ pip show click-extra rich rich-click click | grep -E "Name:|Version:"
147
+ ```
148
+
149
+ Attendu :
150
+ ```
151
+ Name: click
152
+ Version: 8.4.1
153
+ Name: click-extra
154
+ Version: 7.18.0
155
+ Name: rich
156
+ Version: 15.0.0
157
+ Name: rich-click
158
+ Version: 1.9.8
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Tâche 4 : Lancer la suite de tests complète sur les nouvelles versions
164
+
165
+ **Fichiers :** (lecture seule)
166
+
167
+ - [ ] **Étape 1 : Lancer tous les tests**
168
+
169
+ ```bash
170
+ python -m pytest tests/ -v
171
+ ```
172
+
173
+ Attendu : tous les tests passent, couverture ≥80%.
174
+
175
+ Si un test échoue → aller à la Tâche 5. Si tout passe → aller directement à la Tâche 6.
176
+
177
+ ---
178
+
179
+ ## Tâche 5 : Corriger les échecs de tests (conditionnelle — uniquement si la Tâche 4 a des échecs)
180
+
181
+ Sauter entièrement si la Tâche 4 passe.
182
+
183
+ Catégories d'échecs probables et corrections :
184
+
185
+ **A. Test qui vérifie le rendu exact d'un `Panel`**
186
+
187
+ Cause : rich 14.1.0 a corrigé le style du fond du titre des `Panel` — la sortie rendue peut différer.
188
+
189
+ Correction : mettre à jour la chaîne attendue dans l'assertion pour correspondre à la nouvelle sortie (correcte).
190
+
191
+ Motif à rechercher :
192
+ ```bash
193
+ grep -rn "Panel\|panel" tests/ | grep -i "assert\|expect"
194
+ ```
195
+
196
+ **B. Test qui vérifie `click.UsageError` depuis `help` sur une sous-commande inconnue**
197
+
198
+ Cause : click-extra 7.17.0 a changé `HelpCommand` pour lever `click.NoSuchCommand` au lieu de `click.UsageError`.
199
+
200
+ Correction :
201
+ ```python
202
+ # Avant
203
+ with pytest.raises(click.UsageError, match="No such command"):
204
+
205
+ # Après
206
+ with pytest.raises(click.exceptions.NoSuchCommand, match="..."):
207
+ ```
208
+
209
+ **C. Test qui vérifie `ValueError` au chargement de `--config`**
210
+
211
+ Cause : click-extra 7.18.0 a changé les erreurs de chargement de config de `ValueError` en `SystemExit`.
212
+
213
+ Correction :
214
+ ```python
215
+ # Avant
216
+ with pytest.raises(ValueError, match="..."):
217
+
218
+ # Après
219
+ with pytest.raises(SystemExit):
220
+ ```
221
+
222
+ Après chaque correction :
223
+ ```bash
224
+ python -m pytest tests/ -v
225
+ ```
226
+
227
+ Lancer ruff avant de committer :
228
+ ```bash
229
+ ruff check src/ tests/
230
+ ruff format src/ tests/
231
+ ```
232
+
233
+ ---
234
+
235
+ ## Tâche 6 : Vérification du linting
236
+
237
+ - [ ] **Étape 1 : Lancer ruff**
238
+
239
+ ```bash
240
+ ruff check src/ tests/
241
+ ruff format src/ tests/
242
+ ```
243
+
244
+ Attendu : aucune erreur, aucun reformatage nécessaire.
245
+
246
+ ---
247
+
248
+ ## Tâche 7 : Commit
249
+
250
+ - [ ] **Étape 1 : Stager les fichiers modifiés**
251
+
252
+ ```bash
253
+ git add pyproject.toml uv.lock
254
+ # Si la Tâche 5 a tourné, ajouter aussi : git add <fichiers de tests corrigés>
255
+ ```
256
+
257
+ - [ ] **Étape 2 : Créer le commit**
258
+
259
+ ```bash
260
+ git commit -m "$(cat <<'EOF'
261
+ 🔧 chore(deps): upgrade click-extra, rich, rich-click
262
+
263
+ - click-extra 7.16.1 → 7.18.0 (tire Click 8.3.3 → 8.4.1 transitivement)
264
+ - rich 13.9.4 → 15.0.0 (abandonne Python 3.8 ; pyclifer requiert 3.10+)
265
+ - rich-click 1.9.7 → 1.9.8 (corrige la régression de compatibilité Click 8.4.x)
266
+ EOF
267
+ )"
268
+ ```
269
+
270
+ ---
271
+
272
+ ## Checklist de relecture
273
+
274
+ - [x] Les trois packages couverts avec des numéros de version précis
275
+ - [x] Breaking changes recherchés et mappés sur la surface de pyclifer
276
+ - [x] Upgrade couplé (click-extra + rich-click + montée Click transitive) documenté
277
+ - [x] Tâche de correction conditionnelle (Tâche 5) couvre les trois catégories d'échecs les plus probables
278
+ - [x] Aucun placeholder — chaque étape a des commandes exactes et une sortie attendue
279
+ - [x] Message de commit respecte le format du projet défini dans `.claude/CLAUDE.md`