Skip to main content

Your AGENTS.md should grow from your scars, not from a template. A local-first Claude Code companion.

Project description

FrictionLog

Your AGENTS.md should grow from your scars, not from a template. A local-first Claude Code companion.

PyPI License: MIT Python 3.11+

FrictionLog watches your Claude Code sessions and only records correction moments — the times you rolled back an edit, told the AI "no, do X instead," or renamed something it just created. Every week it writes you a markdown digest with your top recurring frictions, drafts AGENTS.md rules you can copy-paste, and flags rules that have gone 30 days without a single match — so your rule set can shrink as well as grow.

It does not analyze your code. It does not phone home. It does not tell you how to be a better engineer.


30-second install

# uv (recommended)
uv tool install frictionlog

# or pipx
pipx install frictionlog

Then, inside your Claude Code project:

cd path/to/your/project
fl init

That's it. Now use Claude Code normally. Seven days later, look in ~/.frictionlog/digest/ for your first weekly digest.


The five commands you actually use

Command What it does
fl init Inject FrictionLog hooks into .claude/settings.local.json. Run once per project.
fl recent List recent friction events, newest first. --days N to widen the window.
fl pattern Show your Top-5 recurring friction categories over the last 30 days.
fl pending Show events FrictionLog flagged but you haven't confirmed yet.
fl digest Generate this week's digest now. fl digest open opens it. fl digest --force bypasses the 7-day cooldown.

There are more (fl confirm, fl status, fl ack, fl share-stats, fl uninstall). Run fl --help to see them.


How it works (60 seconds)

  1. fl init injects two hooks into .claude/settings.local.json:
    • PostToolUse — fires after every Edit / Write / Bash / MultiEdit.
    • UserPromptSubmit — fires when you send Claude your next message.
  2. The PostToolUse hook records the tool call to a local SQLite, runs a fast heuristic ("did the user just undo something?"), and writes one line to stderr if it suspects a correction:
    💡 [FrictionLog] 检测到可能纠正:撤销 try-except. 下条消息以 fl:y / fl:n 开头确认。
    
  3. Start your next message with fl:y to confirm, fl:n to reject, fl:i to ignore. The prefix is stripped before Claude sees the message — you only "pay" 4 characters.
  4. Every 7 days, the hook silently spawns a background worker that scans your AGENTS.md, matches recent confirmed frictions, and writes ~/.frictionlog/digest/YYYY-MM-DD.md.
  5. Read the digest, run fl ack. Connect 4 weeks in a row and you've hit the north-star.

The privacy contract (three rules, no asterisks)

  1. Nothing leaves your machine without your explicit command. No telemetry, no auto-upload, no "anonymous usage stats." The only thing that calls out is the LLM SDK — and only when you've put your own key in ~/.frictionlog/config.toml.
  2. The only place data lives is ~/.frictionlog/. SQLite + markdown files. Uninstall = delete that folder (FrictionLog won't touch it for you).
  3. fl share-stats is the only way to share anything, and it just opens gist.github.com/new with a pre-rendered markdown of counts only — no event summaries, no project paths. You copy. You paste. You publish. We never see it.

If you're worried about the LLM hop: don't configure an LLM key. The heuristic-only path still works — you just lose the "second-pass classification" and the weekly rule-draft generation. The digest still ships with your Top-5.


Where things live

~/.frictionlog/
├── db.sqlite              ← events, digests, rules, share_history
├── config.toml            ← your LLM key (you create this; FrictionLog doesn't)
├── digest/
│   └── 2026-05-24.md      ← weekly digests
├── queue/
│   └── pending_llm.jsonl  ← async LLM classification queue
└── logs/
    └── 2026-05-18.log     ← rolling 7-day hook log

Configuring an LLM key (optional)

Create ~/.frictionlog/config.toml:

[llm]
provider = "deepseek"
base_url = "https://api.deepseek.com"
api_key = "sk-..."
classify_model = "deepseek-chat"
draft_model = "deepseek-reasoner"

Any OpenAI-compatible provider works — just point base_url at it. The hook never calls the LLM directly; classification runs in an async batch worker so you never wait for it.

Without a key, FrictionLog runs heuristic-only:

  • ✅ Events recorded
  • ✅ Stderr nudge + fl:y/n confirmation works
  • ✅ Weekly digest still ships with Top-5
  • ❌ No LLM second-pass classification (heuristic is the only judge)
  • ❌ No LLM-generated rule drafts in the digest

Frequently asked

"Does this slow Claude Code down?" No. Hook function-body p90 is ~26ms; the OS process spawn adds ~300ms but that's invisible next to the tool call itself (1-5s). LLM calls happen in a detached background process.

"What if I have other hooks in .claude/settings.json?" fl init writes to .claude/settings.local.json (which Claude Code gitignores) instead, so it doesn't touch the shared file. It also detects and merges around any existing hooks. fl uninstall only removes the blocks FrictionLog added.

"Does it work on Windows?" Yes. Windows is a first-class target. CI runs on macOS + Ubuntu + Windows × Python 3.11 + 3.12.

"What if my AGENTS.md is in a weird format?" FrictionLog parses bullet (-, *, +) and numbered (1.) lists outside of code fences and headers. Anything else is skipped. If your rule list isn't structured that way, the "stale rules" section just stays empty — nothing breaks.

"What gets sent to my LLM provider?" Per event: heuristic category, tool name, a short sanitized tool-input summary (file basename only, never the path), and your next user message truncated to 300 chars. Per digest: the Top-5 category names, counts, and one example summary each. No file contents. No project paths.


Uninstall

fl uninstall          # removes the hook block from .claude/settings.local.json
rm -rf ~/.frictionlog/  # optional — your data and digests live here
uv tool uninstall frictionlog

fl uninstall deliberately leaves ~/.frictionlog/ alone. If you want to come back later, your event history is still there.


Status

Current version: v0.1.0 — MVP launch. Tested on Python 3.11 / 3.12 across macOS / Ubuntu / Windows. 297 tests in CI.

Roadmap:

Milestone Date What
M4 (current) 2026-07-09 v0.1.0 public on PyPI
M5 2026-10-09 3-month north-star review
v0.2 TBD Cursor + Codex CLI adapters, web view

Contributing

See docs/CONTRIBUTING.md. Bugs and feature requests welcome at GitHub Issues.

For deeper details: docs/PRIVACY.md (the long version of the privacy contract), docs/TROUBLESHOOTING.md (hook not firing, sqlite locked, LLM config), docs/CHANGELOG.md (version history).


License

MIT. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

frictionlog-0.1.0.tar.gz (199.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

frictionlog-0.1.0-py3-none-any.whl (67.3 kB view details)

Uploaded Python 3

File details

Details for the file frictionlog-0.1.0.tar.gz.

File metadata

  • Download URL: frictionlog-0.1.0.tar.gz
  • Upload date:
  • Size: 199.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for frictionlog-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f629a80ebd187e93083b2fc47246ff54d74a1ac932f29cf6074e89101173934a
MD5 f3c8434f945c1e16a950e0af29dba635
BLAKE2b-256 30a91d92d995f9e5b9d350643a9914a55053e6a13bbfda3533bb2fc588d02c79

See more details on using hashes here.

File details

Details for the file frictionlog-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: frictionlog-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 67.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for frictionlog-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 463079035260f60d38b72adc488a54dde4e3d95afcfcb6eaa9d5573ac3cf19e9
MD5 535b810c0b0bd85434bb2e6aa1a5d843
BLAKE2b-256 d6b1670eeb70cd106e2d1de8bd4aa03672d79d6cc3cca95250cf9c33b7ebf011

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page