Help us shape the future of Go! Take the 2026 Go Developer Survey

filiation

module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 30, 2026 License: Apache-2.0

README

Filiation

A citation graph you build and keep on your own machine.
Give it one paper. It follows the references, maps what cites what, and shows you where an idea came from.

Release v0.3.0 Go 1.26 Windows, macOS, Linux No cgo

Quick start · Commands · AI assistants · How it works · Coverage · Roadmap · Docs


In textual criticism, filiation is the work of establishing which manuscript was copied from which, reconstructing the lines of descent between surviving texts. This tool does the same for research papers. Start from one paper, follow its citations back, and see the literature it grew out of.

Most citation-map tools are session-shaped: you run a query, get a picture, and close the tab. Filiation builds your map instead. It keeps growing as you add papers, lives in one file you own, and can be exported to any tool you like.

Highlights

  • 🧭 Best-first expansion under a budget. The papers your library cites most are fetched first, so 500 works cover the literature that matters rather than whatever happened to be nearby. Stop at any time; the next run carries on where this one left off.
  • 🌳 Lineage, not just links. fil path finds how one paper descends from another through its references. Citation cycles are real, and the search is safe on them.
  • 🧹 Duplicates handled carefully. The same paper under two IDs, or a merged record, becomes one node. Papers that only share a title are reported for you to judge and never merged automatically: a book review carries the title of the book it reviews.
  • 📊 Honest about the data. Every run reports how many works came with a reference list, and why that varies by field.
  • 📦 One binary, nothing to install. A single file for Windows, macOS or Linux. No runtime, no database server, no Docker, no account, no API key.
  • 🔒 Local and legal. Your library never leaves your machine. Reference data comes from OpenAlex, which is open, and full text will only ever come from open-access sources.

See it

Real output: one run of fil expand from The state of OA (2018), laid out from fil export graphml.
The early open-access literature (blue) sits on the left; the recent work around the seed (pink) sits on the right.

Quick start

Download

Get the archive for your system from Releases, unpack it, and run fil. There is nothing else to install.

System Archive
Windows fil_<version>_windows_amd64.zip (arm64 also available)
macOS (Apple silicon) fil_<version>_darwin_arm64.tar.gz
macOS (Intel) fil_<version>_darwin_amd64.tar.gz
Linux fil_<version>_linux_amd64.tar.gz (arm64 also available)

Or build from source

git clone https://github.com/codevector-2003/filiation
cd filiation
go build ./cmd/fil        # Go 1.26 or later. Pure Go, no C compiler needed.

First steps

fil add 10.7717/peerj.4375                  # a DOI, arXiv ID, PMID, OpenAlex ID, link, or title
fil expand                                  # follow the references, most-cited first
fil path 10.7717/peerj.4375 W4300313809     # how one paper descends from another
fil export graphml -o library.graphml       # open it in Gephi

On the first run fil asks where to keep your library: a folder you can see, Documents/Filiation/My Library unless you choose another. Papers go in its papers/ folder, one folder per paper with a readable name such as Piwowar-2018-The-state-of-OA--F7K2M9QXA. Move, rename and group those folders however you like; fil finds them again, and each folder you make becomes a collection. fil where shows where everything is.

Commands

