Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions .github/workflows/clean-up-images.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: Clean up doc images

# Renames generically-named images (1.png, "Screenshot ….png", …) to
# descriptive, caption-derived names — and deletes images that nothing
# references — then opens a PR for human review. Never pushes to main directly.
#
# Triggers:
# - workflow_dispatch: run on demand (use this for the first big cleanup)
# - schedule: once a month, to keep newly-added images tidy
on:
workflow_dispatch:
inputs:
sections:
description: "Limit to these top-level dirs (comma-separated, e.g. developer-tools). Blank = all (minus excluded)."
type: string
default: ""
exclude_sections:
description: "Top-level dirs to skip (comma-separated). Defaults to 'docs', which is managed separately."
type: string
default: "docs"
delete_orphans:
description: "Delete unreferenced (orphan) images"
type: boolean
default: true
schedule:
- cron: "0 6 1 * *" # 06:00 UTC on the 1st of each month

permissions:
contents: write
pull-requests: write

jobs:
clean-up-images:
name: clean-up-images
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Scan + build mapping/report
env:
SECTIONS: ${{ github.event.inputs.sections }}
EXCLUDE: ${{ github.event.inputs.exclude_sections }}
run: |
# `docs` is excluded by default (managed separately). On scheduled runs
# github.event.inputs is empty, so fall back to the default here too.
EXCLUDE="${EXCLUDE:-docs}"
python3 scripts/rename_images.py \
${SECTIONS:+--sections "$SECTIONS"} \
${EXCLUDE:+--exclude-sections "$EXCLUDE"} | tee /tmp/scan.log

- name: Apply (rename + delete orphans)
id: apply
run: |
# scheduled runs always delete orphans; manual runs honor the toggle
DELETE_FLAG="--delete-orphans"
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && \
[ "${{ github.event.inputs.delete_orphans }}" = "false" ]; then
DELETE_FLAG=""
fi
python3 scripts/rename_images.py --apply $DELETE_FLAG | tee /tmp/apply.log
echo 'SUMMARY<<EOF' >> "$GITHUB_OUTPUT"
tail -n 1 /tmp/apply.log >> "$GITHUB_OUTPUT"
echo 'EOF' >> "$GITHUB_OUTPUT"

- name: Upload review report
if: always()
uses: actions/upload-artifact@v4
with:
name: image-rename-report
path: |
image-rename-report.md
image-rename-mapping.csv
if-no-files-found: ignore

- uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "chore: rename generic doc images and remove orphans"
title: "chore: clean up doc image names and remove orphan images"
body: |
Automated image cleanup from `scripts/rename_images.py`.

**${{ steps.apply.outputs.SUMMARY }}**

What this PR does:
- Renames generically-named images (`1.png`, `Screenshot ….png`, `unnamed.png`, …) to descriptive, **caption-derived** names, and rewrites every `.gitbook/assets/` reference in the docs to match.
- Deletes images that nothing in the repo references (orphans).
- The `docs` section is **excluded by default** (managed separately). Override via the `exclude_sections` input on a manual run.

**Please review before merging:**
- Spot-check a few renames against the rendered pages — names come from each image's caption/alt text.
- Confirm the deleted images really are unused (they were unreferenced at scan time and re-verified at apply time, but a brand-new image added in the same window could look like an orphan).
- The full report (every rename, every deletion, caption sources, and any low-relevance names) is attached to this workflow run as the **image-rename-report** artifact.
branch: chore/image-cleanup
base: main
sign-commits: true
delete-branch: true
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@
.idea/
error-catalog/
.vscode/settings.json

