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.1.tar.gz (200.9 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.1-py3-none-any.whl (67.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: frictionlog-0.1.1.tar.gz
  • Upload date:
  • Size: 200.9 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.1.tar.gz
Algorithm Hash digest
SHA256 b6d6f83f0d1e8ad84ba03a718e8e68578bcdf1b250dfb86def69abf83cf303bd
MD5 851f185a860956e23e5882ca52f75443
BLAKE2b-256 bd97450edff7d80793fde1ac5e4d25c383a4dd93976dc186c01ad67fd75b9858

See more details on using hashes here.

File details

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

File metadata

  • Download URL: frictionlog-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 67.5 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 3dcb4647fd5a780e4d5bd8c6d3294be58d331d15bf0f3bfd30193ac7b7a109b3
MD5 f7832a22fdc7bd237bb2e931cc716365
BLAKE2b-256 74a6c7f60944ee5916a22fb70f4e9dbc82d29a18f7dc0e9191bc99b6123adaf5

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