Command What it does
fil add <paper> Add a paper, plus a record for everything it cites. Takes a DOI, arXiv ID, PMID, OpenAlex ID, a link to any of those, or a title. A title can match several papers, so fil lists them and asks; --accept-first is for scripts.
fil expand Fetch the works your library cites but doesn't have yet, most-cited first. --max-nodes (default 500) and --max-depth (default 3) bound the run.
fil neighbours <paper> What a paper cites, and what in your library cites it. --context shows the sentence each reference is cited in, with its page, once fil has read the paper's PDF.
fil path <a> <b> The shortest chain of references from one paper to another. --any-direction allows citations either way.
fil stats The library at a glance, including reference coverage, PDFs, text and citation sentences. --duplicates lists works that share a title; --why lists each paper whose PDF gave no text, and why.
fil export graphml The graph as GraphML for Gephi, Cytoscape, yEd, NetworkX or igraph. --include-stubs adds works known so far only by ID.
fil mcp Serve your library to an AI assistant over MCP. Your MCP client runs it. See the MCP guide.
fil fetch Download the legal open-access PDFs of your papers into papers/ — published version first, then accepted manuscript, then preprint. Never from behind a paywall or a login. 50 a run (--max); name a paper to fetch just that one. --dry-run says what it would download and why the others have none. Once the downloads are done, it reads their text so fil search finds them.
fil list Your papers, with their PDF, text and citation sentences beside each. --seeds, --with-pdf, --without-text, --collection, --title, --json.
fil show <paper> Everything about one paper: its place in the graph, its PDF or why there is none, its text or why there is none, and the sentences it cites its references in.
fil search <words> Find passages in your PDFs by keyword, best first, each with its paper and page. Words match by stem; "quote" a phrase; word* matches a prefix. Reference lists are left out unless you pass --references; --paper searches one paper.
fil import [file…] File PDFs you already have. With no files, everything in Inbox/ and any loose PDF in papers/. fil finds the identifier in the PDF — the arXiv stamp, the DOI in the header — and files it only if that paper's title and author are in the PDF; otherwise it searches by title and asks you to choose. --as <DOI> names it yourself; --local "<title>" keeps a paper OpenAlex doesn't know. Files from elsewhere are copied, never moved. fil fetch and fil extract also file what's in Inbox/, so dropping a PDF there is enough.
fil extract Read the text of PDFs fil has not read yet — ones you put in papers/ yourself, say. A scan with no text layer, a gibberish text layer or a damaged file is reported with the reason. --again reads everything again.
fil library Your library folder and what is in papers/. library folder <paper> prints a paper's folder, making it if needed; library scan finds papers you moved, renamed or deleted; library new and library move make another library or move this one. --library <name> picks a library for any command.
fil where Where your library, settings and cache live.
fil cache clear Empty the cache of OpenAlex responses. Your library isn't touched.

Every command has --help. Exit codes are distinct per outcome (not found, ambiguous, network, and so on), so scripts can branch on them.

Use it from an AI assistant

fil mcp lets an assistant such as Claude, Cursor or any other MCP client use your library directly. It can add papers, grow the graph, and answer "how does this paper descend from that one?" against a map you own.

Claude Code

claude mcp add filiation -- fil mcp

Claude Desktop, Cursor and most other clients: add this to the client's MCP settings file.

{
  "mcpServers": {
    "filiation": { "command": "fil", "args": ["mcp"] }
  }
}

Use the full path to fil if it isn't on your PATH. Set-up for Cursor, VS Code and others is in the MCP guide. The assistant gets five tools:

Tool What the assistant can do
add_paper Add a paper by DOI, arXiv ID, PMID, link or title. A title that matches several papers comes back as a list to choose from, never as a guess.
expand_graph Follow references outward, most-cited first, with live progress.
neighbours What a paper cites and what cites it: ranked, capped, and saying what was left out.
find_path The chain of citations between two papers.
library_stats What the library holds, its reference coverage, and possible duplicates.

Every paper the assistant sees carries its IDs — fil's own, which never changes, and OpenAlex's — and its open-access status. Papers known only by ID are marked as such, so the assistant can't talk about a paper as if it had read it.

📘 Full MCP guide: set-up for Claude Desktop, Claude Code, Cursor and VS Code; every tool's arguments and results; limits; troubleshooting.

How it works

  • References come from OpenAlex, already resolved. fil never parses reference lists out of PDFs, a process whose errors compound across a graph.
  • Edges before nodes. A paper's references become records the moment it's fetched. Deduplication is then a primary-key match, and "which unfetched paper matters most?" becomes a cheap count of how often your library cites it.
  • Every batch is one transaction. Interrupting a run loses at most the batch in flight, and the library is never half-written.
  • Polite to OpenAlex. Requests are batched 100 works at a time, rate-limited below the measured limit, retried with backoff, and cached.

The design, and the measurements behind it, are written up in docs/.

Know this before you start

