slopless 0.1.0 → 0.2.1
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.
- package/README.md +150 -24
- package/dist/cli.js +101 -0
- package/dist/cli.js.map +1 -0
- package/package.json +9 -7
package/README.md
CHANGED
|
@@ -1,64 +1,190 @@
|
|
|
1
1
|
# slopless
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Install:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```bash
|
|
6
|
+
npm install -D slopless
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Run:
|
|
6
10
|
|
|
7
11
|
```bash
|
|
8
|
-
|
|
12
|
+
npx slopless
|
|
9
13
|
```
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
Run on a specific path:
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
```bash
|
|
18
|
+
npx slopless "docs/**/*.md"
|
|
19
|
+
```
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
+
Save JSON:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx slopless "docs/**/*.md" > slopless.json
|
|
21
25
|
```
|
|
22
26
|
|
|
23
|
-
##
|
|
27
|
+
## Npm Script
|
|
24
28
|
|
|
25
|
-
Add
|
|
29
|
+
Add this to `package.json`:
|
|
26
30
|
|
|
27
31
|
```json
|
|
28
32
|
{
|
|
29
33
|
"scripts": {
|
|
30
|
-
"lint:prose": "
|
|
34
|
+
"lint:prose": "slopless"
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
```
|
|
34
38
|
|
|
35
|
-
Run
|
|
39
|
+
Run:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm run lint:prose
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## What Slopless Is
|
|
46
|
+
|
|
47
|
+
Slopless is a deterministic prose checker for Markdown.
|
|
48
|
+
|
|
49
|
+
It reports concrete writing patterns that often make generated or careless prose feel padded, vague, generic, or formulaic.
|
|
50
|
+
|
|
51
|
+
It is built for local scripts, CI checks, review pipelines, and tools that need structured prose findings without calling an LLM.
|
|
52
|
+
|
|
53
|
+
## What Slopless Is Not
|
|
54
|
+
|
|
55
|
+
Slopless does not rewrite text.
|
|
56
|
+
|
|
57
|
+
Slopless does not check facts.
|
|
58
|
+
|
|
59
|
+
Slopless does not judge taste.
|
|
60
|
+
|
|
61
|
+
Slopless does not replace editing.
|
|
62
|
+
|
|
63
|
+
It reports rule findings. A person or another tool decides what to do with them.
|
|
64
|
+
|
|
65
|
+
## Defaults
|
|
66
|
+
|
|
67
|
+
- Checks `**/*.md` when no path is passed.
|
|
68
|
+
- Emits JSON only.
|
|
69
|
+
- Requires Node.js 20 or newer.
|
|
70
|
+
- Requires no `.textlintrc.json`.
|
|
71
|
+
- Requires no separate `textlint` install.
|
|
72
|
+
|
|
73
|
+
## Exit Codes
|
|
74
|
+
|
|
75
|
+
- `0`: no findings
|
|
76
|
+
- `1`: prose findings were reported
|
|
77
|
+
- `2`: command failure before linting
|
|
78
|
+
|
|
79
|
+
## Output
|
|
80
|
+
|
|
81
|
+
Output is always textlint JSON.
|
|
82
|
+
|
|
83
|
+
Each result contains the checked file path and its messages. Each message includes the rule ID, line, column, message text, and range data when textlint can provide it.
|
|
84
|
+
|
|
85
|
+
Rule IDs use this shape:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
slopless/<rule-name>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Example:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
slopless/semantic-thinness
|
|
95
|
+
slopless/llm-openers
|
|
96
|
+
slopless/hedge-stacking
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## CI Use
|
|
100
|
+
|
|
101
|
+
Run Slopless in CI:
|
|
36
102
|
|
|
37
103
|
```bash
|
|
104
|
+
npm ci
|
|
38
105
|
npm run lint:prose
|
|
39
106
|
```
|
|
40
107
|
|
|
41
|
-
|
|
108
|
+
Save findings as an artifact:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npx slopless "docs/**/*.md" > slopless.json
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The command exits `1` when findings exist, so CI fails by default on reported prose issues.
|
|
115
|
+
|
|
116
|
+
## Stdin
|
|
117
|
+
|
|
118
|
+
Check text from stdin:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
cat draft.md | npx slopless --stdin --stdin-filename draft.md
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`--stdin-filename` should end in `.md` so textlint parses the input as Markdown.
|
|
125
|
+
|
|
126
|
+
## Supported Options
|
|
127
|
+
|
|
128
|
+
Slopless forwards normal textlint file and execution options.
|
|
42
129
|
|
|
43
|
-
|
|
130
|
+
Useful examples:
|
|
44
131
|
|
|
45
132
|
```bash
|
|
46
|
-
npx
|
|
47
|
-
npx
|
|
133
|
+
npx slopless "docs/**/*.md" --quiet
|
|
134
|
+
npx slopless --stdin --stdin-filename draft.md
|
|
135
|
+
npx slopless --no-color
|
|
48
136
|
```
|
|
49
137
|
|
|
138
|
+
Unsupported:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npx slopless --format stylish
|
|
142
|
+
npx slopless -f json
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`--format` and `-f` are rejected because Slopless always emits JSON.
|
|
146
|
+
|
|
50
147
|
## What It Checks
|
|
51
148
|
|
|
149
|
+
Slopless checks for:
|
|
150
|
+
|
|
52
151
|
- stock AI-style phrasing
|
|
53
152
|
- empty or generic prose patterns
|
|
54
153
|
- rhetorical filler
|
|
55
|
-
- weak
|
|
154
|
+
- weak lead-ins and closers
|
|
56
155
|
- hedge stacking
|
|
57
|
-
- prohibited vocabulary
|
|
156
|
+
- prohibited or overused vocabulary
|
|
157
|
+
- cliches and corporate phrasing
|
|
158
|
+
- fake precision signals
|
|
58
159
|
- readability and sentence metrics
|
|
59
160
|
- Markdown style signals
|
|
60
161
|
|
|
61
|
-
##
|
|
162
|
+
## Why It Exists
|
|
163
|
+
|
|
164
|
+
Generated prose often repeats the same rhetorical moves: vague contrast, empty emotional payoff, overconfident summaries, generic transitions, and formulaic conclusions.
|
|
165
|
+
|
|
166
|
+
General grammar tools are not aimed at those patterns. LLM review can catch them, but it is slower, non-deterministic, and harder to use as a stable CI gate.
|
|
167
|
+
|
|
168
|
+
Slopless keeps that layer deterministic. It gives projects a repeatable JSON report of known prose issues.
|
|
169
|
+
|
|
170
|
+
## Advanced Textlint Use
|
|
171
|
+
|
|
172
|
+
The package also exports a textlint preset for users who already run textlint directly.
|
|
173
|
+
|
|
174
|
+
`.textlintrc.json`:
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"rules": {
|
|
179
|
+
"preset-slopless": true
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Direct textlint use:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
npx textlint "docs/**/*.md"
|
|
188
|
+
```
|
|
62
189
|
|
|
63
|
-
|
|
64
|
-
- textlint 15
|
|
190
|
+
Most users should use `npx slopless` instead.
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { cli } from "textlint/lib/src/cli.js";
|
|
5
|
+
const DEFAULT_TARGET = "**/*.md";
|
|
6
|
+
const FORMAT_FLAGS = new Set(["--format", "-f"]);
|
|
7
|
+
const HELP_FLAGS = new Set(["--help", "-h"]);
|
|
8
|
+
const VERSION_FLAGS = new Set(["--version", "-v"]);
|
|
9
|
+
const VERSION = "0.2.1";
|
|
10
|
+
const HELP_TEXT = `Slopless checks Markdown prose for deterministic slop signals and writes JSON.
|
|
11
|
+
|
|
12
|
+
Install:
|
|
13
|
+
npm install -D slopless
|
|
14
|
+
|
|
15
|
+
Run:
|
|
16
|
+
npx slopless
|
|
17
|
+
npx slopless "docs/**/*.md"
|
|
18
|
+
npx slopless draft.md > slopless.json
|
|
19
|
+
|
|
20
|
+
Package script:
|
|
21
|
+
{
|
|
22
|
+
"scripts": {
|
|
23
|
+
"lint:prose": "slopless"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
Default behavior:
|
|
28
|
+
- If no file path is passed, Slopless checks **/*.md.
|
|
29
|
+
- Output is always JSON.
|
|
30
|
+
- Exit 0 means no findings.
|
|
31
|
+
- Exit 1 means Slopless found prose issues.
|
|
32
|
+
- Exit 2 means the command failed before linting.
|
|
33
|
+
- No .textlintrc.json is required.
|
|
34
|
+
- No separate textlint install is required.
|
|
35
|
+
|
|
36
|
+
What it is for:
|
|
37
|
+
Slopless is for deterministic prose checks in CI, local scripts, and review
|
|
38
|
+
pipelines. It catches repeated AI-style phrasing, empty claims, rhetorical
|
|
39
|
+
filler, weak lead-ins and closers, hedge stacking, readability problems, and
|
|
40
|
+
Markdown style signals.
|
|
41
|
+
|
|
42
|
+
What it is not for:
|
|
43
|
+
Slopless does not rewrite text, check facts, judge taste, or replace human
|
|
44
|
+
editing. It reports concrete rule findings that another tool or person can
|
|
45
|
+
review.
|
|
46
|
+
|
|
47
|
+
Useful forms:
|
|
48
|
+
npx slopless --stdin --stdin-filename draft.md
|
|
49
|
+
npx slopless "docs/**/*.md" > slopless.json
|
|
50
|
+
npx slopless "docs/**/*.md" --quiet
|
|
51
|
+
|
|
52
|
+
Unsupported:
|
|
53
|
+
--format and -f are rejected. JSON is the only output format.
|
|
54
|
+
`;
|
|
55
|
+
function hasFormatOverride(args) {
|
|
56
|
+
return args.some((arg, index) => FORMAT_FLAGS.has(arg) ||
|
|
57
|
+
arg.startsWith("--format=") ||
|
|
58
|
+
(index > 0 && FORMAT_FLAGS.has(args[index - 1] ?? "")));
|
|
59
|
+
}
|
|
60
|
+
function hasFlag(args, flags) {
|
|
61
|
+
return args.some((arg) => flags.has(arg));
|
|
62
|
+
}
|
|
63
|
+
function hasFileTarget(args) {
|
|
64
|
+
return args.some((arg) => !arg.startsWith("-"));
|
|
65
|
+
}
|
|
66
|
+
function packageNodeModules() {
|
|
67
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
68
|
+
return resolve(packageRoot, "..");
|
|
69
|
+
}
|
|
70
|
+
async function main() {
|
|
71
|
+
const userArgs = process.argv.slice(2);
|
|
72
|
+
if (hasFlag(userArgs, HELP_FLAGS)) {
|
|
73
|
+
process.stdout.write(HELP_TEXT);
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
if (hasFlag(userArgs, VERSION_FLAGS)) {
|
|
77
|
+
process.stdout.write(`${VERSION}\n`);
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
if (hasFormatOverride(userArgs)) {
|
|
81
|
+
process.stderr.write("slopless always writes JSON output. Remove --format / -f.\n");
|
|
82
|
+
return 2;
|
|
83
|
+
}
|
|
84
|
+
const args = [
|
|
85
|
+
"node",
|
|
86
|
+
"slopless",
|
|
87
|
+
"--preset",
|
|
88
|
+
"slopless",
|
|
89
|
+
"--rules-base-directory",
|
|
90
|
+
packageNodeModules(),
|
|
91
|
+
"--format",
|
|
92
|
+
"json",
|
|
93
|
+
...userArgs
|
|
94
|
+
];
|
|
95
|
+
if (!hasFileTarget(userArgs)) {
|
|
96
|
+
args.push(DEFAULT_TARGET);
|
|
97
|
+
}
|
|
98
|
+
return cli.execute(args);
|
|
99
|
+
}
|
|
100
|
+
process.exitCode = await main();
|
|
101
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slopless",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Deterministic textlint rules for detecting slop in prose.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"url": "https://github.com/agent-quality-controls/slopless/issues"
|
|
14
14
|
},
|
|
15
15
|
"type": "module",
|
|
16
|
+
"main": "./dist/index.js",
|
|
16
17
|
"engines": {
|
|
17
18
|
"node": ">=20.0.0"
|
|
18
19
|
},
|
|
@@ -30,6 +31,9 @@
|
|
|
30
31
|
"dist",
|
|
31
32
|
"README.md"
|
|
32
33
|
],
|
|
34
|
+
"bin": {
|
|
35
|
+
"slopless": "dist/cli.js"
|
|
36
|
+
},
|
|
33
37
|
"exports": {
|
|
34
38
|
".": "./dist/index.js",
|
|
35
39
|
"./families/metrics/avg-sentence-length": "./dist/families/metrics/avg-sentence-length.js",
|
|
@@ -82,18 +86,17 @@
|
|
|
82
86
|
},
|
|
83
87
|
"dependencies": {
|
|
84
88
|
"@lunarisapp/readability": "^1.1.0",
|
|
89
|
+
"@textlint/ast-node-types": "15.6.1",
|
|
90
|
+
"@textlint/types": "15.6.1",
|
|
85
91
|
"sentence-splitter": "5.0.1",
|
|
92
|
+
"textlint": "15.6.1",
|
|
86
93
|
"textlint-rule-helper": "2.5.0",
|
|
87
94
|
"textlint-util-to-string": "3.3.4"
|
|
88
95
|
},
|
|
89
|
-
"peerDependencies": {
|
|
90
|
-
"textlint": "^15.6.1"
|
|
91
|
-
},
|
|
92
96
|
"devDependencies": {
|
|
93
97
|
"@double-great/stylelint-a11y": "3.4.12",
|
|
94
98
|
"@eslint-community/eslint-plugin-eslint-comments": "4.7.1",
|
|
95
|
-
"@
|
|
96
|
-
"@textlint/types": "15.6.1",
|
|
99
|
+
"@types/node": "^24.10.1",
|
|
97
100
|
"@typescript-eslint/eslint-plugin": "8.59.3",
|
|
98
101
|
"@typescript-eslint/parser": "8.59.3",
|
|
99
102
|
"cspell": "10.0.0",
|
|
@@ -108,7 +111,6 @@
|
|
|
108
111
|
"stylelint": "17.11.0",
|
|
109
112
|
"stylelint-config-standard": "40.0.0",
|
|
110
113
|
"stylelint-config-tailwindcss": "1.0.1",
|
|
111
|
-
"textlint": "15.6.1",
|
|
112
114
|
"type-coverage": "2.29.7",
|
|
113
115
|
"typescript": "5.9.3"
|
|
114
116
|
}
|