# Generated by scripts/rename_images.py (working artifacts, not committed)
image-rename-mapping.csv
image-rename-report.md
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ Before working on larger contributions, [contact support](https://support.snyk.i

If you want to add an integration to Snyk, [apply to become a Snyk partner](https://partners.snyk.io/English/register_email.aspx).

### Maintenance scripts

The [`scripts/`](scripts/) directory holds repository maintenance tooling. See [scripts/README.md](scripts/README.md) for the image cleanup tool, which gives `.gitbook/assets/` images descriptive, caption-derived names and removes unreferenced ones (run monthly, or on demand, as a reviewable PR).

## Security

For any security issues or concerns, go to [SECURITY.md](SECURITY.md).
104 changes: 104 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Image cleanup — `rename_images.py`

A maintenance tool for the images under each section's `.gitbook/assets/` folder. It gives screenshots meaningful, reusable names and removes ones that nothing uses — safely, with a review step.

## The problem it solves

GitBook drops uploaded images into `.gitbook/assets/` with whatever name they arrived with. Over time that produces:

- **Non-descriptive names** — `1.png`, `2 (2).png`, `Screenshot 2025-04-24 at 14.52.22.png`, `unnamed.png`, `image (105).png`. You can't tell what an image is without opening it, and you can't reuse it across pages with confidence.
- **Orphans** — images that no page references anymore (left behind when a page was edited or deleted). They bloat the repo and make the assets folder hard to navigate.
- **Risky manual renames** — renaming an asset by hand means hunting down every `.gitbook/assets/…` reference in the Markdown and updating each one, or you silently break an image.

This script fixes all three: it proposes a descriptive name **derived from each image's own caption/alt text**, rewrites every reference for you, and deletes the orphans — and it never does any of that without a review step.

## What it does

1. **Scans** every section's `.gitbook/assets/` for generically-named images.
2. For each one, finds where it's used and reads the **caption / alt text** around each use.
3. **Proposes a descriptive kebab-case name** from the most common caption (e.g. a figure captioned *"Group Settings: SSO"* → `group-settings-sso.png`).
4. Writes a **review report** and a **mapping CSV** you can edit.
5. On `--apply`: renames the files, **rewrites every reference** to them, and (with `--delete-orphans`) deletes unreferenced images.

### Safety features

- **Dry run** (`--dry-run`) prints the full rename/delete plan without changing anything.
- **Live re-verification** — before deleting an "orphan", it re-scans the repo at apply time and refuses to delete anything that turns out to be referenced.
- **Section-scoped reference rewriting** — the same generic name (`1.png`) can exist in several sections with different meanings; rewrites are scoped per section so a reference is never pointed at the wrong section's file.
- **Robust reference parsing** — handles GitBook's angle-bracket link form `![alt](<…/assets/image (1).png>)` and references in non-Markdown files (`.yaml`, `.json`, `.html`).
- **Caption-relevance check** — flags any proposed name that doesn't actually reflect its captions, so you can correct it before applying.
- **Spell-sanity check** — flags proposed names containing a likely-misspelled word (typos are often carried over from the caption, e.g. `attribue`). Heuristic: uses the system word list plus a product/tech allow-list, and skips words that appear capitalized in the caption (product names). Reported for review; never auto-corrected.

## Requirements

- Python 3.8+ (standard library only).
- Optional: `pip install anthropic` + `ANTHROPIC_API_KEY` — only needed for `--vision` (see below).

## Usage

Run from the repo root.

```bash
# 1. Report only (safe — writes the report + CSV, changes nothing)
python3 scripts/rename_images.py

# 2. Review the outputs
# image-rename-report.md — per-image: proposed name, usage, captions, relevance
# image-rename-mapping.csv — edit proposed_name here if you disagree with any

# 3. See exactly what applying would do (no changes)
python3 scripts/rename_images.py --apply --delete-orphans --dry-run

# 4. Apply: rename + rewrite references + delete orphans
python3 scripts/rename_images.py --apply --delete-orphans
```

### Useful flags

| Flag | What it does |
|---|---|
| *(none)* | Report mode: writes `image-rename-mapping.csv` + `image-rename-report.md`. Changes nothing. |
| `--apply` | Execute the mapping CSV: rename files and rewrite references. |
| `--delete-orphans` | With `--apply`, also delete images that nothing references (re-verified live). |
| `--dry-run` | With `--apply`, print the plan and change nothing. |
| `--sections a,b` | Limit to these top-level section dirs (e.g. for a smaller, reviewable run). |
| `--exclude-sections a,b` | Skip these sections (applied after `--sections`). |
| `--all` | Include every image, not just generically-named ones. |
| `--csv PATH` | Use a custom mapping CSV path (keeps scoped runs from clobbering the canonical one). |
| `--vision` | Name context-less images (orphans / no caption) by having Claude look at them. Needs `anthropic` + `ANTHROPIC_API_KEY`. |

### Mapping CSV columns

| Column | Meaning |
|---|---|
| `repo_location` | Path to the image file. |
| `current_name` | Its current filename. |
| `proposed_name` | Suggested descriptive name (**edit this** to override). |
| `used_on_n_pages` | Number of distinct pages that reference it (`0` = orphan). |
| `source_captions` | The caption/alt text the name was derived from (the dominant one, for readability). |

To **keep** an image the report wants to rename or delete, edit its `proposed_name` (or delete its row) before `--apply`.

## Automated cleanup (GitHub Action)

[`.github/workflows/clean-up-images.yml`](../.github/workflows/clean-up-images.yml) runs this monthly and on manual dispatch, and **opens a PR for review** — it never pushes to `main`.

- **Excludes `docs` by default** (that section is managed separately). Override with the `exclude_sections` input on a manual run.
- Manual runs also accept a `sections` input (limit scope) and a `delete_orphans` toggle.
- The review report is attached to each run as an artifact.

To run the one-time cleanup: trigger the workflow via **Actions → Clean up doc images → Run workflow**.

## How this was built (summary)

The tool was developed iteratively, hardening it against problems found along the way:

1. **Catalog first** — scan the assets, derive names from captions, and emit a human-readable report + CSV (no changes yet).
2. **Add apply** — rename files and rewrite all references; default to a safe report-only mode.
3. **Add orphan deletion** — with a live re-verification so a stale CSV can never cause a wrong delete.
4. **Fix reference parsing** — GitBook's angle-bracket links were being mis-parsed, which had flagged real images as false orphans; fixed, and broadened to non-Markdown files.
5. **Fix section scoping** — a full-repo dry run revealed that rewriting references globally by filename broke links across the repo's parallel section trees; rewriting is now scoped per section.
6. **Add relevancy + dry run + section filters** — verify names match captions, preview the plan, and scope runs to a few folders.
7. **Wrap in a review-gated workflow** — monthly + manual, opening a PR rather than committing to `main`, with `docs` excluded by default.

Throughout, every destructive run was preceded by a dry run and a dangling-reference check (rename → confirm zero broken references → commit).
Loading
Loading