# Grade the golden path: an offline production-readiness scorecard for any repo


Every platform team eventually writes the same document. It has a title like "Definition of Done for a Service" or "Production Readiness Review," and it lists the things a repo needs before anyone will put it on call: CI, tests, a real README, a license, a container image, some logs and metrics, a security contact, a changelog. Then the document goes stale, because nothing checks it. The list lives in a wiki; the repos drift; and the only time anyone notices a service was never actually ready is during the incident.

Internal Developer Platforms tried to fix this with **scorecards** - Backstage's software health checks, Cortex, OpsLevel. They're good, and they're heavy: a catalog, integrations, a running control plane, a YAML entity for every service. That's a lot of machinery to answer a question you often want to ask about a single checkout, right now, on your laptop or in a pull request.

`gradepath` is the small, offline version of that question. Point it at a directory and it scans for the **presence and basic quality** of production-readiness signals, applies a **weighted checklist**, and prints a **maturity grade** with a **prioritized list of the gaps to close** - heaviest first. No catalog, no network, no execution, no AI. One Go binary over a filesystem.

```bash
$ gradepath ./services/orders

gradepath  service production-readiness scorecard

  GRADE  B   78.5/100   (production-ready)
  13 passed · 1 partial · 3 missing  of 17 checks

  ● gaps to close (weighted, highest impact first)
    ✗ Security policy                (+5 pts)
       ↳ Add SECURITY.md with a vulnerability-disclosure process.
    ✗ Changelog maintained           (+4 pts)
       ↳ Keep a CHANGELOG.md so consumers can see what changed between releases.
    ~ Logging / metrics / tracing    (+2.5 pts)
       ↳ Wire structured logging plus metrics or tracing.
```

### A scorecard is a producer, not a linter

Most of the small tools I build are **gates**: they look for one specific problem and exit non-zero so a pre-commit hook or CI turns red. That model is exactly wrong for a readiness score. A grade isn't pass/fail - it's a position on a spectrum, and its job is to *inform prioritization*, not to block a merge.

So gradepath is a **producer**. By default it always exits `0` and simply reports. The value is in the report: the grade tells you where the service sits, and the ranked gaps tell you the shortest route up. If you *do* want a gate - say, "new services must reach 75 before they ship" - you opt in with `--min-score N`, and only then does a low score exit `1`.

```bash
gradepath .                    # always exit 0 - pure report
gradepath --min-score 75 .     # exit 1 if below 75 - an opt-in CI gate
```

That single design decision - report by default, gate by choice - is what makes a scorecard usable across a hundred repos without creating a hundred red builds on day one.

