Skip to main content
Glama

Feedback Graph

Your coding agent keeps making the same mistakes. Feedback Graph remembers the corrections.

How Feedback Graph works: sessions and reviews become lessons you confirm and then approve (or the learning loop does, once it has earned it), agents read them before work and before "done", and the learning loop tidies the graph with every change audited

Every time you tell Claude Code or Codex "that's not good enough" or "you missed X", the lesson dies with the session. Feedback Graph runs on your machine, reads the sessions of the project folders you choose, finds the moments you pushed back, works out what was missed and why, and, once the lesson is approved (by you, or by the learning loop once it has earned it), hands it to the next agent session before it starts and again before it says "done".

  • Local first. Runs in Docker with a local model (Ollama) by default. Nothing leaves your machine unless you configure a provider.

  • You decide what is learned. Every flagged moment waits for your confirmation, and every lesson for your approval, unless your own verdicts have earned the learning loop the right to make that kind of call (approving a lesson, confirming an Inbox item). The loop runs in auto by default and asks you only what it cannot decide, each with why; see The learning loop.

  • Evidence, not vibes. Each lesson links back to the feedback, the miss, its cause, the code it touched and the fix.

  • Works with your agents through MCP and a skill: context before a task, a check before "done".

  • What people said, too (opt-in). Hand it a call transcript or notes and it proposes the claims and open questions in them, with who said each, flags conflicts with what agents are told, and answers "what do we know about X". Agents see only the claims you accept; see Knowledge intake.

Apache-2.0 · Operations · Security · Changelog · Contributing · Editions · Guide

Quickstart

You need Docker, git and Ollama on the host. Node and pnpm are not needed to run it: the scripts use Node when it is there, else Docker's Node image, and ./scripts/fg runs inside the api container (Node and pnpm are for developing it). Claude Code or Codex is needed for the agent hookup in the last step. Start Ollama first (the app, or ollama serve): ollama pull talks to the running server.

git clone https://github.com/ChaosDeSelva/Feedback-Graph.git feedback-graph
cd feedback-graph
ollama pull gemma4 && ollama pull nomic-embed-text   # with Ollama running
./scripts/setup.sh          # asks which folders hold your projects, writes .env with generated passwords, checks Ollama,
                            # then offers to start the stack (docker compose up -d --build) and to connect Claude Code and Codex
./scripts/fg signin         # prints a one-time link that signs your browser in to the Dashboard (http://localhost:8787 unless API_PORT moves it)

In the Dashboard's Settings, add project folders and choose the projects to read. If you declined setup's start, run docker compose up -d --build; if you declined connecting agents, run ./scripts/install-agent.sh (see Connect your agents; --dry-run shows what it would change).

First value in ten minutes, with no history

You do not need past sessions or pushback to see it work. --demo adds this repo's examples/ folder as a project folder, selects demo-shop, starts the stack and seeds demo-shop's demo lessons (fg demo seed, all marked demo):

./scripts/setup.sh --yes --demo
./scripts/fg demo context                       # the lessons an agent is given before it edits demo-shop's routes
./scripts/fg demo check                         # check_before_done catches a console.log a lesson forbids, citing the line
./scripts/fg activity --project demo-shop --limit 5   # what was served and checked, and why

./scripts/fg demo remove takes the demo lessons back. Your own first lesson, in a project you selected, takes four steps and no history either:

  1. In your agent (connected by setup or install-agent.sh), say what it got wrong and the rule, e.g. "record a miss: you added a route without a cross-tenant test; every route needs one", or type /postmortem ... in Claude Code. The agent calls report_feedback.

  2. Inbox: confirm the item. The post-mortem runs (a minute or so with the local model).

  3. Lessons: read the proposed lesson, edit it if needed, and approve it.

  4. Ask the agent for work on those files: get_task_context serves the lesson, and check_before_done checks the finished work against it.

Pushback you type in a session is picked up from its transcript on its own; the Inbox then holds it for step 2.

To feed it what people said as well, turn on knowledge intake (INTAKE_ENABLED=1 in .env, then docker compose up -d), add a call or notes and ask about it:

./scripts/fg ingest - --project shop --kind notes --wait < notes.md   # read into proposed claims and questions; accept them on the Intake page
./scripts/fg know "retry policy"                                      # what is known about it, who said it, and what is disputed

Something not working? ./scripts/doctor.sh checks Docker, ports, Ollama (including that structured output works: on macOS install the Ollama app, not the Homebrew formula), the mounts, the Codex login and GitHub and Linear, and says how to fix each failure. For an unattended install: ./scripts/setup.sh --yes --pull-models --projects ~/code [--github-token-file F] [--linear-key-file F] [--claude-token-file F].

setup.sh asks first for Project folders, comma-separated. There is no default: Enter mounts none, and you add them later in Settings. On a first run it then asks which projects to read, and offers the models, a claude setup-token (its shape checked before it is saved), the API, owner and operator tokens, the stack start and agent registration. It says every .env value it changes. On an existing install, --yes adds only a missing owner token, and only when API_TOKEN is set. A tokenless install is a read-only install for owner actions: ./scripts/setup.sh --yes --enable-auth adds all three tokens (outside a terminal --enable-auth needs --yes), then re-run ./scripts/install-agent.sh so agents get the API token.

Sign in with ./scripts/fg signin. It prints a one-time link that signs this machine's browser in as the owner: it works once, for 60 s, and only at localhost, so the owner token never leaves .env. Pasting FG_OWNER_TOKEN into the sign-in card works too. A sign-out survives API restarts, a restore from backup signs every browser out, and rotating FG_OWNER_TOKEN ends all browser sessions.

