Drei Skills + Importer + Migration der 5 existierenden Venues.
Skills (alle repo-lokal in .claude/skills/):
- seepuls-curate-venue: 5-Stufen-Pipeline (Plan → Recherche → Design
→ Validate → Publish), Reviewer-Stops Pflicht, Nominatim-Geocoding
mit Politeness, YAML als Output. Bulk-Mode für >10 Venues.
- seepuls-validate-venue: read-only, 8 Checks (slug-unique,
country-enum, region-enum, coords-plausibel, description-länge,
robots-allow, source-coverage, enum-fit).
- seepuls-curate-events: zwei Modi (Crawl via admin-API mit
Service-Key, oder Manual aus User-Input). DB-as-SOT (nicht YAML),
weil Events kurzlebig sind.
Importer:
- apps/api/src/jobs/import-venues.ts: liest curated/venues/*.yml,
upsert in seepuls.venues, idempotent (zweiter Run = 0 inserted,
N updated). Zod-Validation der YAML-Form.
- pnpm db:import-venues script.
- Pattern an zitare-Importer orientiert.
SOT-Migration der 5 bestehenden Venues:
- apps/api/src/data/curated/venues/{museum-rosenegg,
seemuseum-kreuzlingen, bodensee-planetarium-kreuzlingen,
rosgartenmuseum, archaeologisches-landesmuseum-konstanz}.yml
- README.md mit Format-Doku.
Doku:
- seepuls/docs/CURATOR.md: Workflow-Übersicht, verweist auf
mana/docs/CONTENT_PIPELINES.md als Cross-Repo-Pattern.
Lokal validiert (gegen Postgres :5441):
- 1. Lauf: 5 inserted ✓
- 2. Lauf: 0 inserted, 5 updated ✓ (idempotent)
- type-check + 43/43 Tests grün.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5.2 KiB
| name | description | argument-hint | allowed-tools | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| seepuls-validate-venue | Validiert ein seepuls-curate-venue-Draft (slug-unique, country-enum, region-enum, coords-plausibel, description-länge, robots-allow, source-coverage) bevor er ins curated/venues/ landet. Read-only, produziert validate/report.md. | <venue-slug> |
|
/seepuls-validate-venue — QA für ein Draft-Venue
Vorletzte Stufe der /seepuls-curate-venue-Pipeline. Liest
~/Documents/seepuls-drafts/<slug>/design/venue.yml und produziert
<slug>/validate/report.md mit allen Findings.
Schwester-Skill von /cards-validate-deck (Cardecky-Spaced-Repetition)
und der inline-Validate-Stage in /zitare-curate-quote.
Wann benutzen
- Direkt nach Stage 3 von
/seepuls-curate-venue(venue.yml steht). - Vor dem Publish (Stage 5). Bei rotem Report: nicht eigenmächtig fixen, sondern dem User die Findings melden + welche Stufe nochmal.
Inputs
- Draft-Slug (Pflicht) — z.B.
museum-rosenegg.
Checks
1. slug-unique
Slug ist neu im Korpus:
ls seepuls/apps/api/src/data/curated/venues/<slug>.yml 2>/dev/null
docker exec seepuls-postgres psql -U seepuls -d seepuls -tAc \
"SELECT slug FROM seepuls.venues WHERE slug='<slug>'"
Treffer → rot. Vorschlag: <slug>-<stadt> oder <slug>-2.
Exception: Wenn der User explizit ergänzen will (--slug <existing> in curate-venue), nur Warning, kein rot.
2. country-enum
countryCode ∈ {DE, CH, AT, LI}. Treffer-Miss → rot.
3. region-enum
regionSlug ∈ {bodensee, hegau, thurgau-west, oberschwaben} ODER
fehlt komplett (null). Andere Werte → rot (Region erst seed-en).
4. coords-plausibel
coordinates.lat ∈ [47.4, 47.9] UND coordinates.lng ∈ [8.5, 9.7]
(Bodensee-Bounding-Box). Außerhalb → rot, vermutlich falscher
Geocode oder falsche Region.
Plus: Cross-Check countryCode:
- DE-Venue mit lat < 47.55 (Schweizer Seeufer) → Warning.
- CH-Venue mit lat > 47.66 (deutsches Ufer) → Warning.
5. description-länge
description.length ∈ [10, 220]:
- < 10 → rot, zu kurz für sinnvolle Anzeige.
-
220 → rot, AGGREGATOR_POLICY §2 verlangt ≤ 200 (mit kleinem Puffer auf 220 für UTF-8-Zähl-Diff).
6. robots-allow
source.url-Domain erlaubt das Crawlen seepuls-Bot:
curl -sf -H "User-Agent: seepuls/0.0.1 (+https://seepuls.mana.how; kontakt@mana.how)" \
--max-time 5 "https://<domain>/robots.txt" | head -40
Manuelles Checken: gibt es Disallow: / für * oder für seepuls?
→ rot, Eintrag nicht anlegen. Domain in blocked_domains tragen.
Wenn robots.txt 404 oder timeout: warnung (default-allow), aber
flaggen.
7. source-coverage
source.refs[] enthält ≥ 1 Eintrag, jeder ≥ 3 Zeichen. Leerer Array
→ rot, Curator hat keine Quelle dokumentiert.
source.url und source.domain müssen übereinstimmen:
echo '<source.url>' | sed -E 's|^https?://([^/]+).*|\1|' | tr 'A-Z' 'a-z'
Sollte gleich source.domain sein (modulo www-Stripping). Sonst rot.
8. enum-fit (crawlStatus)
crawlStatus ∈ {manual-entry, pending-review, claimed}. Andere Werte
→ rot, sonst greift der Importer (oder spätere Constraints).
Output
~/Documents/seepuls-drafts/<slug>/validate/report.md:
# Validate-Report — <slug>
Draft-Path: <…>
Geprüft: 2026-MM-DD HH:MM
## Findings
✓ slug-unique: neu (kein YAML, keine DB-Row)
✓ country-enum: CH
✓ region-enum: bodensee
✓ coords-plausibel: 47.6442, 9.1720 — im Bodensee-BBOX
✓ description-länge: 137 Zeichen
✓ robots-allow: robots.txt erlaubt /
✓ source-coverage: 2 refs
✓ enum-fit: crawlStatus=manual-entry ok
## Empfehlung
Grün — bereit für Publish (Stage 5).
Bei roten Findings:
✘ coords-plausibel: 47.123, 9.456 — Lat zu südlich, außerhalb Bodensee-BBOX
✘ description-länge: 245 Zeichen (Limit 220)
## Empfehlung
Stop vor Publish. Nochmal Stufe 3:
- Adresse re-geocoden — vielleicht falsche Stadt erwischt.
- Description am Satz-Ende kürzen, nicht umformulieren.
Was dieser Skill NICHT macht
- Eigenmächtig fixen. Findings → User entscheidet.
- YAML schreiben oder editieren. Read-only.
- DB-State ändern. Read-only.
- Robots-Tiefen-Parse. Heuristik:
Disallow: /für*oderseepuls. Komplexe Path-Patterns → User soll manuell checken.
Offene Punkte
- Robots-Lookup könnte in
src/crawl/robots.tsder API wiederverwendet werden. Aktuell ist der Validator-Skill bash-only — der echte Parser (mit Cache + Path-Match) lebt in Bun-Code. Bei systematischen False-Positives: Validator alsbun scriptmit Import ausapps/api/src/crawl/robots.tsumbauen. - Geographische Region-Inference. Bei klar falschem Region-Slug
(z.B. „Friedrichshafen" mit
regionSlug: thurgau-west) wäre eine Vorschlag-Heuristik nett. Heute: Warning, kein Auto-Fix.
Referenzen
- Schwester-Skill:
/seepuls-curate-venue - Cross-Repo-Pattern:
/cards-validate-deck - Aggregator-Policy:
../mana/docs/AGGREGATOR_POLICY.md