@kensio/skills 1.13.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 +48 -0
- package/bin/kensio-skills.mjs +213 -0
- package/package.json +39 -0
- package/skills/dynamodb-single-table/SKILL.md +329 -0
- package/skills/dynamodb-single-table/references/aws-guidance.md +183 -0
- package/skills/github-issue-drafting/SKILL.md +260 -0
- package/skills/isolated-testing-style/SKILL.md +293 -0
- package/skills/pangram-check/SKILL.md +125 -0
- package/skills/pangram-check/references/configuration.md +120 -0
- package/skills/pangram-check/references/reading-results.md +69 -0
- package/skills/pangram-check/scripts/pangram-check.mjs +807 -0
- package/skills/part-factory-test-data/SKILL.md +238 -0
- package/skills/skill-template/SKILL.md +126 -0
- package/skills/technical-prose-style/SKILL.md +356 -0
- package/skills/technical-prose-style/references/measurements.md +356 -0
- package/skills/technical-prose-style/scripts/prose-check.mjs +381 -0
- package/skills/yulin-aws-simulation/SKILL.md +324 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# Where the rules came from
|
|
2
|
+
|
|
3
|
+
The measured patterns in `SKILL.md` were selected by comparing human-written and LLM-written
|
|
4
|
+
technical documentation and keeping only what separated them. This file records the method, the
|
|
5
|
+
numbers, and the candidates that were tested and dropped, so the thresholds can be argued with and
|
|
6
|
+
the study can be re-run.
|
|
7
|
+
|
|
8
|
+
## The corpora
|
|
9
|
+
|
|
10
|
+
**Human exemplars.** 65,767 words across 15 documents from four independent sources. Four Django
|
|
11
|
+
documentation topics (`db/queries`, `http/urls`, `testing/overview`, `cache`), Effective Go, six
|
|
12
|
+
chapters of the Rust Book, and four Python documentation pages. All four are long-lived technical
|
|
13
|
+
documentation for developer audiences, written and revised by many hands, and all four predate
|
|
14
|
+
widespread LLM drafting.
|
|
15
|
+
|
|
16
|
+
**LLM corpora.** 192,430 words across 60 documents from three independent sources. The `docs/` tree
|
|
17
|
+
of a TypeScript library, the architecture READMEs under its `src/` tree, and the skills in a
|
|
18
|
+
separate repository. Different projects, different genres, one author model.
|
|
19
|
+
|
|
20
|
+
It matters that the first LLM corpus was written under a style guide already. That guide banned em
|
|
21
|
+
dashes, marketing language, the rule of three, preambles and long sentences. The guide was followed.
|
|
22
|
+
Em dashes fell to 0.33 per 1000 words and promotional vocabulary almost disappeared. The prose still
|
|
23
|
+
read as machine-written. That is the observation that started this.
|
|
24
|
+
|
|
25
|
+
Code blocks, inline code, headings, tables, link targets and URLs were stripped from both before
|
|
26
|
+
counting. Hard-wrapped lines were rejoined into paragraphs first, so sentence boundaries survive.
|
|
27
|
+
|
|
28
|
+
**The 2026 re-measurement.** `contrastive-coda` and the heading finding below come from a later run
|
|
29
|
+
against a freshly fetched corpus, because the original working files were never committed. That run
|
|
30
|
+
used 19 human documents (85,557 words) from the same four sources, and 13 LLM documents (15,022
|
|
31
|
+
words) from this repository. It reproduced the published `contrastive-def` rate to two decimal
|
|
32
|
+
places (1.05 against 1.06) and put `significance-tail` at 0.60 against the published 0.79, close
|
|
33
|
+
enough on a different document selection to treat the two runs as comparable. The LLM side of that
|
|
34
|
+
run is the weaker half. Every document in it had already been rewritten under the rules in
|
|
35
|
+
`SKILL.md`, so its rates show what survives the style guide. An unguided draft would score higher.
|
|
36
|
+
|
|
37
|
+
## Patterns kept
|
|
38
|
+
|
|
39
|
+
| Pattern | Human /1k | LLM /1k | Ratio |
|
|
40
|
+
| ----------------- | --------- | ------- | ----- |
|
|
41
|
+
| significance-tail | 0.79 | 6.41 | 8.1× |
|
|
42
|
+
| contrastive-coda | 0.30 | 2.53 | 8.3× |
|
|
43
|
+
| contrastive-def | 1.06 | 6.62 | 6.2× |
|
|
44
|
+
| negation-frame | 2.04 | 10.50 | 5.2× |
|
|
45
|
+
| appositive-tail | 0.59 | 3.15 | 5.3× |
|
|
46
|
+
| colon-explainer | 2.05 | 4.74 | 2.3× |
|
|
47
|
+
|
|
48
|
+
`contrastive-coda` is the `X, not Y` form of contrastive definition, and it was missed for four
|
|
49
|
+
releases. The rule in `SKILL.md` always described both forms, and the regex only ever matched
|
|
50
|
+
`rather than` and `instead of`. Measured on prose that had already been rewritten to satisfy that
|
|
51
|
+
regex, the coda still ran at 8.3 times the human rate. That is the sharpest separation in the study.
|
|
52
|
+
The construction was moving to the undetected form, in the same way that banning the em dash moved
|
|
53
|
+
it onto colons.
|
|
54
|
+
|
|
55
|
+
Only the `not` form is detected. The `, no ...` variant was tested and dropped, because both of its
|
|
56
|
+
human-corpus hits were ordinary clauses (", no problem." and ", no result is displayed before the
|
|
57
|
+
next interactive prompt.") and it added 4 LLM hits against 2 human false positives.
|
|
58
|
+
|
|
59
|
+
## How the thresholds were set
|
|
60
|
+
|
|
61
|
+
Rates are noisy per file, so thresholds come from the distribution and never from the aggregate. For
|
|
62
|
+
each pattern, `fail` is the lowest value that flags **none** of the 15 human documents, and `warn`
|
|
63
|
+
is the 90th percentile of those documents. Banned marks are the exception, described below.
|
|
64
|
+
|
|
65
|
+
| Pattern | Baseline | Warn (human p90) | Fail (zero human hits) | LLM files caught |
|
|
66
|
+
| ----------------- | -------- | ---------------- | ---------------------- | ---------------- |
|
|
67
|
+
| significance-tail | 0.79 | 1.7 | 2.3 | 93% |
|
|
68
|
+
| contrastive-coda | 0.30 | 0.85 | 1.6 | 54% |
|
|
69
|
+
| contrastive-def | 1.06 | 2.3 | 4.0 | 81% |
|
|
70
|
+
| negation-frame | 2.04 | 4.6 | 5.0 | 86% |
|
|
71
|
+
| appositive-tail | 0.59 | 1.1 | 1.8 | 81% |
|
|
72
|
+
| colon-explainer | 2.05 | 2.0 | 3.0 | 77% |
|
|
73
|
+
|
|
74
|
+
The `contrastive-coda` row follows the same rule as the others. Its human maximum is 1.51 per 1000
|
|
75
|
+
words (a Python tutorial page on exceptions) and its 90th percentile is 0.83, so `fail` sits at 1.6
|
|
76
|
+
and `warn` at 0.85. At 1.6 it flags 0 of the 19 human documents and 7 of the 13 LLM ones. That 54%
|
|
77
|
+
is the lowest catch rate of the six, and it is measured against already-rewritten prose, so treat it
|
|
78
|
+
as a floor.
|
|
79
|
+
|
|
80
|
+
At the file level, where a document fails if any single pattern fails, the measured patterns at
|
|
81
|
+
their zero-false-positive settings flag **0% of the human documents and 100% of the LLM documents**.
|
|
82
|
+
|
|
83
|
+
Two later decisions trade some of that away deliberately, and both are house style rather than
|
|
84
|
+
evidence:
|
|
85
|
+
|
|
86
|
+
| Configuration | Human documents flagged | LLM documents flagged |
|
|
87
|
+
| ----------------------------------------------- | ----------------------- | --------------------- |
|
|
88
|
+
| Measured patterns, zero-false-positive settings | 0 of 15 | 100% |
|
|
89
|
+
| With colon-explainer tightened to 3.0 | 3 of 15 | 94% |
|
|
90
|
+
| With the em dash and semicolon bans | 15 of 15 | 100% |
|
|
91
|
+
|
|
92
|
+
The bans flag every human document by construction, because Django, the Rust Book and the Python
|
|
93
|
+
docs all use em dashes and semicolons freely. A run with `--no-bans` drops them and re-checks the
|
|
94
|
+
measured separation, which is how the numbers above stay verifiable.
|
|
95
|
+
|
|
96
|
+
The first calibration used only Django and Effective Go, and set `fail` at three times the mean.
|
|
97
|
+
Once the human corpus was widened that flagged 3 of the 15 human documents, including a Rust Book
|
|
98
|
+
chapter whose subject is contrasting two representations. A style checker that fires on the Rust
|
|
99
|
+
Book is wrong, so the thresholds moved to the rule above.
|
|
100
|
+
|
|
101
|
+
Colon-explainer is the one place the zero-false-positive rule was overridden deliberately. At a
|
|
102
|
+
threshold no human document trips (6.6) it caught only 27% of LLM documents. It is set to 3.0
|
|
103
|
+
instead, as a house decision to suppress the construction. That costs 3 of the 15 human documents,
|
|
104
|
+
all of them Python pages, which lean on the "term, then explanation" form. The cost is known and
|
|
105
|
+
accepted.
|
|
106
|
+
|
|
107
|
+
## Banned marks
|
|
108
|
+
|
|
109
|
+
Three marks are house rules with no measured threshold behind them. Any occurrence in prose fails.
|
|
110
|
+
|
|
111
|
+
| Mark | Human /1k | LLM /1k, unbanned | Note |
|
|
112
|
+
| ------------------ | --------- | ----------------- | ------------------------------------------- |
|
|
113
|
+
| Em dash | 0.24 | 1.93 | 8× the human rate where no ban was in force |
|
|
114
|
+
| Semicolon | 2.70 | 1.04 | The human corpus uses these 2.6× more |
|
|
115
|
+
| Mid-sentence colon | 1.49 | 4.00 | Banned by house decision, see below |
|
|
116
|
+
|
|
117
|
+
The em dash number corrects an earlier reading. Measured only against the corpus that already banned
|
|
118
|
+
em dashes, the mark looked innocent at 0.33 per 1000 words. Measured against LLM prose written
|
|
119
|
+
without that ban, it runs at 1.93 against a human 0.24. The first measurement was recording
|
|
120
|
+
compliance with the ban and never the underlying habit. Banning it is justified.
|
|
121
|
+
|
|
122
|
+
Semicolons are the opposite case. The human corpus uses them more than twice as often as any LLM
|
|
123
|
+
corpus does. The ban removes almost nothing, and moves the prose slightly toward the machine end of
|
|
124
|
+
the range. It is in place as a house consistency rule and never as a tic detector.
|
|
125
|
+
|
|
126
|
+
Colons are now banned outright, and that is a house decision taken against the evidence rather than
|
|
127
|
+
from it. At zero tolerance every one of the 15 human documents fails, because a mid-sentence colon
|
|
128
|
+
is ordinary English. The reasoning is the same as for the em dash. The construction reads as
|
|
129
|
+
machine-written whoever wrote it, and prose costs little by doing without it. Anyone recalibrating
|
|
130
|
+
this study should treat the colon threshold as a preference and the other four as measurements.
|
|
131
|
+
|
|
132
|
+
A colon ending a line, introducing a list, a code block or the next paragraph, is exempt and stays
|
|
133
|
+
exempt. The human corpus uses that form 4.56 per 1000 words against the LLM corpus's 1.99, and
|
|
134
|
+
documentation cannot be written without it. `toProse` closes a block that ends in a colon so that
|
|
135
|
+
joining paragraphs cannot manufacture a mid-sentence one that nobody wrote.
|
|
136
|
+
|
|
137
|
+
## Headings
|
|
138
|
+
|
|
139
|
+
Headings are stripped before counting, on the grounds that they are labels rather than prose. That
|
|
140
|
+
left them unscored for four releases, and it turned out to be where negation framing collects.
|
|
141
|
+
|
|
142
|
+
| Corpus | Headings | Negation-framed | Share |
|
|
143
|
+
| ------ | -------- | --------------- | -------- |
|
|
144
|
+
| Human | 437 | 2 | 0.5% |
|
|
145
|
+
| LLM | 128 | 8 | **6.3%** |
|
|
146
|
+
|
|
147
|
+
Half of the LLM hits are the `X, not Y` coda in a heading ("Match service errors by name, not
|
|
148
|
+
instanceof"), so the two findings in this section are one habit surfacing in two places.
|
|
149
|
+
|
|
150
|
+
The two human hits are Django headings documenting a genuine prohibition ("When QuerySets are not
|
|
151
|
+
cached" and "Field name hiding is not permitted"), and a heading like that is doing its job. A hard
|
|
152
|
+
failure would flag them, so `heading-frame` reports every occurrence and never fails a file. It is
|
|
153
|
+
the one check that lists all of its hits, because there are only ever a handful and each one is a
|
|
154
|
+
line to go and fix.
|
|
155
|
+
|
|
156
|
+
## Where the construction goes next
|
|
157
|
+
|
|
158
|
+
Banning one mark moves the construction to the next available one. That is what happened when em
|
|
159
|
+
dashes were banned and colons absorbed the traffic. With all three marks closed, the remaining exits
|
|
160
|
+
worth watching are bracketed asides and comma splices.
|
|
161
|
+
|
|
162
|
+
| Mark | Human /1k | LLM /1k |
|
|
163
|
+
| ------------------- | --------- | ------- |
|
|
164
|
+
| Parenthetical aside | 6.19 | 0.15 |
|
|
165
|
+
|
|
166
|
+
That gap is the largest in the whole study. The human corpus uses bracketed asides 40 times more
|
|
167
|
+
often than the LLM corpus does. Brackets are not a tic to watch for. They are the missing habit, and
|
|
168
|
+
the natural home for a remark that no longer has a dash or a colon to hang from.
|
|
169
|
+
|
|
170
|
+
A pattern needs at least three occurrences in a file of at least 200 words before it is flagged. A
|
|
171
|
+
rate computed from a single hit says more about the length of the page than about the prose.
|
|
172
|
+
|
|
173
|
+
## Does it generalise
|
|
174
|
+
|
|
175
|
+
The first version of this study used one human source pair and one LLM corpus, which is enough to
|
|
176
|
+
find a pattern and not enough to trust it. Both sides were then widened to independent sources the
|
|
177
|
+
thresholds had never seen.
|
|
178
|
+
|
|
179
|
+
| Corpus | Words | Files over a fail threshold |
|
|
180
|
+
| ---------------------------------------------------- | ------- | --------------------------- |
|
|
181
|
+
| Django docs, human | 17,244 | 0 of 4 |
|
|
182
|
+
| Effective Go, human | 12,623 | 0 of 1 |
|
|
183
|
+
| Rust Book, human, unseen | 15,430 | 0 of 6 |
|
|
184
|
+
| Python docs, human, unseen | 20,470 | 0 of 4 |
|
|
185
|
+
| Library `docs/` tree, LLM | 108,000 | 29 of 31 |
|
|
186
|
+
| Architecture READMEs, LLM, different genre | 78,000 | 23 of 23 |
|
|
187
|
+
| Skills in another repository, LLM, different project | 6,200 | 5 of 6 |
|
|
188
|
+
|
|
189
|
+
Every pattern held its direction and rough magnitude across all of them. The rules are not an
|
|
190
|
+
artefact of one project's house style.
|
|
191
|
+
|
|
192
|
+
## Patterns tested and dropped
|
|
193
|
+
|
|
194
|
+
Each of these is a common style-guide rule. None of them separated the corpora.
|
|
195
|
+
|
|
196
|
+
| Candidate | Human | LLM | Verdict |
|
|
197
|
+
| ------------------------------------------------------------------------------------- | ---------- | ---------- | ------------------------ |
|
|
198
|
+
| Mean sentence length | 19.8 words | 20.9 words | 1.06×, no signal |
|
|
199
|
+
| Sentences over 32 words | 11.7% | 10.1% | LLM writes fewer |
|
|
200
|
+
| AI vocabulary (`delve`, `robust`, `seamless`, `powerful`, `crucial`, `leverage`, ...) | 0.80 /1k | 0.08 /1k | LLM scores 10× lower |
|
|
201
|
+
| Gerund-led sentences | 2.36 /1k | 2.71 /1k | 1.15×, no signal |
|
|
202
|
+
| Rule of three, prose only (code spans excluded) | 0.63 /1k | 0.34 /1k | LLM writes half as many |
|
|
203
|
+
| Trailing participles (`, leaving X`, `, making Y`) | 0.30 /1k | 0.14 /1k | LLM writes half as many |
|
|
204
|
+
| Verbless list fragment (noun phrases, no main verb) | 2.52 /1k | 2.53 /1k | 1.00×, no signal |
|
|
205
|
+
| Enumeration, four or more comma-separated items | 3.71 /1k | 4.46 /1k | 1.2×, under the bar |
|
|
206
|
+
| Enumeration, five or more | 1.22 /1k | 1.66 /1k | 1.4×, under the bar |
|
|
207
|
+
| `, no ...` coda (as against `, not ...`) | 0.02 /1k | 0.27 /1k | 2 human hits, both wrong |
|
|
208
|
+
|
|
209
|
+
The vocabulary result is the one worth dwelling on. Django and Effective Go use the words on every
|
|
210
|
+
AI-detector word list ten times more often than the Claude-written corpus does, because those words
|
|
211
|
+
are ordinary English and the corpus had been told to avoid them. A word list is measuring compliance
|
|
212
|
+
with a word list.
|
|
213
|
+
|
|
214
|
+
Sentence length is the same story from the other side. "Break up long sentences" was in the style
|
|
215
|
+
guide, the corpus obeyed it, and the result was the same clause count packed into shorter sentences
|
|
216
|
+
joined by colons.
|
|
217
|
+
|
|
218
|
+
The verbless list fragment is the most surprising of the drops, and it was tested because a reader
|
|
219
|
+
of this repository flagged it as the thing that grated most. It measures at 1.00×. Django and the
|
|
220
|
+
Rust Book build sentences out of bare noun phrases exactly as often as the LLM corpus does. The
|
|
221
|
+
dependency-parser study had already found this from the other direction, with the `appos` relation
|
|
222
|
+
at 0.79× in favour of the human corpus. What makes a pile of them grate is repetition inside one
|
|
223
|
+
document, and every measure in this study is a rate per 1000 words, and that is blind to whether six
|
|
224
|
+
hits are spread through a page or stacked in one paragraph. A measure of within-document shape
|
|
225
|
+
repetition would be the honest way to catch it, and none of the shipped rules is one.
|
|
226
|
+
|
|
227
|
+
## The dependency-parser study
|
|
228
|
+
|
|
229
|
+
Regexes cannot see grammar, so both corpora were re-measured with a spaCy dependency parse
|
|
230
|
+
(`en_core_web_sm`) to test whether syntactic features detect the tics more accurately.
|
|
231
|
+
|
|
232
|
+
| Parser feature | Human /1k | LLM /1k | Ratio |
|
|
233
|
+
| ----------------------------------------- | --------- | ------- | ----- |
|
|
234
|
+
| Coordination arity 3+ (rule of three) | 0.78 | 1.89 | 2.4× |
|
|
235
|
+
| `parataxis` relation | 0.49 | 0.07 | 0.14× |
|
|
236
|
+
| Trailing subordinate clause | 20.41 | 25.48 | 1.25× |
|
|
237
|
+
| Trailing subordinate clause after a comma | 4.28 | 9.49 | 2.2× |
|
|
238
|
+
| `appos` relation | 8.62 | 6.79 | 0.79× |
|
|
239
|
+
| Clauses per sentence | 1.42 | 1.37 | 0.96× |
|
|
240
|
+
|
|
241
|
+
Then the two features that separated the corpora were re-tested as plain regexes, to see what the
|
|
242
|
+
parse was buying:
|
|
243
|
+
|
|
244
|
+
| Feature | Parse ratio | Regex ratio |
|
|
245
|
+
| -------------------- | ----------- | ---------------------------------------------- |
|
|
246
|
+
| Comma-plus-`so` tail | 2.2× | **8.2×** |
|
|
247
|
+
| Rule of three | 2.4× | 2.6×, then 0.54× once code lists were excluded |
|
|
248
|
+
|
|
249
|
+
The regex wins, and the reason generalises. A parser recognises a whole grammatical class, and that
|
|
250
|
+
class contains all the legitimate uses along with the tic. Trailing subordinate clauses are ordinary
|
|
251
|
+
English at 1.25×. The tic is one narrow collocation inside that class, and grammatical generality
|
|
252
|
+
dilutes it. Adding a parser made detection worse.
|
|
253
|
+
|
|
254
|
+
The parse earned its cost as a research instrument. It falsified four candidate rules that sound
|
|
255
|
+
right, including two that had been written into a style guide. Use it to find and kill candidates,
|
|
256
|
+
then ship regexes.
|
|
257
|
+
|
|
258
|
+
Two traps it exposed, both worth repeating:
|
|
259
|
+
|
|
260
|
+
- **Dialect masquerading as a tic.** "X, Y and Z" without the Oxford comma separates the corpora at
|
|
261
|
+
4.7×. The exemplars are American and take the Oxford comma. The LLM corpus was written in British
|
|
262
|
+
English. That measurement detects nationality.
|
|
263
|
+
- **Markup masquerading as prose.** Rule of three separates at 2.6× until code spans are excluded,
|
|
264
|
+
at which point it inverts to 0.54×. The whole signal was lists of API names.
|
|
265
|
+
|
|
266
|
+
## The Pangram study
|
|
267
|
+
|
|
268
|
+
The patterns were selected, calibrated and enforced using measurements that all came from the same
|
|
269
|
+
hand. Pangram is a commercial AI-text detector, and it had no part in defining any rule, so it was
|
|
270
|
+
used as an outside judge of whether the rewriting achieves anything.
|
|
271
|
+
|
|
272
|
+
**Instrument check.** Four human documents (Django, two Rust Book chapters, a Python tutorial page)
|
|
273
|
+
scored `fraction_ai` of 0.00 and came back "Human Written". No false positives on this genre, so its
|
|
274
|
+
verdicts here can be relied on.
|
|
275
|
+
|
|
276
|
+
**Treatment.** Six documents in this repository were scored before and after being rewritten to
|
|
277
|
+
satisfy every rule above.
|
|
278
|
+
|
|
279
|
+
| Document | Before | After |
|
|
280
|
+
| ----------------------------- | -------- | -------- |
|
|
281
|
+
| isolated-testing-style SKILL | 0.87 | 0.48 |
|
|
282
|
+
| yulin-aws-simulation SKILL | 1.00 | 0.82 |
|
|
283
|
+
| root README | 0.65 | 0.46 |
|
|
284
|
+
| isolated-testing-style README | 1.00 | 1.00 |
|
|
285
|
+
| part-factory SKILL | 1.00 | 1.00 |
|
|
286
|
+
| yulin-aws-simulation README | 0.68 | 1.00 |
|
|
287
|
+
| **Mean** | **0.87** | **0.79** |
|
|
288
|
+
|
|
289
|
+
All six were still identified as AI after the rewrite. One scored worse. Edit volume failed to
|
|
290
|
+
predict the change. The document with the most edits improved by 0.18, and the one with the fewest
|
|
291
|
+
stayed where it was.
|
|
292
|
+
|
|
293
|
+
The conclusion is that style and provenance are different signals. The patterns are real differences
|
|
294
|
+
between human and LLM technical writing, and a classifier keys on something else. `SKILL.md` says
|
|
295
|
+
so, under "Limits".
|
|
296
|
+
|
|
297
|
+
One reassuring result. No window in any group was flagged `is_humanized`. The rewriting is not
|
|
298
|
+
producing evasion artefacts, largely because it moves the score so little.
|
|
299
|
+
|
|
300
|
+
## What the Pangram study did find
|
|
301
|
+
|
|
302
|
+
Pangram scores windows of roughly 300 words, which gave 109 windows across the four groups. Features
|
|
303
|
+
were computed per window and correlated with the window score.
|
|
304
|
+
|
|
305
|
+
| Feature | Human | LLM | r with score |
|
|
306
|
+
| ----------------------------- | ----- | ----- | ------------ |
|
|
307
|
+
| Distinct words per 100 | 0.627 | 0.699 | **0.51** |
|
|
308
|
+
| Parenthetical asides per 1000 | 5.90 | 0.99 | -0.26 |
|
|
309
|
+
| The five patterns per 1000 | 6.24 | 20.46 | 0.16 |
|
|
310
|
+
| Sentence length variance | 10.30 | 9.42 | -0.23 |
|
|
311
|
+
| Mean sentence length | 19.44 | 18.36 | -0.18 |
|
|
312
|
+
|
|
313
|
+
Lexical spread is the strongest correlate by some distance, and it holds up when length is
|
|
314
|
+
controlled, which matters because raw type-token ratio falls as text gets longer (r of -0.73 against
|
|
315
|
+
window length here). Measuring fixed 100-word chunks removes that.
|
|
316
|
+
|
|
317
|
+
Scored over whole documents with the shipped extraction, 15 human documents average 0.628 and none
|
|
318
|
+
exceeds 0.664, while 55 LLM documents average 0.685 and none falls below 0.658. The distributions
|
|
319
|
+
barely touch.
|
|
320
|
+
|
|
321
|
+
A second measure supports the reading. Taking the ten most frequent content words in a document, the
|
|
322
|
+
human corpus gives them 23.9% of all content tokens against 19.4%, 18.3% and 14.9% for the three LLM
|
|
323
|
+
corpora. Human technical writing concentrates on a small set of terms and repeats them. The Rust
|
|
324
|
+
ownership chapter leans on data, string, heap, memory and stack. That is terminology discipline, and
|
|
325
|
+
it is why the rule is worded as one name for one thing and never as a target number.
|
|
326
|
+
|
|
327
|
+
It ships as advisory. It describes a whole document and points at no line to fix, and padding with
|
|
328
|
+
repeated words would move it the right way while making the prose worse.
|
|
329
|
+
|
|
330
|
+
## Re-running the study
|
|
331
|
+
|
|
332
|
+
The point of a measured style guide is that it can be re-measured. To recalibrate against a
|
|
333
|
+
different exemplar corpus, or to check whether a new candidate pattern earns its place:
|
|
334
|
+
|
|
335
|
+
1. Collect exemplar prose in markdown, or adapt the stripping in `prose-check.mjs` for the format.
|
|
336
|
+
Prefer documents written before 2022, since anything newer may itself be LLM-drafted.
|
|
337
|
+
2. Score both corpora with `--json`. Report the aggregate ratio by total words, and keep the
|
|
338
|
+
per-file rates for step 4.
|
|
339
|
+
3. Keep a candidate only where the aggregate ratio clears 2×.
|
|
340
|
+
4. Set `fail` to the lowest value that flags no human document, and `warn` to the human 90th
|
|
341
|
+
percentile. Report what fraction of LLM documents that catches. A threshold below the human
|
|
342
|
+
maximum is a bug, however good the aggregate ratio looks.
|
|
343
|
+
5. Use at least three independent sources on each side. Two sources cannot tell a real pattern from
|
|
344
|
+
a house style.
|
|
345
|
+
|
|
346
|
+
A pattern that fires on prose you consider good is a bad pattern, however plausible the rule behind
|
|
347
|
+
it sounds. Every dropped candidate above sounded plausible, and two of them were already being
|
|
348
|
+
enforced as style rules.
|
|
349
|
+
|
|
350
|
+
## On distilling from a rewriting tool
|
|
351
|
+
|
|
352
|
+
Feeding bad passages through a third-party rewriter and mining the diffs is a reasonable way to
|
|
353
|
+
generate candidate patterns. It is a poor way to decide which ones to keep, because the tool's own
|
|
354
|
+
preferences arrive along with the improvements, and there is no way to tell one from the other. The
|
|
355
|
+
measurement above is the filter. Generate candidates however you like, then keep the ones that
|
|
356
|
+
separate real human prose from your own output.
|