To change folders later, use Settings and rerun setup. To see the whole loop without your own projects, try the demo project.

  • setup.sh suggests the folders your Claude Code sessions ran in (PROJECTS_DIRS=~/code,~/work ./scripts/setup.sh answers it ahead). docker-compose.yml mounts no project folder: setup.sh writes a generated docker-compose.override.yml with exactly the folders you chose (none is fine: everything starts, with nothing to read), so docker-compose.yml stays as shipped. It also creates ./state, ~/.claude/projects (or CLAUDE_PROJECTS_DIR) and ~/.codex/sessions (or CODEX_SESSIONS_DIR) if missing (Docker would create them owned by root); an empty ~/.codex/sessions does not count as Codex being installed. On Linux it sets HOST_UID/HOST_GID so the containers run as you; see docs/operations.md for that and Ollama's OLLAMA_HOST.

  • Port 8787 taken? Set API_PORT in .env: the UI is then at http://localhost:<API_PORT> and the MCP server at http://localhost:<API_PORT>/mcp. setup.sh prints the right address, and install-agent.sh registers it (re-run it after a change, removing the old registration as it tells you).

  • The scripts (setup.sh, doctor.sh, fg, backup.sh, restore.sh, install-agent.sh) act only on this checkout's install. Once .env exists, its FG_INSTANCE names the install: they stop when FG_INSTANCE in your shell names another one, and they ignore a shell's COMPOSE_PROJECT_NAME and COMPOSE_FILE. A second install on one machine: docs/install.md.

  • Neo4j browser: http://localhost:7476, bolt on 7689 (NEO4J_HTTP_PORT and NEO4J_BOLT_PORT in .env move them; both bound to 127.0.0.1).

  • Transcripts (~/.claude/projects and Codex's ~/.codex/sessions) and the project folders you chose are mounted read-only; nothing else from your home folder. The bundled ./examples folder is a project root only if you add it (setup.sh --demo does).

  • Nothing is read until you choose which project folders to include (Settings). The worker lists every folder under your project roots with how many Claude Code and Codex sessions ran in it, and reads only the ones you select. A folder that is not a git repo but holds repos (~/src/github.com/org/repo, ~/code/client/app) lists each repo as its own project (github.com__org__repo); sessions belong to the deepest repo they ran in. A Claude Code session started in the folder that holds your projects is that project's from the turn it starts working in one (as <id>#2 when it moves on to another); one that never left that folder is listed under Outside the roots in Settings > Projects.

  • Secrets are redacted before anything is stored or sent to a model, and secret files are never read (see SECURITY.md).

  • Health, backups, upgrades, running it behind TLS with a token: docs/operations.md.

Related MCP server: MIHAD

Mac app

On a Mac, a small menu bar app can watch and drive your install: a dot shows whether Feedback Graph is running (green), starting or stopping (orange), stopped (grey) or has a problem (red, with what is wrong), and its menu and window start, stop and restart it, open the dashboard signed in, pause or resume the learning loop and show the proposals waiting, run a health check (./scripts/doctor.sh, beside the Docker engine and Ollama as its last status check saw them), back up, show logs and upgrade. It needs macOS 13 or newer and the same Docker engine (OrbStack, Docker Desktop or Colima) and Ollama as the stack; it bundles neither. Build it from a checkout (the Xcode Command Line Tools, macOS 14 or newer to build):

./scripts/build-mac-app.sh   # dist/Feedback Graph.app
./scripts/build-dmg.sh       # dist/Feedback-Graph-<version>.dmg: open it and drag the app to Applications

It uses the folder you chose (Choose Install Folder…) or that Get Feedback Graph… cloned (it clones this repository and opens Terminal on ./scripts/setup.sh); with neither, ~/Code/feedback-graph when that folder is an install. A saved folder that is no longer an install leaves it with none until you choose again. Quitting it leaves Feedback Graph running. What it runs, what it reads and how to sign a build to share: docs/install.md.

Connect your agents

The plugin in plugin/ bundles the MCP server, the skills and hooks that use the graph without being asked:

/plugin marketplace add ChaosDeSelva/Feedback-Graph
/plugin install feedback-graph@feedback-graph
  • On each request that asks for a change, the task's approved lessons, the past misses on the files it names and the AGENTS.md and CLAUDE.md files on their path are added to the prompt, in about 1,500 characters (a longer lesson is shown whole). The first time in a session it adds them all; after that, only a lesson, miss or instructions file not yet shown.

  • Before the first edit of a file, that file's lessons and past misses, when it has any.

  • Before it stops, once: if it changed files and has not run check_before_done since, it is asked to.

  • After /clear or a compaction, what was shown is forgotten, so it is shown again.

  • Skills: feedback-graph, /postmortem, and test-from-feedback, which turns lessons and past misses into test cases for QA.

Hooks only act in folders you selected in Settings and stay silent when the Feedback Graph is unreachable. Set FEEDBACK_GRAPH_URL and FEEDBACK_GRAPH_TOKEN (your API_TOKEN) in your shell for a non-default install; FEEDBACK_GRAPH_HOOKS=off turns the hooks off. Use the plugin or install-agent.sh claude, not both. With the plugin installed, install-agent.sh registers no second server for the default install (another FG_INSTANCE still gets its own, feedback-graph-<name>, beside the plugin's): it writes this install's URL and API_TOKEN into the plugin's environment in ~/.claude/settings.json (restart Claude Code). Switching to the plugin from an earlier install-agent.sh setup: claude mcp remove feedback-graph --scope user and rm ~/.claude/skills/{feedback-graph,postmortem,test-from-feedback} first.

install-agent.sh (Codex, or Claude Code without hooks)

./scripts/install-agent.sh sets up every coding agent it finds; ./scripts/install-agent.sh claude or ... codex sets up one. It registers http://localhost:<API_PORT>/mcp from .env (8787 by default); point it at another install with FEEDBACK_GRAPH_URL=https://feedback.example.com. Without that it refuses a checkout that is not set up (no .env, or FEEDBACK_GRAPH_ENV naming a missing file), which would register the default port and repoint the skills at an unconfigured folder, and one whose install name (FG_INSTANCE) another folder's running containers use. When .env sets API_TOKEN, the MCP server is registered with it as a Bearer header. A second install on this machine (its own FG_INSTANCE) is registered as feedback-graph-<name> and leaves the first install's skill links alone.

  • Claude Code (claude on your PATH): the feedback-graph MCP server at user scope (claude mcp add --transport http), and the feedback-graph, postmortem and test-from-feedback skills linked into ~/.claude/skills. Type /postmortem what it missed to record a miss. The skills are symlinks into this folder, so a git pull updates them; if you move or delete this folder, re-run the script (or remove the links).

  • Codex (codex on your PATH, or a config.toml or auth.json in ~/.codex; CODEX_HOME is respected): a [mcp_servers.feedback-graph] table in ~/.codex/config.toml with the streamable HTTP url (and http_headers carrying the token, after which the file is made readable by you only), and the same guidance as the skill in ~/.codex/AGENTS.md between <!-- BEGIN feedback-graph --> and <!-- END feedback-graph --> markers. Ask Codex to record a miss for the post-mortem path.

codex exec (Codex without a person to approve) refuses MCP tool calls unless the server allows them: ./scripts/install-agent.sh codex --codex-auto-approve adds default_tools_approval_mode = "approve" to the table (in a terminal it asks); a mode you set to another value is kept, and it says so. --dry-run prints what it would change and changes nothing; --help lists the options. Re-running refreshes this install's registration with its current URL and token, rewrites the marked guidance only when needed, and backs up a Codex config once before changing it (a linked config.toml or AGENTS.md is written through; a link loop or a link into a missing folder is refused), keeping any other headers you added. It changes nothing and exits 1 when config.toml declares this install's server other than as a [mcp_servers.<name>] table, or would not parse after the change (checked with python3's tomllib, 3.11+; without it, a config.toml with multi-line strings or arrays is refused, and an edit of one without says it was not checked), or when a guidance BEGIN marker has no END marker of its own; a skill folder of your own in ~/.claude/skills is left alone and reported as skipped.

Agents and scripts

The MCP tools only read and report (with knowledge intake on, submit_notes also hands you notes to review). Decisions (choosing projects, scanning, confirming Inbox items, approving lessons) are yours in the UI. To let an agent or a script make some of them, set FG_OPERATOR_TOKEN in .env (openssl rand -hex 24) and restart. A request carrying it as a bearer may make the decisions OPERATOR_ALLOW lists; by default that leaves out deciding lessons, choosing which projects are read, deleting data (links included), settings, imports, integration keys, asking in Ask (which spends your subscription), deciding on or configuring the learning loop (loop_decide, loop_settings), and accepting claims or settling conflicts (claims_decide, conflict_decide), so what agents learn stays yours. It may clean up the runs it opened itself (fg cleanup --run <id>). Writes made with it show in Activity as operator:<name>.

./scripts/fg is a small CLI for it. It runs on the host's Node (22.18+ or 23.6+, after pnpm install), else inside the api container (docker compose exec api), and only in a checkout that is set up and runs its own containers:

./scripts/fg status --watch          # what the worker is doing, models, connections
./scripts/fg projects select --key shop,api
./scripts/fg scan --all
./scripts/fg inbox list              # pending items; inbox confirm|reject <id>... [--reason ...]
./scripts/fg lessons list            # lessons approve|reject <id> needs lesson_decide in OPERATOR_ALLOW
./scripts/fg loop status             # the learning loop; loop pause|resume|mode needs loop_settings
./scripts/fg decisions list          # its audit log; approve|reject|keep|rollback|note need loop_decide

--json prints raw answers; --as <name> names the agent in Activity. Reading works without the token. fg status --watch follows the live event stream. ./scripts/fg --help lists every command (status, pipeline, projects, scan, inbox, lessons, demo, map rebuild|drop, links, facts, docs, activity, sessions, evidence, cleanup, loop, decisions, know, conflicts, ingest, cloud, integrations, signin, doctor, setup); fg help <command> shows one command's options. fg decisions rollback <id> is a preview (what else it undoes, what blocks it) until you add --apply; fg decisions rollback --since 24h --apply rolls back every decision applied in that time, newest first.

From inside an agent session: with FG_OPERATOR_TOKEN set, a second MCP endpoint, http://localhost:<API_PORT>/mcp/admin, gives a coding agent the same decisions as tools: pipeline_status, list_projects, select_projects, scan_projects, inbox_list, inbox_get, inbox_decide, lessons_list, lesson_get, lesson_decide, link_decide, set_fact_relevance, cloud_sync_now, run_cleanup, session_evidence, rebuild_code_map, demo_seed, demo_remove, and for the learning loop decisions_list, decision_get, loop_status, decision_decide, decision_rollback, decision_note and loop_pause, and with knowledge intake on intake_submit, claims_list, claim_decide, questions_list, question_answer, conflicts_list and conflict_resolve. It answers only to the operator token (send it as the Bearer when you register the server). Each write tool needs its OPERATOR_ALLOW verb; the demo tools need only the operator token for demo-shop. Its lists are paged at 20 rows and its read tools are marked read-only, so Codex does not ask before each one; export FG_OPERATOR_TOKEN in the shell you start Codex from (see docs/operations.md). The regular /mcp endpoint stays read-and-report only. Headless Claude Code (claude -p) denies, never prompts, a tool it was not allowed: pass --allowedTools "mcp__plugin_feedback-graph_fg" "Bash(git diff *)" (with install-agent.sh claude instead of the plugin, mcp__feedback-graph). Testers: label requests with x-fg-run: <id> (add x-fg-sandbox: 1 to keep them away from real agents), then fg evidence <session> shows what the tool decided and fg cleanup --run <id> takes back what the run wrote. A wrong bearer is refused (401) on reads as well as writes.

For scripts: GET /api/openapi.json describes the whole local API (OpenAPI 3.1), including which routes are owner-only and which OPERATOR_ALLOW verb unlocks each. GET /api/events streams changes as server-sent events (stage, pipeline, scan, chat, activity), so a watcher does not have to poll.

How it works

  1. Ingest: reads the Claude Code and Codex session transcripts of the folders you selected and maps each to its project folder, with the repo files each session read and edited. A project is also identified by its git remote (origin, normalized to e.g. github.com/org/repo, credentials dropped): a second clone of a known repo in another folder is recorded under the existing project.

  2. Code map: each selected project's code is parsed (tree-sitter, no model) into files, symbols, imports, the tests covering each file and 90 days of churn, and kept current as the repo changes. Misses are linked to the files their session edited, so the graph can say which modules agents keep getting corrected on, and why.

  3. Detect: a keyword prefilter, then a classifier (gemma4 by default), flags pushback. It handles the newest feedback first. An unusable answer is asked again strictly, then once with a short prompt; a turn given up after 3 tries is re-queued at the next worker start after the classifier model or the version changes.

  4. Inbox: you confirm or reject each flagged moment. Nothing is learned without your confirmation, except that once your reviews have earned it, the learning loop's triage_inbox may reject an item or confirm a GitHub or Linear comment on its own (an item from your own sessions, or an agent's report, it only proposes to confirm). Agents can also report a miss (report_feedback, e.g. from /postmortem); a report of pushback the detector already flagged becomes part of that same Inbox item, with both sources kept. The same complaint said again in one project joins the first item: within an hour from the same session or in mostly the same words, or reworded (cosine 0.85 or closer) within 3 days.

  5. Post-mortem: works out what was missed, why (a typed cause), and which context would have prevented it. The context hunt covers code facts, uploaded docs, PR reviews and Linear tickets. The proposed lesson is scoped to the files the fix touched when the evidence supports it, and gets lesson triggers: path globs, added-line patterns and symbols, taken from the lesson itself (its paths, the file a review comment was on, the files its fix edited and the code-shaped names its rule uses), never from the code the post-mortem cited. A glob for a kind of file anywhere (**/*.sql) is kept only when the rule says it of every such file, and is flagged trigger fires repo-wide for you; a post-mortem whose answer is still not about the flagged turn when asked again is flagged needs review: not about the flagged turn and never approved on its own. It also reads the instruction files the agent had: CLAUDE.md and AGENTS.md in the repo and each parent folder up to your project root, and Claude Code's memory notes, each labelled with whether that session's agent loads it (Claude Code: CLAUDE.md and memory; Codex: AGENTS.md).

  6. Lessons: you approve, edit (paths and triggers included) or reject them, with a note. Only approved lessons reach agents. Each time a project's code map is rebuilt, approved lessons are checked again: one whose path, trigger glob or named function no longer exists is flagged its code changed, never revoked for you.

  7. Agents use MCP. Only report_feedback (and, with knowledge intake on, submit_notes) writes; the other tools are marked read-only.

    • get_task_context serves approved lessons, past misses, code facts and doc excerpts. Given the files the agent will touch, it puts the lessons tied to those files first, adds a short code map of them (importers, tests, last miss) and lists the AGENTS.md and CLAUDE.md files on their path, nearest first. A lesson whose triggers match the files, or a code-shaped name in the task's words, is served too, saying which trigger fired.

    • A past miss is served or judged only when it shares a file with the work or is at least MISS_MIN_SIMILARITY (0.85) similar to it.

    • get_module_context describes one file (facts, exported symbols, importers, imports, tests, and the lessons and misses tied to it); impact lists what depends on a set of files, the tests to run, and the lessons and misses there. A file past the code map's cap (SCAN_MAX_FILES) is answered in full too, its symbols parsed on demand; a file the map does not hold at all (a new one) gets the lessons its path or a trigger names, then those close to it by meaning, marked to check.

    • check_before_done has the judge (CHECK_LLM) check finished work against those lessons and against lessons whose triggers fire on the diff's added lines or changed files. A lesson with no verdict counts as a gap. When a lesson requires a change to a file it names (a test, a doc) and the change leaves that file out, the gap says so ("you forgot X"). Given files_changed, it also names the importers and tests of those files the change did not touch, and what the last misses there were about.

    • search_graph finds lessons, misses, facts and docs by meaning; get_recent_sessions returns session summaries (step 8); report_feedback records a miss (step 4).

    • With knowledge intake on: what_do_we_know answers what you accepted about a topic, find_contradictions checks a statement against the facts and approved lessons on its scope (no model), and submit_notes hands you an agent's notes as a private source to review. While a conflict is open, the lessons and facts on either side are served marked [disputed: <conflict>], with both sides.

  8. Session summaries: each session of the selected projects gets a running summary (goal, what was done and decided, open threads, pushback, files touched, outcome). The worker keeps a cursor per session, the last turn the summary covers: once a session has been quiet for SUMMARY_QUIET_MINUTES (or SUMMARY_MAX_DELTA_TURNS turns are waiting), only the turns after the cursor are sent, with the previous summary, and the model returns the merged one (a big delta is summarized in parts, then merged). Quiet for SESSION_IDLE_MINUTES and summarized to its last turn, a session is idle ("complete for now"); a new turn makes it active again and the summary is extended. Sessions from before this are summarized a few per cycle, newest first. Agents get them through get_recent_sessions, and get_task_context lists the three most recent; post-mortems read the summary of the session they look at. Summaries are redacted, and never exported or synced.

  9. Activity: every fact, code map build, flag, decision, post-mortem, summary update, served context and check is logged with who or what did it and why.

  10. Learning loop: the worker reads the Activity log into each lesson's track record (served, judged, caught a gap, repeated) and your verdicts on its decisions into how far it may trust itself, then links repeated misses, merges duplicate lessons, re-ranks, retires, rewords, narrows and approves lessons, moves a lesson's paths when its file is renamed, triages the Inbox and, with intake on, helps with claims, by the mode you choose. Every decision is on the Decisions page with what it changed and why, and can be rolled back.

The learning loop

It runs in auto by default. In auto, housekeeping (linking repeated misses, merging duplicates, re-ranking, retiring lessons that never help) applies on its own. A change to what agents are told (approving, rejecting, rewording or narrowing a lesson, turning one into a fact) applies on its own only once your own verdicts on that type's proposals have earned it, counted per type and per confidence band (one of 5, by the call's score): at least 20 of them in the call's band, with that band's lower bound at the type's bar τ or above (0.9 for approve_lesson, which approves a proposed lesson without you), and then at most 5 such changes a day, 2 per project. Until then it proposes, and you approve or reject on the Decisions page, which opens on needs you: only what it could not decide, each with a one-line reason (always your call, not earned yet, under its bar). Every item waiting for you carries an approve score (0 to 10, the chance you approve it, ranked from your own verdicts on the most similar items you decided and pulled toward 5 while evidence is thin; it orders the list and never decides what applies on its own; see the guide). An operator token's verdicts never count.

  • Housekeeping also moves a lesson's paths from a renamed file's old name to its new one (update_paths), and with knowledge intake on links claims that say the same thing and folds a duplicate claim into the one it repeats.

  • Agent-facing, once earned: triage_inbox (bar 0.9, from 30 of your reviews in its band) rejects Inbox items and confirms GitHub and Linear comments, never an item from your own sessions or an agent's report; and with intake on accept_claim (bar 0.92) accepts a fact or decision claim whose quote matches its source and whose speaker you confirmed, never an opinion or an agent's notes.

  • Always proposals: a project's own similarity floors (tune_threshold), and with intake on suggesting how to settle a conflict, who should answer an open question, or a later claim that answers one. Settling a conflict is always yours.

  • Two lessons of one project that are close (cosine 0.85 up to the duplicate flag's 0.92) are judged, by their words or the loop's model, and a likely duplicate is only ever proposed. Rolling back a merge, or Never merge these two on a merge decision, records the pair as not the same: the loop never proposes merging it again, and only you lift that.

observe records what it would do and changes nothing; propose asks you about everything; off stops it. Set the mode in Settings > Learning loop or with fg loop mode propose; LOOP_MODE in .env seeds it on the first start only, and LOOP_DISABLED=1 stops it whatever Settings says. Upgrading to 0.8.0 with API_TOKEN or FG_OWNER_TOKEN set, the first start seeds auto, so housekeeping starts applying on its own: to start more cautiously, put LOOP_MODE=observe or LOOP_MODE=propose in .env before that first start. An install with neither token only observes.

Knowledge intake (0.9.0, opt-in)

Hand Feedback Graph what people said, a call transcript (txt, md, vtt, srt) or notes, and it breaks it into claims (facts, decisions, plans, commitments and opinions, each with who said it, when and the quote) and the questions it left open, for you to review on the Intake page. It also checks claims, facts and approved lessons against each other and flags conflicts: two things agents may be told that disagree, shown side by side for you to say which is true. Agents only ever see the claims you accept (an accepted opinion stays attributed and is never given to them), and each conflict you settle is a decision on the Decisions page that you can roll back.

It is off by default. To turn it on, set INTAKE_ENABLED=1 in .env and run docker compose up -d; Intake then appears in the sidebar's Review group. Add a source with Add a source on its Sources tab (paste it or pick a .txt, .md, .vtt or .srt file), POST /api/intake or the admin MCP tool intake_submit; an agent can hand you its notes with submit_notes. It reads on the local model by default (INTAKE_LLM), and only Ollama or claude-cli unless you widen INTAKE_PROVIDER_ALLOW.

Privacy: credentials and contact details are redacted before anything is stored or sent to a model, and speakers reach the model only as S1, S2, ... (names spoken inside the text do reach it). Transcript text, quotes, questions and sources are never served to agents or synced. An export carries your accepted claims, settled conflicts and who said them, never a source's text or questions, and quotes only when asked for (GET /api/export?quotes=1). Claim facts leave for the cloud only with SYNC_CLAIMS=true (or Settings > Cloud sync), never a private source's: a transcript or an agent's notes is private by default.

From a terminal: fg ingest call.vtt --project k --wait (a pdf is converted on your machine with pdftotext, and a docx, odt, rtf, html or epub with pandoc, when installed). It reads the file with the host's Node; where fg runs inside the api container instead, pipe the text in: fg ingest - --project k < call.vtt.

Ask "what do we know about X": the Ask starter What do we know about …?, fg know "<topic>", GET /api/knowledge or, for agents, the MCP tool what_do_we_know (accepted knowledge only) answer with the claims, facts and approved lessons on it, who said each and when, its open questions, and what is disputed. fg conflicts --open lists the conflicts waiting for you; you settle each on the Intake page's Conflicts tab (A is true, B is true, Both partly or Not a conflict), and Undo on the Decisions page reopens it. Agents can check a statement first with find_contradictions.

How to use the page: docs/guide.md. Settings, privacy, deleting and cost: docs/operations.md.

UI

Page

What

Dashboard

The landing page: a Getting started list until its steps are done, what the worker is doing, model health, pipeline counts, repeat and catch rates, projects, hotspots, recent activity, and an Ask box

Ask

Chat about one project, one conversation per topic ("I want to add a modal", "where would feature X go?"). Answers cite the lessons, misses, facts, docs and code they come from, show existing examples and where they are used, and end with a prompt for your agent to copy into Claude Code or Codex, with the Feedback Graph tools to use. Runs on Claude, Codex or the local model (CHAT_LLM; switch per chat). The model gets only the question and that context, up to 16 KB of code from the project with secret files never read

Inbox

Flagged feedback to confirm or reject (j/k, c, r), from sessions (Claude, Codex) and from GitHub and Linear comments, filterable by source

Intake

With knowledge intake on: Sources (Add a source), Claims to accept or reject, open Questions, Conflicts to settle and the People who said them

Lessons

Evidence chain per lesson, edit (rule, scope, paths, triggers) and approve or reject with a Note, plus its history

Graph

Clickable map from feedback to miss, cause, lesson, context and fix commits (three per item at most, with how many more); Code mode draws a project's code map (hotspot files, imports, tests, and the misses and lessons on them), and a file opens its module panel

Patterns

Misses by cause, repeated misses, weekly trend, pre-done catch rate, where agents struggle (top hotspots)

Sessions

Each session's running summary (filter by project and active/idle), its update history, files touched, the Inbox items and lessons from it, and Rebuild summary

Projects

Pin a project (pinned ones come first in every list), link it to the projects it belongs with (the worker suggests links from what is in them; you confirm), code map (stats, sortable hotspots with an include-tests toggle, a module panel per file linking to its lessons and Inbox items), scan code into facts (choose the model), browse facts by relevance with "how was this created?" and set a fact's relevance, upload docs and images that the worker reads into facts

Decisions

The learning loop's audit log, opening on needs you (what it could not decide, and why): every decision with its before and after, evidence and rationale; approve or reject with a note, keep or roll back (with what else a rollback undoes), Never merge on a merge, notes, JSON and CSV export

Learning loop

The loop at work: its mode and Pause, this round, live events, each type's bar and earned confidence, recent decisions, a lesson's track record and the holdout comparison

Activity

The provenance log, filterable by project and kind

How to read each page, with screenshots: docs/guide.md.

The sidebar shows whether the worker is running, and a connections list (GitHub, Linear, Claude, Codex, Ollama, cloud sync) with each one's state and a link to where it is set. Projects also has Export lessons, misses and facts (JSON) (a versioned bundle; with knowledge intake on it adds your accepted claims, settled conflicts and who said them), Import bundle (its lessons arrive proposed unless you keep them approved; revoked ones stay revoked; claims keep their status) and Delete project data…. Settings has five tabs: Projects (the project folders to read), Integrations (GitHub and Linear), Cloud sync (always shown; it says how to turn team sync on until CLOUD_URL and CLOUD_TOKEN are set, and with knowledge intake on whether claim facts are synced and name who said them), Learning loop (its mode, per-type modes and bars, budget and Pause) and Clean up.

Configuration (.env)

Variable

Purpose

PROJECTS_DIRS

The project folders that are mounted, comma-separated. setup.sh keeps it in step with the folders you add in Settings (editing it here and re-running setup.sh also works). No default.

POLL_SECONDS, CLASSIFY_BATCH, SCAN_MAX_FILES, SCAN_MAX_BYTES, CODE_MAP_MAX_BYTES

Worker pace and bounds: seconds between cycles (30); turns classified per cycle (50); files per project the code map keeps (empty: 40% of the files worth mapping, from 400 to 4000, source and the files your sessions edited first) and changed files a scan reads per run (empty: 400; a number sets both); the largest file a scan reads, in bytes (80000); and the largest source file the code map parses (1000000). Empty: the default.

SCAN_CONCURRENCY

Scans run at once, beside the worker cycle (2). Agentic scans (codex-cli, claude-cli) run one at a time.

OLLAMA_NUM_PREDICT, OLLAMA_NUM_CTX, OLLAMA_REPEAT_PENALTY, OLLAMA_TIMEOUT_SECONDS

Sent with every Ollama call: the most tokens an answer may take (empty: each job's own budget, classify 256, relevance 1024, Ask 6144, post-mortems 3072, the learning loop's rewrites 1024, its rewrite and near-duplicate judges 256 and its replay judge 512, the intake reader 4096 and the conflict judge 512, others 2048), the context window (16384; one value for every call, so the model is not reloaded), the repeat penalty (1.1), and how long one call may take (empty: each job's own limit). Structured calls also send think: false, and are streamed so an answer that turns into 200 whitespace characters in a row is cut there and asked again at once.

MISS_MIN_SIMILARITY

How close (0 to 1, default 0.85) a past miss must be to the work for get_task_context to serve it or check_before_done to judge it when it shares no file with it.

CHECK_CONCURRENCY, CHECK_WAIT_SECONDS

The judge's calls at once (2; the rest wait in line), and how long one check's judge call may run, waiting included (300). A response comes back within 50 s either way, "queued" while the call runs; the same check again joins it. A call still unanswered at the limit is cancelled and the check answers "not judged" with the lessons to check by hand.

CHAT_LLM

The model that answers Ask questions (same forms as the other jobs). Empty: claude-cli when a Claude token is set (anthropic: with only ANTHROPIC_API_KEY), else codex-cli when Codex may run, else the local model. Each chat can switch between Claude, Codex and the local model.

INGEST_PROJECTS

Seeds the project selection on the very first start only (e.g. demo-shop); after that, the selection in Settings decides. Empty: nothing is read until you choose (an upgrade selects the projects that already hold your approved lessons, confirmed feedback, docs or scans). Global lessons reach every folder under a root that is not excluded, selected or not.

EXCLUDE_PROJECTS

Never read, scan or serve these projects, whatever is selected. Settings shows them as excluded and cannot select them.

CLAUDE_PROJECTS_DIR, CODEX_SESSIONS_DIR

Session transcripts on the host: Claude Code's (default ~/.claude/projects, where its memory notes are too) and Codex's (default ~/.codex/sessions; if it does not exist the worker carries on without Codex).

FG_INSTANCE

The Docker Compose name of this install: its containers, network and volumes. Empty: feedback-graph. setup.sh sets a distinct one (and ports 10 or more apart) for a second checkout; see docs/install.md.

API_PORT, NEO4J_HTTP_PORT, NEO4J_BOLT_PORT, POSTGRES_PORT, OLLAMA_PORT

Host ports: the UI, API and MCP server (8787, on API_BIND), and on 127.0.0.1 the Neo4j browser (7476), Neo4j bolt (7689), the bundled Postgres (5434) and the bundled Ollama (11435, --profile ollama).

HOST_UID, HOST_GID

Linux: the uid and gid the worker, api and codex-runner run as (default 1000). setup.sh sets them to yours.

TURN_RETENTION_DAYS

Remove the text of unflagged turns older than this. Empty keeps everything. While summaries are on, a turn keeps its text until its session summary covers it; summaries are never removed.

SUMMARY_LLM, SUMMARY_QUIET_MINUTES, SUMMARY_MAX_DELTA_TURNS, SESSION_IDLE_MINUTES, SUMMARY_BACKFILL_PER_CYCLE

Session summaries: the model (same specs as below; empty: the local model; off: no summaries), the quiet time before new turns are summarized (10), the waiting turns that are summarized at once (40), the quiet time after which a summarized session is idle (30), and older sessions summarized per cycle (3).

LOOP_MODE, LOOP_DISABLED

The learning loop. LOOP_MODE seeds its mode on the first start only (off, observe, propose, or auto, the default: housekeeping applies on its own, changes to what agents are told only once your reviews earned it); after that Settings > Learning loop decides. LOOP_DISABLED=1 stops it whatever Settings says. An install with neither API_TOKEN nor FG_OWNER_TOKEN runs it at most in observe. See The learning loop.

LOOP_LLM, LOOP_LLM_CALLS_PER_DAY, LOOP_AUTO_PER_DAY, LOOP_AUTO_PER_PROJECT, LOOP_SYNC_HOLD_DAYS, LOOP_HOLDOUT, LOOP_TYPES

The loop's model (same forms as the other jobs; empty: the local model) and its model calls a day (40); agent-facing changes it applies on its own a day (5) and per project (2); days a lesson it changed is still shared with the cloud as it was before (7); the share of lessons it never changes, to compare against (0.2, at most 0.5); and the decision types that run (comma-separated; empty: all).

MEMORY_AUTO_APPROVE

false (default): Claude Code feedback memories (type: feedback) are imported as proposed lessons that you approve in Lessons. true: they are approved on import. Either way, once you edit, approve or reject one, re-importing its file never changes it.

FG_OPERATOR_TOKEN, OPERATOR_ALLOW

Let agents and scripts (the fg CLI) make owner decisions without a browser. Empty: none can. OPERATOR_ALLOW names what the token may do; empty means everything except deciding lessons (lesson_decide), choosing projects (select_projects), deleting a project, an uploaded document, a chat or a link (links_delete), settings (roots and cleanup), imports, integration keys, asking in Ask (chat), the learning loop's decisions (loop_decide) and mode (loop_settings), and knowledge intake's claims (claims_decide) and conflicts (conflict_decide). A list replaces those defaults rather than adding to them, so list the defaults with the verb you add: a refused request prints that whole line. See Agents and scripts.

GITHUB_BOT_LOGINS, GITHUB_AGENT_LOGINS

GitHub logins whose comments are a bot's (skipped) or an AI agent's (kept and labelled), comma-separated. Accounts GitHub marks as bots are skipped anyway.

API_TOKEN, FG_OWNER_TOKEN, PUBLIC_HOSTS, API_BIND

Reach the API from other machines: agents use API_TOKEN; people sign in to the browser with the separate FG_OWNER_TOKEN, which must never be given to an agent. Without tokens it is a read-only install: owner actions are refused and the API answers only this machine, but anything on it can read it, other containers included (OrbStack and Docker Desktop route them through the gateway), and an API_BIND other than 127.0.0.1 refuses to start. ./scripts/setup.sh --yes --enable-auth adds all three tokens (API, owner and operator).

CODEX_RUNNER_URL

http://codex-runner:8790, with codex in COMPOSE_PROFILES: codex-cli runs confined in the codex-runner container, which holds only the Codex CLI, your Codex login and the job's copy of the repository (docs/operations.md).

ALLOW_UNCONFINED_CODEX

Without CODEX_RUNNER_URL: set to 1 to let codex-cli run inside the worker, where it can read everything the worker can (SECURITY.md); claude-cli is confined to the project folder.

CLAUDE_CODE_OAUTH_TOKEN

From claude setup-token. Post-mortems then run on your Claude subscription (otherwise the local model, OLLAMA_MODEL, default gemma4).

CLASSIFY_LLM, POSTMORTEM_LLM, SCAN_LLM, CHECK_LLM, DOCS_LLM

Model per job: ollama:<model>, claude-cli, codex-cli, openai:<model>, openrouter:<model>, groq:<model>, anthropic:<model>, jev (classification only). CHECK_LLM (the check_before_done judge, which runs in the api) takes neither CLI. DOCS_LLM reads your uploads into facts (default SCAN_LLM); images need a model with vision (ollama:gemma4, claude-cli, anthropic:, openai:, openrouter:). The scan model also scores the relevance of facts stored before relevance existed, a batch at a time when nothing else is waiting. SCAN_PROVIDER_ALLOW (comma-separated, e.g. ollama,openai) limits which providers a scan request may switch to; empty allows any configured one.

OPENAI_API_KEY, OPENAI_BASE_URL, OPENROUTER_API_KEY, GROQ_API_KEY, ANTHROPIC_API_KEY, TYPESAFE_API_KEY

Bring-your-own keys

GITHUB_TOKEN, LINEAR_API_KEY

GitHub and Linear tokens. Optional: you can paste them in Settings instead. A token saved in Settings wins; the one in .env is used when none is saved there. See GitHub and Linear.

GITHUB_REPOS

Optional override of the repository a project's PR comments come from (demo-shop=me/demo-shop,other=me/other). Without it, each selected project's github.com remote is used.

LINK_SCAN_MINUTES

Minutes between rounds of looking for links between the selected projects (30). No model: package manifests, mentions of another project's repository, and its name in several files.

INTEGRATION_SYNC_MINUTES, GITHUB_SYNC_HOURS

How often each project's review comments and Linear comments are read into the Inbox (10), and how often PR discussions are refreshed as context (6).

STORE, DATABASE_URL, POSTGRES_PASSWORD, COMPOSE_PROFILES

Storage backend: neo4j (default) or postgres. With the bundled Postgres, COMPOSE_PROFILES=postgres includes it in every docker compose command. See Storage.

CLOUD_URL, CLOUD_TOKEN, SYNC_INTERVAL_MINUTES, SYNC_PROJECTS, SYNC_SUGGESTIONS

Optional team sync to a Feedback Graph Cloud org. Off unless the first two are set. See Team sync.

INTAKE_ENABLED, INTAKE_LLM, INTAKE_PROVIDER_ALLOW, INTAKE_LLM_CALLS_PER_DAY, INTAKE_ARCHIVE, CONFLICT_LLM, CONFLICT_LLM_CALLS_PER_DAY, CONFLICT_SWEEP_MINUTES, SYNC_CLAIMS, SYNC_PEOPLE

Knowledge intake, off unless INTAKE_ENABLED=1. SYNC_CLAIMS and SYNC_PEOPLE (both false; each needs cloud sync, and people need claims) are the defaults Settings > Cloud sync can change. See Knowledge intake and docs/operations.md.

The Codex provider uses your ChatGPT login: CODEX_AUTH_FILE (only that file is mounted). It explores a copy of the repository without secret files, in the codex-runner container when CODEX_RUNNER_URL is set.

OLLAMA_EMBED_MODEL is recorded in the graph; changing it later needs pnpm reembed (the services tell you).

GitHub and Linear (optional)

Pushback does not only happen in a session: reviewers push back in pull request reviews, and in comments on Linear issues. Connect GitHub or Linear and those comments are read into the Inbox like pushback in a transcript: the same classifier labels each one, and pushback or a correction becomes an Inbox item marked GitHub or Linear, with the author, a link to the comment, the PR or issue title, and for a review comment the file, line and diff lines. You confirm or reject it as usual; a confirmed one gets a post-mortem.

  1. Settings > Integrations. Paste a token and press Connect: it is checked with one read-only call (the account it belongs to is shown) and saved only if it works. Or set GITHUB_TOKEN / LINEAR_API_KEY in .env (shown as "set in .env"); a token saved in Settings wins over it, so you can replace a .env key without editing the file. Test connection checks it again; Disconnect (after a confirm) deletes a token saved in the app and goes back to the .env one, if any.

  2. Repositories are found for you. Each selected project whose origin is on github.com is linked to that repository (GITHUB_REPOS overrides it). Turn one off in the project table. For Linear, choose the team for each project.

  3. What is read. Every INTEGRATION_SYNC_MINUTES (10), per project: review comments, review bodies and conversation comments on pull requests updated since the last sync, and comments on the mapped team's issues. Bots and integrations are skipped. The first sync reads the last 14 days only. Only selected projects are read.

  4. The work behind a comment. A comment is linked to the agent session that most likely did the work: a session of that project on the PR's head branch (or the Linear issue's branch) that started before the comment; else, of the sessions active in the seven days before it, the one that edited the most of the PR's files (the commented file, for a review comment). Being nearest in time is never enough, and a tie is no match. Its post-mortem then reads that session (its turns, edited files and summary) beside the comment; with no session found it works from the comment and the diff lines alone.

A token pasted in Settings is kept in state/api-settings/integrations.json (mode 600, INTEGRATIONS_FILE): never in the database, never returned by the API, logged, exported or synced. Comment text is redacted before it is stored or sent to a model, and calls go only to api.github.com and api.linear.app. A rejected token (revoked or expired) pauses that service until you replace it, and a rate limit pauses it until the limit lifts; both show in Settings and in the sidebar's connections list, which shows GitHub, Linear, Claude, Codex, Ollama and cloud sync at a glance. PR discussions are also kept as project context for post-mortems and get_task_context, as before.

Team sync (optional)

The tool is complete on its own and sends nothing anywhere by default. Feedback Graph Cloud is a separate, hosted team product built on this core: a shared team graph, single sign-on, a team MCP endpoint and pull request checks. If your team uses it, the sync client in this repo pushes your project knowledge to your org:

  1. In the cloud web app, create an access token with the sync scope.

  2. Set CLOUD_URL=https://<your cloud> and CLOUD_TOKEN=fgc_pat_... in .env, then docker compose up -d.

  3. Press Sync now on the Cloud sync card in Settings, or set SYNC_INTERVAL_MINUTES=60 to sync on a schedule. SYNC_PROJECTS=a,b narrows which selected projects are sent.

What is sent, per selected project: its key and git remote, its approved lessons, misses (what was missed and why), current code facts, document titles and links, and the code map's paths with their hotspot numbers. Only lessons you approved leave: they become the team's (or wait for an admin, if your org reviews them). A proposed lesson stays on this machine until you approve it; SYNC_SUGGESTIONS=true sends proposed ones too, as suggestions for an admin. A lesson you reject or move back to proposed, or anything you delete, is taken back out of the team graph on the next sync, and so is everything sent for a project you stop syncing (unselected, excluded, or left out of SYNC_PROJECTS): it gets one last, empty bundle. What the cloud last accepted is kept in the store, so this holds across restarts. With knowledge intake on, the facts your accepted claims became go only with SYNC_CLAIMS=true (or its switch in Settings > Cloud sync), never a private source's and never a quote. Never sent: transcripts (including the feedback you typed), intake sources with their quotes and questions, session summaries, source code, excerpts, document text, embeddings, or anything new from an unselected or excluded project (its last bundle names only what was already sent). Every sync is logged in Activity. Details: docs/operations.md.

Storage

Everything the tool learns is stored in Neo4j by default, which also gives you the Neo4j browser to explore the graph.

It can run on Postgres with pgvector instead, with the same code: every repository is an interface (packages/core/src/store.ts) with a Neo4j and a Postgres implementation (packages/store-postgres), and one contract test suite runs against both in CI. Redaction, Activity entries, export and delete behave the same on either.

# in .env: STORE=postgres, POSTGRES_PASSWORD=<letters and digits> (setup.sh generates one) and COMPOSE_PROFILES=postgres
docker compose up -d --build

The postgres profile adds a pgvector/Postgres 17 container (127.0.0.1:5434, volume postgres-data). To use your own Postgres 17 server with the vector extension available (pgvector 0.8.0 or newer; an older one is refused at startup), set DATABASE_URL=postgres://user:password@host:5432/db and leave the profile off. The schema is migrated at startup. /api/status and /api/ready report which store is in use. Switching backend starts empty: to carry your lessons and code facts over, Export on the old one and Import bundle on the new one's Projects page (see docs/operations.md). Backups in Postgres mode use pg_dump (./scripts/backup.sh does it for you).

Try it on the demo project

examples/demo-shop is a small multi-tenant API with rules in its CLAUDE.md.

./scripts/setup.sh --demo adds this repo's examples folder as a project folder and selects demo-shop (on an install whose selection was already made in Settings, select demo-shop there too); with the stack started it seeds the demo lessons, and fg demo context and fg demo check show them at work. To see the whole loop from a real session instead, with a Claude Code login:

node scripts/demo-probe.mjs prepare --apply --configure --api-url http://localhost:8787   # a dedicated, selected copy of demo-shop
./scripts/demo-pushback.sh   # a real Claude session there plus a pushback turn; its edits stay in the copy

The copy goes to ~/Code/demo-shop (--folder and FG_PROBE_DIR move it), which must lie under a mounted project folder; docs/demo-probe.md has the details.

Then open the Inbox, confirm the flagged pushback, wait for the post-mortem, and approve the lesson. Ask for context on a similar task (an invoices list) and run check_before_done with a summary that skips the sort order: the lesson and the gap both show up. node scripts/demo-probe.mjs restore --apply puts the project selection back afterwards.

Develop

pnpm install
pnpm test                                                  # unit + ui
docker compose -f docker-compose.test.yml up -d --wait     # the test Neo4j and Postgres
TEST_STORES=neo4j,postgres pnpm test:int                   # contract suites on both backends
TEST_STORES=neo4j,postgres pnpm test:coverage:all          # every suite with coverage, held to the floor in vitest.config.ts
./scripts/check.sh                                         # lint, typecheck, the coverage gate, the uid check, the UI build;
                                                           # the history check only when a `public` branch exists

CI runs lint, typecheck, the coverage gate and the UI build too, always checks the pushed history, and builds both Docker images; it skips the uid check.

See the installation guide for a fresh installation, a second local instance and the small memory profile.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Provides coding agents with durable, cross-session lessons-learned memory, enforcing that success or failure verdicts can only come from human approval, human correction, or objective metrics—never from the agent itself.
      Apache 2.0
    • F
      license
      A
      quality
      B
      maintenance
      Enables coding agents to build and query a verified project memory through the MCP protocol, so skills, facts, and user preferences are captured as experience and adopted only after passing independent checks. It also briefs agents at each request, flags known pitfalls live, and runs an executable review before changes are finalized.
      4
      -
    • A
      license
      Not graded
      quality
      B
      maintenance
      Briefs a coding agent with the repository's conventions, architecture decisions, and proven checks before every task, and lets the agent record new lessons as work proceeds. Entries are proven by running checks and flagged as stale when the code, docs, or diagrams they govern change, so agent behavior and documentation stay from drifting.
      442 PyPI
      4
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables coding agents such as Claude Code and Cursor to check a team's learned decisions from pull requests, reviews, and docs before writing code, showing whether breaking them is blocking or advisory. It also provides locally computed symbol impact/blast-radius analysis so source code never leaves the machine.
      67 npm
      2
      MIT