The graph is only as deep as the reference data behind it, and that depends on your field. In the validation run (20 seed papers across 5 fields, 500 works each), the share of fetched works that came with a reference list was:

Medicine Physics Computer science Social sciences Arts & humanities
93% 91% 88% 59% 56%

Where books and chapters dominate, publishers rarely deposit reference lists, and a random humanities sample fares worse still (spike 4). fil tells you when a work has no references. That's a gap in the data, not a fault in the tool.

Roadmap

Milestone
✅ M0: Skeleton. fil add: any identifier to a stored paper and its references Done
✅ M1: The graph. Budgeted expansion, dedup, path, neighbours, stats, GraphML export, cross-platform release Done: v0.1
✅ M2: MCP server. Let AI assistants query your library directly Done: v0.2
✅ M3: Papers on disk. Open-access PDFs, full text, and the sentence around each citation Done: v0.3
⏳ M4: Retrieval and answers. Search across everything you've read, answers with citations Next
⏳ M5: Web interface. The same library for people who don't use a terminal
⏳ M6: Claim genealogy. Why each paper cites another, and tracing a claim to its earliest source

Documentation

Contributing

Issues and pull requests are welcome. Before a larger change, read docs/STATUS.md and the hard rules in CLAUDE.md. A few are non-negotiable:

  • no cgo;
  • no paywalled full text, ever;
  • no reference parsing;
  • no network access in unit tests.

go test ./... runs the whole suite against recorded OpenAlex responses.

Acknowledgements

Reference data comes from OpenAlex, an open index of the world's research. SQLite runs in pure Go through ncruces/go-sqlite3. PDF text comes from PDFium, compiled to WebAssembly by klippa-app/go-pdfium and run by wazero. The command line is built with spf13/cobra.

Licence

Apache License 2.0. Use it, change it, and build on it, including commercially; keep the notices in NOTICE. The licence includes an explicit patent grant from contributors.

Directories

