working-on-plugins — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited working-on-plugins (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.
Both WASM plugins (plugins/*/parser/) and API adapters (worker/src/adapters/) produce the same GameState shape: identity, summary, and sections. This skill covers how to design sections so they work well with the MCP tools and AI consumers.
For the ndjson contract, WASM runtime, and plugin build system, see docs/plugins.md. For API adapter architecture, see docs/adapters.md.
get_save embeds one section's data as the overview in its response. It picks the first match from this list:
character_overview → player_summary → overview → summaryEvery plugin MUST produce a section matching one of these names. If none match, get_save falls back to the first section alphabetically — which may be a 200KB deck list or item dump.
The overview section is the AI's first look at the save. It should contain enough context to answer "what is this character/save?" and guide the AI to the right detailed sections.
Target: overview section < 15KB. A typical overview is 2-10KB.
| Limit | Value | Enforced by |
|---|---|---|
| Hard max per section | 80KB | SECTION_SIZE_LIMIT in worker/src/mcp/tools.ts — get_section rejects sections over this |
| Overview target | < 15KB | Convention — keeps get_save response usable |
| Individual item section | < 10KB typical | Convention — per-deck, per-character sections |
If a section could exceed 80KB with realistic data, it MUST be split.
When a game has a large collection (decks, characters, items, matches), split into per-item sections with an index in the overview.
overview section:
decks: [{name: "Slivers", format: "Brawl", section: "deck:Slivers"}, ...]
deck:Slivers section: (full card list, ~5KB)
deck:Control section: (full card list, ~3KB)The AI sees the index in the overview and fetches individual items as needed. This is how D2R shared stash (overview + tab1, tab2, ...) and MTGA (player_summary + deck:*, game:*) work.
Split when any of these are true:
Use prefix:human_readable_name — the AI requests sections by name, so readability matters.
deck:[HB] Slivers (MTGA deck)
game:7a0be838-0033-4e16 (MTGA game log by matchId)
tab1, tab2, tab3 (D2R stash tabs)
character:Warrior (per-character sections)The daemon sends allSectionNames in every PushSave message. The worker deletes any sections NOT in this list. Dynamic section names (deck:, game:) work correctly — the parser includes them all in the output, and the worker cleans up sections that no longer exist.
Section descriptions are the AI's guide for when to fetch a section. Write them as directives, not documentation.
Good (tells AI when/why to fetch):
"Deck list for [HB] Slivers (Brawl) — main deck, sideboard, and command zone cards"
"Turn-by-turn game log for match abc123 — use to analyze play sequencing and identify misplays"
"Aggregated character stats: resistances, magic find, FCR/FHR breakpoints — use to evaluate gear upgrades"Bad (explains what it is, not when to use it):
"Contains the player's deck data"
"Game log information"
"Character statistics"| Game | Overview Section | Per-Item Sections | Reference |
|---|---|---|---|
| D2R Character | character_overview | None (sections are small) | plugins/d2r/parser/main.go |
| D2R Stash | overview | tab1, tab2, ... | plugins/d2r/parser/main.go:545 |
| MTGA | player_summary | deck:*, game:* | plugins/magic/parser/main.go |
| Clair Obscur | overview | character:* | plugins/clair-obscur/parser/ |
| WoW | character_overview | None (API data is bounded) | worker/src/adapters/wow/ |
| SDV | player_summary | None (data is naturally bounded) | plugins/sdv/parser/main.go |
Every plugin must declare an [attribution] section in plugin.toml listing the third-party IP sources whose data it uses. The build pipeline reads this to embed legal disclaimers in views.
[attribution]
sources = ["wotc", "scryfall", "17lands"]Valid source keys are defined in views/src/attributions.ts: wotc, scryfall, 17lands, blizzard, raiderio, ludeon, concernedape, kepler. To add a new source, add it to the SOURCES record in that file first.
The build fails if [attribution] is missing or uses an unknown key.
OVERVIEW_SECTION_NAMES[attribution] declared in plugin.toml with valid source keys~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.