Effort: deep
Today scidex/forge/skills_canonical.py only re-scans skills/ at startup or
on a manual python -m scidex.forge.skills_canonical invocation. New
agentskills.io-style bundles dropped in by an external worktree (the
scripts/register_kdense_skills.py flow, plus operator-pulled vendor packs
under .claude/skills/<name>/SKILL.md) require a service bounce before
agents can route to them. Build a watchdog + admin endpoint that detects new
or changed SKILL.md bundles, validates the YAML frontmatter, computes
bundle_sha, and upserts the row — all without restarting scidex-api.
scidex/forge/skill_autoregister.py with:scan(roots: list[Path]) -> list[BundleStatus] andapply(roots: list[Path], dry_run: bool = False) -> dict.
BundleStatus dataclass: slug, path, current_sha, db_sha,state (new | unchanged | drifted | invalid | removed),error (optional).
name field, slug ≠ directory name,metadata.scidex_skill_type not in {tool, persona, bundle},version, body shorter than 200 bytes. Each rejection writesskill_validation_errors(slug, path, error,
detected_at) audit table.
scidex-skill-watchdog.service runs every 60sskills/, personas/,.claude/skills/); falls back to a 5-min timer when inotify isdeploy/bootstrap/systemd/.
POST /api/forge/skills/rescan (admin-only) returns{scanned: int, new: int, drifted: int, invalid: int, removed: int}frozen = TRUE plus a new retired_at column (added in this task'sagent_skill_invocations reference skill_name by string.
tests/test_skill_autoregister.py:new → row inserted.current_sha ≠ db_sha → state=drifted →provenance_commit refreshed.invalid → no DB write, error row written.removed → retired_at set, frozen=TRUE.
/forge/skills/bundles lists all known bundles withRescan button (calls the admin endpoint).
skills/_test_autoreg/,retired_at IS NOT NULL.scidex/forge/skills_canonical.py:list_skill_bundles and_parse_frontmatter — reuse them; the autoregister is a thin wrapperpyinotify dep; degrade toos.scandir polling when inotify is unavailable (e.g., bind-mountedmigrations/20260428_skill_validation.sql addsskill_validation_errors table and skills.retired_at TIMESTAMPTZ.
apply(roots) — single-flighted by a file lock so concurrenttmp_path skills root.scidex/forge/skills_canonical.py — shared parser.migrations/114_skill_registry_canonical.py — base schema.q-skills-versioning-drift — version-drift logic builds on thebundle_sha change-detection this task wires up.
q-skills-quality-leaderboard — needs current registry to look upscidex/forge/skill_autoregister.py with:BundleStatus dataclass (slug, path, current_sha, db_sha, state, error)validate_bundle() enforcing all 5 validation rules + YAML error handlingscan(roots) for change detection across skill bundle rootsapply(roots, dry_run) for DB persistence (upsert + soft-retire)skill_roots() returning [skills/, personas/, .claude/skills/]SkillWatchdog class (inotify + polling fallback)
POST /api/forge/skills/rescan admin endpoint to api_routes/forge.pyGET /forge/skills/bundles HTML page with state badges + Rescan buttonmigrations/20260427_skill_validation_errors.py (skill_validation_errors table + skills.retired_at column)deploy/bootstrap/systemd/scidex-skill-watchdog.{service,path,timer}tests/test_skill_autoregister.py — 14 test cases covering all acceptance criteria