penn-course-planning — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited penn-course-planning (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.
This skill drives the `penn-course` MCP server (tools are named search_courses, get_course_details, etc.). It talks to the public Penn Courses / Penn Course Review API, so data is real and current. No auth is required for catalog data and aggregate ratings.
Detailed reviews are enabled on this setup: a logged-in Penn session cookie (PENN_COURSES_SESSION_COOKIE) is configured, so get_course_reviews returns full per-instructor written reviews — not just aggregates. Call get_current_semester to confirm (reviews_enabled: true). The session cookie expires periodically (logout / Penn timeout); when it lapses, get_course_reviews silently falls back to public aggregates and reports auth_status: unauthenticated — that's the cue to refresh the cookie, not a bug.
SUBJECT-NUMBER, e.g. CIS-1200, MATH-1400, ECON-0100. Alwayshyphenated and uppercase. If the user writes "CIS 120" or "cis120", normalize to CIS-1200 (Penn moved to 4-digit codes; CIS-120 → CIS-1200, CIS-160 → CIS-1600).
CIS-1200-001.2026C (A=spring, B=summer, C=fall). Omit thesemester argument to use the current term; call get_current_semester first if unsure.
course_quality / instructor_quality is better;lower difficulty and work_required mean a lighter course. Some courses are unrated (null) — say so rather than treating null as 0.
HH:MM ranges with day codesM T W R F (R = Thursday). Don't re-parse Penn's raw HH.MM encoding yourself.
| Goal | Tool |
|---|---|
| Current term + auth status | get_current_semester |
| Find courses by keyword/code | search_courses |
| Full course info + sections | get_course_details |
| Just the sections/meeting times | list_course_sections (filters: open_only, activity) |
| Aggregate ratings | get_course_ratings |
| Detailed PCR reviews | get_course_reviews (falls back to aggregates if unauthenticated) |
| Courses with an attribute / Gen-Ed | find_courses_by_attribute |
| Courses fulfilling a requirement | find_courses_by_requirement |
| Do these sections clash? | check_schedule_conflicts |
| Build a weekly schedule | build_schedule |
| Rank by quality/difficulty/workload | recommend_courses (sort_by=...) |
| Side-by-side comparison | compare_courses |
| List your saved PCP schedules | list_schedules (requires auth) |
| Save a schedule to penncourseplan.com | save_schedule (requires auth — writes to your account) |
| Delete a saved schedule | delete_schedule (requires auth) |
"Tell me about CIS 1200 / is it hard / who teaches it?" → get_course_details (description, prereqs, attributes, sections) + get_course_ratings. For per-instructor depth use get_course_reviews — with the configured session cookie this returns full written breakdowns per instructor; if the cookie has expired it returns aggregates plus an unauthenticated note (expected, not an error).
"What's a good / easy class about <topic>?" → recommend_courses with the topic as query. Use sort_by="course_quality" for "good", sort_by="difficulty" or "work_required" for "easy/light". Present the top few with their ratings, and flag unrated ones.
"Compare CIS 1200 vs CIS 1600." → compare_courses with both codes. Summarize the quality/difficulty/workload deltas and note prereq differences.
"Does this schedule work / do these classes conflict?" → Resolve each course to a concrete section id first (use list_course_sections to pick sections, ideally open_only=true). Then check_schedule_conflicts for a quick yes/no, or build_schedule for the full weekly grid + total credits + missing-companion warnings (e.g. a LEC that needs a REC/LAB). Report any not_found section ids back to the user.
"What fulfills <requirement / Gen-Ed attribute>?" → find_courses_by_attribute when you have an attribute code (use match="description" to search by words; add prefilter_query to narrow a big catalog and keep it fast), or find_courses_by_requirement for a requirement name/code.
"Save / push this schedule to Penn Course Plan so I can see it on the site." → save_schedule(name, sections=[...]) writes a new schedule to the user's live penncourseplan.com account; it appears after they refresh and pick it from the schedule dropdown. This is a real mutation on their account — confirm the course list and the name first, and default to creating a new schedule rather than overwriting one. Pass schedule_id only to deliberately overwrite an existing schedule (find ids via list_schedules). Use delete_schedule to remove one. All three need the session cookie (reviews_enabled: true is a good proxy that auth is configured); without it they return a clear WriteAuthRequired error, never a crash.
get_current_semester when the term matters or the user didn't specify one.it doesn't exist — most "not found" results are a CIS 120 vs CIS-1200 normalization miss.
rather than paraphrasing vaguely. Distinguish "lightly rated / no reviews" from "low rating".
status/capacity aspoint-in-time.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.