pytest-given 0.1.0__tar.gz → 0.3.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 (200) hide show
  1. {pytest_given-0.1.0 → pytest_given-0.3.0}/.gitignore +8 -0
  2. pytest_given-0.3.0/AGENTS.md +127 -0
  3. pytest_given-0.3.0/CHANGELOG.md +360 -0
  4. pytest_given-0.3.0/GLOSSARY.md +108 -0
  5. pytest_given-0.3.0/PKG-INFO +131 -0
  6. pytest_given-0.3.0/README.md +103 -0
  7. pytest_given-0.3.0/THIRD-PARTY-LICENSES +137 -0
  8. pytest_given-0.3.0/docs/site/configuration/narration-lint.md +51 -0
  9. pytest_given-0.3.0/noxfile.py +606 -0
  10. pytest_given-0.3.0/pyproject.toml +193 -0
  11. {pytest_given-0.1.0/src/pytest_given/skills_data → pytest_given-0.3.0/src/pytest_given/.agents/skills}/pytest-given-authoring/SKILL.md +17 -8
  12. pytest_given-0.3.0/src/pytest_given/.agents/skills/pytest-given-authoring/references/api.md +61 -0
  13. {pytest_given-0.1.0/src/pytest_given/skills_data → pytest_given-0.3.0/src/pytest_given/.agents/skills}/pytest-given-authoring/references/domain-storytelling.md +12 -11
  14. pytest_given-0.3.0/src/pytest_given/.agents/skills/pytest-given-authoring/references/glossaries.md +82 -0
  15. pytest_given-0.3.0/src/pytest_given/.agents/skills/pytest-given-authoring/references/scenarios.md +81 -0
  16. pytest_given-0.3.0/src/pytest_given/.agents/skills/pytest-given-authoring/references/stories.md +77 -0
  17. pytest_given-0.3.0/src/pytest_given/.agents/skills/pytest-given-navigating/SKILL.md +41 -0
  18. pytest_given-0.3.0/src/pytest_given/.agents/skills/pytest-given-navigating/references/report-json.md +112 -0
  19. pytest_given-0.3.0/src/pytest_given/.agents/skills/pytest-given-reviewing/SKILL.md +69 -0
  20. pytest_given-0.3.0/src/pytest_given/.agents/skills/pytest-given-reviewing/references/pairs.md +64 -0
  21. pytest_given-0.3.0/src/pytest_given/.agents/skills/pytest-given-reviewing/references/story-coverage.md +24 -0
  22. {pytest_given-0.1.0 → pytest_given-0.3.0}/src/pytest_given/__init__.py +7 -5
  23. pytest_given-0.3.0/src/pytest_given/capture/__init__.py +89 -0
  24. pytest_given-0.3.0/src/pytest_given/capture/collector.py +469 -0
  25. pytest_given-0.3.0/src/pytest_given/capture/discovery.py +68 -0
  26. {pytest_given-0.1.0 → pytest_given-0.3.0}/src/pytest_given/capture/file_glossary.py +30 -38
  27. pytest_given-0.3.0/src/pytest_given/capture/glossary.py +261 -0
  28. pytest_given-0.3.0/src/pytest_given/capture/kind_inference.py +113 -0
  29. {pytest_given-0.1.0 → pytest_given-0.3.0}/src/pytest_given/capture/markdown_glossary.py +57 -27
  30. pytest_given-0.3.0/src/pytest_given/capture/process_state.py +70 -0
  31. pytest_given-0.3.0/src/pytest_given/capture/scenario.py +158 -0
  32. {pytest_given-0.1.0 → pytest_given-0.3.0}/src/pytest_given/capture/source.py +99 -115
  33. pytest_given-0.3.0/src/pytest_given/capture/steps.py +396 -0
  34. pytest_given-0.3.0/src/pytest_given/capture/story.py +420 -0
  35. pytest_given-0.3.0/src/pytest_given/capture/template.py +221 -0
  36. {pytest_given-0.1.0 → pytest_given-0.3.0}/src/pytest_given/capture/traceback.py +33 -46
  37. pytest_given-0.3.0/src/pytest_given/cli/__init__.py +32 -0
  38. pytest_given-0.3.0/src/pytest_given/cli/report.py +125 -0
  39. pytest_given-0.1.0/src/pytest_given/cli.py → pytest_given-0.3.0/src/pytest_given/cli/skills.py +44 -55
  40. pytest_given-0.3.0/src/pytest_given/grouping/__init__.py +10 -0
  41. pytest_given-0.3.0/src/pytest_given/grouping/attachments.py +143 -0
  42. pytest_given-0.3.0/src/pytest_given/grouping/checks.py +406 -0
  43. pytest_given-0.3.0/src/pytest_given/grouping/columns.py +268 -0
  44. pytest_given-0.3.0/src/pytest_given/grouping/context.py +104 -0
  45. pytest_given-0.3.0/src/pytest_given/grouping/group.py +102 -0
  46. pytest_given-0.3.0/src/pytest_given/grouping/percase.py +91 -0
  47. pytest_given-0.3.0/src/pytest_given/grouping/templatize.py +265 -0
  48. pytest_given-0.3.0/src/pytest_given/lint/__init__.py +25 -0
  49. pytest_given-0.3.0/src/pytest_given/lint/ast_rules.py +485 -0
  50. pytest_given-0.3.0/src/pytest_given/lint/base.py +65 -0
  51. pytest_given-0.3.0/src/pytest_given/lint/config.py +178 -0
  52. pytest_given-0.3.0/src/pytest_given/lint/runner.py +41 -0
  53. pytest_given-0.3.0/src/pytest_given/lint/runtime_rules.py +180 -0
  54. pytest_given-0.3.0/src/pytest_given/lint/summary.py +45 -0
  55. pytest_given-0.3.0/src/pytest_given/model/__init__.py +162 -0
  56. pytest_given-0.3.0/src/pytest_given/model/errors.py +47 -0
  57. pytest_given-0.3.0/src/pytest_given/model/narration.py +122 -0
  58. pytest_given-0.3.0/src/pytest_given/model/runtime.py +67 -0
  59. pytest_given-0.3.0/src/pytest_given/model/schema.py +436 -0
  60. pytest_given-0.3.0/src/pytest_given/model/serde.py +377 -0
  61. pytest_given-0.3.0/src/pytest_given/model/steps.py +43 -0
  62. pytest_given-0.3.0/src/pytest_given/model/text.py +50 -0
  63. pytest_given-0.3.0/src/pytest_given/plugin/__init__.py +43 -0
  64. pytest_given-0.3.0/src/pytest_given/plugin/collection.py +79 -0
  65. pytest_given-0.3.0/src/pytest_given/plugin/fixtures.py +231 -0
  66. pytest_given-0.3.0/src/pytest_given/plugin/options.py +248 -0
  67. pytest_given-0.3.0/src/pytest_given/plugin/runtest.py +242 -0
  68. pytest_given-0.3.0/src/pytest_given/plugin/session.py +338 -0
  69. pytest_given-0.3.0/src/pytest_given/plugin/state.py +139 -0
  70. pytest_given-0.3.0/src/pytest_given/report/__init__.py +40 -0
  71. pytest_given-0.3.0/src/pytest_given/report/coverage.py +166 -0
  72. pytest_given-0.3.0/src/pytest_given/report/glossary_view.py +372 -0
  73. pytest_given-0.3.0/src/pytest_given/report/html_renderer.py +503 -0
  74. pytest_given-0.3.0/src/pytest_given/report/inline_markdown.py +52 -0
  75. pytest_given-0.3.0/src/pytest_given/report/md_renderer.py +267 -0
  76. pytest_given-0.3.0/src/pytest_given/report/palette.py +204 -0
  77. pytest_given-0.3.0/src/pytest_given/report/sinks.py +209 -0
  78. pytest_given-0.3.0/src/pytest_given/report/slugs.py +107 -0
  79. {pytest_given-0.1.0 → pytest_given-0.3.0}/src/pytest_given/report/source_link.py +60 -42
  80. pytest_given-0.3.0/src/pytest_given/report/story_view.py +170 -0
  81. pytest_given-0.3.0/src/pytest_given/report/templates/_macros.html.j2 +155 -0
  82. pytest_given-0.3.0/src/pytest_given/report/templates/app.js +925 -0
  83. pytest_given-0.3.0/src/pytest_given/report/templates/fonts/source-code-pro-latin-wght-normal.woff2 +0 -0
  84. pytest_given-0.3.0/src/pytest_given/report/templates/fonts/source-sans-3-latin-wght-normal.woff2 +0 -0
  85. pytest_given-0.3.0/src/pytest_given/report/templates/logo.svg +1 -0
  86. pytest_given-0.3.0/src/pytest_given/report/templates/report.html.j2 +648 -0
  87. pytest_given-0.3.0/src/pytest_given/report/templates/styles.css +1391 -0
  88. pytest_given-0.3.0/src/pytest_given/report/text.py +8 -0
  89. pytest_given-0.3.0/src/pytest_given/report/theme.py +30 -0
  90. pytest_given-0.3.0/tests/conftest.py +35 -0
  91. pytest_given-0.3.0/tests/integration/test_cli.py +394 -0
  92. pytest_given-0.3.0/tests/integration/test_plugin.py +3100 -0
  93. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/integration/test_plugin_file_glossary.py +4 -4
  94. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/integration/test_plugin_lint.py +177 -77
  95. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/integration/test_plugin_session_isolation.py +89 -2
  96. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/integration/test_skills_cli.py +19 -2
  97. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/ubiquitous_language.py +43 -19
  98. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/test_collector.py +281 -125
  99. pytest_given-0.3.0/tests/unit/capture/test_discovery.py +119 -0
  100. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/test_file_glossary.py +32 -47
  101. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/test_glossary.py +96 -77
  102. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/test_kind_inference.py +83 -50
  103. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/test_markdown_glossary.py +53 -16
  104. pytest_given-0.3.0/tests/unit/capture/test_rootdir_isolation.py +17 -0
  105. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/test_source.py +27 -11
  106. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/test_step_descriptor.py +479 -221
  107. pytest_given-0.3.0/tests/unit/capture/test_story.py +871 -0
  108. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/test_template.py +105 -79
  109. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/test_traceback_parser.py +22 -46
  110. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/lint/test_ast_rules.py +327 -145
  111. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/lint/test_config.py +95 -26
  112. pytest_given-0.3.0/tests/unit/lint/test_runtime_rules.py +329 -0
  113. pytest_given-0.3.0/tests/unit/lint/test_summary.py +73 -0
  114. pytest_given-0.3.0/tests/unit/model/test_narration.py +15 -0
  115. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/model/test_schema.py +161 -105
  116. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/model/test_serde.py +372 -69
  117. pytest_given-0.3.0/tests/unit/model/test_steps.py +34 -0
  118. pytest_given-0.3.0/tests/unit/model/test_text.py +22 -0
  119. pytest_given-0.3.0/tests/unit/report/test_coverage.py +624 -0
  120. pytest_given-0.3.0/tests/unit/report/test_glossary_view.py +495 -0
  121. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/report/test_html_renderer.py +633 -180
  122. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/report/test_md_renderer.py +312 -41
  123. pytest_given-0.3.0/tests/unit/report/test_palette.py +153 -0
  124. pytest_given-0.3.0/tests/unit/report/test_sinks.py +226 -0
  125. pytest_given-0.3.0/tests/unit/report/test_slugs.py +138 -0
  126. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/report/test_source_link.py +177 -80
  127. pytest_given-0.3.0/tests/unit/report/test_story_view.py +462 -0
  128. pytest_given-0.3.0/tests/unit/report/test_theme.py +22 -0
  129. pytest_given-0.3.0/tests/unit/test_grouping.py +2860 -0
  130. pytest_given-0.3.0/tests/unit/test_percase.py +223 -0
  131. pytest_given-0.3.0/tests/unit/test_plugin.py +593 -0
  132. pytest_given-0.3.0/tests/unit/test_plugin_traceback.py +45 -0
  133. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/test_skills_data.py +20 -8
  134. pytest_given-0.3.0/tests/unit/test_skills_scripts.py +100 -0
  135. {pytest_given-0.1.0 → pytest_given-0.3.0}/uv.lock +209 -5
  136. pytest_given-0.1.0/AGENTS.md +0 -104
  137. pytest_given-0.1.0/CHANGELOG.md +0 -42
  138. pytest_given-0.1.0/GLOSSARY.md +0 -94
  139. pytest_given-0.1.0/PKG-INFO +0 -510
  140. pytest_given-0.1.0/README.md +0 -482
  141. pytest_given-0.1.0/THIRD-PARTY-LICENSES +0 -26
  142. pytest_given-0.1.0/noxfile.py +0 -352
  143. pytest_given-0.1.0/pyproject.toml +0 -127
  144. pytest_given-0.1.0/src/pytest_given/capture/__init__.py +0 -48
  145. pytest_given-0.1.0/src/pytest_given/capture/collector.py +0 -369
  146. pytest_given-0.1.0/src/pytest_given/capture/decorators.py +0 -502
  147. pytest_given-0.1.0/src/pytest_given/capture/glossary.py +0 -258
  148. pytest_given-0.1.0/src/pytest_given/capture/kind_inference.py +0 -102
  149. pytest_given-0.1.0/src/pytest_given/capture/story.py +0 -303
  150. pytest_given-0.1.0/src/pytest_given/capture/template.py +0 -180
  151. pytest_given-0.1.0/src/pytest_given/lint/__init__.py +0 -47
  152. pytest_given-0.1.0/src/pytest_given/lint/ast_rules.py +0 -395
  153. pytest_given-0.1.0/src/pytest_given/lint/base.py +0 -77
  154. pytest_given-0.1.0/src/pytest_given/lint/config.py +0 -137
  155. pytest_given-0.1.0/src/pytest_given/lint/runtime_rules.py +0 -212
  156. pytest_given-0.1.0/src/pytest_given/model/__init__.py +0 -84
  157. pytest_given-0.1.0/src/pytest_given/model/errors.py +0 -2
  158. pytest_given-0.1.0/src/pytest_given/model/ids.py +0 -32
  159. pytest_given-0.1.0/src/pytest_given/model/schema.py +0 -326
  160. pytest_given-0.1.0/src/pytest_given/model/serde.py +0 -263
  161. pytest_given-0.1.0/src/pytest_given/plugin.py +0 -958
  162. pytest_given-0.1.0/src/pytest_given/report/__init__.py +0 -10
  163. pytest_given-0.1.0/src/pytest_given/report/aggregations.py +0 -382
  164. pytest_given-0.1.0/src/pytest_given/report/cli.py +0 -68
  165. pytest_given-0.1.0/src/pytest_given/report/coverage.py +0 -175
  166. pytest_given-0.1.0/src/pytest_given/report/html_renderer.py +0 -370
  167. pytest_given-0.1.0/src/pytest_given/report/inline_markdown.py +0 -35
  168. pytest_given-0.1.0/src/pytest_given/report/md_renderer.py +0 -164
  169. pytest_given-0.1.0/src/pytest_given/report/templates/app.js +0 -438
  170. pytest_given-0.1.0/src/pytest_given/report/templates/report.html.j2 +0 -606
  171. pytest_given-0.1.0/src/pytest_given/report/templates/styles.css +0 -1013
  172. pytest_given-0.1.0/src/pytest_given/skills_data/pytest-given-authoring/references/api.md +0 -62
  173. pytest_given-0.1.0/src/pytest_given/skills_data/pytest-given-authoring/references/glossaries.md +0 -68
  174. pytest_given-0.1.0/src/pytest_given/skills_data/pytest-given-authoring/references/scenarios.md +0 -29
  175. pytest_given-0.1.0/src/pytest_given/skills_data/pytest-given-authoring/references/stories.md +0 -53
  176. pytest_given-0.1.0/src/pytest_given/skills_data/pytest-given-navigating/SKILL.md +0 -40
  177. pytest_given-0.1.0/src/pytest_given/skills_data/pytest-given-navigating/references/report-json.md +0 -69
  178. pytest_given-0.1.0/src/pytest_given/skills_data/pytest-given-reviewing/SKILL.md +0 -47
  179. pytest_given-0.1.0/tests/conftest.py +0 -3
  180. pytest_given-0.1.0/tests/integration/test_cli.py +0 -178
  181. pytest_given-0.1.0/tests/integration/test_plugin.py +0 -1869
  182. pytest_given-0.1.0/tests/unit/capture/test_story.py +0 -648
  183. pytest_given-0.1.0/tests/unit/lint/test_runtime_rules.py +0 -285
  184. pytest_given-0.1.0/tests/unit/model/test_ids.py +0 -21
  185. pytest_given-0.1.0/tests/unit/report/test_aggregations.py +0 -762
  186. pytest_given-0.1.0/tests/unit/report/test_coverage.py +0 -535
  187. pytest_given-0.1.0/tests/unit/test_plugin.py +0 -609
  188. {pytest_given-0.1.0 → pytest_given-0.3.0}/LICENSE.md +0 -0
  189. {pytest_given-0.1.0 → pytest_given-0.3.0}/conftest.py +0 -0
  190. {pytest_given-0.1.0 → pytest_given-0.3.0}/src/pytest_given/py.typed +0 -0
  191. {pytest_given-0.1.0 → pytest_given-0.3.0}/src/pytest_given/report/templates/alpine.min.js +0 -0
  192. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/__init__.py +0 -0
  193. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/integration/__init__.py +0 -0
  194. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/__init__.py +0 -0
  195. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/__init__.py +0 -0
  196. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/lint/__init__.py +0 -0
  197. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/model/__init__.py +0 -0
  198. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/model/test_errors.py +0 -0
  199. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/report/__init__.py +0 -0
  200. {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/report/test_inline_markdown.py +0 -0
@@ -21,3 +21,11 @@ docs/superpowers/plans/
21
21
  .claude/settings.local.json
22
22
  .playwright-mcp/
23
23
  .mcp.json
24
+
25
+ # Documentation site: build output and the copies docs_build stages into docs_dir
26
+ /site/
27
+ /docs/site/examples/
28
+ /docs/site/assets/pytest-given-diagram-full.svg
29
+ /docs/site/assets/fonts/source-sans-3-latin-wght-normal.woff2
30
+ /docs/site/assets/fonts/source-code-pro-latin-wght-normal.woff2
31
+ /docs/site/assets/logo.svg
@@ -0,0 +1,127 @@
1
+ # Agents
2
+
3
+ ## Project overview
4
+
5
+ See [README.md](README.md) for the pitch and quick start, and the [documentation site](https://nwilbert.github.io/pytest-given/latest/) (sources under `docs/site/`) for the public API and CLI flags. The rest of this document is contributor-facing.
6
+
7
+ ## Setup
8
+
9
+ ```bash
10
+ uv sync --group dev
11
+ ```
12
+
13
+ **All Python invocations go through `uv run`** — `uv run pytest …`, `uv run python -m …`, `uv run nox …`. There is no system `python` on PATH; bare `python` / `pytest` calls will fail. This applies to one-off commands (running a single test file, REPL exploration) too, not just nox sessions.
14
+
15
+ **Never prepend `cd <path> &&` to commands.** The working directory is already set to the project root; the `cd` is redundant and triggers a permission prompt.
16
+
17
+ ## Quality gates
18
+
19
+ `uv run nox` runs the default gate — `format`, `lint`, `mypy`, `test`, `coverage` (a 100% target), `audit` (a `pip-audit` of the locked dependencies). **Run it (or at minimum `uv run nox -s format lint mypy test`) before every commit.** The sessions below are on-demand; list them all with `uv run nox -l`.
20
+
21
+ - `uv run nox -s examples` regenerates the JSON, HTML, and Markdown files under `examples/coffeeshop/`, `examples/hotel-booking/`, and `examples/file-glossary-booking/`. Run after changes to the renderer, templates, plugin output schema, or any example test file, and commit the updated outputs.
22
+ - `uv run nox -s self_report` regenerates `examples/self-report/` — pytest-given applied to its own backend tests (see [Writing self-report scenarios](#writing-self-report-scenarios)). Run after decorating more tests or changing decorated ones, and commit the updated outputs.
23
+ - **Only commit a regenerated report when its *content* actually changed.**
24
+ - Every regeneration rewrites `commit_sha` (to current HEAD, including the SHA-pinned source-link URLs), `timestamp`, and `duration_ms` in the JSON and HTML, so a report your change didn't really touch still shows a diff — `git checkout` those files rather than committing the noise.
25
+ - **Read the `.md` diff first**: the Markdown carries none of those fields, so it is the behavioral delta of your change in prose. An unchanged `.md` doesn't by itself prove the JSON/HTML are noise-only (glossary and story data never surface in the Markdown); a shifted source line does show up, in the `relpath:line::test_name` anchor under every heading.
26
+ - Regenerate only the reports a change can affect — `examples` narrates `examples/**`, `self_report` narrates `tests/**`. A shifted line number in a decorated backend test is therefore a real self-report change worth committing, even when no example changed.
27
+ - Both regeneration sessions run the narration lint (`--given-lint`; see [Narration lint](docs/site/configuration/narration-lint.md) and the [design spec](docs/specs/2026-07-05-narration-lint-design.md)). `self_report` fails on any lint error — a real gate, keep the backend suite lint-clean. `examples` tolerates exit 1 for its intentional failures, which masks the lint exit code, so read the printed "narration lint" summary there. A step the lint mis-flags goes on the `given_lint_ignore` list (stale entries fail the run); the rule catalog and ignore mechanics are in the [authoring skill](src/pytest_given/.agents/skills/pytest-given-authoring/references/scenarios.md) under "Mechanical counterparts", and the honest-two-phase test an ignored `missing-phase` has to pass under "Phase structure".
28
+ - `uv run nox -s benchmark` generates the large-scenarios suite and renders its JSON + HTML into `benchmarks/` (gitignored). Run it when a change could move report-generation cost; `benchmarks/bench.py` does size sweeps and cProfile runs directly.
29
+ - `uv run nox -s build` builds the wheel + sdist and verifies them as a consumer would — `py.typed`, templates and bundled skills present, a real scenario run from a throwaway install, and [library-skills](https://library-skills.io) discovery of every bundled skill. The in-repo suite imports from `src/`, so this session is the only thing that catches a packaging regression. CI and the release workflow both run it.
30
+ - `uv run nox -s docs_build` builds the documentation site under `site/` with `zensical build --strict`, after copying the four example reports, the full-size diagram and the report package's two upright fonts into gitignored spots under `docs/site/` (Zensical builds every file in `docs/site/` and cannot exclude any, so those copies are how single-sourced files get in; `CHANGELOG.md` instead reaches the site through a `pymdownx.snippets` include in `docs/site/changelog.md`, between its `site` markers, and the Home-page diagram is inlined the same way from `docs/site/assets/pytest-given-diagram.svg` so page CSS can theme it). The README shows the same diagram as `docs/pytest-given-diagram.svg`, a derived copy with the font embedded and the colour tokens resolved, which the Home page also links to as its full-size view: after editing the source SVG or its tokens, run `uv run nox -s diagram` and commit the result. Run it after changing anything under `docs/site/`; a broken cross-page link fails it. Preview with `uv run --group docs zensical serve` afterwards. The site self-hosts its fonts (`docs/site/assets/fonts/`, latin subsets from Fontsource, OFL notices in `LICENSES.txt` there; the upright faces are the report package's own under `src/pytest_given/report/templates/fonts/`, staged in by the build, only the italics live in the site tree) so a visitor's browser contacts no third party — keep `font = false` in `zensical.toml` and don't `@import` a font CDN; a new face gets its `.woff2` and copyright line added there. `docs_deploy` is the CI-only counterpart that publishes with mike — see [docs/releasing.md](docs/releasing.md#documentation-site).
31
+
32
+ ## Releasing
33
+
34
+ Releases go to PyPI via a manually dispatched [Release workflow](.github/workflows/release.yml), authenticated with Trusted Publishing (no tokens anywhere) and always rehearsed on TestPyPI first. The step-by-step checklist, including the version bump and CHANGELOG section it needs, lives in [docs/releasing.md](docs/releasing.md).
35
+
36
+ ## Architecture
37
+
38
+ `src/pytest_given/` is five library subpackages plus two entry points, with a
39
+ strict dependency direction (convention, not lint-enforced):
40
+
41
+ ```
42
+ model/ the leaf — schema, serde, errors, shared text rules
43
+ capture/ lint/ report/ each on model/ only, never on each other
44
+ grouping/ on model/ + capture/
45
+ plugin/ cli/ the entry points; may import all five, and hold
46
+ nothing the five could
47
+ ```
48
+
49
+ What no filename tells you:
50
+
51
+ - `grouping/` — the parametrize pass: a scenario's cases collapsed into one
52
+ narrated tree plus a parameter table, refusing the authoring forms that would
53
+ make that tree lie. Runs at session finish *before* the sinks are written, so
54
+ a bare `pytest` failing on one is the point, not a side effect.
55
+ - `plugin/__init__.py` is the hook surface pluggy registers — the `pytest11`
56
+ entry point is the package, and pluggy scans a module's attributes for
57
+ `pytest_*` names, so the re-exports there *are* the registration.
58
+ - `capture/` imports pytest nowhere, so all of it is unit-testable without a
59
+ session; a stray step warns with `model.PytestGivenWarning`, not pytest's.
60
+ - `Glossary` exists twice on purpose: `model/schema.py` holds the storage the
61
+ report carries and serde rebuilds, and `capture/glossary.py` subclasses it
62
+ with the registration API, which needs a caller source location that the leaf
63
+ may not reach for. `pytest_given.Glossary` is the subclass; everything
64
+ internal annotates the base. `LookupGlossary` sits between them and owns the
65
+ `g['Guest']` read-back both user-facing glossaries share.
66
+ - Each package exposes the *whole* job, not its parts: `report.emit_sinks`
67
+ (render → write → discard-on-failure, so a failure leaves no half-written
68
+ report) and `lint.run_lint`. Both entry points reach the sinks through
69
+ `emit_sinks`, which is what keeps `pytest-given report` behaving like the
70
+ plugin; the lint is the plugin's alone, since the CLI re-renders a report
71
+ rather than running a suite.
72
+
73
+ `tests/` splits `unit/` (no pytest session needed) from `integration/`, which drives the plugin end to end through `pytester` inner runs (enabled by the root `conftest.py`). Narration written inside an inner run belongs to *that* run's collector — only the outer, decorated test reaches the self-report.
74
+
75
+ The public API, including the step-text authoring forms (t-string vs `Template` vs plain string), is re-exported from `__init__.py` and documented in the skill's [references/api.md](src/pytest_given/.agents/skills/pytest-given-authoring/references/api.md).
76
+
77
+ ## Handling report output
78
+
79
+ Outputs are opt-in; a bare `uv run pytest` writes nothing. The workflow for reading a run's narration (`--given-md`), querying the JSON report with `jq` by tag/term/status, re-rendering a saved run, and the bare-flag-order trap lives in the [navigating skill](src/pytest_given/.agents/skills/pytest-given-navigating/SKILL.md).
80
+
81
+ ## Report testing
82
+
83
+ Any change to `report/templates/` (Jinja, CSS, `app.js`) or the `narration` filter in `html_renderer.py` **must** be Playwright-verified before commit. The project has no JS-side UI tests, and Python tests on rendered HTML cannot stand in: a substring match passes on a broken Alpine expression or a malformed `:class` binding. So:
84
+
85
+ - **No Python tests that pin frontend markup** (class names, wrapper structure, inline-handler shape, SVG strings) — they check implementation, not behavior, and rot on every renderer refactor. Python tests stay on the renderer's data-shaped contract (which `data-param` value, which scenario IDs, which counts).
86
+ - **No TDD for frontend changes**: apply the change, regenerate `examples/`, drive it in Playwright, capture the result.
87
+ - Open e.g. `examples/coffeeshop/coffeeshop.html` (regenerate via `uv run nox -s examples`) with the Playwright MCP server, check `browser_console_messages` for errors after init, then drive the changed surface (hover, click, URL hash). Use `browser_snapshot` (not screenshots) to read page content and interact with elements.
88
+ - Desktop only — assume a minimum viewport width of ~900px.
89
+ - Traceback display and header metadata formatting are known limitations, not current priorities.
90
+ - Never save Playwright screenshots into the project directory — use the session scratchpad or omit the `filename` parameter.
91
+
92
+ **Setup and known traps** (`.mcp.json`, the `file://` page cache, browser installs) live in [docs/playwright-setup.md](docs/playwright-setup.md). `.mcp.json` is read at **session start**, so check that the `browser_*` tools exist before planning a task that ends in Playwright verification.
93
+
94
+ ## Writing self-report scenarios
95
+
96
+ The narration rules live in the **`pytest-given-authoring` skill**, whose canonical source is [src/pytest_given/.agents/skills/](src/pytest_given/.agents/skills/pytest-given-authoring/SKILL.md) — every link in this document points there. The copy under `.claude/skills/` is a mirror: after editing the canonical copy, regenerate it with `uv run pytest-given skills install` and commit both (a sync test fails otherwise).
97
+
98
+ **The skill is documentation with the same sync duty as the site** — downstream agents read it instead of the site, version-matched from the wheel. A change to the public API surface or its rules updates the site page under `docs/site/` *and* [references/api.md](src/pytest_given/.agents/skills/pytest-given-authoring/references/api.md); a change to narration/lint semantics updates [references/scenarios.md](src/pytest_given/.agents/skills/pytest-given-authoring/references/scenarios.md) and friends. No check catches prose drift between the site and the skill, so ask "does the skill need this too?" on every user-facing change; only the `python` blocks are covered — `tests/unit/test_skills_scripts.py` runs each against a report built from the model. The reviewing skill restates authoring rules as rubrics, so they share the same duty: its layer 2 mirrors `scenarios.md` ("Keeping it truthful", "Expected raises", "Vocabulary and tags"), and its layer 4 mirrors `glossaries.md` ("Keeping the glossary honest") and `stories.md` — change one, change both.
99
+
100
+ Narration drifts like any other prose — the lint catches several ways step text and body can part company, not all of them — which is why [Quality gates](#quality-gates) has you read the regenerated `.md` rather than trust the text.
101
+ Before merging changes to narrated tests, run the `pytest-given-reviewing` skill: it layers the lint, a semantic audit of step text against step bodies, and a hygiene pass over glossary, tags and stories.
102
+
103
+ What is specific to this repo's self-report:
104
+
105
+ - The glossary handle is `pg` — `GLOSSARY.md` loaded as a `FileGlossary` in `tests/conftest.py` via `tests/ubiquitous_language.py`. Term-rename mechanics live under [Conventions](#conventions); regeneration and lint gating under [Quality gates](#quality-gates).
106
+ - **New or changed user-facing behavior needs a scenario, not just a test** — otherwise the behavior is invisible in the report. Decorate the test that best *states* the rule, one per rule, not per branch; the edge cases around it stay plain. Two gaps to check for: a rule the [CHANGELOG](CHANGELOG.md) announces that no scenario names, and a [GLOSSARY.md](GLOSSARY.md) row *asserting* behavior (`Templatize`, `Parameter table`) that no scenario demonstrates.
107
+
108
+ ## Conventions
109
+
110
+ - Use the canonical vocabulary from [GLOSSARY.md](GLOSSARY.md) in prose as well as code — docs, skill references, and specs say the official term (`term ref`, not a paraphrase like "narrated term"). Naming and rename mechanics live in the skill's [references/glossaries.md](src/pytest_given/.agents/skills/pytest-given-authoring/references/glossaries.md); here, a rename lands in one commit (glossary row, `pg\[` references, implementation naming) plus a regenerated `uv run nox -s self_report`. Adding a term is safe, but still regenerate.
111
+ - **Docstrings only for what the code cannot say** — a non-obvious invariant, a constraint that makes the shape necessary, a trap for the next reader — and a line or two at most. No signature restatement, no implementation narration, no `X is the only caller of Y` cross-references (they rot fastest; the reader can grep). Prefer a precise name to a sentence explaining a vague one. When you change a module, delete the sentences that have stopped being true rather than repairing them.
112
+ - Avoid `Any` — use precise types, generics, `TYPE_CHECKING` imports, or `ContextVar[T]` over untyped `threading.local`.
113
+ - Use `NewType` for domain-specific IDs (e.g., `NodeId`) and PEP 695 `type` statements for aliases. Avoid raw complex types like `dict[str, tuple[list[str], list[Any]]]` — introduce named types instead.
114
+ - Only module-level imports — no inline/function-level imports.
115
+ - **pytest config lives in `[tool.pytest]`** — native TOML mode since pytest 8.4/9.0, where lists are real arrays rather than newline-separated strings. `[tool.pytest.ini_options]` is the legacy INI-compat table; **never add it alongside** — pytest raises `UsageError` if both are present. New `addini` options take native types (a `type='linelist'` ini takes a TOML array).
116
+ - Cross-platform: plugin and tests must pass on native Windows, macOS, Linux, and WSL (Linux interpreter over a `/mnt/<drive>` Windows checkout). Never hardcode a path separator or assume POSIX semantics — go through `pathlib`, `as_posix()` for stored/serialized paths, resolve before comparing. Path-form folding is confined to `capture/source.py` (mechanics in its docstrings; the why, and how to run one checkout from both Windows and WSL without the venvs colliding, in [docs/wsl-development.md](docs/wsl-development.md)). Only tests asserting WSL `/mnt`-absolute behavior may `skipif(sys.platform == 'win32', …)` — native-Windows pathlib reads `/mnt/<drive>` as drive-relative, not absolute — everything else passes on all four targets.
117
+ - Relative imports inside the package throughout — `from .schema import Scenario` for siblings, `from ..model import Scenario` across subpackages (always through the subpackage root, never into its submodules). Tests use absolute imports and may reach into any internal path. The dependency direction those imports must respect is under [Architecture](#architecture).
118
+ - Prefer `assert` over `# pragma: no cover` for invariant guards. Asserts document the invariant and fail loudly if violated; pragmas hide the line and silently bail. Reserve `# pragma: no cover` for code that genuinely cannot be exercised by a test (e.g. `if __name__ == '__main__':` script entry).
119
+ - JSON reports written by older pytest-given versions are not supported: `pytest-given report` may drop or misread their fields. Don't add readers, migrations, or stale-shape errors for them.
120
+ - Step-down rule: callers before callees, public before private. Read each file top-down from high-level API to implementation details.
121
+ - TDD: write tests first (except frontend changes — see [Report testing](#report-testing)).
122
+ - Commit messages: single line, no co-author trailers, no leading file/area labels like `TODO:` or `README:` — just describe the change ("note example cleanup as todo", not "TODO: note example cleanup"). Conventional-commit-style scope prefixes like `docs:` / `examples:` / `renderer:` are fine when they add information.
123
+ - Keep commits coherent: each commit should represent one logical change. Don't split "do X", "tests for X", and "review-fixup for X" into separate commits — squash them before pushing. Don't bundle unrelated changes either.
124
+ - **A user-facing change adds its `CHANGELOG.md` entry in the same commit**, under `## [Unreleased]`, in the fitting Keep a Changelog category (each category appears at most once per version — extend the existing heading rather than adding a second one). User-facing = public API, CLI flags, report output, lint rules, bundled skills; internal work (refactors, tests, CI, contributor docs) gets no entry.
125
+ - **One sentence per entry**, written for someone upgrading: name the symbol, flag, or surface and say what changed — no rationale, measurements, or before/after detail. Only a breaking change earns more: the migration it needs. Visual and interaction polish gets one collective bullet per release ("the sidebar and its chips are visually tidied"), never a bullet per restyled element; accessibility fixes stay on their own line. When in doubt, the shorter entry is the right one.
126
+ - Plan files under `docs/superpowers/plans/` are scratch artifacts — never commit them. Spec files under `docs/specs/` are committed.
127
+ - New specs land under `docs/specs/proposed/`. When a spec's implementation lands, `git mv` it up one level into `docs/specs/` in the same commit, and fix its relative links in the same edit — a `../`-prefixed link to a sibling spec resolves into `docs/` once the file moves. `ls docs/specs/proposed` is the canonical list of outstanding design work.
@@ -0,0 +1,360 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ The release workflow reads the section matching the version in `pyproject.toml`
9
+ and uses it as the GitHub Release body, so each version needs a heading of the
10
+ form `## [x.y.z] - YYYY-MM-DD`.
11
+
12
+ <!-- --8<-- [start:site] -->
13
+
14
+ ## [0.3.0] - 2026-09-27
15
+
16
+ ### Added
17
+
18
+ - A documentation site at <https://nwilbert.github.io/pytest-given/> has a user guide, configuration and CLI reference, and the example reports. The bundled skills link to it, and the CLI help names the page on source-link templates.
19
+ - The HTML report has a dark theme, with a Light / Dark / System control in its header that each browser remembers.
20
+ - `--given-theme` / `given_theme` (and `--theme` on `pytest-given report`) set whether the HTML report opens light, dark, or following the viewer's system.
21
+ - Tags can be organized hierarchically: a `/` in a tag nests it in the HTML report's Tags sidebar (`ticket/ABC-123` goes under a `ticket` heading), and selecting the heading filters to every tag beneath it.
22
+ - Projects can also install the bundled skills with [library-skills](https://library-skills.io) (`uvx library-skills install --claude`), alongside the skills of their other dependencies.
23
+ - One scenario can document a flow that spans several stories: `@scenario(stories=[a, b])` lists it under each story in the Stories tab, and the Scenarios view's sentence filter names the story when a report has several.
24
+ - A sentence can be named (`sentence(..., name='checkout')`), so a pin can refer to it by name and survives reordering the story. The Stories timeline shows the name beside the sentence's coverage.
25
+
26
+ ### Changed
27
+
28
+ - **Breaking.** Story vocabulary follows Domain Storytelling: a story is made of *sentences*, and *activity* is the verb kind. `activity()` is now `sentence()`, `path()` is now `clause()`, and `Glossary.verb()` is now `Glossary.activity()`. A file glossary's kind column says `activity` instead of `verb`, the report's `#activity-filter=` link parameter is now `#sentence-filter=`, and the HTML report says *Sentence* and *Activities* where it said *Activity* and *Verbs*.
29
+ - **Breaking.** `@scenario(story=)` is now `stories=`.
30
+ - **Breaking.** Pins refer to sentences by handle under `pins=` instead of by number: `given(..., activity=3)` becomes `given(..., pins=the_story[3])`, and `@scenario(activities=[2, 3])` becomes `pins=[the_story[2], the_story[3]]`. `activity_id=` is gone, because sentences are numbered by position; to pin a sentence independently of its position, name it and pin `the_story['name']`. A pin no longer requires the scenario to bind the pinned sentence's story.
31
+ - **Breaking.** A scenario's `pins=` now sets its coverage outright: it covers exactly those sentences plus its steps' pins, with no narration matching. `activities=` only narrowed which sentences narration could cover. Pass `pins=[]` to a step or scenario to turn off narration matching without pinning anything.
32
+ - The HTML report is restyled. It embeds Source Sans 3 and Source Code Pro, so it looks the same on every machine and offline. Lists share one surface instead of a card per row, Given/When/Then sit in a gutter beside the steps, and sidebar labels and counts use sentence case. Step narration and story sentences are set larger and darker than the surrounding labels and counts, at the same line spacing, so a scenario takes no more room.
33
+ - The HTML report strips source comments from its inlined stylesheet and script, which saves about 29 KB per report and offsets part of the size the embedded fonts add.
34
+ - Grouping errors for varying attachment labels and varying `str` narration name the parametrize case that differs. The varying-`str` error also suggests `group_parametrized=False` as a way out.
35
+ - A story that no scenario covers now shows up in the report as uncovered, where before it was missing. The one exception is a story declared before the session started, such as one in a module still imported from an earlier `pytest.main()` in the same process: it is left out with a warning.
36
+ - Story coverage ignores instances: a step narrating `guest`, or any guest instance, now covers a sentence naming `guest('Alice')`. Sentences that differ only by instance can be told apart only with a pin.
37
+ - The authoring skill advises writing generic verbs (*searches for*, *adds*) as plain strings in sentences rather than glossary terms, keeping the glossary to domain vocabulary. The hotel-booking example follows it.
38
+ - The authoring skill explains how coverage matching works (instances are ignored, and two sentences whose terms nest always cover together) and how to check coverage in the JSON report. The reviewing skill checks coverage from the report instead of re-deriving it.
39
+ - The reviewing skill can list each scenario's narration beside its test's source for side-by-side review, and checks that a scenario demonstrates every rule the project's changelog announces. Both skills flag `match=` pins that use alternation, and a `when` that narrates setup while its body acts.
40
+ - The authoring, reviewing and navigating skills cover sentence handles, `pins=` and `stories=`. The reviewing skill also checks that the test body exercises each sentence its scenario pins.
41
+
42
+ ### Fixed
43
+
44
+ - When rendering a report fails unexpectedly, the previous run's report is still discarded instead of staying on disk looking current.
45
+ - A `#scenario=` deep link now opens the right scenario when two node ids differ only in a character the slug folds, and no longer breaks the URL.
46
+ - `pytest-given report` reports a non-UTF-8 input file as an error instead of crashing with a traceback.
47
+ - An unknown `--given-source-link` preset is reported under the flag the user typed, not under the `given_source_link` ini name.
48
+ - A refused scenario on a run with no `--given-*` sink no longer reports itself under a "report not written" heading.
49
+ - `when_then(...)` rejects a `Template` narration in a test body, as `given`/`when`/`then` already do.
50
+ - The Glossary view no longer lists a term's own name in another case (`guest.low`) under Instances.
51
+ - On native Windows, stories and glossary terms now record where they are declared, so the lint rules that depend on it run there too.
52
+ - `tag-shadows-term` now catches a tag that collides with a term id through a non-ASCII character that lowercases into ASCII.
53
+ - A `dead-term` finding's message states the rule's actual criterion, including that a term ref in a `@scenario` name keeps a term alive.
54
+ - The navigating skill shows the failure messages of a parametrized scenario's cases, where it printed an empty message. It also starts from a committed or CI-published report when one exists, instead of always rerunning the suite.
55
+ - The reviewing skill lints the whole suite, where a selection failed on the project's ignore entries as stale, keeps the project's own `given_lint_rules` when enabling `dead-term`, and ranks findings in one explicit order.
56
+ - A pin on an `Annotated` `given(...)` label now takes effect instead of being silently dropped, replacing the pins of the fixture step it relabels (`pins=[]` clears them). A pin on a step fixture's own `@given(...)` label also takes effect now.
57
+ - A pin on a `@given` fixture scoped wider than `function` now counts in every scenario the fixture reaches, even when a test without `@scenario` set it up first. The run used to fail with a bare `AssertionError`.
58
+
59
+ ## [0.2.0] - 2026-09-04
60
+
61
+ ### Added
62
+
63
+ - `pytest_given.PytestGivenWarning` is a top-level export, and a step or
64
+ `attach()` recorded in a test without `@scenario` now warns with it instead of
65
+ `pytest.PytestWarning`.
66
+ - `--given-title=TEXT` (or the `given_title` ini) names the report, replacing the
67
+ rootdir name.
68
+ - A parametrized scenario's parameter table now carries a typed column per varying
69
+ value — `param`, `derived`, or `attachment` for a varying attachment payload —
70
+ rather than one column per parametrize name.
71
+ - `@scenario(group_parametrized=False)` declines the grouping and emits each case
72
+ as its own scenario, titled by its parametrize id.
73
+ - The HTML report's sidebar gains **Terms** as a third browse axis, and all three
74
+ axes — Tags, Terms, Modules — now filter the Scenarios view the same way, with
75
+ each active filter carried in the URL.
76
+ - The sidebar can be ordered by group size as well as by name, and resized by
77
+ dragging its seam or with the arrow keys.
78
+ - A selected activity in the Stories view offers **Open in Scenarios**, filtering
79
+ the Scenarios view down to the scenarios covering it.
80
+
81
+ ### Changed
82
+
83
+ #### CLI
84
+
85
+ - **Breaking.** `--given-lint` is a plain boolean flag: write `--given-lint` and
86
+ `--no-given-lint` instead of `--given-lint=true` / `--given-lint=false`. Either
87
+ form still overrides the `given_lint` ini for one run.
88
+
89
+ #### Authoring API
90
+
91
+ - **Breaking.** Step narration must now be uniform across parametrize cases;
92
+ these fail the run with `PytestGivenError`, writing no report, instead of
93
+ quietly reporting case 1:
94
+
95
+ - a plain `str` (usually an f-string) that renders differently per case;
96
+ - a varying interpolation that is not a bare name (`t"{cup_size * 0.01}"`,
97
+ `t"{m.balance}"`);
98
+ - a t-string narrating a parametrize name that no longer holds the case's
99
+ value — either a local rebound it, or the body mutated it in place;
100
+ - a step whose set of `attach` labels differs between cases;
101
+ - a glossary term ref that names a different term or reads differently between
102
+ cases, including one bound to a parametrize column;
103
+ - passed cases that narrate different templates altogether.
104
+
105
+ Every one but the last is fixed by binding the varying part to a local and
106
+ narrating it with a t-string, keeping labels and term refs constant; varying
107
+ content belongs in the new `attachment` column. The last needs
108
+ `@scenario(..., group_parametrized=False)`, giving each case its own scenario.
109
+ - **Breaking.** `attach()` now takes a plain `str` label; a t-string label raises
110
+ `PytestGivenError` — use an f-string.
111
+ - **Breaking.** `attach()` called with no step open now raises `PytestGivenError`
112
+ instead of silently discarding the payload; move the call inside the step it
113
+ belongs to.
114
+ - **Breaking.** `activity(..., id=N)` is now `activity(..., activity_id=N)`; the
115
+ `Activity.id` field itself is unchanged.
116
+ - **Breaking.** `FileGlossary` is now a `Glossary` subclass rather than a wrapper
117
+ around one, so its `.glossary` attribute is gone — use the `FileGlossary`
118
+ itself wherever that attribute was passed.
119
+ - A glossary term placed in an activity slot its declared kind forbids now raises
120
+ `PytestGivenError` when `activity(...)` is built rather than at session finish.
121
+ - A glossary file whose table has a header and separator but no data rows now
122
+ says so, instead of reporting that no table was found.
123
+ - A `FileGlossary` whose columns are all named now skips a Markdown table that
124
+ carries none of those names, so a glossary file may hold prose tables beside
125
+ the glossary; a table carrying some of them still raises, as does any table
126
+ under an index-based column spec.
127
+ - `@scenario(activities=...)` now rejects a `str` and non-`int` members with a
128
+ `TypeError`.
129
+ - `@scenario` now returns the test function itself rather than a wrapper, so the
130
+ test keeps its own signature.
131
+
132
+ #### Plugin and run behavior
133
+
134
+ - An unknown `given_source_link` preset is now a `UsageError` raised before the
135
+ suite runs.
136
+ - The collection-time `@scenario` checks now report as a `UsageError` instead of
137
+ an `INTERNALERROR` traceback.
138
+ - The narration lint summary prints each finding's location in its own column
139
+ rather than appended to the message.
140
+ - `pytest-given` with no subcommand, and `pytest-given skills` with no
141
+ subcommand, now print that parser's usage and exit 2 instead of the root help
142
+ and exit 1.
143
+
144
+ #### Report content (all formats)
145
+
146
+ - **Breaking (JSON report).** `parameters.names` becomes `parameters.columns`
147
+ (`{id, name, kind}`), cells may hold an attachment object, placeholder parts
148
+ gain `column_id`, term-ref parts lose `param_column`, and a grouped step's
149
+ `narration.text` is the template rather than case 1's rendering.
150
+ - **Breaking (JSON report).** A step no longer carries `status` or `error`;
151
+ failure lives on the scenario and on the parameter table's cases. A consumer
152
+ reading `step.status` should read `scenario.status` instead.
153
+ - The Markdown report now shows a scenario's failure reason — the message and the
154
+ failing frame — under the scenario, and under the parameter table for each
155
+ failed case.
156
+
157
+ #### HTML report
158
+
159
+ - The browse sidebar leads with **Modules** and renders them as a collapsible
160
+ package tree whose nodes filter by path prefix; it no longer lists individual
161
+ scenarios under each group.
162
+ - The report's colors are retuned into one system — a term ref in a step or a
163
+ scenario title reads as a word under a light wash rather than a bordered pill
164
+ (the Glossary view keeps its pills), and column colors are generated per
165
+ column — and the sidebar, its filter chips and the attachment badges are
166
+ tidied along with it.
167
+ - The report opens and filters substantially faster on large suites, and its file
168
+ is smaller — a term reference now points at its glossary entry instead of
169
+ repeating the entry's definition, which takes about 18% off a term-heavy
170
+ report.
171
+ - A glossary term referenced only in a `@scenario` title now contributes an
172
+ instance to the Glossary view, where it previously counted toward the term's
173
+ scenario tally while showing no instance.
174
+
175
+ #### Bundled skills
176
+
177
+ - The authoring and reviewing skills gain the report mechanics their rules depend
178
+ on, a symptom index, a completeness audit, the full lint rule catalog, and
179
+ guidance for sparser tagging.
180
+
181
+ ### Removed
182
+
183
+ - **Breaking.** The `divergent-case-structure` lint rule; delete any
184
+ `given_lint_rules` or `given_lint_ignore` entry naming it, which would
185
+ otherwise fail config parsing.
186
+
187
+ ### Fixed
188
+
189
+ #### Authoring API
190
+
191
+ - Parametrize cases that claim different step activities now say so, instead of
192
+ reporting the more drastic "a different step structure".
193
+
194
+ - A `@given` fixture scoped wider than `function` no longer loses its step when
195
+ the first test to use it has no `@scenario`.
196
+ - `@given`/`@when`/`@then` are signature-preserving, so a decorated helper stays
197
+ callable to a type checker (was: `"StepDecorated" not callable`).
198
+ - `@given(...)` above `@pytest.fixture` now raises and names the fix, instead of
199
+ surfacing as `fixture '<name>' not found`.
200
+ - `@scenario(activities=...)` is now typed `int | Sequence[int] | None`, so a
201
+ bare `activities=2` type-checks.
202
+ - `@scenario(activities=...)` with an unknown id, or without `story=`, is
203
+ rejected at the decorator rather than at collection.
204
+ - A `-k`- or `--deselect`-narrowed run no longer fails on an authoring error in
205
+ a scenario it did not select.
206
+ - Glossary term handles are now hashable, and equal for the same term whichever
207
+ accessor produced them.
208
+ - A `when_then` step in a test without `@scenario` now points its warning at the
209
+ test rather than at pytest-given's own module.
210
+
211
+ #### Narration lint
212
+
213
+ - A `given_lint_ignore` entry beginning with a Windows drive letter
214
+ (`c:/repo/tests/t.py::test_x`) is no longer rejected as an unknown rule prefix.
215
+
216
+ - A rule configured `off` no longer runs; levels were applied only after every
217
+ rule had already produced its findings.
218
+ - A scenario tag with no ASCII alphanumerics (`tags=['日本語']`) no longer takes
219
+ the run down with a traceback from `tag-shadows-term`.
220
+
221
+ #### Report
222
+
223
+ - `pytest-given report` reports an unreadable input as an error instead of a
224
+ traceback (a directory raised `IsADirectoryError` through the console script).
225
+ - A JSON report with an out-of-range scenario `status` or term `kind` is
226
+ rejected by name, instead of crashing a renderer with a bare `KeyError`.
227
+ - A `<br>` inside an inline code span in a glossary definition renders as text
228
+ rather than as a line break.
229
+ - The Terms browse axis no longer lists a term the selected glossary does not
230
+ hold.
231
+
232
+ #### Plugin and run behavior
233
+
234
+ - A `--given-json`/`--given-html`/`--given-md` path that could not be a report
235
+ file is now refused before the suite runs, instead of a bare flag swallowing a
236
+ following test path and overwriting — or, on a failed run, deleting — it.
237
+ - `pytest-given report` now discards a stale report when the *render* fails, not
238
+ only when the write does.
239
+ - `pytest-given report --source-link` is now validated on a `--format md` run
240
+ instead of being accepted and ignored, and an unknown preset is reported under
241
+ the name the user typed.
242
+ - A `git` on PATH that cannot be executed no longer fails the run.
243
+ - A nested in-process pytest run that dies while parsing its arguments no longer
244
+ strands the outer session's captured rootdir, which silently dropped every
245
+ later step's source anchor.
246
+ - `@given`/`@when`/`@then` on an `async def` helper now records around the
247
+ awaited body, and async generator fixtures are handled too.
248
+ - The narration lint now inspects `async def` step helpers, whose bodies were
249
+ invisible to every AST rule.
250
+ - An explicit `--given-source-link=` now disables source links instead of falling
251
+ through to the `given_source_link` ini.
252
+ - A finished scenario no longer leaves its collector — and every scenario and
253
+ step it recorded — reachable from a process-global for the rest of the process.
254
+ - An error-level lint finding no longer overwrites a more specific exit code, so
255
+ an interrupted or nothing-collected run keeps reporting as one.
256
+ - A report that cannot be written into an unwritable directory now reports
257
+ through the terminal summary instead of escaping as a bare traceback.
258
+ - `pytest-given report` and `pytest-given skills install` now report a failed
259
+ write as a CLI error, and a failed `report` write discards the previous run's
260
+ report rather than leaving it to read as current.
261
+ - `pytest-given report` now reports a bad input file — missing, unparsable, or
262
+ JSON that is not a pytest-given report — as a CLI error instead of a traceback,
263
+ as does an unknown `--source-link` preset.
264
+ - A fixture that raises after its `yield` now fails the scenario it tore down,
265
+ instead of leaving it green in a report pytest counted as an error.
266
+ - An error-level narration-lint finding now counts as an error in the run's
267
+ summary line.
268
+ - Every failure building or writing a report — a suite reaching two glossaries, a
269
+ term used in incompatible slots, an unusable source-link template, an
270
+ unwritable output path — now surfaces as a terminal summary and a failing exit
271
+ code, where only grouping errors did.
272
+ - The sinks are now rendered in full before any is written, and a failure on
273
+ either side discards all of them.
274
+
275
+ #### Report content (all formats)
276
+
277
+ - A parametrized scenario whose cases all skipped now keeps its skip reason
278
+ instead of reporting `skipped` with none.
279
+ - A parameter row whose cell count disagreed with the table's columns silently
280
+ truncated in the HTML report; it now fails the same way it already did in
281
+ Markdown.
282
+
283
+ - A parametrized scenario now keeps its place in source order instead of moving
284
+ below every unparametrized one.
285
+ - A glossary term written as a code span keeps the markup inside it, so
286
+ `` `a*b*c` `` canonicalizes to `a*b*c`.
287
+ - A parameter-table cell now reads the way the step pointing at it read, carrying
288
+ the interpolation's own format spec and, under `indirect=True`, the bound test
289
+ argument.
290
+ - A `Template` narration's `text` is now what its parts render, so the report's
291
+ search box and `jq` queries match what the page displays.
292
+ - The grouped step tree now comes from the first case that *passed*, where a
293
+ skipped case 1 used to render an empty tree.
294
+ - A parametrize value that is a glossary term instance now narrates as its
295
+ display rather than the whole `Glossary` dataclass repr — in a step's
296
+ `Template` slot, in a scenario name, and in an
297
+ `Annotated[..., given(Template(...))]` parameter label.
298
+
299
+ #### HTML report
300
+
301
+ - Two test files sharing a basename across directories no longer abort the HTML
302
+ report; the scenarios' `#scenario=` slugs gain directory components instead.
303
+ - A `#view=stories`, `#view=glossary` or `#term=` link opened against a report
304
+ that has no such tab now falls back to the Scenarios view.
305
+ - The Glossary view's kind headings and their term counts now follow the search
306
+ and definition filters, and a filter matching nothing says so.
307
+ - Content reaching past a scenario card's right edge is no longer clipped: a wide
308
+ parameter table and an attachment payload scroll, a source path and a
309
+ traceback's frame location wrap, and the source link no longer overlaps the
310
+ card's last element.
311
+ - Jumping to a scenario from a story activity, or to a term's scenarios from the
312
+ Glossary tab, now clears filters that would hide the target; the filters in a
313
+ `#scenario=` deep link still win.
314
+ - Accent-colored text and the parametrize column colors now meet WCAG AA, and
315
+ term kinds stay distinguishable for red-green color blindness.
316
+ - The report is operable from the keyboard: status pills, browse-axis and
317
+ browse-tree rows, tag pills, story sidebar entries, activity and attachment
318
+ badges, and every expand/collapse chevron are now real buttons, and the view
319
+ tabs report which one is selected.
320
+ - A step pinned with `given(..., activity=N)` now covers an activity regardless
321
+ of its term count; an under-anchored activity previously still rendered as
322
+ "not coverage-tracked".
323
+
324
+ #### Bundled skills
325
+
326
+ - The bundled skills are corrected against the shipped behavior.
327
+
328
+ ## [0.1.0] - 2026-08-08
329
+
330
+ First public release.
331
+
332
+ ### Added
333
+
334
+ - `@scenario` decorator plus `given` / `when` / `then` step blocks, usable as
335
+ both context managers and decorators, including on fixtures.
336
+ - Self-contained interactive HTML report (`--given-html`), Markdown report
337
+ (`--given-md`), and JSON report (`--given-json`). The HTML bundles Alpine.js
338
+ and needs no server or external assets.
339
+ - Structured step text: plain strings, `Template` objects, and t-strings
340
+ ([PEP 750](https://peps.python.org/pep-0750/)), with parameter interpolation
341
+ rendered as highlighted values.
342
+ - `attach()` for text and JSON attachments on a step.
343
+ - Domain Storytelling support: ubiquitous-language glossaries (inline or
344
+ Markdown-backed via `FileGlossary`), Domain Stories, and story coverage.
345
+ - Narration lint (`--given-lint`) with a configurable rule catalog via
346
+ `given_lint_rules` and `given_lint_ignore`.
347
+ - `--given-source-link` with `vscode`, `cursor`, `zed`, `pycharm`, and `github`
348
+ presets for jumping from a report step to its source.
349
+ - `pytest-given` console script: `report` to re-render a saved JSON report, and
350
+ `skills install` to mirror the bundled agent skills into a project's
351
+ `.claude/skills/`.
352
+ - Bundled authoring, navigating, and reviewing skills for AI agents, shipped in
353
+ the wheel and version-matched to the plugin.
354
+
355
+ [Unreleased]: https://github.com/nwilbert/pytest-given/compare/v0.3.0...HEAD
356
+ [0.3.0]: https://github.com/nwilbert/pytest-given/compare/v0.2.0...v0.3.0
357
+ [0.2.0]: https://github.com/nwilbert/pytest-given/compare/v0.1.0...v0.2.0
358
+ [0.1.0]: https://github.com/nwilbert/pytest-given/releases/tag/v0.1.0
359
+
360
+ <!-- --8<-- [end:site] -->