Path Synopsis
cmd
fil command
Command fil is the Filiation command-line interface.
Command fil is the Filiation command-line interface.
internal
blobs
Package blobs is the filesystem half of a library: its folder, and the papers/ folder inside it where PDFs live (ADR-011, ADR-012).
Package blobs is the filesystem half of a library: its folder, and the papers/ folder inside it where PDFs live (ADR-011, ADR-012).
config
Package config resolves where the library lives and how the tool behaves.
Package config resolves where the library lives and how the tool behaves.
embed
Package embed turns text into vectors, over Ollama's local HTTP API.
Package embed turns text into vectors, over Ollama's local HTTP API.
errs
Package errs holds the sentinel errors shared across Filiation.
Package errs holds the sentinel errors shared across Filiation.
export
Package export writes the library out as GraphML, JSON and BibTeX. M1.
Package export writes the library out as GraphML, JSON and BibTeX. M1.
graph
Package graph grows and traverses the citation graph.
Package graph grows and traverses the citation graph.
httpx
Package httpx is the shared HTTP layer for every outbound request.
Package httpx is the shared HTTP layer for every outbound request.
identity
Package identity classifies and normalises whatever the user typed.
Package identity classifies and normalises whatever the user typed.
jobs
Package jobs runs long work in the background and survives interruption.
Package jobs runs long work in the background and survives interruption.
library
Package library is the core of Filiation.
Package library is the core of Filiation.
llm
Package llm generates answers, through either a local Ollama model or a user-supplied API key.
Package llm generates answers, through either a local Ollama model or a user-supplied API key.
mcpsrv
Package mcpsrv exposes the library over the Model Context Protocol.
Package mcpsrv exposes the library over the Model Context Protocol.
model
Package model holds the types shared across Filiation: Work, HydratedWork, Edge, Author, FrontierItem, ExpansionResult.
Package model holds the types shared across Filiation: Work, HydratedWork, Edge, Author, FrontierItem, ExpansionResult.
retrieve
Package retrieve answers questions over the library.
Package retrieve answers questions over the library.
sources
Package sources holds one subpackage per external API.
Package sources holds one subpackage per external API.
sources/openalex
Package openalex is the client for the OpenAlex API — the source of works, metadata, resolved reference lists and open-access locations.
Package openalex is the client for the OpenAlex API — the source of works, metadata, resolved reference lists and open-access locations.
sources/pdfs
Package pdfs downloads open-access PDFs from the links OpenAlex gives (ADR-013).
Package pdfs downloads open-access PDFs from the links OpenAlex gives (ADR-013).
sources/unpaywall
Package unpaywall resolves a DOI to a legal open-access PDF location, as the second opinion when the OA locations from OpenAlex come up empty.
Package unpaywall resolves a DOI to a legal open-access PDF location, as the second opinion when the OA locations from OpenAlex come up empty.
sources/zotero
Package zotero reads a user's existing Zotero library.
Package zotero reads a user's existing Zotero library.
store
Package store owns every SQL statement in Filiation.
Package store owns every SQL statement in Filiation.
text
Package text turns a stored PDF into searchable text.
Package text turns a stored PDF into searchable text.
text/pdftest
Package pdftest writes small, valid PDFs for tests: real files PDFium opens, made in code, so no binary sits in testdata and every test says exactly what its PDF contains.
Package pdftest writes small, valid PDFs for tests: real files PDFium opens, made in code, so no binary sits in testdata and every test says exactly what its PDF contains.
web
Package web serves the local single-page app: graph view, search, reader, notes.
Package web serves the local single-page app: graph view, search, reader, notes.
spikes
1-batch-by-id command
Command spike1 tests whether OpenAlex works can be fetched in batches by ID.
Command spike1 tests whether OpenAlex works can be fetched in batches by ID.
11-reference-matching command
Command reference-matching is spike 11 (ARCHITECTURE_PHASE3.md §6): how well can a reference list be split out of a PDF's text and matched to real works?
Command reference-matching is spike 11 (ARCHITECTURE_PHASE3.md §6): how well can a reference list be split out of a PDF's text and matched to real works?
12-validation-m3 command
Command validation-m3 is the M3 validation run: ARCHITECTURE_PHASE3.md's step 11, the 20 seeds of M1's run (spikes/8-validation) again, now for the papers on disk — PDFs, their text, search, citation context and import.
Command validation-m3 is the M3 validation run: ARCHITECTURE_PHASE3.md's step 11, the 20 seeds of M1's run (spikes/8-validation) again, now for the papers on disk — PDFs, their text, search, citation context and import.
2-per-page command
Command spike2 finds the real per_page maximum on the OpenAlex works endpoint.
Command spike2 finds the real per_page maximum on the OpenAlex works endpoint.
3-rate-limit command
Command spike3 measures the sustained request rate OpenAlex allows in the polite pool.
Command spike3 measures the sustained request rate OpenAlex allows in the polite pool.
4-reference-coverage command
Command spike4 measures OpenAlex reference coverage across research fields.
Command spike4 measures OpenAlex reference coverage across research fields.
5-fts5 command
Command spike5 proves that ncruces/go-sqlite3 can run Filiation's real schema, including FTS5.
Command spike5 proves that ncruces/go-sqlite3 can run Filiation's real schema, including FTS5.
6-vectors command
Command spike6 proves vector search works in the same SQLite file as the graph.
Command spike6 proves vector search works in the same SQLite file as the graph.
8-validation command
Command validation is the M1 validation run: ARCHITECTURE_PHASE1.md §10's "20-paper / 5-field" check, run against live OpenAlex before v0.1.
Command validation is the M1 validation run: ARCHITECTURE_PHASE1.md §10's "20-paper / 5-field" check, run against live OpenAlex before v0.1.
9-pdf-availability command
Command pdf-availability is spike 9 (ARCHITECTURE_PHASE3.md §6): of the works in the M1 validation libraries, how many have a PDF we can actually download — per field, per OA status — and how many more does Unpaywall add? How long does one download take?
Command pdf-availability is spike 9 (ARCHITECTURE_PHASE3.md §6): of the works in the M1 validation libraries, how many have a PDF we can actually download — per field, per OA status — and how many more does Unpaywall add? How long does one download take?

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL