Home › Prompts › Community › Poetry Meter and Rhyme Checker

Poetry Meter and Rhyme Checker

Any chat AICreative writingCommunity (CC0)

Note: Review and test before use. Provided as is, without warranty. See Terms.

Prompt

Download  
---
name: poetry-meter-and-rhyme-checker
description: Counts syllables per line with a transparent heuristic, checks common forms (haiku, limerick, iambic pentameter targets), maps end-rhyme schemes (AABB, ABAB, ABCB), and flags near-misses with suggested edits. Use when a user asks "does this scan?", "is this a haiku?", or "what is my rhyme scheme?". Includes a tested stdlib Python checker.
---

# Poetry Meter and Rhyme Checker

You help writers see how a draft scans: syllables per line, a plausible stress sketch for English meter targets, and the end-rhyme scheme. You are honest that English syllable counting is heuristic, you show your work, and you suggest small edits instead of rewriting the poem unless asked.

## Files in this skill

- `scripts/poetry_check.py` - syllable estimates, form checks, rhyme-scheme detection (Python 3 standard library only)
- `references/forms-and-targets.md` - syllable and rhyme targets for common short forms
- `references/syllable-heuristic.md` - how the counter works and where it fails (names, -ed, diphthongs)
- `templates/poetry-report.md` - the report shape to return
- `examples/example-haiku-and-quatrain.md` - worked examples

## Workflow

### 1. Collect the facts
- The poem text (keep the user's line breaks).
- Target form if any: haiku, tanka, limerick, sonnet couplet, free verse with a rhyme check only, or custom syllable pattern.
- Dialect notes (US/UK spelling) when relevant to endings like `-our` / `-or`.

### 2. Run the checker
```bash
python3 scripts/poetry_check.py poem.txt
python3 scripts/poetry_check.py poem.txt --form haiku
python3 scripts/poetry_check.py poem.txt --form limerick --rhyme
python3 scripts/poetry_check.py - --json < poem.txt
```

### 3. Advise
1. Show a per-line syllable table and the detected rhyme scheme.
2. Say whether the form target passed, and which lines miss by how many syllables.
3. Offer minimal edits (swap a synonym, split a compound, drop a filler word) rather than a full rewrite.
4. Deliver with `templates/poetry-report.md`.

## Rules
- Always label syllable counts as estimates.
- Do not mock the writer's voice; keep suggestions optional.
- For free verse, still report syllables and rhyme if useful, without forcing a form.
FILE:references/forms-and-targets.md
# Forms and targets

| Form | Syllable target (lines) | Typical rhyme |
|---|---|---|
| Haiku (English teaching form) | 5 / 7 / 5 | usually unrhymed |
| Tanka | 5 / 7 / 5 / 7 / 7 | usually unrhymed |
| Limerick | ~8–9 / 8–9 / 5–6 / 5–6 / 8–9 | AABBA |
| Quatrain (ballad) | often 8 / 6 / 8 / 6 | ABCB or ABAB |
| Heroic couplet | ~10 / 10 (iambic pentameter feel) | AA |

Exact classical Japanese mora counts differ from English syllable teaching forms; this skill uses the common English classroom targets unless the user asks for mora-aware analysis.
FILE:references/syllable-heuristic.md
# Syllable heuristic (honest limits)

The script estimates English syllables by:
1. Lowercasing and stripping punctuation.
2. Counting vowel groups (`a e i o u y`) with common silent-`e` and `-ed` / `-es` adjustments.
3. Treating hyphenated compounds as the sum of parts.

## Known failure modes
- Proper names (`Olivia`, `Michael`) and loanwords.
- Words where `y` is consonantal (`beyond`) vs vocalic (`rhythm` is still hard).
- `-ism`, `-tion`, and compressed pronunciations (`family` as 2 or 3).

When a count looks wrong, say so and offer a manual override in the report.
FILE:templates/poetry-report.md
# Poetry meter and rhyme report

## Target form
- …

## Per-line table
| # | Line | Syllables (est.) | End word | Rhyme letter |
|---|---|---|---|---|

## Form check
- Pass / fail: …
- Misses: …

## Rhyme scheme
- …

## Minimal edit ideas
- …

## Notes on uncertain counts
- …
FILE:examples/example-haiku-and-quatrain.md
# Examples

## Haiku (should pass 5-7-5)
```
An old silent pond
A frog jumps into the pond—
Splash! Silence again.
```
Estimated syllables often land near 5 / 7 / 5 depending on `silent` and `silence`.

## Quatrain rhyme ABCB
```
The woods are lovely, dark and deep,
But I have promises to keep,
And miles to go before I sleep,
And miles to go before I sleep.
```
End words deep/keep/sleep/sleep → AAAA if all rhyme together; a true ABCB sample should use different B and C words.
FILE:scripts/poetry_check.py
#!/usr/bin/env python3
"""Estimate syllables, check simple forms, and map end-rhyme schemes.

Usage:
  python3 poetry_check.py poem.txt
  python3 poetry_check.py poem.txt --form haiku
  python3 poetry_check.py poem.txt --form limerick --rhyme
  python3 poetry_check.py - --json < poem.txt
"""
from __future__ import annotations

import argparse
import json
import re
import sys
from pathlib import Path

FORMS = {
    "haiku": [5, 7, 5],
    "tanka": [5, 7, 5, 7, 7],
    "limerick": None,  # soft ranges handled separately
}

VOWELS = set("aeiouy")


def strip_word(w: str) -> str:
    return re.sub(r"[^a-zA-Z']", "", w).lower()


def syllables_word(word: str) -> int:
    w = strip_word(word)
    if not w:
        return 0
    # common overrides
    overrides = {
        "poem": 2, "poems": 2, "fire": 1, "hour": 1, "our": 1, "//": 0,
        "family": 3, "every": 3, "different": 3, "interesting": 4,
        "rhythm": 2, "business": 2, "wednesday": 2, "beautiful": 3,
    }
    if w in overrides:
        return overrides[w]
    if "-" in word:
        return sum(syllables_word(p) for p in word.split("-"))
    # remove trailing silent e
    base = w
    if base.endswith("e") and not base.endswith(("le", "ue", "oe")) and len(base) > 2:
        base = base[:-1]
    # -ed endings: walked=1 extra often silent
    if re.search(r"[^aeiou]ed$", w) and not w.endswith(("ted", "ded")):
        base = re.sub(r"ed$", "", w)
        if base.endswith("e"):
            base = base[:-1]
    groups = re.findall(r"[aeiouy]+", base)
    count = len(groups) if groups else 1
    # adjust ion/ial
    if w.endswith(("tion", "sion", "cial", "tial")):
        count = max(count, 2)
    return max(1, count)


def end_word(line: str) -> str:
    words = [w for w in re.findall(r"[A-Za-z']+", line)]
    return words[-1].lower() if words else ""


def rhyme_key(word: str) -> str:
    w = strip_word(word)
    if not w:
        return ""
    aliases = {
        "you": "oo", "blue": "oo", "too": "oo", "two": "oo", "to": "oo", "through": "oo",
        "threw": "oo", "shoe": "oo", "who": "oo", "new": "oo", "knew": "oo", "true": "oo",
        "lee": "ee", "tea": "ee", "glee": "ee", "sea": "ee", "me": "ee", "we": "ee", "be": "ee",
        "green": "een", "keen": "een", "seen": "een", "mean": "een",
    }
    if w in aliases:
        return aliases[w]
    matches = list(re.finditer(r"[aeiouy]+", w))
    if not matches:
        return w
    start = matches[-1].start()
    if len(matches) >= 2 and len(w) - start < 2:
        start = matches[-2].start()
    tail = w[start:]
    # light normalization for common English end-rhymes
    for a, b in (
        ("igh", "i"), ("ea", "ee"), ("ee", "ee"), ("ey", "ee"), ("ie", "ee"),
        ("ay", "ei"), ("ai", "ei"), ("ou", "ow"), ("oo", "u"), ("ue", "u"),
    ):
        if a in tail:
            tail = tail.replace(a, b)
    return tail


def rhyme_scheme(end_words: list[str]) -> str:
    mapping = {}
    letters = []
    next_ord = 0
    for w in end_words:
        key = rhyme_key(w)
        if not key:
            letters.append("-")
            continue
        # near-rhyme: same key
        if key not in mapping:
            # also merge if share last 2 chars and vowel
            found = None
            for k, let in mapping.items():
                if k == key or (len(k) >= 2 and len(key) >= 2 and k[-2:] == key[-2:]):
                    found = let
                    mapping[key] = let
                    break
            if found is None:
                let = chr(ord("A") + next_ord)
                next_ord += 1
                mapping[key] = let
        letters.append(mapping[key])
    return "".join(letters)


def limerick_ok(counts: list[int]) -> tuple[bool, str]:
    if len(counts) != 5:
        return False, f"expected 5 lines, got {len(counts)}"
    ranges = [(7, 10), (7, 10), (4, 7), (4, 7), (7, 10)]
    misses = []
    for i, (c, (lo, hi)) in enumerate(zip(counts, ranges), 1):
        if not (lo <= c <= hi):
            misses.append(f"line {i}: {c} not in {lo}-{hi}")
    return (not misses), ("; ".join(misses) if misses else "ok")


def analyze(text: str, form: str | None) -> dict:
    lines = [ln.rstrip() for ln in text.splitlines() if ln.strip()]
    rows = []
    counts = []
    ends = []
    for i, ln in enumerate(lines, 1):
        words = re.findall(r"[A-Za-z']+(?:-[A-Za-z']+)*", ln)
        sc = sum(syllables_word(w) for w in words)
        ew = end_word(ln)
        counts.append(sc)
        ends.append(ew)
        rows.append({"n": i, "line": ln, "syllables": sc, "end": ew})
    scheme = rhyme_scheme(ends)
    form_result = None
    if form == "limerick":
        ok, detail = limerick_ok(counts)
        rhyme_ok = scheme == "AABBA"
        form_result = {"form": form, "pass": ok and rhyme_ok, "detail": detail,
                       "rhyme_expected": "AABBA", "rhyme_got": scheme}
    elif form in FORMS and FORMS[form]:
        target = FORMS[form]
        if len(counts) != len(target):
            form_result = {"form": form, "pass": False,
                           "detail": f"expected {len(target)} lines, got {len(counts)}",
                           "target": target, "got": counts}
        else:
            misses = [f"line {i}: got {g}, want {t}" for i, (g, t) in enumerate(zip(counts, target), 1) if g != t]
            form_result = {"form": form, "pass": not misses, "detail": "; ".join(misses) or "ok",
                           "target": target, "got": counts}
    return {"lines": rows, "rhyme_scheme": scheme, "form_check": form_result}


def main(argv: list[str]) -> int:
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument("path", help="Poem file, or - for stdin")
    ap.add_argument("--form", choices=["haiku", "tanka", "limerick"], help="Form to validate")
    ap.add_argument("--rhyme", action="store_true", help="Always print rhyme scheme (default on)")
    ap.add_argument("--json", action="store_true")
    args = ap.parse_args(argv)
    text = sys.stdin.read() if args.path == "-" else Path(args.path).read_text(encoding="utf-8")
    result = analyze(text, args.form)
    if args.json:
        json.dump(result, sys.stdout, indent=2)
        sys.stdout.write("\n")
    else:
        print("n | syl | end | line")
        for r in result["lines"]:
            print(f"{r['n']:<2}| {r['syllables']:<3}| {r['end']:<12}| {r['line']}")
        print(f"rhyme scheme: {result['rhyme_scheme']}")
        fc = result["form_check"]
        if fc:
            print(f"form {fc['form']}: {'PASS' if fc['pass'] else 'FAIL'} ({fc['detail']})")
            if "rhyme_expected" in fc:
                print(f"  rhyme expected {fc['rhyme_expected']}, got {fc['rhyme_got']}")
    if result["form_check"] and not result["form_check"]["pass"]:
        return 1
    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))

How to use

More: all creative writing prompts · all community prompts · prompt library

Related prompts

Writing coach for your manuscript

Get coaching on a chapter (pacing, character, tension) without the AI rewriting your work.

Any chat AICreative writing

Worldbuilding bible for an original setting

Develop an original world's geography, cultures, conflicts and rules, consistent and ready for stories or games.

ChatGPTCreative writing

Short story with a strong voice

An original short story built from a premise, point of view and constraint, with scene-level craft.

ClaudeCreative writing

Poem in a specific form

An original poem in a chosen form (sonnet, villanelle, ghazal, haiku sequence) with notes on the form's rules.

GeminiCreative writing

Comedy sketch from a premise

A short sketch with a clear game, escalation and button, built from an everyday premise.

GrokCreative writing

Agility Story (Fabric pattern)

Agility Story (Fabric pattern): IDENTITY and PURPOSE You are an expert in the Agile framework. You deeply understand user story and acceptance…

Any chat AICreative writingCommunity (MIT)

Get new rules weekly

One email a week: fresh rule files, agent tips, and what's changing in Cursor, Claude Code and Copilot. Free, unsubscribe anytime.

Opens the Rulestack Newsletter on beehiiv in a new tab. Or subscribe directly on beehiiv.