Grove
A file-first framework for curated directories and structured knowledge.
2026–Present · Astro · TypeScript · MIT

What it is
Structured knowledge tends to get entered more than once. The same project ends up described on a website, in a README table, in a spreadsheet, and in a search snippet — each drifting from the others at its own pace.
Grove makes the file the only place that description lives. You keep YAML records, optional Markdown bodies, and taxonomy files in a git repository. One build reads them and writes a static site with search and filters, plus every derived output around it: sitemap.xml, robots.txt, JSON-LD, Open Graph images, JSON datasets, and llms-full.txt for machine readers.
There is no server, no database, and no CMS. A rename or a delete propagates to every output on the next build instead of leaving a dead page behind.
pnpm dlx @grove-dev/cli@latest init my-space
cd my-space
pnpm dev
One record, many outputs
A record is one YAML file at data/records/<slug>.yml, with an optional Markdown body at content/records/<slug>.md. The slug must match the filename.
kind: project
slug: astro
name: Astro
description: A web framework for content-driven sites.
category: frameworks
repoUrl: https://github.com/withastro/astro
stacks: [typescript, go]
platforms: [web]
difficulty: intermediate
codebaseSize: large
bestFor:
- content sites
- docs sites
whyListed:
- Strong content collections model
- First-class MDX support
caveats:
- Newer than Next.js; smaller plugin ecosystem
name, slug, and kind are required. Everything else — category, repoUrl, stacks, platforms, difficulty, codebaseSize, bestFor, whyListed, caveats — is optional but drives the filters, the ranking, and the detail page.
From that single file the build emits a detail page, rows in every browse and filter index, a sitemap entry, JSON-LD, an OG card, a JSON dataset entry, and a line in the AI-readable text output. Writing that record once, in one place, is the entire point.

The documentation runs on Grove's own Starlight theme.

The CLI reference — everything a maintainer repeats is a command.
Collections carry the editorial judgment
A directory is only as useful as the reasoning behind what is in it. Collections live at data/collections/<slug>.yml and pair a query with the curator's note about it.
slug: top-flutter
kind: curated
title: Top Flutter apps
description: Hand-picked Flutter projects demonstrating strong engineering
query:
stacks: [flutter]
categories: [productivity, finance]
excludeStatuses: [archived]
ranking:
preset: quality
editorial:
selectionNote: Re-curated for the 2026 launch
lastReviewedAt: '2026-04-01'
Query fields are ANDed — a record has to satisfy stacks, categories, licenses, minStars, and the rest at once — while array values match on any. Ranking presets are quality, active, curated, recency, and stars.
The editorial block is the part most publishing tools omit: why these entries, chosen on what basis, last checked when.
Staleness is computed, not remembered
The hard part of a directory is not publishing it. It is the entry that quietly stopped being true.
grove sync github pulls repository metadata and derives a health block into data/health.yml — status, maturity, tier, visibility, cleanupCandidate, staleReason, confidence, and reasons. Status comes from measured facts, not memory:
| Status | Rule |
|---|---|
active |
Pushed within the last 183 days |
mature |
Active, 500+ stars, and a recent release or clear license |
stale |
Last push 183–548 days ago |
needs_review |
Last push 548–730 days ago |
inactive |
Last push more than 730 days ago |
archived |
Marked archived on GitHub |
unknown |
No GitHub metadata available |
Three further states — historical, quiet, unavailable — are editor-only, because some entries are deliberately frozen and no heuristic should overwrite that.
grove cleanup turns the derived signals into a human review queue. The machine decides what looks stale; a person still decides what to do about it.
The command surface
Everything a maintainer does repeatedly is a command, so it runs in CI as easily as on a laptop.
| Command | What it does |
|---|---|
grove init |
Scaffolds a complete space |
grove update |
Takes upstream UI changes without overwriting local edits |
grove check |
Validates sources and rebuilds generated outputs |
grove sync github |
Refreshes repository metadata |
grove sync contributors |
Refreshes community metadata |
grove cleanup |
Writes the human review queue |
grove import <github-url> |
Imports an existing list out of a README |
grove audit |
Runs Lighthouse against the configured pages |
grove readme generate |
Updates the generated sections of the README |
grove import matters more than it looks: most of these sites start life as a hand-maintained Markdown list, and the first job is getting that list into records without retyping it.
Packages, and the UI you own
| Package | What it owns |
|---|---|
@grove-dev/core |
Config, schemas, validation, importers, taxonomy, collections, health, sitemap, AI outputs, the data build pipeline |
@grove-dev/cli |
The grove binary: scaffolding, checks, sync, cleanup, imports, audits, README generation |
@grove-dev/astro |
The Astro integration and server view-models |
@grove-dev/starlight |
The Starlight theme the Grove documentation itself runs on |
The four are versioned in lockstep. The UI is deliberately not among them.
Pages and components ship as a shadcn registry. grove init installs them into the generated project as source, and records a hash for every file it wrote. grove update later diffs upstream against those hashes, so it can bring in new UI without ever overwriting a file you changed. Customising a site never means forking the pipeline.


