@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.
@@ -0,0 +1,356 @@
1
+ ---
2
+ name: technical-prose-style
3
+ description: Write documentation, READMEs, code comments, commit messages, release notes and PR text as plain technical prose, by removing the constructions that make unedited LLM prose tiring to read. It targets significance tails, contrastive definition, negation framing, appositive tails, colon explainers and a vocabulary that keeps renaming the same thing. Ships a script that scores prose against Django, Go, Rust and Python documentation. Use when writing or editing docs, a README, a changelog, a blog post or any prose for human readers, when asked to improve, tighten or rewrite writing, when prose "sounds like AI" or "sounds like Claude", and when reviewing documentation in a pull request.
4
+ license: Apache-2.0
5
+ metadata:
6
+ version: "1.13.1"
7
+ ---
8
+
9
+ # Technical prose style
10
+
11
+ For anything a human reads: `docs/`, README files, code comments, commit messages, release notes,
12
+ issue and pull request text.
13
+
14
+ ## What this is for
15
+
16
+ Unedited LLM prose is tiring to read. It is usually accurate, and the reader usually knows it was
17
+ machine-drafted and minds that far less. What wears them down is the shape of it. One construction
18
+ returns over and over. Every fact trails a clause explaining why the fact matters. A fresh synonym
19
+ arrives where the previous term would have done.
20
+
21
+ The goal here is prose that costs the reader less. Concealment is a different aim, and out of scope.
22
+ An independent detector still identifies text that follows every rule below as machine-written. Six
23
+ rewritten documents were put through one to check. See [Limits](#limits) at the end.
24
+
25
+ The rules below come from measurement. 192,000 words of LLM-written technical documentation were
26
+ compared against 66,000 words of Django, Go, Rust and Python documentation, and only the patterns
27
+ where the two differed by a factor of two or more were kept. The six that survived run between 2.3
28
+ and 8.3 times the human rate. Everything else tested came in under 1.2 times. Thresholds are set so
29
+ that no human document in the corpus fails and every LLM document does.
30
+ [references/measurements.md](references/measurements.md) records the method, the thresholds, the
31
+ candidates that were tested and dropped, and a dependency-parser study that falsified four more.
32
+
33
+ Read [What to leave alone](#what-to-leave-alone) before rewriting anything. Several of the usual
34
+ style rules make the prose worse, and the measurements show why.
35
+
36
+ ## The target
37
+
38
+ Write the way Go's package documentation and the Django docs are written.
39
+
40
+ - One claim per sentence.
41
+ - The subject is something the reader can point at, such as a function, a queue, a file or a test.
42
+ Not "what a deterministic assertion wants".
43
+ - Present tense, active voice.
44
+ - Say what the thing does, then stop. Trust the reader to work out what it means for them.
45
+
46
+ ```
47
+ CreateQueue is idempotent. A second request for the same name returns the existing queue's URL
48
+ if the attributes match, and fails with QueueNameExists if they differ.
49
+ ```
50
+
51
+ Two sentences carrying two facts, with the consequences left to the reader.
52
+
53
+ ## The four sentence patterns
54
+
55
+ Each one is a sentence shape. A banned word list can be followed perfectly while the prose still
56
+ reads as machine-written, because vocabulary bans do not touch any of these.
57
+
58
+ ### 1. Significance tail
59
+
60
+ A fact, followed by a clause explaining why the fact matters.
61
+
62
+ Detect: `,\s+so\s+(a|an|the|it|that|this|there|nothing|no|tests?|you|we|they)\b`
63
+
64
+ The comma is what makes this a tail. `so` without one is usually doing honest work.
65
+
66
+ This is the model showing its working. State the fact and leave the working out. Where the
67
+ consequence genuinely matters, give it its own sentence. Most of the time a reader holding the fact
68
+ can derive it.
69
+
70
+ <!-- prose-check:off -->
71
+
72
+ > **Before.** The message records the instant it is hidden until, and it becomes receivable again
73
+ > once simulated time reaches that instant. Advancing the clock is therefore all a test needs to
74
+ > watch an undeleted message come back.
75
+
76
+ <!-- prose-check:on -->
77
+
78
+ > **After.** The message records the instant it is hidden until. It becomes receivable again once
79
+ > simulated time reaches that instant.
80
+
81
+ ### 2. Contrastive definition
82
+
83
+ Defining a thing by what it is not, or by the alternative it displaces. This one has two surface
84
+ forms, and the checker scores them separately.
85
+
86
+ Detect (`contrastive-def`): `\b(rather than|instead of)\b`
87
+
88
+ Detect (`contrastive-coda`): `,\s+not\s+...` running to the end of the sentence
89
+
90
+ The coda is the commoner of the two, and it separates the corpora more sharply (8.3 times the human
91
+ rate, against 6.2 for the two-word forms). It went undetected for four releases because the first
92
+ regex only looks for `rather than` and `instead of`. The rule was right and the regex was half the
93
+ size of it. Any rule expressed as a pattern is worth checking for that same gap.
94
+
95
+ Say what the thing does. Reach for a contrast only when the reader is likely to hold the wrong
96
+ belief and the correction is the point of the sentence. Where that test passes, the correction has
97
+ earned a sentence of its own, and it reads better in one.
98
+
99
+ <!-- prose-check:off -->
100
+
101
+ > **Before.** A service is kept as state rather than by a timer, so a test that finishes with a
102
+ > service running leaves nothing behind it.
103
+
104
+ <!-- prose-check:on -->
105
+
106
+ > **After.** A service is kept as state. Closing the simulated environment stops it.
107
+
108
+ <!-- prose-check:off -->
109
+
110
+ > **Before.** Assert behaviour, not call counts.
111
+
112
+ <!-- prose-check:on -->
113
+
114
+ > **After.** Assert behaviour. A call count passes when the code calls the right method for the
115
+ > wrong reason.
116
+
117
+ ### 3. Negation framing
118
+
119
+ Sentences built on `nothing`, `is not`, `does not`, `neither`.
120
+
121
+ Detect: `\b(is not|are not|does not|nothing|neither)\b`
122
+
123
+ Negation is fine when the absence is the fact being documented, such as a limitation or an
124
+ unsupported option. It is a tic when it is a roundabout way of stating something positive. "Nothing
125
+ here replaces the process clock" is the same fact as "time belongs to a `SimAws` instance", written
126
+ backwards.
127
+
128
+ <!-- prose-check:off -->
129
+
130
+ > **Before.** Nothing here replaces the clock for the whole process. Time belongs to a `SimAws`
131
+ > instance, so moving it never disturbs another simulation running in the same test file, the real
132
+ > clock, or any other code in the process.
133
+
134
+ <!-- prose-check:on -->
135
+
136
+ > **After.** Time belongs to a `SimAws` instance. Moving it affects that instance only. The host
137
+ > clock and any other simulation in the process carry on unchanged.
138
+
139
+ Headings take this worst, and they used to escape the checker (it strips headings before counting,
140
+ so for four releases nothing scored them). A heading framed by what a section excludes makes the
141
+ reader invert it to find out what the section contains. The checker now reports these as advisory,
142
+ because two Django headings in the human corpus are legitimately negative and a hard failure would
143
+ flag them.
144
+
145
+ <!-- prose-check:off -->
146
+
147
+ > **Before.** Get isolation from the data, not from setup and teardown
148
+
149
+ <!-- prose-check:on -->
150
+
151
+ > **After.** Take isolation from randomised data
152
+
153
+ ### 4. Appositive tail
154
+
155
+ A trailing `, which is` / `, which means` clause that comments on the sentence it is attached to.
156
+
157
+ Detect: `, which (is|means|makes|gives|lets|keeps|does)\b`
158
+
159
+ The same reflex as the significance tail, wearing a relative pronoun. Either promote the clause to
160
+ its own sentence or delete it.
161
+
162
+ <!-- prose-check:off -->
163
+
164
+ > **Before.** The schedule has to exist: updating one that is not there is a
165
+ > `ResourceNotFoundException` rather than a create, which is another difference from EventBridge's
166
+ > `PutRule`.
167
+
168
+ <!-- prose-check:on -->
169
+
170
+ > **After.** The schedule has to exist. Updating one that is absent raises
171
+ > `ResourceNotFoundException`. EventBridge's `PutRule` creates it.
172
+
173
+ ## Banned marks
174
+
175
+ Three marks are house rules. The checker fails a file for any occurrence of any of them.
176
+
177
+ - **Em dashes.** None in prose. In LLM prose written without a ban they run at 1.93 per 1000 words
178
+ against a human 0.24. That ban is earned. Replace with a full stop, a comma, or brackets.
179
+ - **Semicolons.** None in prose. This one is consistency and carries no evidence behind it. The
180
+ human corpus uses semicolons more than twice as often as any LLM corpus does. Split the sentence.
181
+ - **Mid-sentence colons.** None in prose. This is a house decision taken against the evidence, and
182
+ worth being clear about. A mid-sentence colon is ordinary English, and at zero tolerance every one
183
+ of the 15 human documents in the corpus fails. It is banned anyway, on the same reasoning as the
184
+ em dash. The construction reads as machine-written whoever wrote it, and the cost of doing without
185
+ it is low. A colon **ending a line** to introduce a list, a code block or the next paragraph is
186
+ exempt and always will be. Documentation cannot be written without it. Only the mid-sentence form
187
+ is banned, and the two are distinguished by whether the colon's object is in the same paragraph.
188
+
189
+ The `- **Term** — description` form in a list is typography, and is exempt.
190
+
191
+ Closing all three at once is the point. Banning the em dash alone moved the same pattern onto
192
+ colons, where nothing was watching for it. A remark with nowhere to hang becomes its own sentence.
193
+
194
+ ## Use brackets
195
+
196
+ The largest single gap in the whole study. The human corpus uses parenthetical asides at 6.19 per
197
+ 1000 words, the LLM corpus at 0.15. Forty times.
198
+
199
+ Brackets are the missing habit here. They are where a subordinate remark should go once the dash and
200
+ the colon are gone. A qualification, an aside, a unit or a caveat worth one clause goes in brackets,
201
+ the way the Rust Book and the Django docs do on nearly every page.
202
+
203
+ Nothing in the checker measures this (a rule that rewards padding would be worse than no rule), so
204
+ it stays a habit to build by hand. It is the one place where the fix is to add rather than to cut.
205
+
206
+ ## Keep one name for one thing
207
+
208
+ Human technical writing names a thing and goes on naming it that. LLM prose reaches for a synonym.
209
+
210
+ Measured as distinct words per 100, averaged across a document, the human corpus sits at 0.628 and
211
+ never exceeds 0.664. The LLM corpus sits at 0.685 and never falls below 0.658. That is the cleanest
212
+ single separation in the whole study, and it is the one that maps most directly onto reader fatigue.
213
+ A new name for an old thing stops the reader to re-resolve what it refers to.
214
+
215
+ So if the thing is a queue, call it the queue every time. Call it the queue, then the message store,
216
+ then the buffer, and the reader pays for each change. The same holds for the identifiers in the code
217
+ being documented.
218
+
219
+ The checker reports this as advisory and never fails a file on it, for two reasons. It scores a
220
+ whole document, and there is no line to go and fix. And it is trivially gamed by padding with
221
+ repeated words, which would move the number the right way while making the prose worse.
222
+
223
+ ## Short strings are a different genre
224
+
225
+ Everything above is calibrated on documents of 200 words and more, and the checker refuses to score
226
+ anything shorter. A package description, a plugin listing, a meta description or a card subtitle is
227
+ a different problem, and the rules above get it wrong in both directions.
228
+
229
+ Forty descriptions from long-established npm packages (express, lodash, axios, webpack, eslint and
230
+ the like) were compared against five written by Claude for this repository:
231
+
232
+ | | Human npm | Claude |
233
+ | ------------------------------------------ | ----------- | -------- |
234
+ | Median length | **7 words** | 38 words |
235
+ | Noun phrase followed by a colon and a list | **0 of 40** | 4 of 5 |
236
+ | Three-item list | 2 of 40 | 5 of 5 |
237
+
238
+ Nothing in the human set uses the shape. Not one. So for a short listing string:
239
+
240
+ - **Say what the thing is, in one declarative clause.** "Promise based HTTP client for the browser
241
+ and node.js". "Terminal string styling done right". Seven words is a normal length.
242
+ - **No colon followed by a catalogue.** `Thing for X: doing A, doing B, and doing C` is the shape to
243
+ avoid, and it is the shape Claude reaches for every time. The permission for a list-introducing
244
+ colon in the section above applies to prose, and it stops there.
245
+ - **No three-item list**, however tempting. The finding that triples are a human marker holds for
246
+ prose inside a document, and it inverts here.
247
+ - **Leave out what the thing covers.** The detail belongs in the body, which a listing page renders
248
+ directly underneath. Enumerating in the description says it twice.
249
+ - **Avoid coy abstraction.** "The style rules that turn out not to matter" withholds the content and
250
+ gestures at it. Either name the thing or leave it out.
251
+
252
+ One human description in the forty ran to 37 words. It did it as three separate sentences.
253
+
254
+ ## What to leave alone
255
+
256
+ These are the measured non-differences. Acting on them costs effort and makes the prose worse.
257
+
258
+ - **Sentence length.** The LLM corpus averaged 20.9 words per sentence and the human exemplars 19.8.
259
+ Long sentences were equally common in both (10.1% over 32 words against 11.7%). Do not chop
260
+ sentences into fragments for rhythm. The problem is what the clauses are doing, and the count is
261
+ beside the point.
262
+ - **Ordinary vocabulary.** The Django and Go docs use `powerful`, `robust`, `simply` and `crucial`
263
+ ten times more often than the LLM corpus did. The signal lives in sentence shape. Cut a
264
+ promotional **claim** where the text is selling instead of explaining, and leave the vocabulary
265
+ alone.
266
+ - **The rule of three.** In prose, the human exemplars write "X, Y and Z" nearly twice as often as
267
+ the LLM corpus does. The apparent signal came entirely from lists of API names, which are
268
+ legitimate enumerations. Leave triples alone.
269
+ - **Verbless list fragments.** A sentence made of comma-separated noun phrases with no main verb
270
+ ("The same construction over and over, every fact trailed by a clause, a fresh synonym where the
271
+ previous term would have done"). This one feels like a tic and measures at 1.0 times the human
272
+ rate. That is as close to no signal as the study found. Django and the Rust Book write them just
273
+ as often. A pile of them in one document is still worth breaking up, for the repetition and not
274
+ for the shape.
275
+ - **Long enumerations.** Four or more comma-separated items in a sentence run at 1.2 times, and five
276
+ or more at 1.4 times. Both sit under the two-times bar.
277
+ - **Parataxis.** The exemplars use it seven times more. Comma-spliced and juxtaposed clauses mark
278
+ the human corpus.
279
+ - **Trailing participles** (`, leaving nothing behind`, `, making it faster`). A well-known
280
+ suspicion that the measurement does not support. The exemplars use them twice as often.
281
+ - **Subordination in general.** Both corpora carry 1.4 clauses per sentence, and trailing
282
+ subordinate clauses separate them by only 1.25×. The tic is the narrow comma-plus-`so` collocation
283
+ on its own.
284
+ - **First person and anecdote.** Do not add either while rewriting. Inventing a voice is a different
285
+ failure.
286
+
287
+ If a passage still reads badly after these patterns are gone, the problem is more likely to be the
288
+ order of the material than the sentences.
289
+
290
+ ## Document shape
291
+
292
+ - No preamble. One sentence saying what the page covers, then the first thing the reader came for.
293
+ - Coverage lists ("Available functionality", "What's supported") go at the end, next to Limitations.
294
+ A list that only repeats the headings below it should not be written at all.
295
+
296
+ ## The audit pass
297
+
298
+ One pass is never enough. The patterns are reflexes, and they reappear in the replacements. Always
299
+ run a second pass over what has just been written.
300
+
301
+ 1. Write the prose.
302
+ 2. Run the checker on the file:
303
+
304
+ ```bash
305
+ node scripts/prose-check.mjs path/to/file.md
306
+ ```
307
+
308
+ That path is relative to this skill's own directory, wherever the skill was installed. Run it from
309
+ there, or prefix it with the directory holding this `SKILL.md`.
310
+
311
+ The checker strips code blocks, counts each pattern per 1000 words, compares against the exemplar
312
+ baseline, and prints the worst sentence for every pattern over threshold. Pass a directory to score
313
+ a whole tree, `--json` for machine-readable output, `--examples 5` for more samples.
314
+
315
+ A page that quotes bad prose deliberately can exclude it with `<!-- prose-check:off -->` and
316
+ `<!-- prose-check:on -->`. This file uses those markers around its own **Before** examples. Use them
317
+ for quoted material only, and never to silence a passage the checker is right about.
318
+
319
+ 3. Fix what it reports, then run it again. Aim for rates below `warn` on every pattern. Zero is the
320
+ wrong target. `warn` is the 90th percentile of the human corpus and `fail` sits above its
321
+ maximum. A `warn` means "at the top of the human range" and a `FAIL` means "outside it".
322
+
323
+ Prose that passes the checker can still be bad, and the checker has no opinion about whether the
324
+ content is correct or the page is in a sensible order. It catches the reflexes. Judgement is still
325
+ required for everything else.
326
+
327
+ ## Limits
328
+
329
+ It fails to make text pass as human-written, and it should not be sold that way.
330
+
331
+ Six documents were rewritten to satisfy every rule here and then submitted to Pangram, a commercial
332
+ detector. All six were still identified as AI. The mean score moved from 0.87 to 0.79, one document
333
+ scored worse after the rewrite, and two were pinned at the maximum in both states. The same detector
334
+ scored four human control documents at zero. The instrument was working.
335
+
336
+ Style and provenance are different signals. The patterns here are real differences between human and
337
+ LLM technical writing, and closing them makes prose plainer. A classifier keys on something else.
338
+ Anyone who wants concealment is holding the wrong tool, and it is worth saying so plainly to a user
339
+ who asks.
340
+
341
+ ## Related skills
342
+
343
+ [`avoid-ai-writing`](https://github.com/conorbronsdon/avoid-ai-writing) (MIT) catalogues about forty
344
+ patterns from a different register. Sycophantic tone, engagement-bait closers, rhetorical-question
345
+ openers, hashtag stuffing, chatbot artifacts and hedge stacking all appear in it. Reach for it when
346
+ the writing is a blog post, a release announcement, landing page copy or a social post. Once
347
+ installed, invoke it as `/avoid-ai-writing:avoid-ai-writing`.
348
+
349
+ The two overlap very little, and both are worth having for that reason. Its catalogue covers none of
350
+ the patterns above, and the corpus measured here scored near zero on its categories before any of
351
+ them were applied.
352
+
353
+ Where they disagree, this skill governs documentation. Its default target for em dashes is zero, and
354
+ the measurement here shows that ban moving the pattern onto colons without removing it. Its own
355
+ `docs` context profile already relaxes the em dash rule, so run it with that profile on anything
356
+ from `docs/`.