sloprefine 1.6.1__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.
- sloprefine-1.6.1/.github/workflows/ci.yml +35 -0
- sloprefine-1.6.1/.github/workflows/publish.yml +47 -0
- sloprefine-1.6.1/.gitignore +8 -0
- sloprefine-1.6.1/.sloprefine.toml +20 -0
- sloprefine-1.6.1/CITATION.cff +66 -0
- sloprefine-1.6.1/CONTRIBUTING.md +72 -0
- sloprefine-1.6.1/LICENSE +21 -0
- sloprefine-1.6.1/PKG-INFO +554 -0
- sloprefine-1.6.1/README.md +526 -0
- sloprefine-1.6.1/assets/cadence.svg +54 -0
- sloprefine-1.6.1/assets/refinery.svg +95 -0
- sloprefine-1.6.1/assets/wanted.svg +58 -0
- sloprefine-1.6.1/calibration/README.md +65 -0
- sloprefine-1.6.1/calibration/fiction-2023.json +73 -0
- sloprefine-1.6.1/corpus/audit-demo/README.md +16 -0
- sloprefine-1.6.1/corpus/audit-demo/human/0.txt +1 -0
- sloprefine-1.6.1/corpus/audit-demo/human/1.txt +1 -0
- sloprefine-1.6.1/corpus/audit-demo/human/2.txt +1 -0
- sloprefine-1.6.1/corpus/audit-demo/human/3.txt +1 -0
- sloprefine-1.6.1/corpus/audit-demo/human/4.txt +1 -0
- sloprefine-1.6.1/corpus/audit-demo/human/5.txt +1 -0
- sloprefine-1.6.1/corpus/audit-demo/machine/0.txt +1 -0
- sloprefine-1.6.1/corpus/audit-demo/machine/1.txt +1 -0
- sloprefine-1.6.1/corpus/audit-demo/machine/2.txt +1 -0
- sloprefine-1.6.1/corpus/audit-demo/machine/3.txt +1 -0
- sloprefine-1.6.1/corpus/audit-demo/machine/4.txt +1 -0
- sloprefine-1.6.1/corpus/audit-demo/machine/5.txt +1 -0
- sloprefine-1.6.1/corpus/choppy_control.txt +9 -0
- sloprefine-1.6.1/corpus/clean_control.txt +7 -0
- sloprefine-1.6.1/corpus/slop_control.txt +5 -0
- sloprefine-1.6.1/pyproject.toml +45 -0
- sloprefine-1.6.1/src/sloprefine/__init__.py +50 -0
- sloprefine-1.6.1/src/sloprefine/__main__.py +3 -0
- sloprefine-1.6.1/src/sloprefine/agent.py +329 -0
- sloprefine-1.6.1/src/sloprefine/align.py +141 -0
- sloprefine-1.6.1/src/sloprefine/audit.py +206 -0
- sloprefine-1.6.1/src/sloprefine/cadence.py +214 -0
- sloprefine-1.6.1/src/sloprefine/calibration.py +157 -0
- sloprefine-1.6.1/src/sloprefine/checks.py +362 -0
- sloprefine-1.6.1/src/sloprefine/cli.py +433 -0
- sloprefine-1.6.1/src/sloprefine/mcp_server.py +214 -0
- sloprefine-1.6.1/src/sloprefine/metrics.py +83 -0
- sloprefine-1.6.1/src/sloprefine/paragraph.py +137 -0
- sloprefine-1.6.1/src/sloprefine/parallel.py +363 -0
- sloprefine-1.6.1/src/sloprefine/person.py +110 -0
- sloprefine-1.6.1/src/sloprefine/punch.py +219 -0
- sloprefine-1.6.1/src/sloprefine/reader.py +342 -0
- sloprefine-1.6.1/src/sloprefine/report.py +318 -0
- sloprefine-1.6.1/src/sloprefine/rules.py +350 -0
- sloprefine-1.6.1/src/sloprefine/scorer.py +137 -0
- sloprefine-1.6.1/src/sloprefine/stylometry.py +172 -0
- sloprefine-1.6.1/src/sloprefine/templates.py +255 -0
- sloprefine-1.6.1/src/sloprefine/text.py +172 -0
- sloprefine-1.6.1/src/sloprefine/voice.py +195 -0
- sloprefine-1.6.1/src/sloprefine/weighting.py +99 -0
- sloprefine-1.6.1/tests/test_sloprefine.py +1757 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python-version }}
|
|
19
|
+
# mcp too, or the nine MCP server tests skip and CI reports green
|
|
20
|
+
# without ever having exercised the agent-facing surface.
|
|
21
|
+
- run: pip install -e ".[dev,mcp]"
|
|
22
|
+
- run: pytest -q
|
|
23
|
+
- run: ruff check .
|
|
24
|
+
- name: dogfood (the docs must pass their own linter)
|
|
25
|
+
run: sloprefine README.md CONTRIBUTING.md --no-color
|
|
26
|
+
- name: agent surfaces smoke test
|
|
27
|
+
run: |
|
|
28
|
+
sloprefine rules
|
|
29
|
+
sloprefine prompt > /dev/null
|
|
30
|
+
sloprefine prompt --profile talk > /dev/null
|
|
31
|
+
sloprefine check corpus/slop_control.txt --format agent || true
|
|
32
|
+
sloprefine drift corpus/slop_control.txt corpus/clean_control.txt || true
|
|
33
|
+
sloprefine prompt --audience expert > /dev/null
|
|
34
|
+
sloprefine check corpus/choppy_control.txt --max-choppiness 0.17 && exit 1 || true
|
|
35
|
+
sloprefine audit --ai corpus/audit-demo/machine --human corpus/audit-demo/human
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
# Trusted publishing: PyPI verifies this workflow's OIDC identity, so there is
|
|
4
|
+
# no API token stored in the repository and none to rotate or leak.
|
|
5
|
+
# Configure the publisher once at
|
|
6
|
+
# https://pypi.org/manage/account/publishing/ before the first release.
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
release:
|
|
10
|
+
types: [published]
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
publish:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
permissions:
|
|
19
|
+
# Job-level permissions REPLACE the workflow-level block rather than
|
|
20
|
+
# adding to it, so contents:read has to be restated here or checkout
|
|
21
|
+
# runs with contents:none and cannot fetch the repository.
|
|
22
|
+
contents: read
|
|
23
|
+
id-token: write # required for trusted publishing
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
- uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.12"
|
|
29
|
+
|
|
30
|
+
# Publish nothing that does not pass its own suite and its own linter.
|
|
31
|
+
- run: pip install -e ".[dev,mcp]"
|
|
32
|
+
- run: pytest -q
|
|
33
|
+
- run: ruff check .
|
|
34
|
+
- run: sloprefine README.md CONTRIBUTING.md --no-color
|
|
35
|
+
|
|
36
|
+
# The tag must match the version the package actually declares, or the
|
|
37
|
+
# release and the artifact disagree for the rest of time.
|
|
38
|
+
- name: tag matches the declared version
|
|
39
|
+
run: |
|
|
40
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
41
|
+
declared=$(python -c "import sloprefine; print(sloprefine.__version__)")
|
|
42
|
+
test "$tag" = "$declared" || {
|
|
43
|
+
echo "tag $tag != declared version $declared"; exit 1; }
|
|
44
|
+
|
|
45
|
+
- run: pip install build
|
|
46
|
+
- run: python -m build
|
|
47
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# sloprefine's own configuration, which doubles as a worked example.
|
|
2
|
+
# "landscape" and "robust" are on the excess-vocabulary list and are also
|
|
3
|
+
# ordinary words in this repository's subject matter.
|
|
4
|
+
[sloprefine]
|
|
5
|
+
allow = ["landscape", "robust"]
|
|
6
|
+
|
|
7
|
+
# The sentence floor. Off by default in code because it is the one rule with
|
|
8
|
+
# no citation behind it and it refuses a device human writers legitimately
|
|
9
|
+
# use. Enabled here as this repository's own house policy.
|
|
10
|
+
# runt_mode = "verbless" refuses "One plot." but keeps "It worked."
|
|
11
|
+
# runt_mode = "all" refuses everything under the floor
|
|
12
|
+
# Parallelism runs allowed per 1000 words. Documentation enumerates things
|
|
13
|
+
# for a living, so this repo's own prose runs higher than a talk would.
|
|
14
|
+
# A conference talk should sit at 1.0 or below.
|
|
15
|
+
parallel_budget_per_1k = 3.0
|
|
16
|
+
|
|
17
|
+
min_sentence_words = 5
|
|
18
|
+
runt_mode = "verbless"
|
|
19
|
+
allow_runts = ["Thank you."]
|
|
20
|
+
max_hits = 0
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
title: "sloprefine: a slop refinery for prose"
|
|
3
|
+
message: "If you use this software, please cite it as below."
|
|
4
|
+
type: software
|
|
5
|
+
authors:
|
|
6
|
+
- family-names: Rumenovski
|
|
7
|
+
given-names: Filip
|
|
8
|
+
affiliation: "Wayne State University"
|
|
9
|
+
email: "frumenovski@gmail.com"
|
|
10
|
+
repository-code: "https://github.com/filiprumenovski/sloprefine"
|
|
11
|
+
license: MIT
|
|
12
|
+
version: 1.6.1
|
|
13
|
+
date-released: 2026-09-22
|
|
14
|
+
abstract: >-
|
|
15
|
+
A dependency-free prose linter that flags the documented surface markers of
|
|
16
|
+
machine-generated text, measures cadence and syntactic template reuse, and
|
|
17
|
+
calibrates its own rule weights against a labelled corpus. Rules carry
|
|
18
|
+
citations to the studies that motivate them; rules that fail to discriminate
|
|
19
|
+
on a supplied corpus are reported rather than silently applied.
|
|
20
|
+
keywords:
|
|
21
|
+
- writing
|
|
22
|
+
- linter
|
|
23
|
+
- large language models
|
|
24
|
+
- stylometry
|
|
25
|
+
- text diversity
|
|
26
|
+
references:
|
|
27
|
+
- type: article
|
|
28
|
+
title: "Delving into LLM-assisted writing in biomedical publications through excess vocabulary"
|
|
29
|
+
authors:
|
|
30
|
+
- family-names: Kobak
|
|
31
|
+
given-names: Dmitry
|
|
32
|
+
year: 2025
|
|
33
|
+
journal: "Science Advances"
|
|
34
|
+
- type: conference-paper
|
|
35
|
+
title: "Detection and Measurement of Syntactic Templates in Generated Text"
|
|
36
|
+
authors:
|
|
37
|
+
- family-names: Shaib
|
|
38
|
+
given-names: Chantal
|
|
39
|
+
- family-names: Elazar
|
|
40
|
+
given-names: Yanai
|
|
41
|
+
- family-names: Li
|
|
42
|
+
given-names: "Junyi Jessy"
|
|
43
|
+
- family-names: Wallace
|
|
44
|
+
given-names: "Byron C."
|
|
45
|
+
year: 2024
|
|
46
|
+
collection-title: "EMNLP"
|
|
47
|
+
- type: article
|
|
48
|
+
title: "AI Writers Have a Consistent Stylometric Footprint, but AI Editors Do Not"
|
|
49
|
+
authors:
|
|
50
|
+
- family-names: Shan
|
|
51
|
+
given-names: Zhengyang
|
|
52
|
+
- family-names: Lee
|
|
53
|
+
given-names: Yukyung
|
|
54
|
+
- family-names: Hao
|
|
55
|
+
given-names: Sophie
|
|
56
|
+
year: 2026
|
|
57
|
+
journal: "arXiv:2608.27855"
|
|
58
|
+
- type: article
|
|
59
|
+
title: "GPT detectors are biased against non-native English writers"
|
|
60
|
+
authors:
|
|
61
|
+
- family-names: Liang
|
|
62
|
+
given-names: Weixin
|
|
63
|
+
- family-names: Zou
|
|
64
|
+
given-names: James
|
|
65
|
+
year: 2023
|
|
66
|
+
journal: "Patterns"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Adding a rule
|
|
4
|
+
|
|
5
|
+
Every rule needs a `fix`: one imperative sentence a model can act on without
|
|
6
|
+
reading the rationale. `why` explains the rule to a person; `fix` is what gets
|
|
7
|
+
shipped into the revision loop and into `sloprefine prompt`. A test asserts
|
|
8
|
+
every rule has one.
|
|
9
|
+
|
|
10
|
+
Every rule also needs a source, meaning not a vibe but something someone put
|
|
11
|
+
their name on: a study, published corpus statistics, or an argument someone
|
|
12
|
+
wrote down and signed. Add the short key and full
|
|
13
|
+
reference to the docstring at the top of `src/sloprefine/rules.py`, then declare
|
|
14
|
+
the rule with `_rule(...)` and give it a `why` that a writer can act on.
|
|
15
|
+
|
|
16
|
+
Two more things are required.
|
|
17
|
+
|
|
18
|
+
1. A positive test in `tests/test_sloprefine.py::test_rule_fires`.
|
|
19
|
+
2. No new hits on `corpus/clean_control.txt`. That file is a regression fence.
|
|
20
|
+
`test_clean_control_is_silent` asserts zero. If your rule fires there,
|
|
21
|
+
either the rule is too broad or the fence was wrong; argue for one.
|
|
22
|
+
|
|
23
|
+
## False positives are the whole game
|
|
24
|
+
|
|
25
|
+
A prose linter that cries wolf gets uninstalled in a week, and then it protects
|
|
26
|
+
nobody. Prefer a rule that misses half the instances over one that fires on
|
|
27
|
+
ordinary writing. If a word is load-bearing in some field, add it to
|
|
28
|
+
`COMMONLY_LEGITIMATE` rather than dropping it from the lexicon.
|
|
29
|
+
|
|
30
|
+
## Adding a reader-preference signal
|
|
31
|
+
|
|
32
|
+
The highest bar in the repository. A positive signal needs a direction
|
|
33
|
+
measured after 2022 by comparing human and machine text against real reader
|
|
34
|
+
judgments. "Good writers do this" is not admissible, and neither is a
|
|
35
|
+
pre-2022 prescription, because prescription is what put the pattern in the
|
|
36
|
+
training data in the first place. That is how the rule of three became a
|
|
37
|
+
detection marker.
|
|
38
|
+
|
|
39
|
+
Cite the corpus, say who the readers were, and give the direction of the
|
|
40
|
+
effect. Say which
|
|
41
|
+
audience it applies to; `reader.py` takes one because the evidence says the
|
|
42
|
+
two reader clusters want different things. If you approximate a feature the
|
|
43
|
+
source measured with a model, name the proxy in the code and in the output.
|
|
44
|
+
|
|
45
|
+
Then run `sloprefine audit` against a real corpus pair and report the
|
|
46
|
+
enrichment. A signal that does not separate machine text from human text on
|
|
47
|
+
current models is not a signal yet.
|
|
48
|
+
|
|
49
|
+
## Adding a stylometric feature
|
|
50
|
+
|
|
51
|
+
The bar here is different. Features go in `stylometry.py`. They must be
|
|
52
|
+
computable offline, with no language model and nothing that needs
|
|
53
|
+
corpus-level statistics, so that a draft can be checked on a laptop and
|
|
54
|
+
the number still means the same thing tomorrow.
|
|
55
|
+
|
|
56
|
+
Length-correct anything derived from token counts. Type-token ratio and
|
|
57
|
+
Shannon entropy both fall with document length for arithmetic reasons, so an
|
|
58
|
+
uncorrected feature measures how long the document is and reports it as style.
|
|
59
|
+
|
|
60
|
+
Do not add a threshold. `stylometry.py` deliberately ships without population cutoffs. The reason
|
|
61
|
+
is in `voice.py`, where a cutoff transferred between corpora turns out to be
|
|
62
|
+
the documented failure mode of deployed detectors, landing hardest on
|
|
63
|
+
non-native speakers. If a feature is only meaningful against a threshold, it belongs in
|
|
64
|
+
the voiceprint layer as a deviation, not in the report as a judgement.
|
|
65
|
+
|
|
66
|
+
## Running everything
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install -e ".[dev]"
|
|
70
|
+
pytest -q
|
|
71
|
+
ruff check .
|
|
72
|
+
```
|
sloprefine-1.6.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Filip Rumenovski
|
|
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.
|