subedit 0.3.1__tar.gz → 0.3.2__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 (113) hide show
  1. {subedit-0.3.1 → subedit-0.3.2}/PKG-INFO +3 -1
  2. {subedit-0.3.1 → subedit-0.3.2}/README.md +2 -0
  3. {subedit-0.3.1 → subedit-0.3.2}/plugins/subedit/.claude-plugin/plugin.json +1 -1
  4. {subedit-0.3.1 → subedit-0.3.2}/plugins/subedit/scripts/run-subedit-hook.sh +23 -1
  5. {subedit-0.3.1 → subedit-0.3.2}/pyproject.toml +1 -1
  6. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/__init__.py +1 -1
  7. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/infrastructure/config_sources/toml_config_source.py +4 -0
  8. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/presentation/hook.py +2 -1
  9. {subedit-0.3.1 → subedit-0.3.2}/tests/test_hook.py +19 -0
  10. {subedit-0.3.1 → subedit-0.3.2}/tests/test_plugin.py +32 -4
  11. {subedit-0.3.1 → subedit-0.3.2}/uv.lock +1 -1
  12. {subedit-0.3.1 → subedit-0.3.2}/.claude/settings.json +0 -0
  13. {subedit-0.3.1 → subedit-0.3.2}/.claude-plugin/marketplace.json +0 -0
  14. {subedit-0.3.1 → subedit-0.3.2}/.github/dependabot.yml +0 -0
  15. {subedit-0.3.1 → subedit-0.3.2}/.github/release.yml +0 -0
  16. {subedit-0.3.1 → subedit-0.3.2}/.github/workflows/ci.yml +0 -0
  17. {subedit-0.3.1 → subedit-0.3.2}/.github/workflows/pr.yml +0 -0
  18. {subedit-0.3.1 → subedit-0.3.2}/.github/workflows/release.yml +0 -0
  19. {subedit-0.3.1 → subedit-0.3.2}/.gitignore +0 -0
  20. {subedit-0.3.1 → subedit-0.3.2}/.vale.ini +0 -0
  21. {subedit-0.3.1 → subedit-0.3.2}/LICENSE +0 -0
  22. {subedit-0.3.1 → subedit-0.3.2}/docs/rules.md +0 -0
  23. {subedit-0.3.1 → subedit-0.3.2}/examples/README.md +0 -0
  24. {subedit-0.3.1 → subedit-0.3.2}/examples/probe.py +0 -0
  25. {subedit-0.3.1 → subedit-0.3.2}/examples/subedit.toml +0 -0
  26. {subedit-0.3.1 → subedit-0.3.2}/examples/walkthrough.py +0 -0
  27. {subedit-0.3.1 → subedit-0.3.2}/plugins/subedit/hooks/hooks.json +0 -0
  28. {subedit-0.3.1 → subedit-0.3.2}/plugins/subedit/skills/judging-findings/SKILL.md +0 -0
  29. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/application/__init__.py +0 -0
  30. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/application/corpus_meter.py +0 -0
  31. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/application/fixer.py +0 -0
  32. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/application/linter.py +0 -0
  33. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/application/report_aggregator.py +0 -0
  34. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/__init__.py +0 -0
  35. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/models/__init__.py +0 -0
  36. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/models/comment.py +0 -0
  37. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/models/comment_layout.py +0 -0
  38. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/models/diagnostic.py +0 -0
  39. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/models/document.py +0 -0
  40. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/models/position.py +0 -0
  41. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/models/prose.py +0 -0
  42. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/models/prose_map.py +0 -0
  43. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/models/rule_set.py +0 -0
  44. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/models/text.py +0 -0
  45. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/ports/__init__.py +0 -0
  46. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/ports/external_linter.py +0 -0
  47. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/ports/language_conventions.py +0 -0
  48. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/ports/rule.py +0 -0
  49. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/ports/source_parser.py +0 -0
  50. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/rules/__init__.py +0 -0
  51. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/rules/layout.py +0 -0
  52. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/rules/rewrite.py +0 -0
  53. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/domain/rules/structure.py +0 -0
  54. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/infrastructure/__init__.py +0 -0
  55. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/infrastructure/config_sources/__init__.py +0 -0
  56. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/infrastructure/config_sources/config_schema.py +0 -0
  57. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/infrastructure/config_sources/formatter_width.py +0 -0
  58. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/infrastructure/linters/__init__.py +0 -0
  59. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/infrastructure/linters/autocorrect_linter.py +0 -0
  60. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/infrastructure/linters/source_path.py +0 -0
  61. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/infrastructure/linters/vale_linter.py +0 -0
  62. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/infrastructure/parsers/__init__.py +0 -0
  63. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/infrastructure/parsers/languages/__init__.py +0 -0
  64. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/infrastructure/parsers/languages/code_line.py +0 -0
  65. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/infrastructure/parsers/languages/language_profile.py +0 -0
  66. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/infrastructure/parsers/languages/python_profile.py +0 -0
  67. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/infrastructure/parsers/languages/typescript_profile.py +0 -0
  68. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/infrastructure/parsers/tree_sitter_parser.py +0 -0
  69. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/presentation/__init__.py +0 -0
  70. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/presentation/cli.py +0 -0
  71. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/presentation/harnesses/__init__.py +0 -0
  72. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/presentation/harnesses/agent_harness.py +0 -0
  73. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/presentation/harnesses/claude_code_harness.py +0 -0
  74. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/presentation/harnesses/codex_harness.py +0 -0
  75. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/presentation/report.py +0 -0
  76. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/presentation/rule_reference.py +0 -0
  77. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/vale/styles/subedit/DateRef.yml +0 -0
  78. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/vale/styles/subedit/EditHistory.yml +0 -0
  79. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/vale/styles/subedit/Ellipsis.yml +0 -0
  80. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/vale/styles/subedit/EmDash.yml +0 -0
  81. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/vale/styles/subedit/ExternalRef.yml +0 -0
  82. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/vale/styles/subedit/MidSentenceColon.yml +0 -0
  83. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/vale/styles/subedit/MidSentenceColonMarkup.yml +0 -0
  84. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/vale/styles/subedit/Semicolon.yml +0 -0
  85. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/vale/styles/subedit/SignatureTag.yml +0 -0
  86. {subedit-0.3.1 → subedit-0.3.2}/src/subedit/vale/vale.ini +0 -0
  87. {subedit-0.3.1 → subedit-0.3.2}/subedit.toml +0 -0
  88. {subedit-0.3.1 → subedit-0.3.2}/tests/fixtures/autocorrect/unspaced.py +0 -0
  89. {subedit-0.3.1 → subedit-0.3.2}/tests/fixtures/vale/clean.md +0 -0
  90. {subedit-0.3.1 → subedit-0.3.2}/tests/fixtures/vale/violations.py +0 -0
  91. {subedit-0.3.1 → subedit-0.3.2}/tests/test_cli.py +0 -0
  92. {subedit-0.3.1 → subedit-0.3.2}/tests/test_comment.py +0 -0
  93. {subedit-0.3.1 → subedit-0.3.2}/tests/test_config.py +0 -0
  94. {subedit-0.3.1 → subedit-0.3.2}/tests/test_corpus_meter.py +0 -0
  95. {subedit-0.3.1 → subedit-0.3.2}/tests/test_diagnostic.py +0 -0
  96. {subedit-0.3.1 → subedit-0.3.2}/tests/test_fixer.py +0 -0
  97. {subedit-0.3.1 → subedit-0.3.2}/tests/test_harnesses.py +0 -0
  98. {subedit-0.3.1 → subedit-0.3.2}/tests/test_language_parity.py +0 -0
  99. {subedit-0.3.1 → subedit-0.3.2}/tests/test_linter.py +0 -0
  100. {subedit-0.3.1 → subedit-0.3.2}/tests/test_parser.py +0 -0
  101. {subedit-0.3.1 → subedit-0.3.2}/tests/test_parser_ocp.py +0 -0
  102. {subedit-0.3.1 → subedit-0.3.2}/tests/test_parser_oracle.py +0 -0
  103. {subedit-0.3.1 → subedit-0.3.2}/tests/test_position.py +0 -0
  104. {subedit-0.3.1 → subedit-0.3.2}/tests/test_prose.py +0 -0
  105. {subedit-0.3.1 → subedit-0.3.2}/tests/test_report_aggregator.py +0 -0
  106. {subedit-0.3.1 → subedit-0.3.2}/tests/test_rules_layout.py +0 -0
  107. {subedit-0.3.1 → subedit-0.3.2}/tests/test_rules_rewrite.py +0 -0
  108. {subedit-0.3.1 → subedit-0.3.2}/tests/test_rules_structure.py +0 -0
  109. {subedit-0.3.1 → subedit-0.3.2}/tests/test_schema.py +0 -0
  110. {subedit-0.3.1 → subedit-0.3.2}/tests/test_self_hosting.py +0 -0
  111. {subedit-0.3.1 → subedit-0.3.2}/tests/test_text.py +0 -0
  112. {subedit-0.3.1 → subedit-0.3.2}/tests/test_typescript.py +0 -0
  113. {subedit-0.3.1 → subedit-0.3.2}/tests/test_vale_style.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: subedit
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: A linter and formatter for the prose inside code comments, across languages.
5
5
  Project-URL: Homepage, https://github.com/mroops0111/subedit
6
6
  Project-URL: Repository, https://github.com/mroops0111/subedit
@@ -109,6 +109,8 @@ Rewriting a sentence needs its meaning, which a deterministic process cannot sup
109
109
 
110
110
  `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. In Claude Code a warning reaches the model too, as context beside the tool result, without blocking the edit.
111
111
 
112
+ The hook acts only in a project that opts in, with a `subedit.toml` or a `[tool.subedit]` table in `pyproject.toml`, in the edited file's directory or a parent. An empty table is enough. Elsewhere it exits 0 and changes nothing, so a plugin installed for every session stays out of projects that never asked for it. `check` and `fix` run anywhere, with the defaults when no config is found.
113
+
112
114
  ### Claude Code
113
115
 
114
116
  The plugin needs only [uv](https://docs.astral.sh/uv/). It runs subedit from PyPI with the `vale` extra, and warms that environment in the background when a session starts, so no edit waits on the first download.
@@ -75,6 +75,8 @@ Rewriting a sentence needs its meaning, which a deterministic process cannot sup
75
75
 
76
76
  `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. In Claude Code a warning reaches the model too, as context beside the tool result, without blocking the edit.
77
77
 
78
+ The hook acts only in a project that opts in, with a `subedit.toml` or a `[tool.subedit]` table in `pyproject.toml`, in the edited file's directory or a parent. An empty table is enough. Elsewhere it exits 0 and changes nothing, so a plugin installed for every session stays out of projects that never asked for it. `check` and `fix` run anywhere, with the defaults when no config is found.
79
+
78
80
  ### Claude Code
