Skip to content

chore: add compare-examples-size skill#1074

Open
redfish4ktc wants to merge 3 commits into
mainfrom
chore/create_claude_skill_compare_examples_size
Open

chore: add compare-examples-size skill#1074
redfish4ktc wants to merge 3 commits into
mainfrom
chore/create_claude_skill_compare_examples_size

Conversation

@redfish4ktc

@redfish4ktc redfish4ktc commented May 21, 2026

Copy link
Copy Markdown

Summary

  • Add a Claude Code skill that compares maxGraph example bundle sizes between two git references (commit SHA, branch, or tag) and emits a markdown table with deltas in kB and %.
  • Useful to measure the size impact of a PR, refactor, or release without manually checking out, building, and tabulating sizes for each ref.
  • Wraps a bash script that builds packages/core, runs scripts/build-all-examples.bash at each ref, parses its trailing CSV, and prints a markdown table on stdout (build logs go to stderr).

Design notes

  • Working tree must be strictly clean; the script refuses to auto-stash to avoid silent data loss.
  • Original ref is restored via a trap on graceful exit (build failure, Ctrl-C, etc.).
  • Forced kill (SIGKILL) cannot run the trap, so a recovery lock at .git/compare-examples-size.lock is written before any checkout. The next invocation refuses to run and prints which ref to restore.
  • Uses npm ci (not npm install) so package-lock.json is never rewritten across the two checkouts.
  • Annotated tags are dereferenced via ^{commit}, so short SHAs in the output table are always findable via git log.
  • Column order is normalized by committer timestamp: older commit in column 1, newer in column 2, regardless of CLI argument order. Δ is therefore always newer − older.

Test plan

  • Smoke test on HEAD vs HEAD produces a table with all Δ = +0.00 kB / +0.00%.
  • Original branch is restored after a successful run, working tree stays clean.
  • Lock file refusal path: pre-existing lock causes the next run to abort with explicit recovery instructions; the lock is preserved across the refusal.
  • main vs v0.23.0 produces a table with correct deltas (validated against an ad-hoc reproduction).
  • Reviewer: try the skill end-to-end with two arbitrary refs from a clean tree (this will take ~5-6 minutes — two full packages/core + examples builds).
  • Reviewer: confirm SKILL.md trigger description picks up natural phrasings like "compare bundle sizes between main and my-branch".

Summary by CodeRabbit

  • Documentation

    • Added user-facing docs describing how to run a bundle-size comparison between two revisions, expected CSV/markdown output, prerequisites, and failure-handling guidance.
  • Chores

    • Added a command-line comparison tool that captures example bundle sizes for two revisions and prints a markdown table of absolute and percentage deltas, with workspace preservation, efficient dependency handling, and interrupted-run recovery.

Review Change Stack

Add a Claude Code skill that compares maxGraph example bundle sizes
between two git references (commit SHA, branch, or tag). Useful to
measure the size impact of a PR, refactor, or release without manually
checking out, building, and tabulating sizes for each ref.

The skill wraps a bash script that:

- refuses to run on a dirty working tree to avoid silent data loss
- writes a recovery lock at .git/compare-examples-size.lock so
  SIGKILL-interrupted runs surface explicit restoration instructions
  on the next invocation (bash traps cannot run on SIGKILL)
- restores the original ref via a trap on graceful exit, including
  on build failure or Ctrl-C
- uses `npm ci` instead of `npm install` so package-lock.json is
  never rewritten across the two checkouts
- dereferences annotated tags via `^{commit}` so the short SHAs
  shown in the table are findable via `git log` (not tag-object SHAs)
- normalizes column order by committer timestamp: older commit
  always in column 1, newer in column 2, regardless of CLI argument
  order; Delta is therefore always newer minus older
- builds packages/core then runs scripts/build-all-examples.bash
  at each ref and parses its trailing CSV
