# The Repository Liveness Standard: Active, Stable, Dormant, or Dead

*Grade any repository Active, Stable, Dormant, or Dead from written thresholds, so two analysts on the same snapshot date assign the same label and log the same evidence.*

- Canonical URL: https://www.refolk.ai/guides/repository-liveness-standard
- Pillar: Market and talent intelligence
- Format: Standard
- Published: 2026-10-26
- Last reviewed: 2026-10-26
- Reading time: 21 min
- Keywords: how to tell if an open source project is abandoned, count active open source projects in a category, open source project health criteria for landscape, dormant vs feature complete github repository, exclude bot commits from repository activity

## Key takeaways

- Public definitions of a maintained project differ by about a factor of four: OpenSSF Scorecard looks back 90 days, while Read the Docs and Avelino et al. use 12 months.
- In Coelho et al.'s survey of owners, 41% of repositories a model flagged as unmaintained were finished projects, so a liveness standard needs a Stable label or it counts working software as dead.
- Bots made nearly one-fifth of commits in one study and sat in the top three committers of 18 of 27 popular repositories, so no repository should be labeled Active on bot commits alone.
- Filtering for 'bot' in the login catches only 48% of bot accounts, so ambiguous authors need a classifier such as BoDeGiC and a hand check of the top committers.
- Explicit end markers are rarely set: one check found only 6 archived PyPI projects among about 145 obsolete or abandoned ones, so a Dead label must not require a flag.
- In Refolk's index, US profiles with maintainer titles outnumber those with Open Source Program Office titles about 143 to 1, which makes maintainer issue response, not an OSPO contact, the practical evidence of support.

This standard is for strategy teams, talent-intelligence analysts, and operators who map an open-source category or compare rival ecosystems and need to say how many live alternatives exist. It defines four labels (Active, Stable, Dormant, Dead), the written thresholds that assign each one, and the evidence row each repository must carry. If your team adopts it as policy, two analysts given the same repository and the same snapshot date will reach the same label.

## What the four liveness labels mean

A liveness label says whether humans are still changing or supporting a repository as of a fixed snapshot date. Active and Stable count as live, and Dormant and Dead do not. That one rule is how this standard answers the question of how to count active open source projects in a category.

| Label | Plain meaning | Counts as live |
|---|---|---|
| Active | Humans changed it in the last 90 days | Yes |
| Stable | No recent change, maintainers still answer | Yes |
| Dormant | Support has lapsed, no end declared | No |
| Dead | End declared, or a year of ignored silence | No |

These terms are used throughout, each with a fixed meaning:

- **Snapshot date**: the single date every measurement in a landscape is taken against, written on every row.
- **Human-authored commit**: a commit on the default branch whose author is still in the data after the bot-exclusion step.
- **Maintainer**: a collaborator, member, or owner of the repository. This is the same group OpenSSF Scorecard credits for issue activity.
- **Outside item**: an issue or pull request opened by someone who is not a maintainer.
- **Unanswered**: an outside item more than 90 days old at the snapshot date that has no comment, review, or close from a maintainer's human account.
- **Human signal**: any human-authored commit, human-published release, or maintainer response.

### Why Stable is a separate label

Finished software is common, and a commit window can't see it. Coelho et al. built a model that flagged GitHub repositories as unmaintained, then asked the owners. The most common answer, 54 of the responses (41%), was that the project was finished: the maintainers considered it feature-complete and would only resume work if someone reported a relevant bug. Scorecard's own documentation says the same thing: some software, especially smaller utility functions, does not normally need to be maintained. Without a Stable label, roughly 4 in 10 inactivity flags put working software in the Dead column.

### Why Dormant is separate from Dead

Dormancy often reverses. Avelino et al. studied 1,932 popular projects and found 16% abandoned, but 41% of those survived because new core developers took over. A separate study found that developers who pause have a 35 to 55% chance of returning to an active state. Merging Dormant into Dead throws away the projects most likely to come back.

## Why do public definitions of "maintained" disagree?

They measure different things over windows that differ by about a factor of four: 90 days for OpenSSF Scorecard, 12 months for Read the Docs and Avelino et al., and no stated window at all for the CNCF Technical Oversight Committee (TOC). None of them was designed for counting a category, and none handles bot-only commit streams, finished utilities, or renamed repositories.

