First, a huge thank you for dedicating your time to helping us improve GraphGarden ❤️
Tip
New to open source? Check out https://github.com/firstcontributions/first-contributions for helpful information on contributing
GraphGarden aims to be a straightforward toolkit with a stable public API and reliable performance: crawl your site, build a link graph, and share it. The goal is to make it easy to create connections between personal sites, highlight projects from collectives, and contribute to a more open and interconnected web.
The protocol is open and decentralised — there is no invitation or acceptance mechanism. Adding someone as a friend pulls their graph into yours, but does not alter theirs. This means you can generate an isolated graph containing only your own site, or act as a bridge between many different communities.
We're also committed to fostering a welcoming and respectful community. Any issue, PR, or discussion that violates our code of conduct will be deleted, and the authors will be banned.
- Do not report security vulnerabilities publicly (e.g., in issues or discussions), please refer to our security policy.
- Do not create issues for questions about using GraphGarden. Instead, ask your question in our GitHub Discussions.
- Do not create issues for ideas or suggestions. Instead, share your thoughts in our GitHub Discussions.
- Check for duplicates. Look through existing issues and discussions to see if your topic has already been addressed.
- In general, provide as much detail as possible. No worries if it's not perfect, we'll figure it out together.
- Check for duplicates. Look through existing PRs to see if your changes have already been submitted.
- Check Clippy warnings. Run
cargo clippy --all --all-targetsto ensure your code adheres to Rust's best practices. - Run formatting. Run
cargo fmt --allto ensure your code is properly formatted. - Write and run tests. If you're adding new functionality or fixing a bug, please include tests to cover it. Run
cargo test --allto ensure all existing tests pass. - Prefer small, focused PRs that address a single issue or feature. Larger PRs can be harder to review, and can often be broken down into smaller, more manageable pieces.
- PRs don't need to be perfect. Submit your best effort, and we will gladly assist in polishing the work.
- Prefer self-documenting code first, with expressive names and straightforward logic. Comments should explain why (intent, invariants, trade-offs), not how. Variable and function names should be clear and descriptive, not cryptic abbreviations. Avoid hidden state and side effects.
- Tests should assert observable behavior (inputs/outputs, effects), not internal implementation details. Keep tests deterministic and independent of global state.
- For errors, use typed error enums in library crates (derived with
thiserror), andanyhowin the binary crate (for CLI-level error propagation). Per-cratepub type Result<T>aliases for ergonomic signatures. Add context at the boundary (CLI) rather than deep in core, keep library error messages concise. - Prefer
?propagation when possible, and reserve.expect()/.unwrap()for cases where failure is a programmer bug (e.g. hardcoded regex literals, test helpers). - Document any new public APIs, configuration options, or user-facing changes in the relevant README files. If you're unsure where or how to document something, just ask and we'll help you out.
- We deeply value idiomatic, easy-to-maintain Rust code. Avoid code duplication when possible. And prefer clarity over cleverness, and small focused functions over dark magic.
- Explicit
useimports for standard library types (e.g.use std::collections::HashMap;).
Each feature or bug fix that changes the public API or user-facing behavior should be accompanied by a Sampo changeset.
Structure:
- Breaking prefix (if applicable):
**⚠️ breaking change:** - Verb:
Added,Removed,Fixed,Changed,Deprecated, orImproved. - Description.
- Usage example (optional): A minimal snippet if it clarifies the change.
Description guidelines: concise (1-2 sentences), specific (mention the command/option/API), actionable (what changed, not why), user-facing (written for changelog readers), and in English. Don't detail internal implementation changes.
GraphGarden is a polyglot monorepo. The Rust side uses Cargo workspaces (crates/), and the TypeScript side uses pnpm workspaces (packages/). Prerequisites: the latest stable Rust toolchain, plus Node.js and pnpm for the web component and e2e tests.
graphgarden-protocol holds the protocol specification and versions it independently from the implementation crates. Its Rust library is intentionally minimal — it only exposes a PROTOCOL_VERSION constant. Changes to this crate should be rare and carefully considered, as they affect all implementations.
graphgarden-core is the core library that implements the GraphGarden protocol. It owns the data model, walks a built site's HTML output, extracts links, classifies them, and assembles the public graphgarden.json file.
graphgarden is the CLI built on top of graphgarden-core. It provides the user-facing commands to crawl a site and generate its protocol file.
graphgarden-web is the <graph-garden> custom element that renders an interactive node graph from a site's graphgarden.json file. It is bundled with esbuild into ESM and IIFE formats so sites can drop it in via <script> tag or npm install.
cd packages/graphgarden-web && pnpm install && pnpm run buildfixtures/ contains end-to-end test sites and a Vitest test suite that exercises the full pipeline — Astro build, CLI run, and JSON validation. It includes two fixture sites: Alice (a minimal Astro site processed by the CLI) and Bob (a pre-written static graphgarden.json served via a mock HTTP server).
Running the e2e tests requires Node.js and pnpm in addition to Rust:
cd fixtures && pnpm install && pnpm testThank you once again for contributing, we deeply appreciate all contributions, no matter how small or big.