- emits a markdown table on stdout (build logs go to stderr) with
  columns: example name, older kB, newer kB, delta kB, delta %
@redfish4ktc redfish4ktc added the chore Build, CI/CD or repository tasks (issues/PR maintenance, environments, ...) label May 21, 2026
@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 83605d2f-bfc6-4b54-80d4-e19f00117b81

📥 Commits

Reviewing files that changed from the base of the PR and between fb2f7ba and 6379963.

📒 Files selected for processing (1)
  • packages/website/docs/development/tools.md
✅ Files skipped from review due to trivial changes (1)
  • packages/website/docs/development/tools.md

Walkthrough

Adds a Claude skill, developer docs, and a new bash script that, given two git refs, checks out each ref, conditionally runs npm install, builds examples to CSV, and aggregates per-example kB and percentage deltas into a markdown table.

Changes

compare-examples-size Skill

Layer / File(s) Summary
Skill specification and contract
.claude/skills/compare-examples-size/SKILL.md (lines 1–28)
Skill metadata, title, overview, example prompts, and input rules requiring two refs.
Skill workflow and output specification
.claude/skills/compare-examples-size/SKILL.md (lines 29–82)
Documents the script workflow (clean-tree check, ref resolution, per-ref checkout/build, CSV capture), output format (kB, Δ kB, Δ %, signed deltas, N/A, sorting), prerequisites, and failure-handling guidance.
Script entry point, validation, and ref resolution
.claude/skills/compare-examples-size/scripts/compare-examples-size.bash (lines 1–104)
Enforces two CLI args, enables strict bash options, validates git repo and clean working tree with a lock-file recovery mechanism, resolves/peels refs to SHAs, orders commits by timestamp, and records original HEAD for restoration.
Build env, temp lifecycle, and per-ref capture
.claude/skills/compare-examples-size/scripts/compare-examples-size.bash (lines 106–174)
Creates temp directory and cleanup traps, defines build_and_capture() to checkout a commit, run conditional npm ci based on package-lock.json hash, build core and run ./scripts/build-all-examples.bash, and capture the two-line CSV output; invoked for both refs.
CSV aggregation and markdown table output
.claude/skills/compare-examples-size/scripts/compare-examples-size.bash (lines 175–225)
Embedded Python loads both CSVs, aligns example names, computes Δ kB and Δ %, formats header labels, and prints a markdown table to stdout while build logs are written to stderr.
Developer tooling docs
packages/website/docs/development/tools.md (lines 1–78)
Adds developer docs documenting build-all-examples usage and how to compare example bundle sizes across git revisions using the new skill and script.

Sequence Diagram

sequenceDiagram
  participant User
  participant Script as compare-examples-size.bash
  participant Git
  participant npm
  participant Builder as build-all-examples.bash
  participant Aggregator as Python-formatter

  User->>Script: provide two git refs
  Script->>Git: resolve refs -> commit SHAs
  Script->>Git: checkout older SHA
  Script->>npm: conditional npm ci (if package-lock changed)
  Script->>Builder: run build-all-examples.bash -> CSV_old
  Script->>Git: checkout newer SHA
  Script->>npm: conditional npm ci (if package-lock changed)
  Script->>Builder: run build-all-examples.bash -> CSV_new
  Script->>Aggregator: load CSV_old, CSV_new and compute table
  Aggregator->>Script: output markdown table (stdout)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • maxGraph/maxGraph#1037: Modifies scripts/build-all-examples.bash option parsing and CSV/table output that the new compare script depends on.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The PR description includes a detailed summary, design notes, and test plan that fully cover the changes. However, it does not include a checklist or address the required PR template sections. Fill out the PR Checklist from the template, including issue reference (closes #xxx), test confirmation, documentation status, and conventional commits verification.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'chore: add compare-examples-size skill' directly and clearly describes the main change: adding a new Claude Code skill for comparing bundle sizes.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
.claude/skills/compare-examples-size/scripts/compare-examples-size.bash (1)

156-169: ⚖️ Poor tradeoff

Update last-two-lines parsing: currently OK, still brittle to whitespace/future output

build-all-examples.bash ends by printing exactly:

  1. echo "$csv_header"
  2. echo "$csv_values"
    with no further non-empty stdout output after that, so grep -v '^$' "$raw_out" | tail -n 2 should capture the intended CSV lines today.

The remaining fragility is that grep -v '^$' doesn’t drop whitespace-only lines; if those ever appear after the CSV, the tail -n 2 contract breaks. Consider filtering whitespace-only lines (e.g., grep -vE '^[[:space:]]*$') and/or validating the header line format before writing $out_csv.

.claude/skills/compare-examples-size/SKILL.md (1)

56-58: 💤 Low value

Consider adding a concrete example row.

The template header clearly shows the column structure, but adding one or two sample data rows would make the format more tangible.

📊 Example enhancement
+| Example                  | main 8f3b2c1 (kB) | feature/perf a1b2c3d (kB) | Δ kB   |Δ %     |
+|--------------------------|-------------------|----------------------------|--------|---------|
+| Hello World              | 245.32            | 248.15                     | +2.83  | +1.15%  |
+| Graph Layout             | 312.47            | N/A                        | N/A    | N/A     |
+| Custom Shapes            | N/A               | 198.76                     | N/A    | N/A     |

The existing explanations in lines 60-66 are thorough, so this enhancement is optional.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: af3dca0b-4fd1-4d51-bf2a-1a43e413f880

📥 Commits

Reviewing files that changed from the base of the PR and between 47421aa and 1590926.

📒 Files selected for processing (2)
  • .claude/skills/compare-examples-size/SKILL.md
  • .claude/skills/compare-examples-size/scripts/compare-examples-size.bash

Comment on lines +101 to +121
# 4. Save current ref so we can restore it on exit (branch name preferred, fallback to SHA),
# then write the lock so a forced kill leaves a recovery breadcrumb (see step 1).
ORIGINAL_REF=$(git symbolic-ref --quiet --short HEAD 2>/dev/null || git rev-parse HEAD)
echo "$ORIGINAL_REF" > "$LOCK_FILE"

# Temp files for CSV captures
TMP_DIR=$(mktemp -d -t maxgraph-sizes-XXXXXX)
FROM_CSV="$TMP_DIR/from.csv"
TO_CSV="$TMP_DIR/to.csv"

cleanup() {
local status=$?
echo >&2
echo "Restoring original ref: $ORIGINAL_REF" >&2
git checkout --quiet "$ORIGINAL_REF" 2>/dev/null || \
echo "Warning: failed to restore $ORIGINAL_REF. Run 'git checkout $ORIGINAL_REF' manually." >&2
rm -rf "$TMP_DIR"
rm -f "$LOCK_FILE"
exit $status
}
trap cleanup EXIT INT TERM

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Install the cleanup trap before creating side effects.

The lock file (line 104) and TMP_DIR (line 107) are created before trap cleanup EXIT INT TERM is installed on line 121. If SIGINT/SIGTERM arrives in that window (e.g., during mktemp), the shell takes the default action and exits without running cleanup, leaving an orphaned lock file and a temp dir behind — and on next run the user is told a previous run was killed even though nothing was checked out. Move the trap installation to immediately after ORIGINAL_REF is captured, then write the lock and create the temp dir.

🛡️ Proposed reordering
 ORIGINAL_REF=$(git symbolic-ref --quiet --short HEAD 2>/dev/null || git rev-parse HEAD)