![](https://cdn.hashnode.com/uploads/covers/6a548baa47c6120a79511a69/f677b69d-31a6-4078-8f44-a87ab95bb6ea.svg align="center")

### The scoring model

The core is deliberately boring, because a grade people will act on has to be explainable in one breath.

Every check on the checklist has a **weight** and returns one of three states:

*   **pass** - the signal is present and looks non-trivial → full weight
    
*   **partial** - present but weak (a stub README, or only one of logging/metrics/tracing) → half weight
    
*   **fail** - missing → zero
    

The score is just the weighted percentage:

```plaintext
score (0..100) = Σ earned weight / Σ total weight × 100
```

and the grade is the highest tier whose cutoff the score clears:

| Grade | Score | Tier |
| --- | --- | --- |
| **A** | ≥ 90 | golden path |
| **B** | ≥ 75 | production-ready |
| **C** | ≥ 60 | maturing |
| **D** | ≥ 40 | early |
| **F** | < 40 | bare |

The **gaps** list is simply the non-passing checks sorted by descending weight. That ordering is the whole point: it turns "you're missing eight things" into "fix these two first, they're worth the most."

### What it actually looks for

Seventeen checks across eight categories. The heaviest signals are the ones whose absence most reliably says *not production software*:

| Category | Checks (weight) |
| --- | --- |
| **CI/CD** | a CI pipeline - GitHub Actions, GitLab CI, Jenkins, CircleCI, Travis, Azure, Bitbucket (12) |
| **Tests** | a tests dir or `*_test.go` / `test_*.py` / `*.spec.ts` (12); coverage tracked (4) |
| **Docs** | a substantive README (10); CONTRIBUTING (4); `docs/` (3) |
| **Licensing** | a LICENSE file (8) |
| **Containerization** | Dockerfile (6); `.dockerignore` (2); `HEALTHCHECK` or a `/health` endpoint (3) |
| **Observability** | logging / metrics / tracing hints (5) |
| **Security** | SECURITY.md (5); a dependency lockfile (5); no committed secrets (4) |
| **Ops** | CHANGELOG (4); CODEOWNERS (3); release versioning (3) |

A few of the detectors are more than a file-exists check:

*   **README** is graded on substance, not presence. A two-line stub is a *partial*; a README past a size threshold with real sections is a *pass*. The classic "we have a README" that says nothing doesn't get full marks.
    
*   **Observability** scans a bounded set of source files for structured-logging, metrics/tracing, and health-endpoint signals (`slog`, `zap`, Prometheus, OpenTelemetry, `/healthz`, …). Two of the three is a pass; one is a partial; none fails.
    
*   **No committed secrets** is an inverted check - it *passes* when it finds nothing, and fails if it spots a committed `.env`, `id_rsa`, `*.pem`, or `credentials.json`. It deliberately ignores `.env.example` and `*.sample`, because a template is a good thing, not a leak.
    

Everything is a **read-only filesystem scan**. gradepath never runs git, never executes the target code, never opens a socket. It walks the tree once, skipping `.git`, `vendor`, `node_modules`, and build output, indexes files by basename, and caches a bounded amount of doc/source/CI content for the detectors that need to read inside a file.

### Honesty: presence is not correctness

The most important sentence in the README is the one admitting what gradepath doesn't do. It scores the **presence** of a signal, not its **correctness**. A Dockerfile counts even if it runs as root. A README is "substantive" by size and structure, not by whether it's accurate. The observability check sees that you imported a metrics library, not that your dashboards are any good.

This is on purpose. A holistic, offline, deterministic *first pass* that you can run on every repo in an org and rank by what to fix first is enormously useful - and it is a completely different thing from a security audit or a code review. gradepath is a **map of the golden path**, not a judge of the code that walks it. Selling it as more than that would be dishonest, and it would break the moment someone gamed a check with an empty file.

### Your golden path, not mine

"Production-ready" means different things to a fintech API and an internal batch job. The default weights encode a reasonable general opinion, but they're just that - an opinion. So the entire checklist is overridable with `--config` (JSON): reweight checks, disable the ones you don't care about, and set your own grade cutoffs.

```json
{
  "weights": { "ci_workflow": 20, "tests_present": 20, "codeowners": 0 },
  "disable": ["dockerignore"],
  "grades": [
    { "grade": "A", "min": 92, "tier": "golden path" },
    { "grade": "B", "min": 80, "tier": "production-ready" },
    { "grade": "F", "min": 0,  "tier": "needs work" }
  ]
}
```

A weight of `0` (or the `disable` list) turns a check off entirely; a team that doesn't containerize can drop the whole category. Custom `grades` replace the defaults wholesale. The point is that gradepath ships an opinion you can overrule, which is the only kind of opinion a platform tool should ship.

### Two fixtures, two grades

The test suite pins the behavior against two repositories so the numbers can't silently drift.

The **bare** fixture is a lone `main.go` and a two-line README. It scores **9.7 / 100, grade F ("bare")** - only the no-secrets check passes, the README is a partial, and every heavy signal is a ranked gap.

The **well-equipped** fixture has the lot: CI with coverage, a tests directory *and* a `_test.go`, a real README, CONTRIBUTING, `docs/`, a LICENSE, a Dockerfile with `.dockerignore` and a `HEALTHCHECK`, a `/healthz` + `/metrics` service using `slog`, a SECURITY policy, a lockfile, a CHANGELOG, CODEOWNERS, and a VERSION. It scores **100 / 100, grade A ("golden path")** with zero gaps.

Those exact scores are assertions in the tests, alongside the `--min-score` gate (bare exits `1`, well exits `0`), a bad path exiting `2`, and a config override that provably reduces the check count and lifts the score. The whole thing is stdlib-only Go, `gofmt`/`go vet` clean, and green on `go test ./...`.

### Distinct from the neighbors

gradepath sits next to a few focused tools I've written and is deliberately not any of them. Where those each flag one specific problem and fail - dev/AI shipping cruft left in a repo, a specific insecure production setting, `.env.example` drift - gradepath **scores overall readiness across many categories** and, by default, only reports. It's the holistic map, not the single tripwire; and unlike the hosted scorecard platforms, it needs no catalog, no integrations, and no network to run.

### Try it

```bash
go install github.com/jay-tank/gradepath@latest
gradepath .
```

The source, the two fixtures, and the full checklist are on GitHub: [**github.com/jay-tank/gradepath**](https://github.com/jay-tank/gradepath) (MIT-licensed). Grade a repo you own and see which two gaps are worth the most - that's the report doing its job.
