docwrap 0.2.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 (131) hide show
  1. {docwrap-0.2.0 → docwrap-0.3.0}/PKG-INFO +24 -19
  2. {docwrap-0.2.0 → docwrap-0.3.0}/README.md +8 -3
  3. {docwrap-0.2.0 → docwrap-0.3.0}/_build.py +11 -0
  4. docwrap-0.3.0/plugins/agy/_docwrap/_adapter.py +5 -0
  5. docwrap-0.3.0/plugins/agy/tests/test_agy_adapter.py +50 -0
  6. {docwrap-0.2.0 → docwrap-0.3.0}/plugins/claude/.claude-plugin/plugin.json +1 -1
  7. docwrap-0.3.0/plugins/claude/_docwrap/_adapter.py +5 -0
  8. docwrap-0.3.0/plugins/claude/tests/test_claude_adapter.py +129 -0
  9. {docwrap-0.2.0 → docwrap-0.3.0}/plugins/codex/.codex-plugin/plugin.json +1 -1
  10. docwrap-0.3.0/plugins/codex/_docwrap/_adapter.py +5 -0
  11. docwrap-0.3.0/plugins/codex/tests/test_codex_adapter.py +50 -0
  12. {docwrap-0.2.0 → docwrap-0.3.0}/pyproject.toml +4 -4
  13. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/__init__.py +2 -0
  14. docwrap-0.3.0/src/docwrap/_canonical.py +56 -0
  15. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_cli.py +462 -374
  16. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_config.py +180 -108
  17. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_convert.py +22 -29
  18. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_core/__init__.py +0 -1
  19. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_core/_codeblock.py +22 -0
  20. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_core/_discover.py +74 -45
  21. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_core/_escape.py +6 -17
  22. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_core/_incremental.py +15 -11
  23. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_core/_normalize.py +6 -0
  24. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_core/_process.py +114 -96
  25. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_core/_wrap.py +84 -50
  26. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_display/__init__.py +0 -2
  27. docwrap-0.3.0/src/docwrap/_display/_color.py +48 -0
  28. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_display/_print.py +5 -4
  29. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_display/_recommend.py +6 -12
  30. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_display/_skips.py +4 -4
  31. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_display/_stats.py +6 -14
  32. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_display/_summary.py +8 -6
  33. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_exceptions.py +7 -2
  34. docwrap-0.3.0/src/docwrap/_files.py +207 -0
  35. docwrap-0.3.0/src/docwrap/_git.py +167 -0
  36. docwrap-0.3.0/src/docwrap/_git_utils.py +56 -0
  37. docwrap-0.3.0/src/docwrap/_hook.py +1210 -0
  38. docwrap-0.3.0/src/docwrap/_hook_front.py +91 -0
  39. docwrap-0.3.0/src/docwrap/_incremental_git.py +122 -0
  40. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_library.py +2 -0
  41. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_main.py +1 -1
  42. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_stats.py +90 -81
  43. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_styles/__init__.py +31 -10
  44. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_styles/_google.py +15 -10
  45. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_styles/_numpy.py +2 -2
  46. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_styles/_sphinx.py +10 -14
  47. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_types.py +5 -1
  48. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_validate.py +4 -5
  49. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/__init__.py +1 -0
  50. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_adds.py +29 -0
  51. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_blobs.py +200 -0
  52. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_config.py +30 -0
  53. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_discovery.py +296 -0
  54. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_index.py +64 -0
  55. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_listings.py +235 -0
  56. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_pathspecs.py +153 -0
  57. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_repo.py +127 -0
  58. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_run.py +97 -0
  59. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/manifest.json +27 -0
  60. docwrap-0.3.0/src/docwrap/_vendor/hookfront/__init__.py +1 -0
  61. docwrap-0.2.0/plugins/agy/_docwrap/_adapter.py → docwrap-0.3.0/src/docwrap/_vendor/hookfront/_agy.py +59 -17
  62. docwrap-0.2.0/plugins/claude/_docwrap/_adapter.py → docwrap-0.3.0/src/docwrap/_vendor/hookfront/_claude.py +34 -11
  63. docwrap-0.2.0/plugins/codex/_docwrap/_adapter.py → docwrap-0.3.0/src/docwrap/_vendor/hookfront/_codex.py +34 -10
  64. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_color.py +72 -0
  65. docwrap-0.2.0/src/docwrap/hooks/__init__.py → docwrap-0.3.0/src/docwrap/_vendor/hookfront/_contract.py +38 -34
  66. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_diff.py +238 -0
  67. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_front.py +510 -0
  68. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_hints.py +17 -0
  69. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_loader.py +101 -0
  70. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_notes.py +97 -0
  71. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_parser.py +184 -0
  72. docwrap-0.2.0/src/docwrap/_downstream.py → docwrap-0.3.0/src/docwrap/_vendor/hookfront/_streams.py +88 -28
  73. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_terminal.py +46 -0
  74. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_tool.py +36 -0
  75. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_version.py +29 -0
  76. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_writes.py +76 -0
  77. docwrap-0.3.0/src/docwrap/_vendor/hookfront/manifest.json +37 -0
  78. docwrap-0.3.0/src/docwrap/_vendor/hookshell/_classify.py +277 -0
  79. docwrap-0.3.0/src/docwrap/_vendor/hookshell/_detector.py +500 -0
  80. docwrap-0.3.0/src/docwrap/_vendor/hookshell/_forms.py +695 -0
  81. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_vendor/hookshell/_grammar.py +104 -78
  82. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_vendor/hookshell/_shell.py +5 -5
  83. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_vendor/hookshell/_shellparse.py +559 -230
  84. docwrap-0.3.0/src/docwrap/_vendor/hookshell/_source.py +130 -0
  85. docwrap-0.3.0/src/docwrap/_vendor/hookshell/manifest.json +22 -0
  86. docwrap-0.3.0/src/docwrap/_vendor/srcfiles/__init__.py +58 -0
  87. docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_errors.py +56 -0
  88. docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_keys.py +128 -0
  89. docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_labels.py +79 -0
  90. docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_paths.py +69 -0
  91. docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_recovery.py +123 -0
  92. docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_signals.py +82 -0
  93. docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_write.py +212 -0
  94. docwrap-0.3.0/src/docwrap/_vendor/srcfiles/manifest.json +22 -0
  95. docwrap-0.3.0/src/docwrap/hooks/__init__.py +60 -0
  96. docwrap-0.2.0/plugins/agy/tests/test_agy_adapter.py +0 -135
  97. docwrap-0.2.0/plugins/claude/tests/test_claude_adapter.py +0 -268
  98. docwrap-0.2.0/plugins/codex/tests/test_codex_adapter.py +0 -160
  99. docwrap-0.2.0/src/docwrap/_canonical.py +0 -66
  100. docwrap-0.2.0/src/docwrap/_color.py +0 -152
  101. docwrap-0.2.0/src/docwrap/_display/_diff.py +0 -64
  102. docwrap-0.2.0/src/docwrap/_files.py +0 -316
  103. docwrap-0.2.0/src/docwrap/_git.py +0 -734
  104. docwrap-0.2.0/src/docwrap/_git_utils.py +0 -133
  105. docwrap-0.2.0/src/docwrap/_hook.py +0 -823
  106. docwrap-0.2.0/src/docwrap/_hook_front.py +0 -405
  107. docwrap-0.2.0/src/docwrap/_hosts/__init__.py +0 -50
  108. docwrap-0.2.0/src/docwrap/_vendor/hookshell/_classify.py +0 -74
  109. docwrap-0.2.0/src/docwrap/_vendor/hookshell/_detector.py +0 -201
  110. docwrap-0.2.0/src/docwrap/_vendor/hookshell/manifest.json +0 -18
  111. {docwrap-0.2.0 → docwrap-0.3.0}/.gitignore +0 -0
  112. {docwrap-0.2.0 → docwrap-0.3.0}/LICENSE +0 -0
  113. {docwrap-0.2.0 → docwrap-0.3.0}/plugins/agy/_docwrap/__init__.py +0 -0
  114. {docwrap-0.2.0 → docwrap-0.3.0}/plugins/agy/adapter.toml +0 -0
  115. {docwrap-0.2.0 → docwrap-0.3.0}/plugins/agy/hooks.json +0 -0
  116. {docwrap-0.2.0 → docwrap-0.3.0}/plugins/agy/plugin.json +0 -0
  117. {docwrap-0.2.0 → docwrap-0.3.0}/plugins/claude/_docwrap/__init__.py +0 -0
  118. {docwrap-0.2.0 → docwrap-0.3.0}/plugins/claude/adapter.toml +0 -0
  119. {docwrap-0.2.0 → docwrap-0.3.0}/plugins/claude/hooks/hooks.json +0 -0
  120. {docwrap-0.2.0 → docwrap-0.3.0}/plugins/codex/_docwrap/__init__.py +0 -0
  121. {docwrap-0.2.0 → docwrap-0.3.0}/plugins/codex/adapter.toml +0 -0
  122. {docwrap-0.2.0 → docwrap-0.3.0}/plugins/codex/hooks/hooks.json +0 -0
  123. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/__main__.py +0 -0
  124. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_arguments.py +0 -0
  125. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_core/_parse.py +0 -0
  126. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_detect.py +0 -0
  127. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_display/_json.py +0 -0
  128. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_display/_text.py +0 -0
  129. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_vendor/__init__.py +0 -0
  130. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_vendor/hookshell/__init__.py +0 -0
  131. {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: docwrap
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Wrap Python docstrings with support for Google, NumPy, and Sphinx styles
5
5
  Project-URL: Documentation, https://github.com/dactylo/docwrap/tree/main/docs
6
6
  Project-URL: Homepage, https://github.com/dactylo/docwrap
@@ -25,12 +25,12 @@ Google, NumPy, and Sphinx/reST conventions.
25
25
 
26
26
  Run `docwrap` to see which docstrings would change across your project:
27
27
 
28
- ![docwrap summary of six Python files, with 10 of 32 docstrings requiring wrapping](https://raw.githubusercontent.com/dactylo/docwrap/v0.2.0/images/docwrap-summary.png)
28
+ ![docwrap summary of six Python files, with 10 of 32 docstrings requiring wrapping](https://raw.githubusercontent.com/dactylo/docwrap/v0.3.0/images/docwrap-summary.png)
29
29
 
30
30
  Then use `docwrap --preview` to compare the original and proposed formatting. For example, using
31
31
  the default 79-column limit:
32
32
 
33
- ![docwrap preview showing prose and an argument description before and after reflow](https://raw.githubusercontent.com/dactylo/docwrap/v0.2.0/images/docwrap-preview.png)
33
+ ![docwrap preview showing prose and an argument description before and after reflow](https://raw.githubusercontent.com/dactylo/docwrap/v0.3.0/images/docwrap-preview.png)
34
34
 
35
35
  ## Installation
36
36
 
@@ -76,17 +76,17 @@ line-length = 88
76
76
  ```
77
77
 
78
78
  Configuration is discovered from each file's `pyproject.toml`. The
79
- [wrapping policy guide](https://github.com/dactylo/docwrap/blob/v0.2.0/docs/design.md#choosing-a-wrapping-policy) explains the tradeoffs between
80
- reflowing prose and preserving authored layout. See the [CLI reference](https://github.com/dactylo/docwrap/blob/v0.2.0/docs/cli.md) for output
79
+ [wrapping policy guide](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/design.md#choosing-a-wrapping-policy) explains the tradeoffs between
80
+ reflowing prose and preserving authored layout. See the [CLI reference](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/cli.md) for output
81
81
  modes, configuration precedence, and advanced options.
82
82
 
83
83
  ## Integrations and library use
84
84
 
85
85
  A local [pre-commit](https://pre-commit.com/) hook can run docwrap when you commit. See the
86
- [pre-commit setup](https://github.com/dactylo/docwrap/blob/v0.2.0/docs/hook.md#pre-commit) for a writing hook and its report-only variant. Agent
86
+ [pre-commit setup](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/hook.md#pre-commit) for a writing hook and its report-only variant. Agent
87
87
  hooks for Claude Code, Codex, and Antigravity can run before commits or after edits. Their
88
88
  installation, host settings, commit behavior, and composition limits are in the
89
- [agent hook guide](https://github.com/dactylo/docwrap/blob/v0.2.0/docs/hook.md).
89
+ [agent hook guide](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/hook.md).
90
90
 
91
91
  For Python callers, `wrap_source()` returns rewritten source, `check()` reports whether it would
92
92
  change, and `wrap_source_report()` supplies structured results:
@@ -99,28 +99,33 @@ result = docwrap.check(source)
99
99
  wrapped = docwrap.wrap_source(source)
100
100
  ```
101
101
 
102
- See the [library reference](https://github.com/dactylo/docwrap/blob/v0.2.0/docs/library.md) for file operations, configuration, result types, and
102
+ See the [library reference](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/library.md) for file operations, configuration, result types, and
103
103
  safety skips.
104
104
 
105
105
  ## Documentation
106
106
 
107
107
  | If you want to... | Read |
108
108
  | ------------------------------------------------------ | ------------------------------------------ |
109
- | Look up commands, output, configuration, or exit codes | [CLI reference](https://github.com/dactylo/docwrap/blob/v0.2.0/docs/cli.md) |
110
- | Set up agent hooks, commit handling, or host responses | [Agent hook guide](https://github.com/dactylo/docwrap/blob/v0.2.0/docs/hook.md) |
111
- | Wrap only changed files or definitions | [Incremental mode](https://github.com/dactylo/docwrap/blob/v0.2.0/docs/incremental.md) |
112
- | Call docwrap from Python | [Library reference](https://github.com/dactylo/docwrap/blob/v0.2.0/docs/library.md) |
113
- | Choose a style and understand what docwrap preserves | [Concepts and safety](https://github.com/dactylo/docwrap/blob/v0.2.0/docs/design.md) |
114
- | Check supported environments and file behavior | [Platform compatibility](https://github.com/dactylo/docwrap/blob/v0.2.0/docs/platform.md) |
109
+ | Look up commands, output, configuration, or exit codes | [CLI reference](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/cli.md) |
110
+ | Set up agent hooks, commit handling, or host responses | [Agent hook guide](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/hook.md) |
111
+ | Wrap only changed files or definitions | [Incremental mode](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/incremental.md) |
112
+ | Call docwrap from Python | [Library reference](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/library.md) |
113
+ | Choose a style and understand what docwrap preserves | [Concepts and safety](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/design.md) |
114
+ | Check supported environments and file behavior | [Platform compatibility](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/platform.md) |
115
115
 
116
116
  ## Development
117
117
 
118
- See the [development guide](https://github.com/dactylo/docwrap/blob/v0.2.0/docs/development.md) for pinned CI dependencies and lock updates.
118
+ See the [development guide](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/development.md) for pinned CI dependencies and lock updates.
119
119
 
120
- From the checkout, run `make check` for repository checks. Run `make format` to apply the
121
- repository's formatters. The Makefile also provides `make install`, `make reinstall`, and
122
- `make test`; install targets use uv.
120
+ Run `make setup` with uv 0.13.6 to create the pinned `.venv` before project commands.
121
+ `make install` and `make reinstall` install only the CLI through uv.
122
+
123
+ ```bash
124
+ make quality # read-only quality checks
125
+ make check # quality checks and one full test run
126
+ make format # apply repository formatters
127
+ ```
123
128
 
124
129
  ## License
125
130
 
126
- docwrap is available under the [MIT License](https://github.com/dactylo/docwrap/blob/v0.2.0/LICENSE).
131
+ docwrap is available under the [MIT License](https://github.com/dactylo/docwrap/blob/v0.3.0/LICENSE).
@@ -98,9 +98,14 @@ safety skips.
98
98
 
99
99
  See the [development guide](docs/development.md) for pinned CI dependencies and lock updates.
100
100
 
101
- From the checkout, run `make check` for repository checks. Run `make format` to apply the
102
- repository's formatters. The Makefile also provides `make install`, `make reinstall`, and
103
- `make test`; install targets use uv.
101
+ Run `make setup` with uv 0.13.6 to create the pinned `.venv` before project commands.
102
+ `make install` and `make reinstall` install only the CLI through uv.
103
+
104
+ ```bash
105
+ make quality # read-only quality checks
106
+ make check # quality checks and one full test run
107
+ make format # apply repository formatters
108
+ ```
104
109
 
105
110
  ## License
106
111
 
@@ -12,6 +12,17 @@ class PluginMetadataHook(MetadataHookInterface):
12
12
 
13
13
  @override
14
14
  def update(self, metadata: dict[str, object]) -> None:
15
+ """Replace distribution entry points with the bundled plugin registrations.
16
+
17
+ Args:
18
+ metadata: Distribution metadata to update in place.
19
+
20
+ Raises:
21
+ OSError: If a plugin manifest cannot be read.
22
+ tomllib.TOMLDecodeError: If a plugin manifest is invalid TOML.
23
+ ValueError: If two plugins register the same entry-point group and
24
+ name.
25
+ """
15
26
  metadata['entry-points'] = _plugin_entry_points(Path(self.root))
16
27
 
17
28
 
@@ -0,0 +1,5 @@
1
+ """Translate Antigravity hook events for docwrap."""
2
+
3
+ from docwrap._vendor.hookfront._agy import AntigravityAdapter
4
+
5
+ __all__ = ['AntigravityAdapter']
@@ -0,0 +1,50 @@
1
+ # pyright: reportPrivateUsage=false
2
+
3
+ import json
4
+ import shlex
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ from docwrap._hook_front import _TOOL, _build_hook_parser
10
+ from docwrap._vendor.hookfront._loader import load_adapter
11
+
12
+ _ROOT = Path(__file__).resolve().parents[3]
13
+
14
+
15
+ @pytest.mark.usefixtures('registered_hosts')
16
+ def test_native_manifest_command_loads_this_adapter() -> None:
17
+ argv = shlex.split('docwrap hook --agent agy --event PreToolUse')
18
+ args = _build_hook_parser().parse_args(argv[2:])
19
+ adapter = load_adapter(_TOOL, args.agent)
20
+ assert adapter.name == 'agy'
21
+ assert args.event == 'PreToolUse'
22
+ assert adapter.requires_event
23
+
24
+
25
+ def test_native_manifest_is_unchanged() -> None:
26
+ assert json.loads((_ROOT / 'plugins' / 'agy' / 'hooks.json').read_text(encoding='utf-8')) == {
27
+ 'docwrap': {
28
+ 'PreToolUse': [
29
+ {
30
+ 'matcher': 'run_command',
31
+ 'hooks': [
32
+ {
33
+ 'type': 'command',
34
+ 'command': 'docwrap hook --agent agy --event PreToolUse',
35
+ }
36
+ ],
37
+ }
38
+ ]
39
+ }
40
+ }
41
+
42
+
43
+ def test_native_plugin_identity_and_layout() -> None:
44
+ plugin = json.loads((_ROOT / 'plugins' / 'agy' / 'plugin.json').read_text(encoding='utf-8'))
45
+ assert set(plugin) == {'$schema', 'name', 'description'}
46
+ assert plugin['$schema'] == 'https://antigravity.google/schemas/v1/plugin.json'
47
+ assert plugin['name'] == 'docwrap-agy'
48
+ assert isinstance(plugin['description'], str)
49
+ assert plugin['description'].strip()
50
+ assert not (_ROOT / 'plugins' / 'agy' / 'hooks').exists()
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docwrap",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Install the docwrap commit-time docstring wrapping hook for Claude Code.",
5
5
  "author": {
6
6
  "name": "docwrap contributors"
@@ -0,0 +1,5 @@
1
+ """Translate Claude Code hook events for docwrap."""
2
+
3
+ from docwrap._vendor.hookfront._claude import ClaudeAdapter
4
+
5
+ __all__ = ['ClaudeAdapter']
@@ -0,0 +1,129 @@
1
+ # pyright: reportPrivateUsage=false
2
+
3
+ import json
4
+ import shlex
5
+ from pathlib import Path
6
+ from typing import Literal
7
+
8
+ import pytest
9
+
10
+ from docwrap._hook_front import _TOOL, _build_hook_parser
11
+ from docwrap._hook_front import _run_hook as run_hook
12
+ from docwrap._vendor.hookfront._loader import load_adapter
13
+ from tests.helpers import decode_text, git_commit_all, run_setup
14
+
15
+ _ROOT = Path(__file__).resolve().parents[3]
16
+
17
+
18
+ class TestClaudeAdapter:
19
+ @pytest.mark.usefixtures('example_hosts')
20
+ @pytest.mark.parametrize('host', ['claude', 'example'])
21
+ @pytest.mark.parametrize(
22
+ ('command', 'outcome'),
23
+ [
24
+ pytest.param('ls', 'unrelated', id='unrelated'),
25
+ pytest.param('git commit --future-option -m x', 'denied', id='denied'),
26
+ pytest.param('git commit -m x', 'wrapped', id='supported'),
27
+ ],
28
+ )
29
+ def test_hook_decisions_preserve_adapter_independence(
30
+ self,
31
+ tmp_path: Path,
32
+ capsys: pytest.CaptureFixture[str],
33
+ host: str,
34
+ command: str,
35
+ outcome: Literal['unrelated', 'denied', 'wrapped'],
36
+ ) -> None:
37
+ run_setup(['git', 'init'], cwd=tmp_path)
38
+ run_setup(['git', 'config', 'docwrap.hook', 'true'], cwd=tmp_path)
39
+ (tmp_path / 'pyproject.toml').write_text(
40
+ '[tool.docwrap]\nline-length = 40\nfill-style = "greedy"\n', encoding='utf-8'
41
+ )
42
+ target = tmp_path / 'sample.py'
43
+ target.write_text('pass\n', encoding='utf-8')
44
+ git_commit_all(tmp_path)
45
+ source = (
46
+ 'def f():\n """Summary.\n\n'
47
+ ' one two three four five six seven eight nine ten eleven twelve\n'
48
+ ' """\n'
49
+ )
50
+ target.write_text(source, encoding='utf-8')
51
+ run_setup(['git', 'add', 'sample.py'], cwd=tmp_path)
52
+ payload = json.dumps(
53
+ _payload('PreToolUse', 'Bash', {'command': command}) | {'cwd': str(tmp_path)}
54
+ )
55
+
56
+ status = run_hook(payload, host=host)
57
+
58
+ captured = capsys.readouterr()
59
+ expected_decision = 'deny' if outcome == 'denied' else None
60
+ assert (status, _decision(captured.out)) == (0, expected_decision)
61
+ if expected_decision == 'deny':
62
+ assert 'unsupported commit command' in captured.err
63
+ else:
64
+ assert captured.err == ''
65
+ expected = (
66
+ 'def f():\n """Summary.\n\n'
67
+ ' one two three four five six seven\n'
68
+ ' eight nine ten eleven twelve\n'
69
+ ' """\n'
70
+ if outcome == 'wrapped'
71
+ else source
72
+ )
73
+ staged = run_setup(['git', 'show', ':sample.py'], cwd=tmp_path)
74
+ assert decode_text(staged.stdout) == expected
75
+ assert target.read_text(encoding='utf-8') == expected
76
+
77
+
78
+ def _decision(stdout: str) -> str | None:
79
+ if not stdout:
80
+ return None
81
+ data = json.loads(stdout)
82
+ if 'systemMessage' in data:
83
+ return None
84
+ if 'hookSpecificOutput' in data:
85
+ return str(data['hookSpecificOutput']['permissionDecision'])
86
+ return str(data['example']['decision'])
87
+
88
+
89
+ def _payload(event: str, tool: str, tool_input: dict[str, object]) -> dict[str, object]:
90
+ return {'hook_event_name': event, 'tool_name': tool, 'tool_input': tool_input, 'cwd': '/r'}
91
+
92
+
93
+ @pytest.mark.usefixtures('registered_hosts')
94
+ def test_native_manifest_command_loads_this_adapter() -> None:
95
+ argv = shlex.split('docwrap hook --agent claude')
96
+ args = _build_hook_parser().parse_args(argv[2:])
97
+ adapter = load_adapter(_TOOL, args.agent)
98
+ assert adapter.name == 'claude'
99
+ assert args.event is None
100
+ assert not adapter.requires_event
101
+
102
+
103
+ def test_native_manifest_is_unchanged() -> None:
104
+ manifest = _ROOT / 'plugins' / 'claude' / 'hooks' / 'hooks.json'
105
+ assert json.loads(manifest.read_text(encoding='utf-8')) == {
106
+ 'hooks': {
107
+ 'PreToolUse': [
108
+ {
109
+ 'matcher': '^Bash$',
110
+ 'hooks': [{'type': 'command', 'command': 'docwrap hook --agent claude'}],
111
+ }
112
+ ]
113
+ }
114
+ }
115
+
116
+
117
+ def test_native_plugin_identity_and_marketplace_source() -> None:
118
+ plugin = json.loads(
119
+ (_ROOT / 'plugins' / 'claude' / '.claude-plugin' / 'plugin.json').read_text(
120
+ encoding='utf-8'
121
+ )
122
+ )
123
+ marketplace = json.loads(
124
+ (_ROOT / '.claude-plugin' / 'marketplace.json').read_text(encoding='utf-8')
125
+ )
126
+ assert plugin['name'] == 'docwrap'
127
+ assert marketplace['plugins'][0]['source'] == './plugins/claude'
128
+ assert not (_ROOT / 'plugins' / 'claude' / 'hooks.json').exists()
129
+ assert not (_ROOT / 'plugins' / 'claude' / '.codex-plugin').exists()
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docwrap",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Install the docwrap commit-time docstring wrapping hook for Codex.",
5
5
  "author": {
6
6
  "name": "docwrap contributors"
@@ -0,0 +1,5 @@
1
+ """Translate Codex hook events for docwrap."""
2
+
3
+ from docwrap._vendor.hookfront._codex import CodexAdapter
4
+
5
+ __all__ = ['CodexAdapter']
@@ -0,0 +1,50 @@
1
+ # pyright: reportPrivateUsage=false
2
+
3
+ import json
4
+ import shlex
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ from docwrap._hook_front import _TOOL, _build_hook_parser
10
+ from docwrap._vendor.hookfront._loader import load_adapter
11
+
12
+ _ROOT = Path(__file__).resolve().parents[3]
13
+
14
+
15
+ @pytest.mark.usefixtures('registered_hosts')
16
+ def test_native_manifest_command_loads_this_adapter() -> None:
17
+ argv = shlex.split('docwrap hook --agent codex')
18
+ args = _build_hook_parser().parse_args(argv[2:])
19
+ adapter = load_adapter(_TOOL, args.agent)
20
+ assert adapter.name == 'codex'
21
+ assert args.event is None
22
+ assert not adapter.requires_event
23
+
24
+
25
+ def test_native_manifest_is_unchanged() -> None:
26
+ manifest = _ROOT / 'plugins' / 'codex' / 'hooks' / 'hooks.json'
27
+ assert json.loads(manifest.read_text(encoding='utf-8')) == {
28
+ 'hooks': {
29
+ 'PreToolUse': [
30
+ {
31
+ 'matcher': '^Bash$',
32
+ 'hooks': [{'type': 'command', 'command': 'docwrap hook --agent codex'}],
33
+ }
34
+ ]
35
+ }
36
+ }
37
+
38
+
39
+ def test_native_plugin_identity_and_marketplace_source() -> None:
40
+ plugin = json.loads(
41
+ (_ROOT / 'plugins' / 'codex' / '.codex-plugin' / 'plugin.json').read_text(encoding='utf-8')
42
+ )
43
+ marketplace = json.loads(
44
+ (_ROOT / '.agents' / 'plugins' / 'marketplace.json').read_text(encoding='utf-8')
45
+ )
46
+ assert plugin['name'] == 'docwrap'
47
+ assert 'hooks' not in plugin
48
+ assert marketplace['plugins'][0]['source'] == {'source': 'local', 'path': './plugins/codex'}
49
+ assert not (_ROOT / 'plugins' / 'codex' / 'hooks.json').exists()
50
+ assert not (_ROOT / 'plugins' / 'codex' / '.claude-plugin').exists()
@@ -10,7 +10,7 @@ requires = [ "editables~=0.3", "hatch-fancy-pypi-readme>=25.1", "hatchling>=1.27
10
10
  # ==========================================================
11
11
  [project]
12
12
  name = "docwrap"
13
- version = "0.2.0"
13
+ version = "0.3.0"
14
14
  description = "Wrap Python docstrings with support for Google, NumPy, and Sphinx styles"
15
15
  keywords = [ "docstring", "formatter", "google", "numpy", "sphinx", "wrap" ]
16
16
  license = "MIT"
@@ -44,6 +44,7 @@ dev = [
44
44
  "pyright",
45
45
  "ruff",
46
46
  "vulture",
47
+ "zizmor",
47
48
  { include-group = "test" },
48
49
  ]
49
50
  test = [
@@ -103,9 +104,6 @@ lint.ignore = [
103
104
  "T201", # print() found
104
105
  "TRY003", # Avoid specifying long messages outside exception class
105
106
  ]
106
- # Same-file private-member collaboration follows the module privacy boundary.
107
- lint.per-file-ignores."src/docwrap/_vendor/hookshell/_grammar.py" = [ "SLF001" ]
108
- lint.per-file-ignores."src/docwrap/_vendor/hookshell/_shellparse.py" = [ "SLF001" ]
109
107
  lint.per-file-ignores."{tests,plugins/*/tests}/**/*.py" = [
110
108
  "D", # No docstrings required in tests
111
109
  "PLR2004", # Allow magic values in tests
@@ -139,6 +137,8 @@ max_supported_python = "3.15"
139
137
  # ==========================================================
140
138
  [tool.pyright]
141
139
  pythonVersion = "3.13" # Keep in sync with tool.ruff.target-version
140
+ venv = ".venv" # Created by make setup
141
+ venvPath = "."
142
142
  typeCheckingMode = "strict"
143
143
  extraPaths = [ ".", "src" ]
144
144
  executionEnvironments = [
@@ -18,6 +18,7 @@ if TYPE_CHECKING:
18
18
  DocwrapConfig,
19
19
  DocwrapConfigWarning,
20
20
  DocwrapSkipWarning,
21
+ FileChangedError,
21
22
  FillStyle,
22
23
  InvalidLineLengthError,
23
24
  ReflowMode,
@@ -45,6 +46,7 @@ __all__ = [
45
46
  'DocwrapConfig',
46
47
  'DocwrapConfigWarning',
47
48
  'DocwrapSkipWarning',
49
+ 'FileChangedError',
48
50
  'FillStyle',
49
51
  'InvalidLineLengthError',
50
52
  'ReflowMode',
@@ -0,0 +1,56 @@
1
+ """Canonical section registry for docstring roles."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from docwrap._types import CanonicalRole
6
+
7
+
8
+ @dataclass(frozen=True, slots=True)
9
+ class _CanonicalSection:
10
+ """Registry entry whose tuple position determines canonical section order."""
11
+
12
+ role: CanonicalRole
13
+ google_header: str | None = None
14
+ numpy_header: str | None = None
15
+ return_like: bool = False
16
+
17
+
18
+ CANONICAL_SECTIONS: tuple[_CanonicalSection, ...] = (
19
+ _CanonicalSection('param', google_header='Args', numpy_header='Parameters'),
20
+ _CanonicalSection('keyword_param', google_header='Keyword Args'),
21
+ _CanonicalSection('other_param', numpy_header='Other Parameters'),
22
+ _CanonicalSection(
23
+ 'returns',
24
+ google_header='Returns',
25
+ numpy_header='Returns',
26
+ return_like=True,
27
+ ),
28
+ _CanonicalSection(
29
+ 'yields',
30
+ google_header='Yields',
31
+ numpy_header='Yields',
32
+ return_like=True,
33
+ ),
34
+ _CanonicalSection('raises', google_header='Raises', numpy_header='Raises'),
35
+ _CanonicalSection('warns', google_header='Warns', numpy_header='Warns'),
36
+ _CanonicalSection(
37
+ 'attributes',
38
+ google_header='Attributes',
39
+ numpy_header='Attributes',
40
+ ),
41
+ _CanonicalSection('methods', google_header='Methods', numpy_header='Methods'),
42
+ _CanonicalSection('see_also', google_header='See Also', numpy_header='See Also'),
43
+ _CanonicalSection('notes', google_header='Notes', numpy_header='Notes'),
44
+ _CanonicalSection('references', google_header='References', numpy_header='References'),
45
+ _CanonicalSection('examples', google_header='Examples', numpy_header='Examples'),
46
+ _CanonicalSection('todo', google_header='Todo', numpy_header='Todo'),
47
+ _CanonicalSection('warnings', google_header='Warnings', numpy_header='Warnings'),
48
+ )
49
+
50
+ CANONICAL_ORDER: tuple[CanonicalRole, ...] = tuple(section.role for section in CANONICAL_SECTIONS)
51
+
52
+ CANONICAL_ORDER_SET: frozenset[CanonicalRole] = frozenset(CANONICAL_ORDER)
53
+
54
+ RETURN_LIKE_ROLES: frozenset[CanonicalRole] = frozenset(
55
+ s.role for s in CANONICAL_SECTIONS if s.return_like
56
+ )