-echo "$ORIGINAL_REF" > "$LOCK_FILE"
-
-# Temp files for CSV captures
-TMP_DIR=$(mktemp -d -t maxgraph-sizes-XXXXXX)
-FROM_CSV="$TMP_DIR/from.csv"
-TO_CSV="$TMP_DIR/to.csv"
+TMP_DIR=""
 
 cleanup() {
   local status=$?
+  trap - EXIT INT TERM
   echo >&2
   echo "Restoring original ref: $ORIGINAL_REF" >&2
   git checkout --quiet "$ORIGINAL_REF" 2>/dev/null || \
     echo "Warning: failed to restore $ORIGINAL_REF. Run 'git checkout $ORIGINAL_REF' manually." >&2
-  rm -rf "$TMP_DIR"
+  [[ -n "$TMP_DIR" ]] && rm -rf "$TMP_DIR"
   rm -f "$LOCK_FILE"
   exit $status
 }
 trap cleanup EXIT INT TERM
+
+echo "$ORIGINAL_REF" > "$LOCK_FILE"
+TMP_DIR=$(mktemp -d -t maxgraph-sizes-XXXXXX)
+FROM_CSV="$TMP_DIR/from.csv"
+TO_CSV="$TMP_DIR/to.csv"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# 4. Save current ref so we can restore it on exit (branch name preferred, fallback to SHA),
# then write the lock so a forced kill leaves a recovery breadcrumb (see step 1).
ORIGINAL_REF=$(git symbolic-ref --quiet --short HEAD 2>/dev/null || git rev-parse HEAD)
echo "$ORIGINAL_REF" > "$LOCK_FILE"
# Temp files for CSV captures
TMP_DIR=$(mktemp -d -t maxgraph-sizes-XXXXXX)
FROM_CSV="$TMP_DIR/from.csv"
TO_CSV="$TMP_DIR/to.csv"
cleanup() {
local status=$?
echo >&2
echo "Restoring original ref: $ORIGINAL_REF" >&2
git checkout --quiet "$ORIGINAL_REF" 2>/dev/null || \
echo "Warning: failed to restore $ORIGINAL_REF. Run 'git checkout $ORIGINAL_REF' manually." >&2
rm -rf "$TMP_DIR"
rm -f "$LOCK_FILE"
exit $status
}
trap cleanup EXIT INT TERM
# 4. Save current ref so we can restore it on exit (branch name preferred, fallback to SHA),
# then write the lock so a forced kill leaves a recovery breadcrumb (see step 1).
ORIGINAL_REF=$(git symbolic-ref --quiet --short HEAD 2>/dev/null || git rev-parse HEAD)
TMP_DIR=""
cleanup() {
local status=$?
trap - EXIT INT TERM
echo >&2
echo "Restoring original ref: $ORIGINAL_REF" >&2
git checkout --quiet "$ORIGINAL_REF" 2>/dev/null || \
echo "Warning: failed to restore $ORIGINAL_REF. Run 'git checkout $ORIGINAL_REF' manually." >&2
[[ -n "$TMP_DIR" ]] && rm -rf "$TMP_DIR"
rm -f "$LOCK_FILE"
exit $status
}
trap cleanup EXIT INT TERM
echo "$ORIGINAL_REF" > "$LOCK_FILE"
# Temp files for CSV captures
TMP_DIR=$(mktemp -d -t maxgraph-sizes-XXXXXX)
FROM_CSV="$TMP_DIR/from.csv"
TO_CSV="$TMP_DIR/to.csv"

Comment on lines +111 to +121
cleanup() {
local status=$?
echo >&2
echo "Restoring original ref: $ORIGINAL_REF" >&2
git checkout --quiet "$ORIGINAL_REF" 2>/dev/null || \
echo "Warning: failed to restore $ORIGINAL_REF. Run 'git checkout $ORIGINAL_REF' manually." >&2
rm -rf "$TMP_DIR"
rm -f "$LOCK_FILE"
exit $status
}
trap cleanup EXIT INT TERM

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

cleanup runs twice on INT/TERM.