79
81
 
80
82
  The plugin needs only [uv](https://docs.astral.sh/uv/). It runs subedit from PyPI with the `vale` extra, and warms that environment in the background when a session starts, so no edit waits on the first download.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "subedit",
3
3
  "displayName": "subedit",
4
- "version": "0.3.1",
4
+ "version": "0.3.2",
5
5
  "description": "Repairs comment layout silently on write, and returns only what needs rewriting.",
6
6
  "author": {
7
7
  "name": "mroops0111"
@@ -4,10 +4,32 @@
4
4
  # because the first run installs subedit and downloads Vale, which no edit should wait on.
5
5
  set -uo pipefail
6
6
 
7
- SPEC='subedit[vale]>=0.3.1'
7
+ SPEC='subedit[vale]>=0.3.2'
8
8
  # A bare uvx takes the first Python it finds, which on a stock Mac is too old for subedit.
9
9
  PYTHON='>=3.11'
10
10
 
11
+ # The plugin reaches every session, so it acts only in a project that configures subedit,
12
+ # with a subedit.toml or a [tool.subedit] table in pyproject.toml, here or in a parent directory.
13
+ adopts_subedit() {
14
+ local directory="$1"
15
+ while :; do
16
+ if [ -f "$directory/subedit.toml" ]; then
17
+ return 0
18
+ fi
19
+ if [ -f "$directory/pyproject.toml" ] && grep -q '^\[tool\.subedit[].]' "$directory/pyproject.toml"; then
20
+ return 0
21
+ fi
22
+ if [ "$directory" = / ] || [ -z "$directory" ]; then
23
+ return 1
24
+ fi
25
+ directory=$(dirname "$directory")
26
+ done
27
+ }
28
+
29
+ if ! adopts_subedit "${CLAUDE_PROJECT_DIR:-$PWD}"; then
30
+ exit 0
31
+ fi
32
+
11
33
  if ! command -v uvx >/dev/null 2>&1; then
12
34
  cat >&2 <<'MESSAGE'
13
35
  subedit needs uv, so comment checks are not running.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "subedit"
3
- version = "0.3.1"
3
+ version = "0.3.2"
4
4
  description = "A linter and formatter for the prose inside code comments, across languages."
5
5
  authors = [
6
6
  {name = "Yun-Tai Yang"},
@@ -1,3 +1,3 @@
1
- __version__ = '0.3.1'
1
+ __version__ = '0.3.2'
2
2
 
3
3
  __all__ = ['__version__']
@@ -47,6 +47,10 @@ class TomlConfigSource:
47
47
  relative = target.resolve().as_posix()
48
48
  return any(fnmatch.fnmatch(relative, pattern) for pattern in patterns)
49
49
 
50
+ def is_adopted(self, path: str) -> bool:
51
+ """Whether a subedit.toml or a pyproject.toml with [tool.subedit] covers the path, as a project opts in."""
52
+ return self._locate(pathlib.Path(path)) is not None
53
+
50
54
  def root_for(self, path: str) -> pathlib.Path:
51
55
  config_path = self._locate(pathlib.Path(path))
52
56
  return config_path.parent if config_path else pathlib.Path.cwd()
@@ -45,7 +45,8 @@ def run(payload: Payload, *, write: bool = True) -> HookResult:
45
45
  except (OSError, UnicodeDecodeError):
46
46
  continue
47
47
  language = language_for(source_path)
48
- if language is None:
48
+ # A plugin installed once reaches every project, so only one that configures subedit opts in.
49
+ if language is None or not DEFAULT_CONFIG_SOURCE.is_adopted(str(source_path)):
49
50
  continue
50
51
  document = Document(uri=source_path.resolve().as_uri(), text=source_text, language_id=language.language_id)
51
52
  fix_result = fixer.run(document, DEFAULT_CONFIG_SOURCE.resolve(str(source_path)))
@@ -31,6 +31,25 @@ def seed(tmp_path: pathlib.Path, body: str, name: str = 'probe.py') -> pathlib.P
31
31
  return target
32
32
 
33
33
 
34
+ def test_a_project_without_subedit_config_is_left_alone(tmp_path: pathlib.Path) -> None:
35
+ body = MIS_WRAPPED + 'def g():\n # this one carries no comma and runs on' + ' and on' * 16 + '.\n pass\n'
36
+ target = tmp_path / 'probe.py'
37
+ target.write_text(body, encoding='utf-8')
38
+
39
+ hook_result = hook.run(payload(target))
40
+
41
+ assert hook_result.exit_code == 0
42
+ assert target.read_text(encoding='utf-8') == body
43
+
44
+
45
+ def test_a_tool_subedit_table_in_pyproject_opts_in(tmp_path: pathlib.Path) -> None:
46
+ (tmp_path / 'pyproject.toml').write_text('[tool.subedit]\nline-width = 74\n', encoding='utf-8')
47
+ target = tmp_path / 'probe.py'
48
+ target.write_text(UNBREAKABLE, encoding='utf-8')
49
+
50
+ assert hook.run(payload(target), write=False).is_blocking
51
+
52
+
34
53
  def test_a_non_edit_tool_is_ignored(tmp_path: pathlib.Path) -> None:
35
54
  target = seed(tmp_path, MIS_WRAPPED)
36
55
  assert hook.run(payload(target, tool='Read')).exit_code == 0
@@ -58,20 +58,48 @@ def test_the_wrapper_is_valid_shell() -> None:
58
58
  subprocess.run(['bash', '-n', str(WRAPPER)], check=True, capture_output=True)
59
59
 
60
60
 
61
- def test_the_wrapper_says_what_to_install_when_nothing_is_found() -> None:
62
- completed = subprocess.run(
63
- ['bash', str(WRAPPER)],
61
+ def run_wrapper(project: pathlib.Path, *arguments: str) -> subprocess.CompletedProcess[str]:
62
+ return subprocess.run(
63
+ ['bash', str(WRAPPER), *arguments],
64
64
  check=False,
65
65
  capture_output=True,
66
66
  text=True,
67
- env={'PATH': '/usr/bin:/bin'},
67
+ env={'PATH': '/usr/bin:/bin', 'CLAUDE_PROJECT_DIR': str(project)},
68
68
  input='',
69
69
  )
70
70
 
71
+
72
+ def test_the_wrapper_says_what_to_install_when_nothing_is_found(tmp_path: pathlib.Path) -> None:
73
+ (tmp_path / 'subedit.toml').write_text('[subedit]\n', encoding='utf-8')
74
+
75
+ completed = run_wrapper(tmp_path)
76
+
71
77
  assert completed.returncode == 1
72
78
  assert 'astral.sh/uv' in completed.stderr, 'a missing command must say how to get it'
73
79
 
74
80
 
81
+ @pytest.mark.parametrize('arguments', [(), ('prepare',)])
82
+ def test_the_wrapper_stays_out_of_a_project_without_subedit_config(
83
+ tmp_path: pathlib.Path, arguments: tuple[str, ...]
84
+ ) -> None:
85
+ (tmp_path / 'pyproject.toml').write_text('[tool.ruff]\nline-length = 100\n', encoding='utf-8')
86
+
87
+ completed = run_wrapper(tmp_path, *arguments)
88
+
89
+ assert (completed.returncode, completed.stderr) == (0, '')
90
+
91
+
92
+ @pytest.mark.parametrize('config', [('subedit.toml', '[subedit]\n'), ('pyproject.toml', '[tool.subedit.rules]\n')])
93
+ def test_the_wrapper_acts_in_a_subdirectory_of_a_configured_project(
94
+ tmp_path: pathlib.Path, config: tuple[str, str]
95
+ ) -> None:
96
+ name, text = config
97
+ (tmp_path / name).write_text(text, encoding='utf-8')
98
+ (tmp_path / 'packages').mkdir()
99
+
100
+ assert 'astral.sh/uv' in run_wrapper(tmp_path / 'packages').stderr
101
+
102
+
75
103
  def test_the_wrapper_runs_the_released_version() -> None:
76
104
  pyproject = (ROOT / 'pyproject.toml').read_text(encoding='utf-8')
77
105
  version = next(line for line in pyproject.splitlines() if line.startswith('version')).split('"')[1]
@@ -349,7 +349,7 @@ wheels = [
349
349
 
350
350
  [[package]]
351
351
  name = "subedit"
352
- version = "0.3.1"
352
+ version = "0.3.2"
353
353
  source = { editable = "." }
354
354
  dependencies = [
355
355
  { name = "autocorrect-py" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes