subedit 0.1.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 (101) hide show
  1. subedit-0.1.0/.claude/settings.json +15 -0
  2. subedit-0.1.0/.claude-plugin/marketplace.json +11 -0
  3. subedit-0.1.0/.claude-plugin/plugin.json +12 -0
  4. subedit-0.1.0/.github/dependabot.yml +13 -0
  5. subedit-0.1.0/.github/release.yml +24 -0
  6. subedit-0.1.0/.github/workflows/ci.yml +77 -0
  7. subedit-0.1.0/.github/workflows/pr.yml +78 -0
  8. subedit-0.1.0/.github/workflows/release.yml +83 -0
  9. subedit-0.1.0/.gitignore +20 -0
  10. subedit-0.1.0/.vale.ini +13 -0
  11. subedit-0.1.0/LICENSE +21 -0
  12. subedit-0.1.0/PKG-INFO +193 -0
  13. subedit-0.1.0/README.md +163 -0
  14. subedit-0.1.0/docs/rules.md +16 -0
  15. subedit-0.1.0/examples/README.md +47 -0
  16. subedit-0.1.0/examples/probe.py +5 -0
  17. subedit-0.1.0/examples/subedit.toml +2 -0
  18. subedit-0.1.0/examples/walkthrough.py +68 -0
  19. subedit-0.1.0/hooks/hooks.json +17 -0
  20. subedit-0.1.0/hooks/run-subedit-hook.sh +21 -0
  21. subedit-0.1.0/pyproject.toml +118 -0
  22. subedit-0.1.0/src/subedit/__init__.py +3 -0
  23. subedit-0.1.0/src/subedit/application/__init__.py +16 -0
  24. subedit-0.1.0/src/subedit/application/corpus_meter.py +87 -0
  25. subedit-0.1.0/src/subedit/application/fixer.py +68 -0
  26. subedit-0.1.0/src/subedit/application/linter.py +55 -0
  27. subedit-0.1.0/src/subedit/application/report_aggregator.py +23 -0
  28. subedit-0.1.0/src/subedit/domain/__init__.py +63 -0
  29. subedit-0.1.0/src/subedit/domain/models/__init__.py +36 -0
  30. subedit-0.1.0/src/subedit/domain/models/comment.py +168 -0
  31. subedit-0.1.0/src/subedit/domain/models/diagnostic.py +123 -0
  32. subedit-0.1.0/src/subedit/domain/models/document.py +23 -0
  33. subedit-0.1.0/src/subedit/domain/models/position.py +108 -0
  34. subedit-0.1.0/src/subedit/domain/models/prose.py +215 -0
  35. subedit-0.1.0/src/subedit/domain/models/rule_set.py +30 -0
  36. subedit-0.1.0/src/subedit/domain/models/text.py +23 -0
  37. subedit-0.1.0/src/subedit/domain/ports/__init__.py +14 -0
  38. subedit-0.1.0/src/subedit/domain/ports/external_linter.py +14 -0
  39. subedit-0.1.0/src/subedit/domain/ports/language_conventions.py +25 -0
  40. subedit-0.1.0/src/subedit/domain/ports/rule.py +28 -0
  41. subedit-0.1.0/src/subedit/domain/ports/source_parser.py +12 -0
  42. subedit-0.1.0/src/subedit/domain/rules/__init__.py +15 -0
  43. subedit-0.1.0/src/subedit/domain/rules/layout.py +57 -0
  44. subedit-0.1.0/src/subedit/domain/rules/rewrite.py +83 -0
  45. subedit-0.1.0/src/subedit/domain/rules/structure.py +141 -0
  46. subedit-0.1.0/src/subedit/infrastructure/__init__.py +47 -0
  47. subedit-0.1.0/src/subedit/infrastructure/config_sources/__init__.py +11 -0
  48. subedit-0.1.0/src/subedit/infrastructure/config_sources/config_schema.py +43 -0
  49. subedit-0.1.0/src/subedit/infrastructure/config_sources/toml_config_source.py +102 -0
  50. subedit-0.1.0/src/subedit/infrastructure/linters/__init__.py +5 -0
  51. subedit-0.1.0/src/subedit/infrastructure/linters/autocorrect_linter.py +71 -0
  52. subedit-0.1.0/src/subedit/infrastructure/linters/vale_linter.py +87 -0
  53. subedit-0.1.0/src/subedit/infrastructure/parsers/__init__.py +12 -0
  54. subedit-0.1.0/src/subedit/infrastructure/parsers/languages/__init__.py +11 -0
  55. subedit-0.1.0/src/subedit/infrastructure/parsers/languages/language_profile.py +26 -0
  56. subedit-0.1.0/src/subedit/infrastructure/parsers/languages/python_profile.py +71 -0
  57. subedit-0.1.0/src/subedit/infrastructure/parsers/tree_sitter_parser.py +219 -0
  58. subedit-0.1.0/src/subedit/presentation/__init__.py +0 -0
  59. subedit-0.1.0/src/subedit/presentation/cli.py +193 -0
  60. subedit-0.1.0/src/subedit/presentation/harnesses/__init__.py +40 -0
  61. subedit-0.1.0/src/subedit/presentation/harnesses/agent_harness.py +43 -0
  62. subedit-0.1.0/src/subedit/presentation/harnesses/claude_code_harness.py +37 -0
  63. subedit-0.1.0/src/subedit/presentation/harnesses/codex_harness.py +43 -0
  64. subedit-0.1.0/src/subedit/presentation/hook.py +108 -0
  65. subedit-0.1.0/src/subedit/presentation/report.py +79 -0
  66. subedit-0.1.0/src/subedit/presentation/rule_reference.py +21 -0
  67. subedit-0.1.0/styles/mroops/EditHistory.yml +8 -0
  68. subedit-0.1.0/styles/mroops/Ellipsis.yml +9 -0
  69. subedit-0.1.0/styles/mroops/EmDash.yml +6 -0
  70. subedit-0.1.0/styles/mroops/ExternalRef.yml +7 -0
  71. subedit-0.1.0/styles/mroops/MidSentenceColon.yml +6 -0
  72. subedit-0.1.0/styles/mroops/MidSentenceColonMarkup.yml +7 -0
  73. subedit-0.1.0/styles/mroops/Semicolon.yml +6 -0
  74. subedit-0.1.0/styles/mroops/SignatureTag.yml +7 -0
  75. subedit-0.1.0/subedit.toml +13 -0
  76. subedit-0.1.0/tests/fixtures/vale/clean.md +8 -0
  77. subedit-0.1.0/tests/fixtures/vale/violations.py +13 -0
  78. subedit-0.1.0/tests/test_cli.py +156 -0
  79. subedit-0.1.0/tests/test_comment.py +147 -0
  80. subedit-0.1.0/tests/test_config.py +100 -0
  81. subedit-0.1.0/tests/test_corpus_meter.py +78 -0
  82. subedit-0.1.0/tests/test_diagnostic.py +101 -0
  83. subedit-0.1.0/tests/test_fixer.py +159 -0
  84. subedit-0.1.0/tests/test_harnesses.py +107 -0
  85. subedit-0.1.0/tests/test_hook.py +157 -0
  86. subedit-0.1.0/tests/test_linter.py +140 -0
  87. subedit-0.1.0/tests/test_parser.py +268 -0
  88. subedit-0.1.0/tests/test_parser_ocp.py +104 -0
  89. subedit-0.1.0/tests/test_parser_oracle.py +92 -0
  90. subedit-0.1.0/tests/test_plugin.py +78 -0
  91. subedit-0.1.0/tests/test_position.py +138 -0
  92. subedit-0.1.0/tests/test_prose.py +181 -0
  93. subedit-0.1.0/tests/test_report_aggregator.py +119 -0
  94. subedit-0.1.0/tests/test_rules_layout.py +136 -0
  95. subedit-0.1.0/tests/test_rules_rewrite.py +99 -0
  96. subedit-0.1.0/tests/test_rules_structure.py +119 -0
  97. subedit-0.1.0/tests/test_schema.py +84 -0
  98. subedit-0.1.0/tests/test_self_hosting.py +121 -0
  99. subedit-0.1.0/tests/test_text.py +61 -0
  100. subedit-0.1.0/tests/test_vale_style.py +49 -0
  101. subedit-0.1.0/uv.lock +418 -0