| Source | Window | What counts | Finished software |
|---|---|---|---|
| OpenSSF Scorecard | 90 days | Commits; maintainer issue activity | Low score; "users should investigate" |
| Read the Docs | 12 months | Releases or builds; owner reachable and active | Not addressed |
| CNCF TOC | None stated | Health, adoption, security responsiveness | Not addressed |
| Avelino 2019 | 1 year | Last commit by a truck-factor developer | Not addressed |
| Coelho 2018 | Model, no fixed window | 13 activity features | Counted as unmaintained |

Each one carries a trap when you borrow it for a landscape:

- **Scorecard** gives an archived project its lowest score and weekly commits its highest, and the check only succeeds once the repository is more than 90 days old. One repository with 414 commits in the last 90 days still scored 0 because it was 72 days old.
- **Read the Docs** calls a project abandoned only when three conditions hold together: the owner is unreachable, there have been no successful builds or no releases in twelve months, and the owner shows no activity on the project's home page. It stops trying to reach the owner after six weeks. The policy is essentially a reformulation of Python's PEP 541, and it was built to decide who may take over a name, not whether a project is alive.
- **CNCF** archiving is a governance vote, with a two-week comment period, a two-week vote, and a two-thirds majority. The rkt project was archived because end user adoption had severely declined, which says nothing about whether its code still worked.
- **The research literature** uses thresholds that run from 2 months to 1 year without activity. Coelho's model doesn't depend on an inactivity interval at all.

Two claims circulate that I could not trace to a primary source: that six months without commits is the standard dormancy line, and that CNCF applies a twelve-month rule. Treat both as unverified. This standard uses 90 days and 12 months because each can be traced to a published source.

## The threshold table: which label does a repository get?

Run five tests in a fixed order and stop at the first one that matches. The first match sets the label, and the matching test's basis code goes into the evidence row so a reviewer can see why.

#### Liveness decision order

1. **Declared end** - Archived flag, registry status, npm deprecation, or README notice means Dead (declared)
2. **Human change** - At least one human-authored commit in 90 days means Active
3. **Answered queue** - No unanswered outside items in 365 days, plus a human signal, means Stable
4. **Ignored silence** - No human signal in 365 days and an unanswered outside item means Dead (inactive)
5. **Remainder** - Everything else is Dormant

*Declared ends are checked first and silence last, so a repository is only called Dead on inactivity after every route to a live label has failed.*

| Order | Test at the snapshot date | Label | Basis code |
|---|---|---|---|
| 1 | Any declared-end flag present | Dead | DECL |
| 2 | 1 or more human commits in 90 days | Active | H90 |
| 3 | 0 unanswered outside items in 365 days, and a human signal in 365 days | Stable | RESP |
| 4 | No human signal in 365 days, and 1 or more unanswered outside items | Dead | SIL |
| 5 | None of the above | Dormant | REM |

Two pre-checks run before the table:

- **Young repositories.** If the repository was created less than 90 days before the snapshot, it can be Active (with at least one human commit) or Dead (declared), but never Dormant or Dead on inactivity. Without a human commit, mark it ungraded and leave it out of both counts.
- **Quarantined packages.** PyPI's PEP 792 defines three statuses: active (the default), archived, and quarantined. A quarantined package is a registry safety action, not an end-of-life declaration. Leave the repository ungraded and escalate it.

> **Rule:** Active requires a human
>
> A repository whose only commits in the 90-day window come from bots is never Active, however many commits there are. Bots can supply the volume, but the label requires at least one human-authored commit.

The test for a dormant vs feature-complete GitHub repository is the response test in row 3, not the commit log. A finished utility and an abandoned one look identical in commits. The difference shows up when someone files a bug: one gets an answer and the other doesn't. The matrix below is the shorthand.

#### Change versus support

Horizontal axis runs from No human change in 90 days to Human change in 90 days. Vertical axis runs from Outside requests ignored to Outside requests answered.

| Quadrant | What it means |
| --- | --- |
| Stable | Count as live and log the last maintainer response date |
| Active | Count as live |
| Dormant or Dead | Apply the 365-day silence test in row 4 |
| Active with a backlog | Count as live and note the unanswered count in the row |

*Commit activity separates Active from the rest, but only maintainer response separates finished software from abandoned software.*

