entropy-check
Use when checking doc freshness and computing domain quality grades without making fixes.
How do I install this agent skill?
npx skills add https://github.com/fellowship-dev/dogfooded-skills --skill entropy-checkIs this agent skill safe to install?
- Gen Agent Trust Hubpass
The skill is a documentation health monitoring tool that analyzes repositories for staleness. It is generally safe but carries a minor risk of indirect prompt injection because it uses file names and domain names found within the repository directly in shell commands without sanitization.
- Socketpass
No alerts
- Snykwarn
Risk: MEDIUM · 2 issues
What does this agent skill do?
Entropy
Sensor. Detect, grade, report. Never fix.
Install via npx:
npx skills add fellowship-dev/dogfooded-skills/skills/ops/entropy-check
When to Use
- Event-driven (PR merge): Triggered after a PR merges. Scans affected domains for doc staleness.
- Weekly cron (full sweep): Runs against each product repo, updates QUALITY_SCORE.md with fresh grades.
- Manual: When you suspect docs have drifted or want a current health snapshot before a refactor.
What It Does
Entropy is a read-only sensor. It computes domain quality grades from mechanical signals:
| Signal | What it measures | Weight |
|---|---|---|
| Doc coverage | Does docs/code-structure.md cover this domain? | Binary |
| Flow coverage | FlowChad flow defined for critical paths? | Binary (frontend repos only) |
| Staleness delta | Days since last code commit vs. last doc update | >30d = stale |
| Open issues | Issues tagged to domain in GitHub | >3 open = signal |
| Test coverage | From coverage report if available | <60% = signal |
| Hookshot coverage | Is doc-coverage.json current vs docs? | Staleness |
Grade scale:
- A — All applicable signals green
- B — 1 applicable signal missing or yellow
- C — 2 applicable signals missing
- D — 3+ applicable signals missing
- F — No docs at all for this domain
Inapplicable signals are excluded from the grade denominator. A repo with 4 applicable signals all passing = grade A.
Instructions
0. Identify Target Repo
REPO_ROOT=$(git rev-parse --show-toplevel)
REPO_NAME=$(basename $(git remote get-url origin) .git)
ORG=$(git remote get-url origin | sed 's/.*github.com[:/]\([^/]*\).*/\1/')
FULL_REPO="$ORG/$REPO_NAME"
TODAY=$(date +%Y-%m-%d)
Read QUALITY_SCORE.md if it exists — this is the baseline to update.
Read ARCHITECTURE.md if it exists — extract domain list.
If neither exists, infer domains from directory structure (same logic as setup-harness).
1. Determine Signal Applicability
Before grading, determine which signals apply to this repo. Record applicability for the report.
Frontend Detection (S2 FlowChad)
HAS_FRONTEND=false
if [ -f "$REPO_ROOT/package.json" ]; then
grep -q '"next"\|"react"\|"vue"\|"angular"\|"svelte"' "$REPO_ROOT/package.json" && HAS_FRONTEND=true
fi
if [ -d "$REPO_ROOT/app" ] || [ -d "$REPO_ROOT/pages" ] || [ -d "$REPO_ROOT/src/components" ]; then
HAS_FRONTEND=true
fi
If HAS_FRONTEND=false: S2 FlowChad is inapplicable — exclude from grade denominator.
Hookshot Detection (S6)
HOOKSHOT_EXISTS=false
git -C "$REPO_ROOT" log -1 --format="%ci" -- .claude/doc-coverage.json 2>/dev/null | grep -q . && HOOKSHOT_EXISTS=true
[ -f "$REPO_ROOT/.claude/doc-coverage.json" ] && HOOKSHOT_EXISTS=true
S6 is always applicable but the score message differs — see Signal 6 below.
2. Determine Scope
PR-triggered mode: Given a PR number, identify which files changed:
# Get changed files from PR
GH_TOKEN=$GH_TOKEN gh api repos/$FULL_REPO/pulls/{PR_NUMBER}/files \
--jq '.[].filename'
Map each changed file to its domain using directory prefixes. Only grade domains with changes.
Full sweep mode: Grade all domains. Read domain list from ARCHITECTURE.md or infer.
3. Per-Domain Grading
For each domain in scope:
Signal 1: Doc Coverage
# Is this domain mentioned in docs/code-structure.md?
grep -i "{domain_name}" $REPO_ROOT/docs/code-structure.md 2>/dev/null && echo "COVERED" || echo "MISSING"
Score: ✅ covered / ❌ missing
Signal 2: FlowChad Coverage
Skip entirely if HAS_FRONTEND=false. Record as "N/A — no frontend detected" in applicability table.
# Only run if HAS_FRONTEND=true
if [ "$HAS_FRONTEND" = "true" ]; then
ls $REPO_ROOT/.flowchad/flows/ 2>/dev/null | grep -i "{domain_slug}" && echo "COVERED" || echo "MISSING"
fi
Score (frontend only): ✅ has flow / ❌ no flow
Signal 3: Staleness Delta
Find the most recent code commit date for files in this domain:
# Last commit touching domain files
git -C $REPO_ROOT log -1 --format="%ci" -- "{domain_directory_glob}" 2>/dev/null
Find the last update date for the domain's doc section:
# Last commit touching docs/code-structure.md
git -C $REPO_ROOT log -1 --format="%ci" -- docs/code-structure.md 2>/dev/null
Delta = days between last code commit and last doc update. Score: ✅ delta ≤30 days / ⚠️ delta 31-60 days (yellow) / ❌ delta >60 days
Signal 4: Open Issues
# Open issues tagged to this domain (use domain name as label or search term)
GH_TOKEN=$GH_TOKEN gh issue list --repo $FULL_REPO \
--state open --search "{domain_name}" \
--json number,title | jq length 2>/dev/null || echo "0"
Score: ✅ 0-3 open / ⚠️ 4-6 open (yellow) / ❌ >6 open
Signal 5: Test Coverage
# Look for coverage reports
ls $REPO_ROOT/coverage/ $REPO_ROOT/.nyc_output/ $REPO_ROOT/tmp/coverage/ 2>/dev/null
cat $REPO_ROOT/coverage/index.html 2>/dev/null | grep -o '[0-9.]*%' | head -1
# Rails: simplecov
cat $REPO_ROOT/coverage/.last_run.json 2>/dev/null | jq '.result.covered_percent'
If no coverage report is available → skip this signal (treat as neutral, not ❌). Score: ✅ ≥80% / ⚠️ 60-79% / ❌ <60% (only when coverage data is available)
Signal 6: Hookshot Coverage Staleness
COVERAGE_DATE=$(git -C $REPO_ROOT log -1 --format="%ci" -- .claude/doc-coverage.json 2>/dev/null)
DOCS_DATE=$(git -C $REPO_ROOT log -1 --format="%ci" -- docs/code-structure.md 2>/dev/null)
Distinguish two cases:
-
Hookshot not configured —
.claude/doc-coverage.jsonhas never existed (no git history for it, file absent): Score: ❌ "Hookshot not configured — recommend setup" -
Hookshot stale — file has existed (git history found) but docs were updated more recently: Compute delta days between COVERAGE_DATE and DOCS_DATE. Score: ⚠️ "Hookshot stale by {N} days"
-
Hookshot current — coverage was updated after or same day as docs: Score: ✅ "Hookshot current"
Detection logic:
if [ -z "$COVERAGE_DATE" ] && [ ! -f "$REPO_ROOT/.claude/doc-coverage.json" ]; then
# Never been set up
S6_SCORE="❌"
S6_NOTE="Hookshot not configured — recommend setup"
elif [ -n "$DOCS_DATE" ] && [ -n "$COVERAGE_DATE" ]; then
# Both exist — compare dates
DOCS_EPOCH=$(date -d "$DOCS_DATE" +%s 2>/dev/null || date -j -f "%Y-%m-%d %H:%M:%S %z" "$DOCS_DATE" +%s 2>/dev/null)
COV_EPOCH=$(date -d "$COVERAGE_DATE" +%s 2>/dev/null || date -j -f "%Y-%m-%d %H:%M:%S %z" "$COVERAGE_DATE" +%s 2>/dev/null)
STALE_DAYS=$(( (DOCS_EPOCH - COV_EPOCH) / 86400 ))
if [ "$STALE_DAYS" -gt 0 ]; then
S6_SCORE="⚠️"
S6_NOTE="Hookshot stale by ${STALE_DAYS} days"
else
S6_SCORE="✅"
S6_NOTE="Hookshot current"
fi
else
S6_SCORE="❌"
S6_NOTE="Hookshot not configured — recommend setup"
fi
Compute Grade
Collect only applicable signals. Count failing signals (❌) among applicable signals only:
APPLICABLE_SIGNALS = all signals minus inapplicable ones
FAILING = count of ❌ in APPLICABLE_SIGNALS
YELLOW = count of ⚠️ in APPLICABLE_SIGNALS
SCORE = FAILING + (YELLOW * 0.5), rounded up
- SCORE = 0 → A
- SCORE = 1 → B
- SCORE = 2 → C
- SCORE ≥ 3 → D
- No docs at all → F
Example: repo with no frontend (S2 skipped), 5 applicable signals all green → grade A, not B.
4. Update QUALITY_SCORE.md
Read the existing file. Update each graded domain's row. Add new domains if discovered. Preserve existing rows for domains not in scope (only update what was re-scanned).
## Domains
| Domain | Grade | Last audit | Notes |
|--------|-------|------------|-------|
| {domain} | {grade} | {TODAY} | {brief note on what's missing, or "All signals green"} |
Write the updated file, then append to the History section:
| {TODAY} | {trigger: PR #{N} / weekly sweep / manual} | {N} domains scanned, {N} regressions, {N} improvements |
4a. Deliver the write-back as a PR — never push to the default branch
Direct pushes to main/master/develop are always rejected in Pylot workers
(git-push-guard.sh, deliberate, no bot exception). Do not retry a rejected push, do not
look for a workaround, and do not wait for the PR to merge. Deliver like this:
- Nothing changed → report only. If no grade changed, no domain was added and no new
Tooling drift was found, the only edit would be a History row. Discard it
(
git -C "$REPO_ROOT" checkout -- QUALITY_SCORE.md), open no branch and no PR, post the step 8/9 report and finish. - Otherwise open a write-back PR against the scanned repo's actual default branch,
resolved at run time (never hardcode
develop):
BASE=$(GH_TOKEN=$GH_TOKEN gh repo view "$FULL_REPO" --json defaultBranchRef --jq .defaultBranchRef.name)
BRANCH="entropy-scan/pr-{N}" # weekly sweep: entropy-scan/weekly-$TODAY; manual: entropy-scan/manual-$TODAY
TITLE="chore: entropy scan — PR #{N} {title} [skip ci]" # weekly/manual: chore: entropy scan — weekly sweep $TODAY [skip ci]
git -C "$REPO_ROOT" fetch origin "$BASE"
git -C "$REPO_ROOT" switch -c "$BRANCH" "origin/$BASE" # QUALITY_SCORE.md edits carry over
git -C "$REPO_ROOT" add QUALITY_SCORE.md
git -C "$REPO_ROOT" commit -m "$TITLE"
git -C "$REPO_ROOT" push -u origin "$BRANCH"
GH_TOKEN=$GH_TOKEN gh label create entropy-writeback --repo "$FULL_REPO" --color BFD4F2 \
--description "entropy-check QUALITY_SCORE write-back; post-merge entropy rules skip it" 2>/dev/null || true
GH_TOKEN=$GH_TOKEN gh pr create --repo "$FULL_REPO" --base "$BASE" --head "$BRANCH" \
--title "$TITLE" --label entropy-writeback \
--body "Entropy write-back for {trigger}. Domains re-graded: {list}. No code changes."
- The commit message and PR title must both keep the literal
[skip ci]tag — it prevents production deploys, including from the squash-merge commit. - The diff is
QUALITY_SCORE.mdonly. - The
entropy-writebacklabel marks the PR as entropy's own write-back so post-merge entropy rules can exclude it (match.labels_exclude) and not re-trigger a no-op scan. - The PR merges through the repo's standing review pipeline. Do not merge it yourself and do not block the run waiting for it: the run is done once the PR is open.
5. Signal Applicability Section
Include a "Signal Applicability" table in every report output:
## Signal Applicability
| Signal | Applicable? | Reason |
|--------|------------|--------|
| S1 Doc Coverage | Yes | — |
| S2 FlowChad | No | No frontend framework detected (bash/python scripts only) |
| S3 Staleness | Yes | — |
| S4 Open Issues | Yes | — |
| S5 Tests | Yes | No test framework found — recommend adding bats for shell scripts |
| S6 Hookshot | Yes | — |
Populate the Reason column with specifics:
- S2 not applicable: "No frontend framework detected" + what was found (e.g., "bash/python scripts only", "Go/Rails repo")
- S2 applicable: list detected framework (e.g., "Next.js detected in package.json")
- S5 no data: "No coverage report found — signal skipped"
- S5 data found: leave Reason blank or note the coverage percentage
- S6: always applicable; Reason shows the hookshot status detail
6. Staleness Report
For any domain graded C, D, or F — or where grade regressed from previous — output a finding:
## Entropy Findings — {TODAY}
### Regressions (grade dropped)
- {Domain}: {old grade} → {new grade}
Missing: {list of failing signals}
Last doc update: {date}
Last code commit: {date}
Delta: {N} days
### Stable issues (same low grade)
- {Domain}: {grade} (unchanged since {date})
Missing: {list}
### Improvements (grade improved)
- {Domain}: {old grade} → {new grade}
### Clean (A or B)
- {Domain}: {grade}
7. Imported Lib Drift (Architecture Check)
This check moved here from maintenance. It is an architecture signal, not an infra check.
Compare installed speckit commands against the source of truth (fellowship-dev/spec-kit):
# Source of truth: fellowship-dev/spec-kit templates/commands/
GH_TOKEN=$GH_TOKEN gh api repos/fellowship-dev/spec-kit/contents/templates/commands \
--jq '.[].name' 2>/dev/null
# For each active repo with speckit installed:
for repo in Lexgo-cl/rails-backend fellowship-dev/booster-pack fellowship-dev/farmesa-v2 \
fellowship-dev/mtg-lotr fellowship-dev/inbox-angel fellowship-dev/inbox-angel-worker; do
echo "=== $repo ==="
for cmd in specify plan tasks implement analyze checklist clarify; do
SOURCE=$(GH_TOKEN=$GH_TOKEN gh api repos/fellowship-dev/spec-kit/contents/templates/commands/${cmd}.md \
--jq '.sha' 2>/dev/null)
INSTALLED=$(GH_TOKEN=$GH_TOKEN gh api repos/${repo}/contents/.claude/commands/speckit.${cmd}.md \
--jq '.sha' 2>/dev/null)
if [ "$SOURCE" != "$INSTALLED" ]; then
echo " DRIFT: speckit.${cmd}.md (source: $SOURCE, installed: $INSTALLED)"
fi
done
done
SHA mismatch = drift. Add drifted repos/commands to the staleness report. Note: repos may have intentional customizations — flag for review, don't auto-fix. inbox-angel-worker exception: speckit is installed locally and gitignored. Drift sync must be done locally on Spacestation.
Include drift findings in QUALITY_SCORE.md under a ## Tooling section if any drift is found.
They ride in the same step 4a write-back PR; this check never commits or pushes anywhere else.
8. PR-Triggered Output
When triggered by a PR merge event, output a comment-ready summary and post it on the merged PR once step 4a is done (write-back PR opened, or report-only). Do not wait for the write-back PR to merge:
## Entropy Scan — PR #{N} merged
Domains affected: {list}
Write-back: {entropy PR URL, or "none — no QUALITY_SCORE.md change"}
| Domain | Grade | Change | Notes |
|--------|-------|--------|-------|
| {domain} | {grade} | {→ or unchanged or ↑ or ↓} | {note} |
{If any regressions:}
⚠️ Doc staleness detected in: {domain list}
Recommended: update docs/code-structure.md for these domains before the next PR in this area.
Include the Signal Applicability table (see step 5) at the end of the comment.
9. Full Sweep Output (weekly cron)
## Entropy Weekly Sweep — {DATE}
{N} domains across {REPO_NAME}
### Grades
{Full grade table}
### Signal Applicability
{Signal applicability table}
### Action Items
{Domains graded D or F → recorded via file-finding.sh (existing issue or weekly digest)}
For D/F domains, record the finding through the shared filing helper (pylot-cli →
scripts/file-finding.sh). Stale docs are non-blocking, so the helper comments on an open
issue for the same domain or appends to the weekly digest issue — it never opens a new one:
FF="${PYLOT_WORKSPACE:-$HOME/.claude}/skills/pylot-cli/scripts/file-finding.sh"
GH_TOKEN=$GH_TOKEN bash "$FF" --repo "$FULL_REPO" \
--title "Entropy: {domain} docs critically stale (grade {grade})" \
--search "\"{domain} docs critically stale\"" \
--body "Domain **{domain}** scored **{grade}** in the weekly entropy scan.
**Missing signals:**
{list}
**Last code commit to this domain:** {date}
**Last doc update:** {date}
Fix: update \`docs/code-structure.md\` and run \`/hookshot\` to regenerate hooks."
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/fellowship-dev/dogfooded-skills/entropy-check">View entropy-check on skillZs</a>