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.
- subedit-0.1.0/.claude/settings.json +15 -0
- subedit-0.1.0/.claude-plugin/marketplace.json +11 -0
- subedit-0.1.0/.claude-plugin/plugin.json +12 -0
- subedit-0.1.0/.github/dependabot.yml +13 -0
- subedit-0.1.0/.github/release.yml +24 -0
- subedit-0.1.0/.github/workflows/ci.yml +77 -0
- subedit-0.1.0/.github/workflows/pr.yml +78 -0
- subedit-0.1.0/.github/workflows/release.yml +83 -0
- subedit-0.1.0/.gitignore +20 -0
- subedit-0.1.0/.vale.ini +13 -0
- subedit-0.1.0/LICENSE +21 -0
- subedit-0.1.0/PKG-INFO +193 -0
- subedit-0.1.0/README.md +163 -0
- subedit-0.1.0/docs/rules.md +16 -0
- subedit-0.1.0/examples/README.md +47 -0
- subedit-0.1.0/examples/probe.py +5 -0
- subedit-0.1.0/examples/subedit.toml +2 -0
- subedit-0.1.0/examples/walkthrough.py +68 -0
- subedit-0.1.0/hooks/hooks.json +17 -0
- subedit-0.1.0/hooks/run-subedit-hook.sh +21 -0
- subedit-0.1.0/pyproject.toml +118 -0
- subedit-0.1.0/src/subedit/__init__.py +3 -0
- subedit-0.1.0/src/subedit/application/__init__.py +16 -0
- subedit-0.1.0/src/subedit/application/corpus_meter.py +87 -0
- subedit-0.1.0/src/subedit/application/fixer.py +68 -0
- subedit-0.1.0/src/subedit/application/linter.py +55 -0
- subedit-0.1.0/src/subedit/application/report_aggregator.py +23 -0
- subedit-0.1.0/src/subedit/domain/__init__.py +63 -0
- subedit-0.1.0/src/subedit/domain/models/__init__.py +36 -0
- subedit-0.1.0/src/subedit/domain/models/comment.py +168 -0
- subedit-0.1.0/src/subedit/domain/models/diagnostic.py +123 -0
- subedit-0.1.0/src/subedit/domain/models/document.py +23 -0
- subedit-0.1.0/src/subedit/domain/models/position.py +108 -0
- subedit-0.1.0/src/subedit/domain/models/prose.py +215 -0
- subedit-0.1.0/src/subedit/domain/models/rule_set.py +30 -0
- subedit-0.1.0/src/subedit/domain/models/text.py +23 -0
- subedit-0.1.0/src/subedit/domain/ports/__init__.py +14 -0
- subedit-0.1.0/src/subedit/domain/ports/external_linter.py +14 -0
- subedit-0.1.0/src/subedit/domain/ports/language_conventions.py +25 -0
- subedit-0.1.0/src/subedit/domain/ports/rule.py +28 -0
- subedit-0.1.0/src/subedit/domain/ports/source_parser.py +12 -0
- subedit-0.1.0/src/subedit/domain/rules/__init__.py +15 -0
- subedit-0.1.0/src/subedit/domain/rules/layout.py +57 -0
- subedit-0.1.0/src/subedit/domain/rules/rewrite.py +83 -0
- subedit-0.1.0/src/subedit/domain/rules/structure.py +141 -0
- subedit-0.1.0/src/subedit/infrastructure/__init__.py +47 -0
- subedit-0.1.0/src/subedit/infrastructure/config_sources/__init__.py +11 -0
- subedit-0.1.0/src/subedit/infrastructure/config_sources/config_schema.py +43 -0
- subedit-0.1.0/src/subedit/infrastructure/config_sources/toml_config_source.py +102 -0
- subedit-0.1.0/src/subedit/infrastructure/linters/__init__.py +5 -0
- subedit-0.1.0/src/subedit/infrastructure/linters/autocorrect_linter.py +71 -0
- subedit-0.1.0/src/subedit/infrastructure/linters/vale_linter.py +87 -0
- subedit-0.1.0/src/subedit/infrastructure/parsers/__init__.py +12 -0
- subedit-0.1.0/src/subedit/infrastructure/parsers/languages/__init__.py +11 -0
- subedit-0.1.0/src/subedit/infrastructure/parsers/languages/language_profile.py +26 -0
- subedit-0.1.0/src/subedit/infrastructure/parsers/languages/python_profile.py +71 -0
- subedit-0.1.0/src/subedit/infrastructure/parsers/tree_sitter_parser.py +219 -0
- subedit-0.1.0/src/subedit/presentation/__init__.py +0 -0
- subedit-0.1.0/src/subedit/presentation/cli.py +193 -0
- subedit-0.1.0/src/subedit/presentation/harnesses/__init__.py +40 -0
- subedit-0.1.0/src/subedit/presentation/harnesses/agent_harness.py +43 -0
- subedit-0.1.0/src/subedit/presentation/harnesses/claude_code_harness.py +37 -0
- subedit-0.1.0/src/subedit/presentation/harnesses/codex_harness.py +43 -0
- subedit-0.1.0/src/subedit/presentation/hook.py +108 -0
- subedit-0.1.0/src/subedit/presentation/report.py +79 -0
- subedit-0.1.0/src/subedit/presentation/rule_reference.py +21 -0
- subedit-0.1.0/styles/mroops/EditHistory.yml +8 -0
- subedit-0.1.0/styles/mroops/Ellipsis.yml +9 -0
- subedit-0.1.0/styles/mroops/EmDash.yml +6 -0
- subedit-0.1.0/styles/mroops/ExternalRef.yml +7 -0
- subedit-0.1.0/styles/mroops/MidSentenceColon.yml +6 -0
- subedit-0.1.0/styles/mroops/MidSentenceColonMarkup.yml +7 -0
- subedit-0.1.0/styles/mroops/Semicolon.yml +6 -0
- subedit-0.1.0/styles/mroops/SignatureTag.yml +7 -0
- subedit-0.1.0/subedit.toml +13 -0
- subedit-0.1.0/tests/fixtures/vale/clean.md +8 -0
- subedit-0.1.0/tests/fixtures/vale/violations.py +13 -0
- subedit-0.1.0/tests/test_cli.py +156 -0
- subedit-0.1.0/tests/test_comment.py +147 -0
- subedit-0.1.0/tests/test_config.py +100 -0
- subedit-0.1.0/tests/test_corpus_meter.py +78 -0
- subedit-0.1.0/tests/test_diagnostic.py +101 -0
- subedit-0.1.0/tests/test_fixer.py +159 -0
- subedit-0.1.0/tests/test_harnesses.py +107 -0
- subedit-0.1.0/tests/test_hook.py +157 -0
- subedit-0.1.0/tests/test_linter.py +140 -0
- subedit-0.1.0/tests/test_parser.py +268 -0
- subedit-0.1.0/tests/test_parser_ocp.py +104 -0
- subedit-0.1.0/tests/test_parser_oracle.py +92 -0
- subedit-0.1.0/tests/test_plugin.py +78 -0
- subedit-0.1.0/tests/test_position.py +138 -0
- subedit-0.1.0/tests/test_prose.py +181 -0
- subedit-0.1.0/tests/test_report_aggregator.py +119 -0
- subedit-0.1.0/tests/test_rules_layout.py +136 -0
- subedit-0.1.0/tests/test_rules_rewrite.py +99 -0
- subedit-0.1.0/tests/test_rules_structure.py +119 -0
- subedit-0.1.0/tests/test_schema.py +84 -0
- subedit-0.1.0/tests/test_self_hosting.py +121 -0
- subedit-0.1.0/tests/test_text.py +61 -0
- subedit-0.1.0/tests/test_vale_style.py +49 -0
- subedit-0.1.0/uv.lock +418 -0
|
@@ -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,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/*
|
subedit-0.1.0/.gitignore
ADDED
|
@@ -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
|
subedit-0.1.0/.vale.ini
ADDED
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
|