With trap cleanup EXIT INT TERM, a SIGINT invokes cleanup, which calls exit $status, which in turn fires the EXIT trap and runs cleanup again. The second pass re-prints "Restoring original ref: …", attempts another checkout (harmless but noisy), and tries to rm -f an already-removed lock. Clear the traps at the top of cleanup to make it idempotent.

♻️ Proposed fix
 cleanup() {
   local status=$?
+  trap - EXIT INT TERM
   echo >&2
   echo "Restoring original ref: $ORIGINAL_REF" >&2
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cleanup() {
local status=$?
echo >&2
echo "Restoring original ref: $ORIGINAL_REF" >&2
git checkout --quiet "$ORIGINAL_REF" 2>/dev/null || \
echo "Warning: failed to restore $ORIGINAL_REF. Run 'git checkout $ORIGINAL_REF' manually." >&2
rm -rf "$TMP_DIR"
rm -f "$LOCK_FILE"
exit $status
}
trap cleanup EXIT INT TERM
cleanup() {
local status=$?
trap - EXIT INT TERM
echo >&2
echo "Restoring original ref: $ORIGINAL_REF" >&2
git checkout --quiet "$ORIGINAL_REF" 2>/dev/null || \
echo "Warning: failed to restore $ORIGINAL_REF. Run 'git checkout $ORIGINAL_REF' manually." >&2
rm -rf "$TMP_DIR"
rm -f "$LOCK_FILE"
exit $status
}
trap cleanup EXIT INT TERM

Comment on lines +182 to +187
def load(path):
with open(path) as f:
lines = [ln.rstrip("\n") for ln in f if ln.strip()]
header = lines[0].split(",")
values = lines[1].split(",")
return dict(zip(header, values))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Silent column mismatch in CSV parsing.

dict(zip(header, values)) silently truncates to the shorter of the two lists. If build-all-examples.bash ever emits a CSV with a header/values length mismatch (or trailing garbage line picked up by tail -n 2), the script will produce a partial table with no warning. A length check before zipping turns this into a loud failure that matches the rest of the script's strict posture.

🛡️ Proposed fix
 def load(path):
     with open(path) as f:
         lines = [ln.rstrip("\n") for ln in f if ln.strip()]
     header = lines[0].split(",")
     values = lines[1].split(",")
+    if len(header) != len(values):
+        sys.exit(
+            f"Error: CSV header/values column count mismatch in {path} "
+            f"({len(header)} vs {len(values)})."
+        )
     return dict(zip(header, values))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def load(path):
with open(path) as f:
lines = [ln.rstrip("\n") for ln in f if ln.strip()]
header = lines[0].split(",")
values = lines[1].split(",")
return dict(zip(header, values))
def load(path):
with open(path) as f:
lines = [ln.rstrip("\n") for ln in f if ln.strip()]
header = lines[0].split(",")
values = lines[1].split(",")
if len(header) != len(values):
sys.exit(
f"Error: CSV header/values column count mismatch in {path} "
f"({len(header)} vs {len(values)})."
)
return dict(zip(header, values))

Document the two internal tools used during development:
- `scripts/build-all-examples.bash` to build all examples and report
  the size of the maxGraph chunk for each one
- `compare-examples-size`, available as a Claude Code skill or as a
  standalone bash script, to compare the maxGraph chunk size between
  two git revisions (commit SHAs, branches, or tags)

The second tool is presented as useful for tracking the evolution of
the codebase, enriching pull request descriptions with the bundle-size
impact of a change, and supporting release notes by surfacing positive
changes or warning about negative impacts.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 86d98179-6bd1-416a-8e87-ed9e74c336d4

📥 Commits

Reviewing files that changed from the base of the PR and between 1590926 and fb2f7ba.

📒 Files selected for processing (1)
  • packages/website/docs/development/tools.md

Comment thread packages/website/docs/development/tools.md Outdated
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

chore Build, CI/CD or repository tasks (issues/PR maintenance, environments, ...)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant