Natural-voice linter: flags phrasing that addresses the prompter instead of
the document's actual reader. See references/natural-voice.md.
Usage: python3 voice_lint.py <file.md> [file2.md ...]
Always exits 0 — advisory only, same as prose_lint.py's non-link findings.
# Phrasing that only makes sense if the reader were the person who wrote
# the prompt. Deliberately narrow — each entry has ~no legitimate use in a
# document meant for someone else to read cold.
r"\bhere'?s the (document|guide|report) you (requested|asked for)\b",
r"\bas (you|per your) (requested|asked)\b",
r"\bI'?ve (created|written|put together) this (document|guide|report) (to|for)\b",
r"\bI hope this (helps|guide helps|document helps)\b",
r"\bbased on (your|the) feedback\b",
r"\bnote: I added this\b",
r"\blet me know if you (need|want|have)\b",
TELL_RE = re.compile("|".join(PROMPTER_TELLS), re.IGNORECASE)
# A bullet that is entirely a quoted phrase is a cited example ("don't write
# this"), not live prose — natural-voice.md's own bad-example lists look
# exactly like this. Skip them the same way a direct quote is exempt from
QUOTED_EXAMPLE_RE = re.compile(r'^\s*-\s*".*"\.?\s*$')
text = open(path, encoding="utf-8").read()
for i, line in enumerate(text.splitlines(), start=1):
if QUOTED_EXAMPLE_RE.match(line):
hits.append((i, m.group(0), line.strip()[:100]))
print("Usage: voice_lint.py <file.md> [file2.md ...]")
for lineno, phrase, ctx in hits:
print(f" line {lineno}: reads like it's addressing the "
f"prompter, not the reader — {phrase!r}")
print("\nAdvisory — see references/natural-voice.md. "
"Rewrite for the document's actual reader.")
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))