Repository navigation
Reality Score
The Reality Score is one number for how much of your public world matches your chart: the share of live world artifacts that are bound, in sync with starchart.lock, not expired and, optionally, passing a live audit. Put it in your README as a badge and watch it drop the moment someone changes a price without shipping the screenshots. This page covers the exact formula, what counts as in sync, the --audit mode, the SVG badge and shields.io endpoint, the color thresholds, and how to publish the badge from CI. Source: analysis/score.ts.
artifacts = every node with kind "artifact" and status != "retired"
bad = unbound ∪ stale ∪ expired ∪ failingAudit (a set; each artifact counts once)
inSync = |artifacts| − |bad|
score = round(100 × inSync / |artifacts|) (100 when there are no artifacts)
An artifact is in sync when all four hold:
| Condition | Breakdown key | Fails when |
|---|---|---|
| Bound | unbound |
It has no binding. |
| Not stale | stale |
Any dependency pinned in starchart.lock (fact value or code hash) changed, a new dependency appeared, or the artifact was never locked. See Lockfile and Drift. |
| Not expired | expired |
Its validThrough has passed. A bare date lasts through the end of that day (UTC). |
| Passing audit | failingAudit |
Only with --audit: the live audit reported any diff for it. |
Retired artifacts are excluded from both the numerator and the denominator. Retiring an expired promo is how you get its points back, not by deleting it.
An artifact that fails several conditions (unbound and expired, say) still costs one point. The breakdown lists it under every key it fails.
starchart score # text (-f accepts text or json only)
starchart score -f json # ScoreReport
starchart score --audit # also run the live audit (slower, needs credentials)
starchart score --badge badge.svg # write a README badge
starchart score --badge-json reality.json # write a shields.io endpoint JSONstarchart here is shorthand for npx @space-pirate-zero/starchart (or npx starchart once the package is in your devDependencies; see Getting Started).
--badge and --badge-json paths resolve against the current directory (or -C), and parent directories are created. The command always exits 0. The score is a metric. Use starchart check as the gate (see GitHub Action).
Demo (examples/pro-universe), freshly locked:
$ starchart score
reality 83% 10/12 artifacts in sync
unbound privacy:appstore-label
expired reel:spring-2026
{
"score": 83,
"total": 12,
"inSync": 10,
"breakdown": {
"unbound": ["privacy:appstore-label"],
"stale": [],
"expired": ["reel:spring-2026"],
"failingAudit": []
}
}With --audit and no Stripe or App Store credentials, artifacts the audit can't check are skipped, not failed. Only real diffs count:
$ starchart score --audit
reality 75% 9/12 artifacts in sync
unbound privacy:appstore-label
expired reel:spring-2026
failingAudit web:og-pro
Here web:og-pro's rendered PNG hasn't been generated, which is a real diff. web:live-pricing couldn't be fetched (the demo site doesn't exist), but a network failure is an audit error, not a diff, so it doesn't cost a point. Only a 404/410 from the url adapter counts as a diff. Run starchart audit to see errors. See Audit and Break Detection.
The score is harsh on purpose. After a one-line change to apps/ios/Sources/Core/Pricing.swift (a hardcoded price) in a copy of the demo, every artifact that depends on it, transitively through the fact it anchors, went stale:
$ starchart score
reality 0% 0/12 artifacts in sync
unbound privacy:appstore-label
stale appstore:iap/pro-monthly, appstore:listing/description, appstore:screenshots/6.9/03, email:onboarding-day-3, reel:spring-2026, stripe:price/pro-monthly, web:landing-hero, web:live-pricing, web:messages-en, web:og-pro, web:pricing-page
expired reel:spring-2026
It recovers as you ship: starchart apply relocks the artifacts it updates, and starchart ack <id…> relocks the ones you updated by hand. Relocking only pins those ids. Artifacts you haven't shipped yet keep the old values of their dependencies, so they stay stale (and keep costing points) until you ack or fix them. See Apply, Revert and Journals.
| Score | Color | Badge text |
|---|---|---|
| ≥ 95 |
#00ff41 (green) |
dark |
| 80–94 |
#ffd000 (yellow) |
dark |
| < 80 |
#ff1493 (pink) |
white |
The CLI tints the text output the same way when color is on.
--badge <file> writes a standalone, shields-style flat SVG that reads reality | 83%. The score is clamped to 0–100 and rounded. The demo's badge starts:
<svg xmlns="http://www.w3.org/2000/svg" width="81" height="20" role="img" aria-label="reality: 83%"><title>reality: 83%</title>…
--badge-json <file> writes a shields.io endpoint document:
{"schemaVersion":1,"label":"reality","message":"83%","color":"#ffd000"}Host it at any public URL and reference it with https://img.shields.io/endpoint?url=<url-encoded URL>. Shields handles styling and caching.
Both are also in the library: badgeSvg(score), badgeJson(score), realityScore(graph, lock, { now, auditDiffs }).
The simplest setup commits the badge to a dedicated badges branch on every push to main, and the README points at the raw file. The workflow runs the published package with npx. See GitHub Action for the PR-comment side.
# .github/workflows/reality-badge.yml
name: Reality badge
on:
push:
branches: [main]
schedule:
- cron: "0 6 * * *" # expiry dates pass even when nobody pushes
permissions:
contents: write
jobs:
badge:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Score
run: |
npx --yes @space-pirate-zero/starchart score \
--badge "$RUNNER_TEMP/badges/reality.svg" \
--badge-json "$RUNNER_TEMP/badges/reality.json"
- name: Publish to the badges branch
run: |
cd "$RUNNER_TEMP/badges"
git init -q -b badges
git add reality.svg reality.json
git -c user.name="github-actions[bot]" -c user.email="41898282+github-actions[bot]@users.noreply.github.com" \
commit -qm "reality badge ${GITHUB_SHA::7}"
git push -f "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${GITHUB_REPOSITORY}.git" badgesThen pick one of these for the README:
<!-- the SVG STARCHART rendered -->

<!-- or shields.io styling from the endpoint JSON -->
For a live-audit score, add --audit and pass the adapter credentials as secrets: STRIPE_SECRET_KEY (or the variable named by adapters.stripe.secretEnv), and ASC_KEY_ID, ASC_ISSUER_ID plus ASC_PRIVATE_KEY or ASC_PRIVATE_KEY_PATH for App Store Connect. Config values aren't env-interpolated, so don't put ${VAR} in config.yaml. Without credentials, those artifacts are skipped, not failed.
The starchart_score MCP tool returns the same JSON (without --audit).
★ STARCHART
☠ a Space Pirate Zero joint
STARCHART Every dependency. Code to cosmos.
Guides
Concepts
- Three-Layer Model
- Facts and Entities
- Artifacts and Bindings
- Edge Types
- Node IDs
- Impact Analysis
- Lockfile and Drift
- Rollout Ordering
Authoring
Code layer
Commands & API
Adapters & engine
- Adapters Overview
- Adapter: fs
- Adapter: url
- Adapter: Stripe
- Adapter: App Store Connect
- Writing an Adapter
- Audit and Break Detection
- Apply, Revert and Journals
- Future Universe Preview
- Viewer and Serve
- Reality X-Ray
Rules & analysis
Integrations
Project