An .mjs pack
Save this as .claude/concise/patterns/long-sentences.mjs:
export default {
id: "long-sentences",
feature: "aiWriting",
category: {
id: "long-sentences",
label: "long sentences",
description: "Flags a sentence past the word limit.",
tags: ["statistical"],
},
scope: ["files", "gh", "reply"],
presets: ["ryan", "statistical", "all"],
options: { minWords: 100, maxSentenceWords: 40 },
notes: "One finding per sentence over the limit.",
detect(text, ctx) {
const o = ctx.options;
if (ctx.stats.words().length < o.minWords) return [];
return ctx.stats
.sentences()
.filter((s) => s.text.split(/\s+/).filter(Boolean).length > o.maxSentenceWords)
.map((s) => ({
index: s.start,
match: `sentence of ${s.text.split(/\s+/).filter(Boolean).length} words (max ${o.maxSentenceWords})`,
fix: "split the sentence",
}));
},
};
detect(text, ctx) returns an array of { index, match, fix } findings. index is a character offset into text and has to be an integer. match is the flagged text printed in the deny message. fix defaults to rewrite. A finding with a missing index or match is dropped. A detect that throws is reported as a pack problem and the rest of the scan continues.
ctx holds:
| Key | Value |
|---|---|
ctx.path | The file path, or null for a reply, commit message, gh body, or command. |
ctx.scope | The scope name the text came from. |
ctx.options | The pack’s options, merged with features.aiWriting.options.<packId>. |
ctx.raw | The text before code spans, fences, comments, and URLs are blanked. It has the same length as ctx.stats.text, so an index into one is an index into the other. |
ctx.stats.text | The text being scanned. |
ctx.stats.words() | { text, start, end } per word. |
ctx.stats.sentences() | { text, start, end } per sentence. |
ctx.stats.paragraphs() | { text, start, end } per paragraph. |
ctx.stats.lines() | { text, start, end } per line. |
ctx.stats.listBlocks() | { start, end, items } per list block, items holding { text, start, end, marker }. |
ctx.stats.headings() | { text, start, end, level, title } per markdown heading. |
ctx.stats.syllables(word) | The syllable count for one word. |
Each ctx.stats helper is memoized per scan.