Maintain health signals
How to triage stale, archived, and unknown records, and what each health status means.
Health signals answer one question for the visitor of your directory: “Should I trust this entry?” A record with status: active and tier: curated says “yes, this is current and well-maintained”. A record with status: archived says “the maintainer archived the upstream repo”. A record with status: unknown says “we have no GitHub data — proceed with caution”.
This guide is for site maintainers triaging the periodic cleanup report. If you’re a contributor adding a single record, see Author a record.
Where the health data comes from
Section titled “Where the health data comes from”There are two places health data lives:
- The record’s
healthblock — auto-populated whengrove sync githubruns. Containsstatus,maturity,tier,visibility,cleanupCandidate,staleReason,confidence, andreasons. data/health.yml— the per-recordhealthEntrySchemaform of the same data, written by the sync step. Useful for diffing across runs.
The health block in the record is the source of truth at render time. Don’t hand-edit it — grove sync github will overwrite any manual changes on its next run. If you want to override the auto-derived value, write a decision.
The status enum
Section titled “The status enum”Ten values, mapped from a small set of signals. packages/core/src/health.ts: classifyHealth is the implementation.
| Status | When | Action |
|---|---|---|
active |
Repo pushed within the last 6 months | Leave alone |
mature |
Active + ≥500 stars + license or recent release | Promote to featured if not already |
stale |
No commits in 6-18 months | Decide: still relevant? |
inactive |
No commits in 24+ months | Add a historical or remove decision |
archived |
GitHub repo is archived | Add a hide or historical decision |
unavailable |
GitHub repo is disabled or 404’d | Add a remove decision |
unknown |
No GitHub metadata at all (e.g., the project is not on GitHub) | Add metadata or write a decision explaining why we list it |
historical |
Editor-set; record is kept for reference, not promotion | This value isn’t derived — it can only be set via a decision |
needs_review |
Editor-set; human review pending | Same — decisions.yml only |
quiet |
Editor-set; “watch this one” | Same — decisions.yml only |
The last three (historical, needs_review, quiet) are decision-only. They are not produced by grove sync github. If you see them in a record’s health.status field, it means someone wrote a decision that set it. See Manage decisions.
The tier enum
Section titled “The tier enum”Four values, derived from stars and activity.
curated— ≥500 stars or ≥4 active months in the commit history. Goes to the top of the index.listed— ≥50 stars. The default tier for established projects.experimental— <50 stars and few commits. Not a punishment — it means “we don’t have enough data to vouch for this yet”.hidden— archived or inactive. The record is still in the data, but the index does not list it.
curated vs listed is purely a function of stars. Promotion isn’t an editor decision; it’s a function of the project’s adoption. If you want to override the tier, use a decision.
The cleanupCandidate flag
Section titled “The cleanupCandidate flag”cleanupCandidate: true is the “this record needs a human look” flag. It’s true when:
statusisstale,archived,inactive, orunavailable, orstatusisunknownand the record has no GitHub data
The grove cleanup stale command filters by this flag and writes a report. In a public GitHub site, the cleanup-stale-records.yml workflow runs this on a schedule and posts the report as a workflow artifact. Pull the artifact, walk the list, write decisions for the records that need an explicit verdict.
A triage session
Section titled “A triage session”When the cleanup report lands, work through it in this order. Don’t try to handle all 200 candidates in one PR — break it into batches of 10-20.
-
Open
data/generated/cleanup-report.json. Sort bystaleReason.github_archivedandgithub_unavailablerecords are usually batchable — they all need ahideorremovedecision with the same justification. -
For each
archivedcandidate: visit the live site to confirm the archive notice is there. If the project is also superseded by something in your directory, add ahistoricaldecision and aseeAlsolink. If it’s just dead, addremove. -
For each
stalecandidate: read the last commit. If the project is feature-complete (a library that hit 1.0 and stopped), mark itmaturein your editorial review and add akeepdecision. If the project was abandoned without an archive, this is a judgment call —hideis the safe default;removeif the GitHub repo is also gone. -
For each
unknowncandidate: usually means the project is not on GitHub. Either add the correctrepoUrl, or write a decision noting the project lives elsewhere (e.g., a private registry, a self-hosted repo). -
Run
grove validateafter each batch to make sure your decisions file parses. -
Run
grove generateto refresh the index, thenpnpm devto spot-check that the records still render correctly. Thehealth.visibilityfield controls the index list, so a typo here can hide 20 records at once.
When signals lie
Section titled “When signals lie”The signals are heuristics. Three common cases where they get it wrong:
- “No commits in 18 months” but the project is feature-complete. A library at 1.0 with a stable API has every right to be quiet. Don’t auto-
hidethese. Mark themmatureinscores.maturityand write akeepdecision. - “Fewer than 50 stars” but it’s a brand-new launch.
experimentalis correct — the tier reflects “we don’t have adoption data yet”, not “this is bad”. Don’t write a decision to override the tier; let it climb as stars arrive. - “The GitHub repo was transferred”. The sync step will see the new owner and the new push dates. If the project is the same project under new stewardship, just let it sync. If the new owner is unrelated, treat it as
unavailableand remove.
Confidence
Section titled “Confidence”confidence is low, medium, or high. It’s low when there’s no GitHub metadata at all. It’s medium when GitHub metadata exists but the record is missing other signals (e.g., a license). It’s high when the GitHub data is complete (full name, license, recent activity).
Low confidence doesn’t mean the record is bad — it means “we’re guessing, not measuring”. A low-confidence record that’s been around for two years and still has community traction is fine; the heuristic just can’t see it.
What “fixing” a record looks like
Section titled “What “fixing” a record looks like”If you want a record to surface better in the index:
- Stars are below 50, but you think it deserves
listed? You can’t promote by hand. The tier is a function of signals. Either wait for the project to grow, or — and this is the only legitimate override — use a decision to set a customvisibility: highlightso the record gets editorially featured. The tier staysexperimental; visibility is a separate axis. - Stars are above 500 but the record is
experimental? The sync step ran before the stars crossed the threshold. Rungrove sync githubagain, or wait for the next scheduled run. The threshold is checked every time. - The record is
hiddenbut you want it back? It gothiddenbecausestatusisarchivedorinactive. Resolve the underlying status first (most often: the upstream repo came back to life). The next sync will reclassify it.
See Sync GitHub metadata for the upstream data the signals are computed from.