## Which evidence counts, and how does each signal lie?

Five kinds of evidence decide the label: hard flags, human-authored commits, releases, maintainer issue response, and repository moves. Stars and forks are not evidence. Each signal below comes with what it proves and what it looks like when it misleads.

### Hard flags and moves

A hard flag is a maintainer or platform declaring the end. A flag that is present proves the end was declared. A missing flag proves nothing, because maintainers rarely bother to set one.

| Signal | Where to read it | What it proves | How it lies |
|---|---|---|---|
| GitHub `archived` | Repository object | Read-only; end declared | Work may have moved elsewhere |
| PyPI status | Simple API only | PEP 792 archived marker | JSON API omits it; rarely set |
| npm deprecated | Latest version metadata | Per-version message | Older versions only may be flagged |
| README notice | Default branch | Maintainer states the end | Stale text after a revival |
| 301 on GET | Repository endpoint | Renamed or transferred | Name-keyed tools drop the repo |

How rarely the markers are used is the main reason Dead can't require one. One implementer found only 6 archived PyPI projects among about 145 obsolete or abandoned projects checked, and the status is only visible through the Simple API, not the JSON API. PyPI also has the per-file Trove classifier `Development Status :: 7 - Inactive`. Count it as a declared end only if it appears on the files of the latest release.

When a repository is renamed or transferred, GitHub answers a request with 301 Moved Permanently and a body containing only a message, a URL, and a documentation URL. The redirect points to `/repositories/<id>`. That numeric ID is the only stable key. GitHub also will not redirect calls to an Action hosted by a renamed repository, so anything downstream that refers to the repo by its old name can break quietly. Development can also move without a rename: the SMI maintainers consolidated their work under GAMMA in Kubernetes SIG Network, and SMI became the fifth CNCF project to be archived. When the code has moved, label the original repository on its own evidence, record the successor's ID, and count the work only once.

### Human-authored commits

To exclude bot commits from repository activity, combine three methods. No single filter is reliable. Golzadeh et al. examined 27 popular repositories, found bots in all of them, and found a bot among the top three committers in 18. Another study attributed nearly one-fifth of all commits in its projects to bots, and in one dataset 92.3% of the activity by GitHub's internal automation services was pushing commits. The most active bots are github-actions[bot] and dependabot[bot].

| Method | Metric | Value |
|---|---|---|
| "bot" substring in login | Share of bot accounts caught | 48% |
| BoDeGHa (PR and issue comments) | F1 | 0.98 |
| BoDeGHa on a commit dataset | Precision | 0.77 |
| BoDeGiC (commit messages) | Precision | 0.80 |
| BotHunter, Random Forest | F1 / AUC | 92.4% / 98.3% |

The table shows why the exclusion runs in layers. First drop the `[bot]` suffixes and known bot logins. Then run BoDeGiC or BoDeGHa on the authors you can't place. Then look at the top committers by hand, because a classifier with 0.77 or 0.80 precision still mislabels some humans as bots. Record every excluded author in the row so a second rater can disagree with a specific name.

> **Watch out:** The login filter misses half the bots
>
> Only 48% of bot accounts have "bot" in their login, so a substring filter on its own leaves about half the automation in your human count. Always inspect the top committers of any repository you are about to label Active.

### Releases

A release counts as a human signal only if a human account published it or it contains at least one human-authored commit since the previous release. Release automation can cut a version that contains nothing but dependency bumps, and that proves only that a pipeline exists. Releases never make a repository Active on their own, because Active needs a human commit. What they can do is keep it out of the Dead (inactive) row.

### Maintainer issue response

This is the signal that tells finished from abandoned, and it follows Scorecard's rule of crediting only collaborators, members, and owners. A response is a comment, review, or close from a maintainer's human account. Closures by automated stale-issue tools are bot actions and do not count. Count the outside items opened in the 365 days before the snapshot that have waited more than 90 days with no response. Zero means the queue is answered.

It's tempting to skip the issue tracker and ask someone at the sponsoring company instead. That route is thinner than it looks.

**143:1 - US profiles with maintainer titles per profile with an Open Source Program Office title, in Refolk's index**

Refolk's index holds 1,568 US profiles matching maintainer titles against 11 with OSPO titles, so a program office is rarely there to confirm a project's status.

