Skip to content

GitHub Projects Format

GHS stores all scan results, health findings, and issue tracking as items in a GitHub Project (ProjectsV2). There are no local markdown files — everything lives on GitHub.

Project Naming

Each scanned repository gets one GitHub Project, owned by the repo owner (user or org):

[GHS] {owner}/{repo}

Examples:

  • [GHS] phmatray/Formidable
  • [GHS] Atypical-Consulting/NewSLN

The [GHS] prefix enables reliable discovery across all projects for an owner.

Project Discovery

bash
# Find all GHS projects for an owner
gh project list --owner {owner} --format json \
  --jq '.projects[] | select(.title | startswith("[GHS]"))'

# Find the project number for a specific repo
gh project list --owner {owner} --format json \
  --jq '.projects[] | select(.title == "[GHS] {owner}/{repo}") | .number'

Board Layout

The project uses the Board layout (Kanban) as the default view, grouped by the built-in Status field.

ColumnMeaningContains
TodoAwaiting actionFAIL health findings, open issues awaiting triage
In ProgressCurrently being worked onItems with active worktree agents
DoneResolvedPASS findings, closed issues, merged PRs

WARN items (permission-related, cannot verify) are not added to the project. They appear in terminal output only.

Custom Fields

Each project is provisioned with 9 custom fields:

FieldTypeOptions / FormatPurpose
SourceSingle SelectHealth Check, GitHub IssueDistinguish health findings from issues
ModuleSingle Selectcore, dotnetWhich module detected the finding
TierSingle Select1 — Required, 2 — Recommended, 3 — Nice to HaveImportance tier
PointsNumber4, 2, or 1Point value for scoring
SlugTexte.g., readme, licenseMachine-readable check identifier
CategorySingle SelectA — API-only, B — File changes, CIFix classification
DetectedDateYYYY-MM-DDDate of first scan
PR URLTextURL stringLink to the fix PR
ScoreNumber0–100Health score percentage (score record only)

Field creation is idempotent — GHS looks up existing fields before creating new ones.

Item Types

Health Findings (Draft Issues)

Created by ghs-repo-scan for each FAIL check result.

PropertyValue
Title[Health] {Check Name} (e.g., [Health] README, [Health] License)
TypeDraft issue
StatusTodo (FAIL) or Done (PASS)
BodyDescription of what is missing and how to fix it
SourceHealth Check
Modulecore or dotnet
Tier1 — Required, 2 — Recommended, or 3 — Nice to Have
Points4, 2, or 1
SlugCheck slug (e.g., readme, license)
CategoryA — API-only, B — File changes, or CI
DetectedDate of scan

The [Health] {Check Name} title convention enables deduplication — re-scanning updates existing items rather than creating duplicates.

GitHub Issues (Linked Items)

Added by ghs-repo-scan for open GitHub issues on the repository.

PropertyValue
StatusTodo (open) or Done (closed)
SourceGitHub Issue

Linked issues carry their own title and body from GitHub. The Source custom field is set after adding.

Score Record (Draft Issue)

A single draft item per project that stores the computed health score.

PropertyValue
Title[GHS Score]
TypeDraft issue
BodyJSON with score breakdown
Score fieldHealth score percentage (0–100)

Body format:

json
{
  "score": 46,
  "core": {"earned": 30, "possible": 74, "pct": 41},
  "dotnet": {"earned": 18, "possible": 34, "pct": 53},
  "combined": true,
  "formula": "round(41 * 0.6 + 53 * 0.4)",
  "updated": "2026-02-28"
}

State Record (Real GitHub Issue)

Session state (decisions, blockers, history) is stored as a real GitHub Issue on the target repository — not as a project draft item.

PropertyValue
Title[GHS State] {owner}/{repo}
Labelghs:state
StateOpen (always — closed only when repo is removed from GHS)
bash
# Look up the state issue
gh issue list --repo {owner}/{repo} --label "ghs:state" --state open \
  --json number,title,body --limit 1

Scoring via jq

Health score is calculated by querying project items directly — no local files or scripts required.

bash
# Get all project items
ITEMS=$(gh project item-list $PROJECT_NUM --owner {owner} --format json --limit 500)

# Count earned points (Done health checks)
EARNED=$(echo "$ITEMS" | jq '[.items[] | select(.source == "Health Check" and .status == "Done") | .points] | add // 0')

# Count possible points (Todo + Done health checks)
POSSIBLE=$(echo "$ITEMS" | jq '[.items[] | select(.source == "Health Check" and (.status == "Todo" or .status == "Done")) | .points] | add // 0')

# Calculate percentage
SCORE=$(echo "scale=0; $EARNED * 100 / $POSSIBLE" | bc)

Items with status In Progress are counted as Todo for scoring — they have not passed yet. WARN items are never added to the project, so they are automatically excluded.

Per-Module Scoring

For repos with language modules, filter by the Module field:

bash
# Core module score
CORE_EARNED=$(echo "$ITEMS" | jq '[.items[] | select(.source == "Health Check" and .module == "core" and .status == "Done") | .points] | add // 0')
CORE_POSSIBLE=$(echo "$ITEMS" | jq '[.items[] | select(.source == "Health Check" and .module == "core" and (.status == "Todo" or .status == "Done")) | .points] | add // 0')

# .NET module score
DOTNET_EARNED=$(echo "$ITEMS" | jq '[.items[] | select(.source == "Health Check" and .module == "dotnet" and .status == "Done") | .points] | add // 0')
DOTNET_POSSIBLE=$(echo "$ITEMS" | jq '[.items[] | select(.source == "Health Check" and .module == "dotnet" and (.status == "Todo" or .status == "Done")) | .points] | add // 0')

# Combined weighted score
COMBINED=$(echo "scale=0; ($CORE_EARNED * 100 / $CORE_POSSIBLE) * 60 / 100 + ($DOTNET_EARNED * 100 / $DOTNET_POSSIBLE) * 40 / 100" | bc)

Tier System

TierLabelPointsDescription
1Required4Non-negotiable for any public or team-shared repository
2Recommended2Important for maintainability and collaboration
3Nice to Have1Polish items that signal a mature, well-maintained project

Released under the MIT License.