@@ -0,0 +1,15 @@
1
+ {
2
+ "hooks": {
3
+ "PostToolUse": [
4
+ {
5
+ "matcher": "Edit|Write",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "uv run --project \"$CLAUDE_PROJECT_DIR\" subedit-hook"
10
+ }
11
+ ]
12
+ }
13
+ ]
14
+ }
15
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "mroops",
3
+ "owner": { "name": "mroops0111" },
4
+ "plugins": [
5
+ {
6
+ "name": "subedit",
7
+ "source": { "source": "github", "repo": "mroops0111/subedit" },
8
+ "description": "Repairs comment layout silently on write, and returns only what needs rewriting."
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "subedit",
3
+ "displayName": "subedit",
4
+ "version": "0.1.0",
5
+ "description": "Repairs comment layout silently on write, and returns only what needs rewriting.",
6
+ "author": {
7
+ "name": "mroops0111"
8
+ },
9
+ "homepage": "https://github.com/mroops0111/subedit",
10
+ "repository": "https://github.com/mroops0111/subedit",
11
+ "license": "MIT"
12
+ }
@@ -0,0 +1,13 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "uv"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ groups:
8
+ minor-and-patch:
9
+ patterns:
10
+ - "*"
11
+ update-types:
12
+ - "minor"
13
+ - "patch"
@@ -0,0 +1,24 @@
1
+ changelog:
2
+ exclude:
3
+ labels:
4
+ - ignore-for-release
5
+ authors:
6
+ - dependabot
7
+ categories:
8
+ - title: Breaking Changes
9
+ labels:
10
+ - breaking
11
+ - title: Features
12
+ labels:
13
+ - feature
14
+ - enhancement
15
+ - title: Bug Fixes
16
+ labels:
17
+ - bug
18
+ - fix
19
+ - title: Documentation
20
+ labels:
21
+ - documentation
22
+ - title: Other Changes
23
+ labels:
24
+ - "*"
@@ -0,0 +1,77 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+ workflow_dispatch:
9
+ workflow_call:
10
+
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }}
13
+ cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
14
+
15
+ jobs:
16
+ lint:
17
+ name: Lint & Format
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - uses: astral-sh/setup-uv@v6
22
+ with:
23
+ enable-cache: true
24
+ - run: uv sync --extra dev
25
+ - run: uv run ruff check src/ tests/
26
+ - run: uv run ruff format --check src/ tests/
27
+
28
+ typecheck:
29
+ name: Type Check
30
+ runs-on: ubuntu-latest
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ - uses: astral-sh/setup-uv@v6
34
+ with:
35
+ enable-cache: true
36
+ - run: uv sync --extra dev
37
+ - run: uv run basedpyright src/ tests/
38
+
39
+ test:
40
+ name: Test (Python ${{ matrix.python-version }})
41
+ runs-on: ubuntu-latest
42
+ strategy:
43
+ fail-fast: false
44
+ matrix:
45
+ python-version: ["3.11", "3.12", "3.13"]
46
+ steps:
47
+ - uses: actions/checkout@v4
48
+ - uses: astral-sh/setup-uv@v6
49
+ with:
50
+ python-version: ${{ matrix.python-version }}
51
+ enable-cache: true
52
+ - run: uv sync --extra dev
53
+ - run: uv run pytest tests/ -v --tb=short
54
+
55
+ - name: Self-hosting
56
+ run: uv run subedit check src/ tests/
57
+
58
+ prose:
59
+ name: Prose
60
+ runs-on: ubuntu-latest
61
+ steps:
62
+ - uses: actions/checkout@v4
63
+ - uses: errata-ai/vale-action@reviewdog
64
+ with:
65
+ version: 3.18.0
66
+ files: src tests README.md
67
+ fail_on_error: true
68
+ reporter: github-check
69
+
70
+ build:
71
+ name: Build
72
+ runs-on: ubuntu-latest
73
+ needs: [lint, typecheck, test, prose]
74
+ steps:
75
+ - uses: actions/checkout@v4
76
+ - uses: astral-sh/setup-uv@v6
77
+ - run: uv build
@@ -0,0 +1,78 @@
1
+ name: PR
2
+
3
+ on:
4
+ pull_request_target:
5
+ types: [opened, edited, synchronize, reopened]
6
+
7
+ permissions:
8
+ pull-requests: write
9
+ issues: write
10
+
11
+ jobs:
12
+ label:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/github-script@v7
16
+ with:
17
+ script: |
18
+ const title = context.payload.pull_request.title || '';
19
+ const match = title.match(/^([a-zA-Z]+)(\([^)]*\))?(!)?:/);
20
+ if (!match) {
21
+ core.info(`Title "${title}" has no conventional prefix, skipping`);
22
+ return;
23
+ }
24
+ const [, type, , bang] = match;
25
+ const labels = new Set();
26
+ if (bang) labels.add('breaking');
27
+ switch (type.toLowerCase()) {
28
+ case 'feat':
29
+ case 'feature':
30
+ labels.add('feature');
31
+ break;
32
+ case 'fix':
33
+ labels.add('bug');
34
+ break;
35
+ case 'docs':
36
+ labels.add('documentation');
37
+ break;
38
+ case 'enhance':
39
+ case 'enhancement':
40
+ labels.add('enhancement');
41
+ break;
42
+ }
43
+ if (labels.size === 0) {
44
+ core.info(`Type "${type}" has no label mapping`);
45
+ return;
46
+ }
47
+ const colors = {
48
+ breaking: 'B60205',
49
+ feature: '0E8A16',
50
+ bug: 'D73A4A',
51
+ documentation: '0075CA',
52
+ enhancement: 'A2EEEF',
53
+ };
54
+ for (const name of labels) {
55
+ try {
56
+ await github.rest.issues.getLabel({
57
+ ...context.repo,
58
+ name,
59
+ });
60
+ } catch (e) {
61
+ if (e.status === 404) {
62
+ await github.rest.issues.createLabel({
63
+ ...context.repo,
64
+ name,
65
+ color: colors[name] || 'EDEDED',
66
+ });
67
+ core.info(`Created missing label "${name}"`);
68
+ } else {
69
+ throw e;
70
+ }
71
+ }
72
+ }
73
+ await github.rest.issues.addLabels({
74
+ ...context.repo,
75
+ issue_number: context.payload.pull_request.number,
76
+ labels: [...labels],
77
+ });
78
+ core.info(`Applied labels: ${[...labels].join(', ')}`);
@@ -0,0 +1,83 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ build:
13
+ name: Build
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Verify tag matches pyproject.toml version
19
+ run: |
20
+ TAG="${GITHUB_REF_NAME#v}"
21
+ VERSION=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
22
+ if [ "$TAG" != "$VERSION" ]; then
23
+ echo "::error::Tag v$TAG does not match pyproject.toml version $VERSION"
24
+ exit 1
25
+ fi
26
+
27
+ - uses: astral-sh/setup-uv@v6
28
+ with:
29
+ enable-cache: true
30
+
31
+ - run: uv build
32
+
33
+ - uses: actions/upload-artifact@v4
34
+ with:
35
+ name: dist
36
+ path: dist/
37
+
38
+ publish-pypi:
39
+ name: Publish
40
+ needs: build
41
+ runs-on: ubuntu-latest
42
+ environment:
43
+ name: pypi
44
+ url: https://pypi.org/p/subedit
45
+ permissions:
46
+ id-token: write
47
+ attestations: write
48
+ contents: read
49
+ steps:
50
+ - uses: actions/download-artifact@v4
51
+ with:
52
+ name: dist
53
+ path: dist/
54
+
55
+ - uses: actions/attest-build-provenance@v2
56
+ with:
57
+ subject-path: 'dist/*'
58
+
59
+ - uses: pypa/gh-action-pypi-publish@release/v1
60
+
61
+ github-release:
62
+ name: GitHub Release
63
+ needs: publish-pypi
64
+ runs-on: ubuntu-latest
65
+ permissions:
66
+ contents: write
67
+ steps:
68
+ - uses: actions/checkout@v4
69
+
70
+ - uses: actions/download-artifact@v4
71
+ with:
72
+ name: dist
73
+ path: dist/
74
+
75
+ - name: Create GitHub Release
76
+ env:
77
+ GH_TOKEN: ${{ github.token }}
78
+ run: |
79
+ gh release create "${GITHUB_REF_NAME}" \
80
+ --repo "${GITHUB_REPOSITORY}" \
81
+ --title "${GITHUB_REF_NAME}" \
82
+ --generate-notes \
83
+ dist/*
@@ -0,0 +1,20 @@
1
+ # Design notes stay local. The generated rule reference is tracked.
2
+ docs/*
3
+ !docs/rules.md
4
+
5
+ # Python
6
+ __pycache__/
7
+ *.py[cod]
8
+ *.egg-info/
9
+ .venv/
10
+ dist/
11
+ build/
12
+
13
+ # Tooling caches
14
+ .ruff_cache/
15
+ .pytest_cache/
16
+ .coverage
17
+ htmlcov/
18
+
19
+ # macOS
20
+ .DS_Store
@@ -0,0 +1,13 @@
1
+ StylesPath = styles
2
+ MinAlertLevel = error
3
+
4
+ [*.{py,ts,tsx,js,go,rs}]
5
+ BasedOnStyles = mroops
6
+ mroops.MidSentenceColonMarkup = NO
7
+
8
+ [*.md]
9
+ BasedOnStyles = mroops
10
+ mroops.MidSentenceColon = NO
11
+
12
+ [tests/fixtures/vale/*]
13
+ BasedOnStyles =
subedit-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mroops0111
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
subedit-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.5
2
+ Name: subedit
3
+ Version: 0.1.0
4
+ Summary: A linter and formatter for the prose inside code comments, across languages.
5
+ Project-URL: Homepage, https://github.com/mroops0111/subedit
6
+ Project-URL: Repository, https://github.com/mroops0111/subedit
7
+ Project-URL: Issues, https://github.com/mroops0111/subedit/issues
8
+ Author: Yun-Tai Yang
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: comments,formatter,linter,lsp,prose,tree-sitter
12
+ Classifier: Development Status :: 1 - Planning
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Quality Assurance
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: pydantic>=2.13.5
22
+ Requires-Dist: tree-sitter-language-pack>=1.17.0
23
+ Requires-Dist: tree-sitter>=0.26.0
24
+ Requires-Dist: typer>=0.27.2
25
+ Provides-Extra: dev
26
+ Requires-Dist: basedpyright>=1.40.1; extra == 'dev'
27
+ Requires-Dist: pytest>=9.0.0; extra == 'dev'
28
+ Requires-Dist: ruff>=0.16.7; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # subedit
32
+
33
+ Your house style, enforced in the places prose hides inside a codebase.
34
+
35
+ A subeditor is the person at a publication who makes every piece of copy match its house style. `subedit` does that job for the prose in your code.
36
+
37
+ Linters check your code. Almost nothing checks the sentences you write around it. `subedit` reads the comments and docstrings in your source, lays them out one sentence per line, and only tells you about the sentences a tool cannot fix for you.
38
+
39
+ ## Example
40
+
41
+ An editor wraps a comment at whatever column it reaches, so sentences break mid clause.
42
+
43
+ ```python
44
+ # Cap the buffer at the runner limit, because an unbounded
45
+ # read faults the job. Retry once, then give up.
46
+ ```
47
+
48
+ `subedit fix --write` repairs the layout and says nothing.
49
+
50
+ ```python
51
+ # Cap the buffer at the runner limit, because an unbounded read faults the job.
52
+ # Retry once, then give up.
53
+ ```
54
+
55
+ A sentence that runs past the width with no comma has nowhere legal to break, and only its author knows where the meaning divides. That one comes back to you, or to the agent that wrote it.
56
+
57
+ ```text
58
+ 1 comment finding needs rewriting in src/cache.py.
59
+ Each states the fix. Apply it in place, do not add a suppression comment.
60
+
61
+ line 2 [unbreakable-sentence]
62
+ This sentence runs to 116 characters against a budget of 114 characters and contains no comma, so there is no legal place to break it. Add a comma at a clause boundary, or split it into two sentences.
63
+ ```
64
+
65
+ ## Installation
66
+
67
+ Install the command line tool from PyPI.
68
+
69
+ ```bash
70
+ uv tool install subedit
71
+ ```
72
+
73
+ `pipx install subedit` and `pip install subedit` work as well.
74
+
75
+ ## Commands
76
+
77
+ Every command takes files or directories.
78
+
79
+ | Command | Result |
80
+ | --- | --- |
81
+ | `subedit check src/` | every finding |
82
+ | `subedit check src/ --output=actionable` | only the sentences that need rewriting |
83
+ | `subedit fix src/` | the repairs, as a diff |
84
+ | `subedit fix src/ --write` | the repairs, applied |
85
+ | `subedit stats src/` | comment shapes across the codebase, for setting thresholds from data |
86
+ | `subedit rules` | every rule and who resolves it |
87
+
88
+ `check` and `fix` exit 1 while anything needs an author, so they drop into pre-commit or CI as they are.
89
+
90
+ ## Fix Kinds
91
+
92
+ Every finding says who can resolve it, and the whole design turns on that answer.
93
+
94
+ | Kind | Resolved by | Reaches you |
95
+ | --- | --- | --- |
96
+ | `TARGETED` | the tool, silently | no |
97
+ | `REFLOW` | the tool, silently | no |
98
+ | `REWRITE` | only the author | yes |
99
+
100
+ Rewriting a sentence needs its meaning, which a deterministic process cannot supply. Everything else is repaired without a word, because each message costs the reader attention.
101
+
102
+ ## Agent Hook
103
+
104
+ `subedit-hook` runs after every file an agent edits. It repairs the mechanical findings in place, and exits 2 only when a sentence needs rewriting, which hands the message back to the model.
105
+
106
+ ### Claude Code
107
+
108
+ The plugin registers the hook. It cannot install the tool, so install that first.
109
+
110
+ ```text
111
+ /plugin marketplace add mroops0111/subedit
112
+ /plugin install subedit@mroops
113
+ ```
114
+
115
+ To register it by hand instead, add this to `.claude/settings.json`.
116
+
117
+ ```json
118
+ {
119
+ "hooks": {
120
+ "PostToolUse": [
121
+ {
122
+ "matcher": "Edit|Write|MultiEdit|NotebookEdit",
123
+ "hooks": [{ "type": "command", "command": "subedit-hook" }]
124
+ }
125
+ ]
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### Codex
131
+
132
+ Add this to `~/.codex/hooks.json`.
133
+
134
+ ```json
135
+ {
136
+ "hooks": {
137
+ "PostToolUse": [{ "command": "subedit-hook" }]
138
+ }
139
+ }
140
+ ```
141
+
142
+ ### Several Agents
143
+
144
+ Registering the hook with both agents is the normal case. Each agent names its edit tools differently, so every payload identifies its sender, and a payload from anything else exits 0 untouched. `subedit-hook --help` prints the payload shapes it accepts.
145
+
146
+ ## Configuration
147
+
148
+ Settings come from `subedit.toml`, or from `[tool.subedit]` in `pyproject.toml`, whichever sits closest above the file being checked.
149
+
150
+ ```toml
151
+ [subedit]
152
+ line-width = 120
153
+ exclude = ["tests/fixtures/**"]
154
+
155
+ [rules]
156
+ mid-clause-break = "error"
157
+ block-too-long = { severity = "warning", line = 3, doc-public = 20 }
158
+ ```
159
+
160
+ Rule names describe the problem they report. `docs/rules.md` lists every rule, generated from the rules themselves.
161
+
162
+ ## Delegated Tools
163
+
164
+ `subedit check` also runs two tools where they are installed, and folds their findings into its own report.
165
+
166
+ - **Vale**: punctuation, banned phrasing, and anything a markup aware prose linter already handles
167
+ - **AutoCorrect**: spacing and punctuation width between CJK and Latin text
168
+
169
+ `--no-delegate` skips both. `fix` and the agent hook do not run them.
170
+
171
+ ## Surfaces
172
+
173
+ Code comments are the first surface, and Markdown comes next.
174
+
175
+ | Surface | Status | Mostly enforces |
176
+ | --- | --- | --- |
177
+ | Code comments and docstrings | working | layout, since comments sit inside a column limit |
178
+ | Markdown | planned | structure, since soft wrapped text barely needs layout |
179
+ | Markdown slides | planned | Markdown, with front matter left as it is |
180
+
181
+ ## Related Tools
182
+
183
+ `subedit` covers the one thing the usual Python tools leave alone.
184
+
185
+ | Tool | Checks |
186
+ | --- | --- |
187
+ | ruff | code structure |
188
+ | basedpyright | types |
189
+ | **subedit** | **the natural language inside source** |
190
+
191
+ ## License
192
+
193
+ MIT