| Segment in Refolk's index | Count | Notable employers |
|---|---|---|
| Maintainer titles, US | 1,568 | Akuity, Okta, Svix |
| Maintainer titles, Germany | 56 | CNCF, Zephyr Project, Dash0, IBM |
| OSPO titles, US | 11 | Goldman Sachs, Apple, SAS |

Read this table with its caveat. The most common current titles in the US sample were Founder or CEO, which suggests many profiles matched on "maintainer" in the headline while the current title is something else. Even so, the US count is about 28 times the German one, and in Germany 8 of 25 sampled profiles carry the literal title "Maintainer". The people who can actually confirm a project's status are the maintainers themselves, and their public trace is the issue tracker.

You still need to know who those maintainers are. When the collaborator list is private or a company account handles responses, the quickest way to tie a GitHub handle to a named person and employer is to ask [Refolk](/) for them by role and recent activity:

I ran this search: `Core maintainers of Python packaging tools who have committed in the last 90 days` - [see the full result list](https://www.refolk.ai/s/4fa41jkmgr).

*Returns named people matched across GitHub, LinkedIn, and the open web, which you can use to check whether a responding account is really a maintainer.*

### Stars and forks

Do not use stars as a liveness signal. An archived repository blocks new issues, pull requests, and comments, but users can still fork it and star it. That means star counts keep rising after a project ends. How fast stars decay after a project dies has not been established publicly, so dated human signals are the honest replacement.

## How to grade a repository, step by step

Grade every repository with the same seven steps against one snapshot date and finish with one evidence row. By a desk estimate (unverified, since no public source gives durations), steps 1 to 6 take about 19 minutes per repository.

#### Grading procedure

1. **Freeze the snapshot and resolve the repo ID** - Write one snapshot date for the whole landscape, request each repository, and follow any 301 to /repositories/<id>. Key the row by that numeric ID and list every name the repo has used.
2. **Check the hard flags** - Read the GitHub archived field, the PyPI Simple API status, the deprecated field on the latest npm version, and the README. Record each as present or absent.
3. **Count human commits after removing bots** - Pull default-branch commits for the 90 days before the snapshot, drop [bot] suffixes and known bot logins, run BoDeGiC or BoDeGHa on ambiguous authors, and check top committers by hand. Record the human count and the excluded authors.
4. **Record the last release** - Pull release dates from the repository and any registry it publishes to. Record the last release date and whether it qualifies as a human signal.
5. **Measure maintainer issue response** - Count only collaborators, members, and owners on human accounts. Record the last maintainer response date and the number of outside items from the last 365 days that have waited more than 90 days unanswered.
6. **Apply the threshold table and log the row** - Run the five tests in order, stop at the first match, and write the label, basis code, and rule version.
7. **Have a second rater audit a sample** - A second analyst grades a sample blind from the same snapshot. Trace every disagreement to the data or the rule text.

> **Rule:** Key every row by repository ID
>
> Owner and name are display fields, not keys. A landscape keyed by name can double-count a moved repository or drop it, and both errors change your headline number.

Sources disagree about the order of checks. Scorecard checks the archive flag first, and Read the Docs starts with whether the owner can be reached. This standard checks flags first because that test is cheap and final. It checks response last because it is the slowest and the most open to judgment.

**Liveness evidence row**

```
repo_id,current_full_name,names_seen,snapshot_date,created_date,young_repo,gh_archived,pypi_status,npm_latest_deprecated,readme_eol_notice,human_commits_90d,last_human_commit,bot_authors_excluded,last_release,release_is_human_signal,last_maintainer_response,unanswered_outside_365d,label,basis_code,successor_repo_id,rater,rule_version,notes
label values: Active | Stable | Dormant | Dead | Ungraded
basis_code values: DECL | H90 | RESP | SIL | REM
```

*Use as the CSV header for the landscape sheet. Keep the column order so rows from two raters can be compared line by line.*

## How does liveness grading go wrong?

Most mislabels come from eight failure modes. Each one has a specific check, and the checklist below is built from them. I'd rank this section above the threshold table: the thresholds are a choice, but these errors are what make a published count fall apart when someone else checks it.

1. **A bot-only commit stream counted as Active.** Dependabot alone can meet a weekly-commit window. A repository can look busier than most of the category while no human has touched it. *Check:* the human commit count after exclusion is greater than zero.
2. **The login filter misses about half the bots.** A substring match on "bot" catches only 48% of bot accounts, so renamed automation and service accounts stay in the human count. *Check:* run BoDeGiC and look at the top committers by hand.
3. **A renamed repository treated as missing or dead.** In one tool, the archived field came back nil and the repository stayed in the list. The data looked fine and was wrong. *Check:* compare repository IDs and follow the 301.
4. **A missing registry flag read as evidence of life.** Only 6 of about 145 obsolete projects checked carried a PyPI archived marker, and the JSON API doesn't show the marker at all. *Check:* query the Simple API (v1.4) and never treat "no flag" as "alive."
5. **A feature-complete utility labeled Dead.** In Coelho's owner survey, 41% of repositories flagged as unmaintained were finished. *Check:* look for maintainer responses to new issues within the window before any inactivity label.
6. **The one-year rule overcounting abandonment.** One review argues that this kind of rule classifies a 13-month hiatus as permanent detachment, and the 35 to 55% return rate for pausing developers backs that up. *Check:* report Dead (SIL) separately from Dead (DECL) and re-grade at the next snapshot.
7. **A young repository labeled not maintained.** Scorecard scores any repository under 90 days old as 0, which is how a project with 414 recent commits scored zero. *Check:* compare the creation date with the snapshot date before applying any window.
8. **CNCF Archived treated as Dead.** CNCF states that maintainers are welcome to continue working on an archived project, and archived projects can be reactivated through the normal proposal process. *Check:* look for commits after the archive date and grade the repository on its own evidence.

Two more errors are worth naming because they happen in the analyst's process, not in the data:

- **Rolling snapshot dates.** If grading takes a week and each analyst measures "the last 90 days" from the day they open the repository, the windows don't match and repositories near a boundary flip labels. Every row gets the frozen date.
- **Silent rule edits.** If someone changes a threshold midway through a landscape, the count mixes two standards. Every row records the rule version, and a version change means re-grading.

> A finished utility and an abandoned one look identical in the commit log; only the issue tracker tells them apart.

## How do you check that two raters grade the same way?

Grade a sample twice, independently, and trace every disagreement to a cause. No published inter-rater agreement figure (such as a kappa value) exists for manual liveness labels, so you are setting your own baseline, not matching a benchmark.

Here's what comparable work does. The ground truth for one bot-detection dataset was labeled as bots or humans by three raters. Coelho et al. checked their model against owners rather than raters: 103 true positives and 26 false positives, a precision of 80%, with recall of 96%. Both approaches carry over:

- **Blind second grading.** The second rater gets the repository ID and snapshot date only, not the first rater's row.
- **Compare basis codes as well as labels.** Two raters who both say Dormant, one via REM and one via a missed DECL flag, have found a data problem the label hides.
- **Sort each disagreement into one of two causes.** A data disagreement (different commit counts, a missed bot) is fixed in the row. A rule disagreement (two honest readings of "unanswered") is fixed in the rule text, and the rule version goes up.
- **Break ties with a third rater**, as in the bot ground-truth work, and record who settled it.
- **Use owner confirmation sparingly** for cases that stay disputed. Coelho's survey shows that owners will say "finished" when asked. Finding the right person by name is the slow part, and that is where Refolk's people search saves a round of guesswork.

Pick the sample size and the agreement level you will accept before grading starts, and write both into the policy, so nobody tunes the audit after seeing the results.

## Verification checklist before publishing a count

A landscape count is ready to publish when every item below is true. Each item is something a reviewer can check against the evidence sheet, not a matter of judgment.

#### Liveness count sign-off

- [ ] Every row is keyed by numeric repository ID, and renamed or transferred repositories were followed through the 301.
- [ ] Every row carries the same snapshot date and the same rule version.
- [ ] GitHub archived, PyPI Simple API status, latest npm deprecation, and README notice are each recorded as present or absent on every row.
- [ ] No repository is labeled Active without at least one human-authored commit in the 90-day window.
- [ ] The excluded bot authors are listed on every row, and the top committers of every Active repository were inspected by hand.
- [ ] Maintainer response counts only collaborators, members, and owners on human accounts, with automated closures excluded.
- [ ] Repositories younger than 90 days are flagged and none is labeled Dormant or Dead on inactivity.
- [ ] CNCF Archived projects were graded on repository evidence, with commits after the archive date checked.
- [ ] Where development moved, the successor repository ID is recorded and the work is counted once.
- [ ] The published count reports Active and Stable separately, and Dead (DECL) separately from Dead (SIL).
- [ ] A second rater graded a sample blind, and every disagreement was resolved in the data or the rule text.

## Keeping the map current

A liveness label holds only for its snapshot date. Re-grade the whole landscape at each new snapshot and keep the old rows. Changes between snapshots are findings in their own right, and Dormant projects in particular come back often enough to matter.

- **Keep history, never overwrite.** Store each snapshot's rows side by side, keyed by repository ID. A Dormant project that turns Active, or a Dead (SIL) one that revives, is exactly the reversal that Avelino's 41% survival rate and the 35 to 55% return rate predict.
- **Re-check the public definitions you rely on.** The Scorecard Maintained check's window and scoring are published on the Scorecard project's documentation and viewer at scorecard.dev. The CNCF archiving process is in the TOC repository. Read the Docs' abandoned-project policy is on its documentation site. If any of them changes, record it in the rule version, not in an individual row.
- **Watch the registry marker mechanism, not its current adoption.** PEP 792 status markers are new enough that most projects have never set one. When re-checking, sample a set of known-obsolete packages through the Simple API and see whether the share carrying a marker has moved. Until it moves a long way, absence still proves nothing.
- **Re-resolve every ID.** Repositories get renamed and transferred between snapshots. Request each one again by ID before measuring anything.
- **Refresh who the maintainers are.** Collaborator lists change, and the response test is only as good as the list behind it. A periodic people search by project and role, which Refolk handles in plain English, keeps that list current without re-reading every contributor page.

The standard is meant to be boring to apply. If two analysts on your team disagree about a repository, the evidence row should show exactly which field caused it, and fixing that field or the rule text is the whole job.

## Frequently asked questions

### How do I tell if an open source project is abandoned?

Check for a declared end first: the GitHub archived flag, a PyPI archived status, a deprecated latest npm version, or a README notice. If none is set, look for human-authored commits in the last 90 days, then for maintainer replies to outside issues. Call a project abandoned (Dead) without a flag only after 12 months with no human commit, release, or maintainer response while at least one outside request went unanswered.

### Should bot commits count as repository activity?

No. Bot commits should never make a repository Active. Bots commit on a schedule whatever the humans are doing, and one study found them behind nearly one-fifth of all commits. Drop [bot] suffixes and known bot logins, run a classifier such as BoDeGiC on authors you can't place, and check the top committers by hand, because only 48% of bot accounts have 'bot' in the login.

### What is the difference between a dormant and a feature-complete GitHub repository?

A feature-complete repository still answers bug reports. A dormant one doesn't. Both can go months without commits, so the commit log can't tell them apart. The issue tracker can. If no outside issue opened in the last year has waited more than 90 days for a maintainer response, and there is some human signal in that year, label it Stable. If outside requests sit unanswered, label it Dormant.

### Why not just use the OpenSSF Scorecard Maintained score?

Scorecard measures something else. It is a security check. Its 90-day window rewards weekly commits, gives finished utilities a low score while saying users should investigate, and scores repositories under 90 days old as zero. One repository with 414 commits in 90 days still scored 0 because it was 72 days old. Use it as one input, not as the label.

### Does an archived CNCF project count as dead?

Not automatically. CNCF's Archived stage is a foundation decision about support and recommendation. It doesn't freeze the code, and maintainers are welcome to keep working, so grade the repository on its own evidence. Look at the GitHub archived flag and at human commits after the archive date. CNCF also lets archived projects be reactivated through the normal proposal process.

### Is six months without commits a standard dormancy threshold?

No. It isn't established. The six-month rule turns up in secondary discussion, but no primary source confirmed it. Published windows run from 2 months to 1 year: Scorecard uses 90 days, and Read the Docs and Avelino et al. use 12 months. This standard uses 90 days and 12 months because both can be traced to a public source.

---

*From the Refolk guide library. I revise these guides rather than replacing them, so the current version is always at https://www.refolk.ai/guides/repository-liveness-standard*
