repo-hygiene — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited repo-hygiene (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A structured, periodic health check for repositories. Run anytime to detect drift, accumulation of tech debt, and configuration issues before they become problems.
Not a release gate. For pre-release checks, use the pre-release skill instead. This skill is for ongoing maintenance — run it weekly, after major refactors, when onboarding to a repo, or whenever things feel "off".
| Stack | Package manager | Detected by |
|---|---|---|
| Node.js / TypeScript | npm | package.json + package-lock.json |
| Python | uv / pip | pyproject.toml or requirements.txt |
| Go | go modules | go.mod |
Detect the stack from the project root. Multiple stacks in one repo is fine — run applicable checks for each. If the stack isn't listed, skip stack-specific checks and run the universal ones (git, CI, docs, security).
# What are we working with?
ls package.json pyproject.toml go.mod 2>/dev/null
git rev-parse --show-toplevelDetermine: stack(s), git remote, default branch, CI system (GitHub Actions, GitLab CI, etc.).
#### Node.js / npm
| # | Check | Command | Severity | |
|---|---|---|---|---|
| D1 | Known vulnerabilities | npm audit --json | 🔴 critical/high = Fix Now, moderate = Fix Soon | |
| D2 | Outdated dependencies | npm outdated --json | 🟡 major bumps = Fix Soon, minor/patch = Info | |
| D3 | Unused dependencies | npx depcheck --json | 🟡 Fix Soon | |
| D4 | Phantom dependencies (used but undeclared) | npx depcheck --json → missing | 🔴 Fix Now | |
| D5 | Lockfile freshness | See below | 🟡 Fix Soon | |
| D6 | Duplicate dependencies | `npm ls --all --json 2>/dev/null \ | grep -c '"deduped"'` | ℹ️ Info |
D5 — Lockfile freshness check:
# package.json changed more recently than lockfile?
LOCK_DATE=$(git log -1 --format=%ct -- package-lock.json 2>/dev/null || echo 0)
PKG_DATE=$(git log -1 --format=%ct -- package.json 2>/dev/null || echo 0)
if [ "$PKG_DATE" -gt "$LOCK_DATE" ]; then
echo "⚠️ package.json modified after lockfile — run npm install"
fiHow to fix:
npm audit fix for compatible fixes; npm audit fix --force for breaking (review changes). For stubborn advisories: check if the vuln is reachable, or override in package.json overrides.npm update for minor/patch; npm install <pkg>@latest for major (check changelogs).npm uninstall <pkg> for each unused dep.npm install <pkg> for each missing dep.npm install to regenerate lockfile, commit it.npm dedupe then verify tests pass.#### Python (uv / pip)
| # | Check | Command | Severity |
|---|---|---|---|
| D1 | Known vulnerabilities | pip-audit --format=json | 🔴 Fix Now |
| D2 | Outdated dependencies | uv pip list --outdated or pip list --outdated --format=json | 🟡 Fix Soon |
| D3 | Unused dependencies | deptry . --json (if available) | 🟡 Fix Soon |
| D5 | Lockfile freshness | Compare uv.lock vs pyproject.toml timestamps | 🟡 Fix Soon |
How to fix:
uv pip install --upgrade <pkg> for each vulnerable package. Check advisories for minimum safe version.uv pip install --upgrade <pkg> per package, or uv lock --upgrade for all.[project.dependencies] in pyproject.toml, then uv sync.uv lock && uv sync.#### Go
| # | Check | Command | Severity |
|---|---|---|---|
| D1 | Known vulnerabilities | govulncheck ./... | 🔴 Fix Now |
| D2 | Outdated dependencies | go list -m -u all | 🟡 Fix Soon |
| D3 | Unused dependencies | go mod tidy -v (reports removed) | 🟡 Fix Soon |
How to fix:
go get <module>@latest for vulnerable deps, then go mod tidy.go get -u ./... for all, or go get <module>@latest selectively.go mod tidy removes unused; commit go.mod and go.sum.| # | Check | Command | Severity | |||
|---|---|---|---|---|---|---|
| G1 | Stale local branches (merged) | `git branch --merged main \ | grep -v '^\*\ | main\ | develop'` | 🟡 Fix Soon |
| G2 | Stale remote branches (merged) | `git branch -r --merged origin/main \ | grep -v 'HEAD\ | main\ | develop'` | 🟡 Fix Soon |
| G3 | Large files in repo | See below | 🟡 Fix Soon (🔴 if >10MB) | |||
| G4 | .gitignore completeness | See below | 🟡 Fix Soon | |||
| G5 | Untracked files that should be ignored | `git status --porcelain \ | grep '^??'` — look for build artifacts, IDE files, env files | ℹ️ Info | ||
| G6 | Uncommitted changes | git status --porcelain | ℹ️ Info |
G3 — Large files check:
# Top 10 largest tracked files
git ls-files -z | xargs -0 -I{} git log --diff-filter=A --format='%H' -1 -- '{}' | head -20
# Simpler: just check current tree
git ls-files -z | xargs -0 du -sh 2>/dev/null | sort -rh | head -10G4 — .gitignore completeness:
Must include (per stack):
.env, .env.*, *.local, .DS_Store, Thumbs.db, *.swp, .idea/, .vscode/ (or be deliberate about tracking it)node_modules/, dist/, build/, coverage/, .turbo/, .next/__pycache__/, *.pyc, .venv/, venv/, .mypy_cache/, .pytest_cache/, *.egg-info/go build -o), vendor/ (if not vendoring)How to fix:
git branch -d <branch> for each merged local branch.git push origin --delete <branch> for each merged remote branch. Be careful — confirm with team..gitignore, git rm --cached <file>. For files already in history: git filter-repo or BFG Repo-Cleaner (destructive — confirm first)..gitignore. Use a generator like gitignore.io as a starting point..gitignore or git add if they should be tracked.Skip if no CI configuration found.
| # | Check | Command | Severity |
|---|---|---|---|
| C1 | Workflow files exist | ls .github/workflows/*.yml 2>/dev/null | ℹ️ Info |
| C2 | Actions pinned by SHA | grep -rE 'uses: [^@]+@v[0-9]' .github/workflows/ — should return nothing | 🟡 Fix Soon |
| C3 | Least-privilege permissions | Scan for permissions: blocks; flag write-all or missing job-level perms | 🟡 Fix Soon |
| C4 | No secret leaks in workflows | Check for echo ${{ secrets.* }}, secret in $GITHUB_OUTPUT/$GITHUB_ENV | 🔴 Fix Now |
| C5 | Deprecated actions | Check for known deprecated: actions/create-release@v1, set-output commands, ::set-env | 🟡 Fix Soon |
| C6 | Node/Python version matches project | Compare workflow matrix with engines, .nvmrc, pyproject.toml [requires-python] | 🟡 Fix Soon |
How to fix:
uses: actions/checkout@v4 with uses: actions/checkout@<full-sha>. Find SHA: gh api repos/actions/checkout/git/ref/tags/v4 --jq .object.sha or check the releases page.permissions: at job level. Start with contents: read and add only what's needed.environment: blocks or write to files with masking.set-output → $GITHUB_OUTPUT file..nvmrc or engines as the source of truth.| # | Check | Command | Severity | ||||||
|---|---|---|---|---|---|---|---|---|---|
| Q1 | TODO/FIXME/HACK count | `git grep -ciE '(TODO\ | FIXME\ | HACK)' -- '.ts' '.js' '.py' '.go' ':!node_modules' ':!vendor' ':!.venv'` | ℹ️ Info (🟡 if >20) | ||||
| Q2 | console.log in src (JS/TS) | git grep -c 'console\.log' -- 'src/**/*.ts' 'src/**/*.js' ':!*.test.*' ':!*.spec.*' | 🟡 Fix Soon | ||||||
| Q3 | Disabled/skipped tests | `git grep -cE '(it\.skip\ | test\.skip\ | describe\.skip\ | xit\ | xdescribe\ | @pytest\.mark\.skip\ | t\.Skip)' -- '.test.' '.spec.' '_test.' '*_test.go'` | 🟡 Fix Soon |
| Q4 | Lint passes | npm run lint / ruff check . / golangci-lint run | 🟡 Fix Soon | ||||||
| Q5 | Tests pass | npm test / pytest / go test ./... | 🔴 Fix Now | ||||||
| Q6 | Build succeeds | npm run build / uv build / go build ./... | 🔴 Fix Now | ||||||
| Q7 | Type errors (TS) | npx tsc --noEmit | 🟡 Fix Soon | ||||||
| Q8 | Dead exports (TS) | `npx ts-prune 2>/dev/null \ | grep -v '(used in module)'` | ℹ️ Info |
How to fix:
grep -rn 'console.log' src/ to find them.// ts-prune-ignore-next if they're part of the public API.| # | Check | Command | Severity |
|---|---|---|---|
| F1 | README.md exists | File check | 🔴 Fix Now |
| F2 | README freshness vs. source | Compare git log -1 --format=%cr -- README.md vs git log -1 --format=%cr -- src/ | 🟡 if src is >30 days newer |
| F3 | CHANGELOG exists | File check | 🟡 Fix Soon (for published packages) |
| F4 | Broken internal links | See below | 🟡 Fix Soon |
| F5 | LICENSE file present | File check | 🔴 Fix Now |
| F6 | LICENSE matches package metadata | Compare LICENSE text with package.json license / pyproject.toml license | 🟡 Fix Soon |
| F7 | AGENTS.md references valid paths | If .pi/AGENTS.md exists, check that referenced files/dirs exist | 🟡 Fix Soon |
F4 — Broken link check:
# Find markdown links and verify targets exist
grep -roE '\[([^]]+)\]\(([^)]+)\)' *.md docs/**/*.md 2>/dev/null | \
grep -v 'http' | \
while IFS= read -r line; do
# Extract path from markdown link
path=$(echo "$line" | sed 's/.*](\([^)]*\)).*/\1/' | sed 's/#.*//')
if [ -n "$path" ] && [ ! -e "$path" ]; then
echo "BROKEN: $line"
fi
doneHow to fix:
@changesets/cli for automated generation (see pre-release skill).| # | Check | How | Severity |
|---|---|---|---|
| X1 | EditorConfig present | .editorconfig exists | ℹ️ Info |
| X2 | Strict mode (TS) | tsconfig.json → "strict": true | 🟡 Fix Soon |
| X3 | Formatter configured | .prettierrc / ruff.toml / gofmt (built-in) | 🟡 Fix Soon |
| X4 | Linter configured | .eslintrc* or eslint.config.* / ruff.toml / golangci-lint config | 🟡 Fix Soon |
| X5 | Engine constraints match CI | package.json engines vs CI matrix; pyproject.toml requires-python vs CI | 🟡 Fix Soon |
| X6 | .nvmrc / .python-version matches | Compare with engines / requires-python / CI config | ℹ️ Info |
How to fix:
.editorconfig. Minimal: root = true, [*] block with indent_style, indent_size, end_of_line, insert_final_newline."strict": true in tsconfig.json. Fix resulting type errors (usually worth it).engines / requires-python) and align everything else.Lightweight security checks for ongoing hygiene. For the full pre-release security audit (gitleaks, trufflehog, workflow audit), use the pre-release skill.
| # | Check | Command | Severity | |||
|---|---|---|---|---|---|---|
| S1 | No tracked .env or .local files | git ls-files '*.env' '*.env.*' '*.local' '*.local.*' '.env' '.env.local' | 🔴 Fix Now | |||
| S2 | .env.example exists (if .env in .gitignore) | File check | 🟡 Fix Soon | |||
| S3 | No hardcoded secrets in source | `git grep -iE '(api[_-]?key\ | secret\ | password\ | token)\s[:=]\s["\x27][^"\x27]{8,}' -- ':!.lock' ':!node_modules' ':!.example' ':!*.sample'` | 🔴 Fix Now |
| S4 | Secrets scanning config present | .gitleaks.toml or pre-commit hooks | ℹ️ Info | |||
| S5 | No broad file permissions | Check for chmod 777 or 0777 in scripts | 🔴 Fix Now |
How to fix:
git rm --cached <file>, add to .gitignore, commit. If the file contained real secrets, rotate them immediately — they're in git history..env.example with placeholder values (<REPLACE_ME>) for every var in .env.process.env.VAR / os.environ["VAR"]..gitleaks.toml (even a minimal one enables CI scanning). Or add gitleaks to pre-commit hooks.644 for files, 755 for executables).| # | Check | How | Severity |
|---|---|---|---|
| M1 | Required package fields | name, version, description, license in package.json / pyproject.toml | 🟡 Fix Soon |
| M2 | Repository URL set | repository field in package metadata | 🟡 Fix Soon |
| M3 | Keywords present | keywords array | ℹ️ Info |
| M4 | FUNDING.yml (public repos) | .github/FUNDING.yml exists | ℹ️ Info |
| M5 | Pi package compliance | If ships skills/extensions: pi-package keyword, pi manifest, files includes skill dirs | 🟡 Fix Soon (if applicable) |
How to fix:
package.json or pyproject.toml..github/FUNDING.yml with github: <username>.Save a baseline after each run to detect drift over time. Store at .pi/hygiene-baseline.json:
{
"timestamp": "2026-02-14T23:00:00Z",
"stack": ["node"],
"scores": {
"dependencies": { "status": "healthy", "vulns": 0, "outdated": 3, "unused": 0 },
"git": { "status": "healthy", "stale_branches": 0, "large_files": 0 },
"ci": { "status": "warning", "unpinned_actions": 2, "permission_issues": 0 },
"quality": { "status": "healthy", "todos": 5, "skipped_tests": 0, "lint_clean": true },
"docs": { "status": "warning", "readme_stale_days": 45, "broken_links": 1 },
"config": { "status": "healthy", "strict_ts": true, "formatter": true, "linter": true },
"security": { "status": "healthy", "tracked_env": 0, "hardcoded_secrets": 0 },
"metadata": { "status": "healthy", "complete": true }
},
"overall": "7/10"
}On subsequent runs, compare with baseline and flag regressions:
📉 Dependencies: 0 → 3 vulnerabilities (regression since last check)
📈 Quality: 15 → 5 TODOs (improvement!)
→ CI: unchanged — 2 unpinned actions remainWhen the user approves the report, offer to update the baseline.
Present the final report as a health scorecard:
# Repo Health: <project-name>
## Score: 7/10 — GOOD
## Stack: Node.js + TypeScript
## Last check: 2026-01-15 (30 days ago) | Baseline: 6/10 📈
### 🔴 Fix Now (2)
| # | Category | Issue | Fix |
|---|----------|-------|-----|
| S1 | Security | `.env.local` tracked in git | `git rm --cached .env.local` |
| D1 | Deps | 2 high-severity npm audit findings | `npm audit fix` |
### 🟡 Fix Soon (4)
| # | Category | Issue | Fix |
|---|----------|-------|-----|
| D2 | Deps | 8 outdated packages (2 major) | `npm outdated` → upgrade |
| C2 | CI | 3 actions not pinned by SHA | Pin to commit SHA |
| F2 | Docs | README 45 days behind source | Review and update |
| Q3 | Quality | 2 skipped tests | Fix or remove |
### 🟢 Healthy (12)
- ✅ Dependencies: no unused, no phantom, lockfile fresh
- ✅ Git: clean tree, no stale branches, no large files
- ✅ Code: lint clean, build passes, tests pass, strict TS
- ✅ Config: EditorConfig, Prettier, ESLint all configured
- ✅ Security: no tracked secrets, .env.example present
- ✅ Metadata: all fields present, license matches
### 📊 Trends (vs. baseline 2026-01-15)
| Category | Then | Now | Trend |
|----------|------|-----|-------|
| Vulnerabilities | 0 | 2 | 📉 |
| Outdated deps | 5 | 8 | 📉 |
| TODOs | 15 | 8 | 📈 |
| Skipped tests | 0 | 2 | 📉 |
### Recommendations
1. **Immediate**: Fix the 2 security/vulnerability items above
2. **This week**: Pin CI actions and update stale README
3. **Ongoing**: Address skipped tests and outdated deps in next sprint
Use your project's task tracking to schedule these items.Calculate the score from check results:
| Result | Points deducted |
|---|---|
| Each 🔴 Fix Now | −1.5 |
| Each 🟡 Fix Soon | −0.5 |
| ℹ️ Info | 0 |
Start at 10, apply deductions, floor at 0. Round to nearest integer.
| Score | Label |
|---|---|
| 9–10 | 🟢 EXCELLENT |
| 7–8 | 🟢 GOOD |
| 5–6 | 🟡 FAIR |
| 3–4 | 🟠 NEEDS WORK |
| 0–2 | 🔴 POOR |
After presenting the report, offer to fix issues that are safe and mechanical. Always present what will be done and get confirmation before executing.
git branch -d)npm audit fix (compatible fixes only, not --force)npm dedupe / go mod tidy.gitignore patterns.editorconfig from templateconsole.log from source files.env.example from .env (with values replaced by <REPLACE_ME>)package.json fields (description, repository, keywords).github/FUNDING.ymlgit push origin --delete)npm audit fix --force (may have breaking changes).env files (may need secret rotation)repo-hygiene for ongoing health, pre-release when you're ready to ship. They complement each other — hygiene keeps the baseline high so pre-release has fewer surprises.git init to establish a perfect baseline. It's easier to maintain 10/10 than to recover from 4/10.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.