Skip documentation navigation

Pattern packs

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:

KeyValue
ctx.pathThe file path, or null for a reply, commit message, gh body, or command.
ctx.scopeThe scope name the text came from.
ctx.optionsThe pack’s options, merged with features.aiWriting.options.<packId>.
ctx.rawThe 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.textThe 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.