SQL Optimization Patterns
ynulihao/AgentSkillOS
Master SQL query optimization, indexing strategies, and EXPLAIN analysis to dramatically improve database performance and eliminate slow queries.
PhotoTag, PhotoTagExtraTags, categories, litter objects, materials, brands, ClassifyTagsService, GeneratePhotoSummaryService, tag migration, and the v4-to-v5 conversion.
$ npx skills add OpenLitterMap/openlittermap-web --skill tagging-system -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install OpenLitterMap/openlittermap-web tagging-system --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/OpenLitterMap/openlittermap-web.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.ai/skills/tagging-system .claude/skills/tagging-system && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "tagging-system" agent skill from https://github.com/OpenLitterMap/openlittermap-web/tree/master/.ai/skills/tagging-system into .claude/skills/tagging-system/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "tagging-system", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/OpenLitterMap/openlittermap-web/tree/master/.ai/skills/tagging-systemType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add OpenLitterMap/openlittermap-web --skill tagging-system -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install OpenLitterMap/openlittermap-web tagging-system --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/OpenLitterMap/openlittermap-web.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.ai/skills/tagging-system .agents/skills/tagging-system && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "tagging-system" agent skill from https://github.com/OpenLitterMap/openlittermap-web/tree/master/.ai/skills/tagging-system into .agents/skills/tagging-system/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "tagging-system", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add OpenLitterMap/openlittermap-web --skill tagging-system -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install OpenLitterMap/openlittermap-web tagging-system --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/OpenLitterMap/openlittermap-web.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.ai/skills/tagging-system .cursor/skills/tagging-system && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "tagging-system" agent skill from https://github.com/OpenLitterMap/openlittermap-web/tree/master/.ai/skills/tagging-system into .cursor/skills/tagging-system/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "tagging-system", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/OpenLitterMap/openlittermap-web.git --path .ai/skills/tagging-system--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add OpenLitterMap/openlittermap-web --skill tagging-system -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install OpenLitterMap/openlittermap-web tagging-system --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/OpenLitterMap/openlittermap-web.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.ai/skills/tagging-system .gemini/skills/tagging-system && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "tagging-system" agent skill from https://github.com/OpenLitterMap/openlittermap-web/tree/master/.ai/skills/tagging-system into .gemini/skills/tagging-system/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "tagging-system", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install OpenLitterMap/openlittermap-web tagging-systemInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add OpenLitterMap/openlittermap-web --skill tagging-system -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/OpenLitterMap/openlittermap-web.git skills-src && mkdir -p .github/skills && cp -r skills-src/.ai/skills/tagging-system .github/skills/tagging-system && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "tagging-system" agent skill from https://github.com/OpenLitterMap/openlittermap-web/tree/master/.ai/skills/tagging-system into .github/skills/tagging-system/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "tagging-system", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add OpenLitterMap/openlittermap-web --skill tagging-system -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install OpenLitterMap/openlittermap-web tagging-system --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/OpenLitterMap/openlittermap-web.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.ai/skills/tagging-system .opencode/skills/tagging-system && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "tagging-system" agent skill from https://github.com/OpenLitterMap/openlittermap-web/tree/master/.ai/skills/tagging-system into .opencode/skills/tagging-system/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "tagging-system", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
tagging-systemPhotoTag, PhotoTagExtraTags, categories, litter objects, materials, brands, ClassifyTagsService, GeneratePhotoSummaryService, tag migration, and the v4-to-v5 conversion.
Tagging System is an agent skill from OpenLitterMap/openlittermap-web. PhotoTag, PhotoTagExtraTags, categories, litter objects, materials, brands, ClassifyTagsService, GeneratePhotoSummaryService, tag migration, and the v4-to-v5 conversion.
Its SKILL.md is about 4.5k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Databases, covering Database schema design. It works with PHP. The repository describes itself as: https://opengeospatialdata.springeropen.com/articles/10.1186/s40965-018-0050-y. The licence is GPL-3.0.
4 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit ac688aa. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are php, json and sql).
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Tagging System loads about 4.5k tokens when it runs. Until then it costs about 46 tokens; SKILL.md has 1,388 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from OpenLitterMap/openlittermap-web at commit ac688aa, republished under its GPL-3.0 licence (© OpenLitterMap). 1,388 words, ~4,454 tokens.
.claude/skills/tagging-system/SKILL.md (or your agent's skills folder).V5 uses a normalized hierarchy: Photo -> PhotoTag (category + object + quantity) -> PhotoTagExtraTags (materials, brands, custom tags). All tag data lives in photo_tags and photo_tag_extra_tags tables — not the old per-category tables.
V5.1 Architecture (Phase 1 complete — schema + seed only, no behavior changes): Added LitterObjectType dimension ("what was in the container" — beer, water, soda, etc.), category_object_types pivot controlling which types are valid per category+object combo, and category_litter_object_id/litter_object_type_id nullable FK columns on photo_tags. Full spec: readme/TaggingArchitectureSpec.md.
app/Models/Litter/Tags/PhotoTag.php — Primary tag record (category + object)app/Models/Litter/Tags/PhotoTagExtraTags.php — Materials, brands, custom tags per tagapp/Models/Litter/Tags/Category.php — Tag categories (smoking, food, etc.)app/Models/Litter/Tags/LitterObject.php — Taggable objects (butts, wrapper, etc.)app/Models/Litter/Tags/BrandList.php — Brand records (brandslist table)app/Models/Litter/Tags/Materials.php — Material records (materials table)app/Models/Litter/Tags/CustomTagNew.php — Custom tags (custom_tags_new table)app/Models/Litter/Tags/CategoryObject.php — Pivot: category_litter_object + types() BelongsToManyapp/Models/Litter/Tags/LitterObjectType.php — Type lookup: "what was in the container" (beer, water, etc.)database/seeds/Tags/GenerateTagsSeeder.php — Seeds all categories, objects, CLO pivots, materials, and types from TagsConfig. Also ensures unclassified system category exists.app/Services/Tags/ClassifyTagsService.php — Tag classification + deprecated key mappingapp/Services/Tags/UpdateTagsService.php — V4->V5 migration per photoapp/Services/Tags/GeneratePhotoSummaryService.php — Summary JSON + XP from PhotoTagsapp/Services/Tags/XpCalculator.php — XP scoring rulesapp/Enums/Dimension.php — Tag type enum (object, category, material, brand, custom_tag)photo_tags uses FK columns: category_id and litter_object_id (not string columns). Tests must create Category/LitterObject records and use their IDs. These columns are now NULLABLE — extra-tag-only tags (brands, materials, custom tags) can exist without a litter object.photo_tag_extra_tags is polymorphic: tag_type is 'material'|'brand'|'custom_tag', tag_type_id is the FK to the respective table.App\Models\Litter\Tags\PhotoTag, not App\Models\PhotoTag.$photo->generateSummary() after creating/updating/deleting PhotoTags.LitterObject::firstOrCreate(['key' => $key], ['crowdsourced' => true]).category_litter_object_id, category_id, litter_object_id are all nullable. AddTagsToPhotoAction::createExtraTagOnly() creates standalone extra-tag PhotoTags with null CLO fields. GeneratePhotoSummaryService counts objects only when objectId > 0 (variable renamed $totalLitter → $totalObjects). XpCalculator awards object XP only when object_id > 0 — extra-tag-only tags don't get phantom object XP. Frontend useXpCalculator.js mirrors this logic.photo_tags for (CLO, type) pairs. There is no DB-level unique constraint on (photo_id, category_litter_object_id, litter_object_type_id). Duplicate CLO+type pairs are possible (each is a separate PhotoTag row). Do NOT assume uniqueness. Extra-tag deduplication (materials/brands within a single tag) is handled via upsert inside a single PhotoTag's extra tags, not across multiple PhotoTag rows.getNewTags() serializer contract. UsersUploadsController::getNewTags() conditionally includes category and object only when both category_id and litter_object_id resolve. For extra-tag-only PhotoTags (brand/material/custom-only), category and object are returned as null. Always includes litter_object_type_id (may be null), quantity, picked_up (cast to bool with photo-level fallback), and extra_tags array.// Create primary tag
$photoTag = PhotoTag::create([
'photo_id' => $photo->id,
'category_id' => $category->id,
'litter_object_id' => $object->id,
'quantity' => 5,
'picked_up' => true,
]);
// Attach materials
$photoTag->attachExtraTags([
['id' => $plasticId, 'quantity' => 5],
['id' => $paperId, 'quantity' => 3],
], 'material', 0);
// Attach brands
$photoTag->attachExtraTags([
['id' => $marlboroId, 'quantity' => 3],
], 'brand', 0);$photoTag = PhotoTag::create([
'photo_id' => $photo->id,
'custom_tag_primary_id' => $customTag->id,
'quantity' => $quantity,
'picked_up' => $pickedUp,
]);$photoTag = PhotoTag::create([
'photo_id' => $photo->id,
'category_id' => Category::where('key', 'brands')->value('id'),
'quantity' => array_sum($brandQuantities),
]);
$photoTag->attachExtraTags($brands, Dimension::BRAND->value, 0);// ClassifyTagsService::normalizeDeprecatedTag('beerBottle')
// Returns: ['object' => 'beer_bottle', 'materials' => ['glass']]
// ClassifyTagsService::normalizeDeprecatedTag('coffeeCups')
// Returns: ['object' => 'cup', 'materials' => ['paper']]
// ClassifyTagsService::normalizeDeprecatedTag('butts')
// Returns: ['object' => 'butts', 'materials' => ['plastic', 'paper']]130+ mappings from old camelCase keys to normalized keys with inferred materials.
ClassifyTagsService::CATEGORY_ALIASES resolves deprecated v4 category keys: coastal→marine, trashdog→pets, dogshit→pets, automobile→vehicles, pathway→unclassified, drugs→unclassified, political→unclassified, stationery→unclassified. The public getCategory(string $rawKey) method checks aliases before DB lookup.
TagsConfig defines 16 active categories (ordered alphabetically): alcohol, art, civic, coffee, dumping, electronics, food, industrial, marine, medical, other, pets, sanitary, smoking, softdrinks, vehicles. The unclassified system category is NOT in TagsConfig but is created by GenerateTagsSeeder for v4 alias resolution.
enum Dimension: string
{
case LITTER_OBJECT = 'object'; // table: litter_objects
case CATEGORY = 'category'; // table: categories
case MATERIAL = 'material'; // table: materials
case BRAND = 'brand'; // table: brandslist
case CUSTOM_TAG = 'custom_tag'; // table: custom_tags_new
public function table(): string
public static function fromTable(string $table): ?self
}-- photo_tags: FK columns, NOT strings
photo_tags (
id, photo_id, category_id, litter_object_id,
category_litter_object_id, -- v5.1: nullable FK to category_litter_object (Phase 3: NOT NULL)
litter_object_type_id, -- v5.1: nullable FK to litter_object_types
custom_tag_primary_id, -- for custom-only tags
quantity, picked_up,
created_at, updated_at
)
-- photo_tag_extra_tags: polymorphic extras
photo_tag_extra_tags (
id, photo_tag_id,
tag_type, -- 'material'|'brand'|'custom_tag'
tag_type_id, -- FK to materials/brandslist/custom_tags_new
quantity, index,
created_at, updated_at
)
-- Reference tables
categories (id, key, parent_id) -- includes 'unclassified' (hidden from UI)
litter_objects (id, key, crowdsourced)
litter_object_types (id, key, name) -- v5.1: "what was in the container" (~17 rows)
materials (id, key)
brandslist (id, key, crowdsourced)
custom_tags_new (id, key)
category_litter_object (id, category_id, litter_object_id) -- CLO pivot
-- v5.1: controls which types are valid per CLO
category_object_types (
category_litter_object_id, -- FK to category_litter_object
litter_object_type_id, -- FK to litter_object_types
UNIQUE(category_litter_object_id, litter_object_type_id)
)use App\Services\Achievements\Tags\TagKeyCache;
// Lookup
$id = TagKeyCache::idFor('material', 'glass'); // null if not found
$id = TagKeyCache::getOrCreateId('material', 'glass'); // creates if missing
$key = TagKeyCache::keyFor('material', $id); // reverse lookup
// Bulk preload (call once at script startup)
TagKeyCache::preloadAll();Three-layer cache: in-memory array -> Redis hash (24h TTL) -> database fallback.
The Vue frontend sends 4 distinct tag types to AddTagsToPhotoAction:
{ "object": { "id": 5, "key": "butts" }, "quantity": 3, "picked_up": true,
"materials": [{ "id": 2, "key": "plastic" }], "brands": [], "custom_tags": [] }Backend auto-resolves category from object->categories()->first(). Category need NOT be sent.
Materials and brands accept flexible formats:
[50, 51] (plain IDs) or [{"id": 50}] (objects). Quantity inherits from parent tag.[10] (plain IDs, quantity=1) or [{"id": 10, "quantity": 3}] (objects with per-brand quantity).attachMaterials() and attachBrands() both check is_array($item) ? $item['id'] : $item.{ "custom": true, "key": "dirty-bench", "quantity": 1, "picked_up": null }$tag['custom'] is boolean true (flag), $tag['key'] is the actual tag name. Creates CustomTagNew via $tag['key'].
Custom tag sanitization (AddTagsToPhotoAction::attachCustomTags). Custom tag keys are free text — there is no allowlist regex. The key is sanitized with mb_substr(trim(strip_tags($key)), 0, 255) (caps to the custom_tags_new.key varchar(255)) and accepted, including punctuation like & . ' / (real brand/product names, e.g. "Black & Mild"). An empty-after-sanitize key is skipped (continue) — never thrown. Do NOT reintroduce a throwing allowlist: the throw lived inside run()'s DB::transaction, so one bad custom tag would 500 the request and roll back the user's valid object tags. Same path for standalone (createExtraTagOnly) and object-attached (createTagFromClo) custom tags. (bn:→brand resolution is a separate deferred ticket; bn: currently stores as a literal custom string.)
{ "brand_only": true, "brand": { "id": 1, "key": "coca-cola" }, "quantity": 1 }Creates PhotoTag with null category/object, attaches brand as extra tag.
{ "material_only": true, "material": { "id": 2, "key": "plastic" }, "quantity": 1 }Same pattern as brand-only — PhotoTag with null FKs, material as extra tag.
{
"categories": [{"id": 1, "key": "alcohol"}],
"objects": [{"id": 5, "key": "bottle", "categories": [{"id": 1, "key": "alcohol"}]}],
"materials": [{"id": 1, "key": "glass"}],
"brands": [{"id": 7, "key": "heineken"}],
"types": [{"id": 3, "key": "beer", "name": "Beer"}],
"category_objects": [{"id": 42, "category_id": 1, "litter_object_id": 5}],
"category_object_types": [{"category_litter_object_id": 42, "litter_object_type_id": 3}]
}unclassified category is excluded from the response. category_object_types maps which types are valid per CLO.
| File | Purpose |
|---|---|
resources/js/views/General/Tagging/v2/AddTags.vue | Main tagging page — dark glass UI, 55/45 split layout, search index with per-(object,category) entries, progress bar, auto-advance, success flash, keyboard shortcuts (/, Escape, J/K/←/→, Enter, Ctrl+Enter, ?), empty state |
resources/js/views/General/Tagging/v2/components/UnifiedTagSearch.vue | Debounced (100ms) search combobox, grouped results (object/type/material/brand/customTag), i18n translated labels, category breadcrumbs, emerald accent |
resources/js/views/General/Tagging/v2/components/TagCard.vue | Tag card with "Object · Category" display, type pills, picked-up pills, dark glass styling, red border on unresolved CLO |
resources/js/views/General/Tagging/v2/components/TaggingHeader.vue | XP bar (emerald), level titles, unresolved tags warning, submit disabled when unresolved, edit mode badge |
resources/js/views/General/Tagging/v2/components/ActiveTagsList.vue | Container for active tags, keyboard hint in empty state |
resources/js/stores/photos/requests.js | UPLOAD_TAGS() → POST, REPLACE_TAGS() → PUT, GET_SINGLE_PHOTO() |
resources/js/stores/user/requests.js | REFRESH_USER() — refreshes user XP/level after tag submission |
resources/js/stores/tags/requests.js | GET_ALL_TAGS() → GET /api/tags/all |
The search index generates one entry per (object, category) pair with pre-resolved cloId, categoryId, categoryKey. Each entry has:
label — i18n translated display name via translateTag(key, prefix) (e.g. coke → "Coca-Cola" from litter.brands.coke). Falls back to formatKey() if no translation exists.categoryLabel — translated category name (e.g. litter.categories.alcohol → "Alcohol")lowerKey — includes both raw key AND translated label for search matching (e.g. "coke coca-cola")Translation prefixes: objects use litter.{categoryKey}.{objectKey}, brands use litter.brands.{key}, materials use litter.material.{key}, categories use litter.categories.{key}.
formatKey(key) converts snake_case → Title Case (e.g., six_pack_rings → "Six Pack Rings"). Used as fallback when no i18n translation exists.
hasUnresolvedTags computed blocks submit when any object tag lacks a cloId. Keyboard shortcuts guard against firing inside form inputs (INPUT/SELECT/TEXTAREA).
All tagging components use a dark glass UI with emerald accent:
bg-gradient-to-br from-slate-900 via-blue-900 to-emerald-900bg-white/5 border border-white/10 rounded-xltext-emerald-400, bg-emerald-500, focus:border-emerald-500/50)text-white / text-white/60 / text-white/40 / text-white/30Auto-advance flow: Submit → success flash (green border pulse, 400ms) → clear tags → advance to next photo.
Keyboard shortcuts: / focus search, Escape blur/close, J/← prev, K/→ next, Enter confirm (bare), Ctrl+Enter confirm (in input), ? toggle hints.
photo_tags. The table uses category_id and litter_object_id (integer FKs), not string columns like 'smoking' or 'butts'.$photo->generateSummary() after modifying PhotoTags.App\Models\.** The namespace is App\Models\Litter\Tags\PhotoTag.brandslist table name. Not brands — the table is literally brandslist.attachExtraTags() or as brand-only PhotoTags.custom_tag_primary_id. Custom-only tags have no category_id or litter_object_id — they use custom_tag_primary_id instead.object.id but NOT category. Backend auto-resolves category from object->categories()->first().$tag['custom'] as the tag name. It's a boolean flag. The actual name is $tag['key'].$tag['brands'] for brand-only tags. Brand-only tags use $tag['brand'] (singular) + $tag['brand_only'] flag.cot.id for type entries. The category_object_types API only returns category_litter_object_id and litter_object_type_id — no id column. Use composite key type-${cot.category_litter_object_id}-${cot.litter_object_type_id}.cloId. Filter them out on mount: parsed.filter((t) => t.type !== 'object' || t.cloId).litter_object_type_id on edit round-trip. UsersUploadsController::getNewTags() must include litter_object_type_id in the response, and convertExistingTags() must read it into typeId. Without this, the type dimension (e.g., "beer" on a "bottle") is lost when editing tags.PhotoTagsController::update() must wrap delete + reset + add in DB::transaction(). If AddTagsToPhotoAction::run() throws after tags are deleted, the photo loses all data.|| instead of ?? for counts that can be zero. photosStore.untaggedStats.leftToTag || fallback treats 0 as falsy. Use ?? (nullish coalescing) to only fall through on null/undefined.category_litter_object_id and litter_object_type_id can exist on the same photo. Don't add a UNIQUE index or query logic that assumes uniqueness across rows.category/object to always be present in getNewTags() output. For brand-only, material-only, or custom-only PhotoTags, category and object are null in the serializer output. The frontend must handle null gracefully.© OpenLitterMap, GPL-3.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .ai/skills/tagging-system of OpenLitterMap/openlittermap-web.
Open the folder on GitHubat commit ac688aa
Tagging System next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Tagging System this skillOpenLitterMap/openlittermap-web | 134 | — | ~4.5k | Automated safety check: Pass | GPL-3.0 | |
| SQL Optimization Patternsynulihao/AgentSkillOS | 617 | 10 repos | ~3.3k | Automated safety check: Pass | None | |
| Add Mpk Taskmirage-project/mirage | 2.5k | — | ~4.5k | Automated safety check: Pass | Apache-2.0 | |
| Datamodellmnimbalyst/nimbalyst | 1.8k | — | ~713 | Automated safety check: Pass | MIT | |
| Sqlite Schema Designfastrepl/anarlog | 9.4k | — | ~1.9k | Automated safety check: Pass | MIT | |
| DB Migrationskurealnum/dotfiles | 290 | — | ~820 | Automated safety check: Pass | None |
ynulihao/AgentSkillOS
Master SQL query optimization, indexing strategies, and EXPLAIN analysis to dramatically improve database performance and eliminate slow queries.
mirage-project/mirage
Step-by-step guide for adding a new task implementation to Mirage Persistent Kernel (MPK).
nimbalyst/nimbalyst
Create visual data models for database schemas using Nimbalyst's DataModelLM editor.
fastrepl/anarlog
Design or review schemas for crates/cloudsync using SQLite Sync constraints, not generic SQLite advice.
kurealnum/dotfiles
A skill your agent uses when generating or regenerating Drizzle migration files, changing database schema tables or columns, resolving migration sequence conflicts after rebase, reviewing migration…
jolars/panache
Grow Panache's CommonMark spec conformance under Flavor::CommonMark by running every spec.txt example through the shared parser, comparing rendered HTML against the spec's expected HTML…
OpenLitterMap/openlittermap-web
Styles applications using Tailwind CSS v3 utilities. An agent skill from OpenLitterMap/openlittermap-web.
OpenLitterMap/openlittermap-web
OpenLitterMap v5 architecture reference. An agent skill from OpenLitterMap/openlittermap-web.
OpenLitterMap/openlittermap-web
AchievementEngine, AchievementRepository, milestone checkers, AchievementsSeeder, userachievements pivot, AchievementsController API, and achievement evaluation flow.
OpenLitterMap/openlittermap-web
AdminController, photo approval, tag editing, deletion, MetricsService integration, admin middleware, verification queue, and admin XP.
OpenLitterMap/openlittermap-web
REST API endpoints, route structure, auth guards, request/response contracts, error patterns, and the full API surface for web SPA and mobile clients.
OpenLitterMap/openlittermap-web
ClusteringService, tile keys, dirty tiles/teams, clustering commands, ClusterController GeoJSON API, PhotoObserver dirty marking, and map cluster rendering.
Works with
Categories
PhotoTag, PhotoTagExtraTags, categories, litter objects, materials, brands, ClassifyTagsService, GeneratePhotoSummaryService, tag migration, and the v4-to-v5 conversion. Tagging System is an agent skill from OpenLitterMap/openlittermap-web. PhotoTag, PhotoTagExtraTags, categories, litter objects, materials, brands, ClassifyTagsService, GeneratePhotoSummaryService, tag migration, and the v4-to-v5 conversion.
Tagging System fits situations like: tasks that involve Database schema design.
Run `npx skills add OpenLitterMap/openlittermap-web --skill tagging-system -a claude-code`. Or copy the skill folder (.ai/skills/tagging-system in OpenLitterMap/openlittermap-web) into .claude/skills/tagging-system in your project. Claude Code loads it when a task matches its description.
Run `npx skills add OpenLitterMap/openlittermap-web --skill tagging-system -a codex`. Or copy the skill folder (.ai/skills/tagging-system in OpenLitterMap/openlittermap-web) into .agents/skills/tagging-system in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add OpenLitterMap/openlittermap-web --skill tagging-system -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/tagging-system, .gemini/skills/tagging-system, .github/skills/tagging-system and .opencode/skills/tagging-system in your project.
SKILL.md names no scripts, command-line tools or credentials: Tagging System is instructions for the agent only.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Tagging System is published under the GPL-3.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 4.5k tokens (SKILL.md is roughly 18k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Tagging System: SQL Optimization Patterns (ynulihao/AgentSkillOS, 617 stars), Add Mpk Task (mirage-project/mirage, 2.5k stars), Datamodellm (nimbalyst/nimbalyst, 1.8k stars) and Sqlite Schema Design (fastrepl/anarlog, 9.4k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
OpenLitterMap (a GitHub organization) maintains it in OpenLitterMap/openlittermap-web, which has 134 GitHub stars. The repository holds 16 skills in this directory. The repository was last updated on September 14, 2026.
Source: OpenLitterMap/openlittermap-web on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.