lessismore/grunts.py
type-two 34c9184065 lessismore: deterministic prompt compression + grunts history miner
Regex/stdlib passes (dedupe, alias, ANSI/timestamp/blob strip, JSON minify,
caveman word-drop) measured at 8.9-12.2x on real logs with o200k counts.
Optional LLMLingua-2 wrap. grunts.py mines Claude Code transcripts for
retyped prompts -> slash-command stubs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 13:43:00 +10:00

82 lines
3.0 KiB
Python

"""grunts — mine your Claude Code prompt history for your most-retyped grunts.
Your typed prompts are the cheapest tokens in the context, so shorthand the
MODEL decodes can never pay for its codebook (see README). But shorthand your
KEYBOARD expands is free: this finds what you actually retype, so the long
ones can become slash commands (~/.claude/commands/<name>.md).
python3 grunts.py # report
python3 grunts.py --emit # also write ./grunts/<slug>.md command stubs
"""
import json
import re
import sys
from collections import Counter
from pathlib import Path
_SKIP = ("<system-reminder>", "<command-name>", "<local-command",
"[Request interrupted", "Caveat: The messages below", "<task-notification>")
def user_messages(root=Path.home() / ".claude" / "projects"):
for f in root.glob("*/*.jsonl"):
for line in open(f, encoding="utf-8", errors="replace"):
# ponytail: substring prefilter before json.loads keeps 1GB scans fast
if '"type":"user"' not in line or '"isMeta":true' in line:
continue
if len(line) > 20000: # giant rows are pastes/images, never typed grunts
continue
try:
c = json.loads(line)["message"]["content"]
except (KeyError, TypeError, json.JSONDecodeError):
continue
if isinstance(c, list):
if any(p.get("type") == "tool_result" for p in c if isinstance(p, dict)):
continue
c = " ".join(p.get("text", "") for p in c if isinstance(p, dict))
if not isinstance(c, str):
continue
t = c.strip()
if t and len(t) <= 400 and not any(s in t for s in _SKIP):
yield t
def main():
msgs = list(user_messages())
norm = Counter(re.sub(r"\s+", " ", m.lower()) for m in msgs)
grams = Counter()
for m, n in norm.items():
words = m.split()
for k in (4, 5, 6):
for i in range(len(words) - k + 1):
grams[" ".join(words[i:i + k])] += n
print(f"{len(msgs):,} typed prompts scanned across {len(norm):,} distinct\n")
top = [(m, c) for m, c in norm.most_common(30) if c >= 3]
print("== most retyped whole prompts ==")
for m, c in top[:20]:
print(f"{c:5d}x {m[:90]}")
print("\n== most retyped phrases (4-6 words, not already above) ==")
shown = 0
for g, c in grams.most_common(300):
if c < 5 or any(g in m for m, _ in top[:20]):
continue
print(f"{c:5d}x {g}")
shown += 1
if shown == 15:
break
if "--emit" in sys.argv:
out = Path("grunts")
out.mkdir(exist_ok=True)
for m, c in top:
if len(m) < 25: # a short grunt is already faster to type than /slash
continue
slug = "-".join(re.findall(r"[a-z0-9]+", m)[:4])
(out / f"{slug}.md").write_text(m + "\n")
print("\nwrote stubs to ./grunts/ — copy keepers to ~/.claude/commands/")
if __name__ == "__main__":
main()