Skip to content

feat(skills): SkillRegistry, ClaudeAdapter, GeminiAdapter, skills:sync & skills:list commands#113

Open
bedus-creation wants to merge 10 commits into
mainfrom
feat/skills-core
Open

feat(skills): SkillRegistry, ClaudeAdapter, GeminiAdapter, skills:sync & skills:list commands#113
bedus-creation wants to merge 10 commits into
mainfrom
feat/skills-core

Conversation

@bedus-creation

Copy link
Copy Markdown
Contributor

Summary

Implements tasks #139, #140, and #141 — the core fastapi-startkit[skills] feature.

Task #139 — Skill Registry + Provider Discovery

  • skills/registry.py: SkillRegistry scans only providers currently registered in the Application container. Each provider declares skills via a skills/ directory co-located with its provider.py. SKILL.md files are read and YAML front-matter (name, description) is parsed into a Skill dataclass.
  • skills/provider.py: SkillsServiceProvider registers the registry under 'skills.registry' in the container and boots the CLI commands.

Task #140 — Adapter Layer (ClaudeAdapter + GeminiAdapter)

  • skills/adapters/base.py: BaseAdapter ABC with render() and prune() — extensible for future Codex adapter.
  • skills/adapters/claude.py: ClaudeAdapter writes idempotent .claude/skills/<name>/SKILL.md files with YAML front-matter. --prune removes directories for skills no longer in the registry.
  • skills/adapters/gemini.py: GeminiAdapter splices a <!-- skills:start --> … <!-- skills:end --> block into GEMINI.mdnever touches content outside the markers. Idempotent.

Task #141 — Cleo Commands

  • skills:sync — resolves SkillRegistry, selects adapter(s) via --target (claude|gemini|all, default all), calls render(), optionally calls prune() when --prune is passed.
  • skills:list — shows provider key, skill name, Claude/Gemini sync status (synced / pending), and truncated description for every discovered skill.

Test Plan

  • 34 new pytest tests in tests/skills/ — all passing
    • test_registry.py — front-matter parsing, discovery, caching, get(), reset()
    • test_adapters.py — idempotency, prune, marker preservation (Gemini), append when no markers
    • test_commands.py — target routing, prune flag, unknown target error, status display
  • Full test suite: 1296 passed, 0 failures (no regressions)

🤖 Generated with Claude Code

Implements tasks #139, #140, #141 — the fastapi-startkit[skills] core:

**Task #139 — SkillRegistry + provider discovery**
- `skills/registry.py`: SkillRegistry scans only providers currently
  registered in the Application container, discovering skills/ dirs
  co-located with each provider.py. SKILL.md YAML front-matter (name,
  description) is parsed into Skill dataclass objects.
- `skills/provider.py`: SkillsServiceProvider registers the registry
  under 'skills.registry' in the container and boots the CLI commands.

**Task #140 — Adapter layer (ClaudeAdapter + GeminiAdapter)**
- `skills/adapters/base.py`: BaseAdapter ABC with render() and prune().
- `skills/adapters/claude.py`: ClaudeAdapter writes idempotent
  .claude/skills/<name>/SKILL.md files with YAML front-matter.
  --prune removes directories for skills no longer in the registry.
- `skills/adapters/gemini.py`: GeminiAdapter splices a
  <!-- skills:start/end --> block into GEMINI.md without touching any
  user-authored content outside the markers. Idempotent.

**Task #141 — Cleo commands**
- `skills/commands/sync.py`: `skills:sync` — resolves SkillRegistry,
  selects adapter(s) via --target (claude|gemini|all, default all),
  calls render(), optionally prune().
- `skills/commands/list.py`: `skills:list` — shows provider, name,
  sync status (claude/gemini), and description for every discovered
  skill.

**Tests**: 34 new pytest tests covering registry discovery, front-matter
parsing, both adapters (idempotency, prune, marker preservation), and
both commands (target routing, prune flag, status display).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@bedus-creation

Copy link
Copy Markdown
Contributor Author

Code Review — Conditional Approval (pending fix in PR #114)

PR #113 is well-structured and consistent with the existing provider/container patterns. All 34 tests pass. The architecture is correct and extensible.

✅ What looks good

  • Architecture: SkillsServiceProvider follows the two-phase register/boot pattern correctly. Container key "skills.registry" is clear.
  • SkillRegistry: Lazy discovery with caching, reset() for tests, get(name) convenience method — all good.
  • ClaudeAdapter: Idempotent writes (only writes when content changes), correct prune logic (removes dirs not in the skill set), YAML front-matter format looks correct.
  • GeminiAdapter: Marker-based approach preserves everything outside the managed block. Handles the case where markers don't exist yet by appending. Idempotent.
  • BaseAdapter ABC: Clean extension point for future Codex or other adapters.
  • YAML fallback: The _parse_frontmatter fallback parser when PyYAML is absent is minimal but sufficient for simple key: value front-matter. Good defensive coding.
  • Test coverage: 34 tests covering registry parsing, discovery, caching, adapter idempotency, prune, and command routing.

🟡 One concern (minor, does not block this PR)

_skills_dir_for looks for skills/ next to the provider's module file. This is a correct and explicit contract. However, PR #114 places the canonical SKILL.md files in the wrong directories (next to the package root, not the provider module). This is a bug in #114, not here — but it's worth adding a note in the docstring to make the expectation unmistakable:

@staticmethod
def _skills_dir_for(provider) -> Path | None:
    """Return the ``skills/`` directory co-located with *provider*'s module file.

    For a provider defined in ``mypackage/providers/my_provider.py``, this
    returns ``mypackage/providers/skills/``.  Skills placed anywhere else
    (e.g. at the package root) will NOT be discovered.
    """

Verdict

Approve this PR on its own merits once PR #114 fixes its file placement bug. The contract is correct; the canonical SKILL.md files just need to move to the right location.

bedus-creation and others added 3 commits June 12, 2026 15:52
…e.html

Replace per-provider skills/ directory scanning with a single central
.ai/deployments/core.html file as the source of truth for all skills.

Architecture change:
- SkillRegistry now reads from {base_path}/.ai/deployments/core.html
  instead of scanning each registered provider for a skills/ dir
- core.html uses <section data-skill="..." data-description="...">
  blocks — plain HTML, Jinja2-compatible for future template rendering
- _CoreHtmlParser (html.parser, zero extra deps) extracts section
  blocks, preserving body content verbatim
- SkillsServiceProvider.boot() publishes stubs/core.html to
  .ai/deployments/core.html via `artisan provider:publish --provider=skills`
- Adapters (ClaudeAdapter, GeminiAdapter) unchanged — they still deploy
  Skill objects to .claude/skills/<name>/SKILL.md and GEMINI.md

Added: skills/stubs/core.html with three example skills (fastapi-routing,
orm-queries, console-commands) ready to publish to any project.

Tests: 43 passing (9 new registry tests added for HTML parser + path
behaviour; command tests updated to write core.html instead of mocking
provider module structure).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Redesigns the skills module to match the Laravel Boost convention and adds
a full per-topic rules system.

**Skills — .ai/fastapi-startkit/skill/{name}/SKILL.md**
- SkillRegistry now scans .ai/fastapi-startkit/skill/*/SKILL.md (individual
  folders per skill, YAML frontmatter: name, description, license, metadata)
- Falls back to directory name when frontmatter has no 'name' key
- Published stub: .ai/fastapi-startkit/skill/fastapi-best-practices/SKILL.md
  with a 19-section Quick Reference pointing to rules/ files

**Rules — rules/{name}.md**
- RulesRegistry scans rules/*.md (plain Markdown, optional frontmatter)
- Rule name = file stem (http-client.md → "http-client")
- ClaudeRulesAdapter → .claude/rules/<name>.md (idempotent, prune)
- GeminiRulesAdapter → GEMINI.md <!-- rules:start/end --> block
- rules:sync command (--target claude|gemini|all, --prune)
- rules:list command (shows name, Claude/Gemini status, path)
- Published stub: rules/http-client.md with Python/httpx best practices:
    explicit timeout + connect_timeout, exponential backoff, raise_for_status(),
    asyncio.gather() for concurrent requests, respx fakes in tests

**SkillsServiceProvider** now registers both skills.registry and rules.registry
and boots all four commands (skills:sync, skills:list, rules:sync, rules:list).

Tests: 48 passing (14 new for rules registry, commands, and adapters).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove duplicate `ClaudeAdapter`/`GeminiAdapter` and
`ClaudeRulesAdapter`/`GeminiRulesAdapter` imports from `handle()` in
both sync commands — these are only needed in `_resolve_adapters()`.

Note: `_skills_dir_for()` referenced in the reviewer comment no longer
exists — it was removed as part of the redesign from per-provider
skills/ directories to the centralised .ai/fastapi-startkit/skill/*/SKILL.md
layout. The SkillRegistry._load() static method now handles file
discovery directly.

ruff check src/ → All checks passed.
pytest tests/skills/ → 48 passed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@bedus-creation

Copy link
Copy Markdown
Contributor Author

Re-review — APPROVED ✅

Redesign confirmed correct. All 48 tests pass (34 skills + 14 new rules tests). F401 lint fix applied. Summary:

  • SkillRegistry now reads from .ai/fastapi-startkit/skill/*/SKILL.md — clean centralized layout, no _skills_dir_for() hack needed
  • RulesRegistry reads from rules/*.md — good separation
  • SkillsServiceProvider.publishes() deploys stubs via artisan provider:publish --provider=skills
  • provider:publish command confirmed to exist (console/publish_command.py)
  • Rules commands (rules:sync, rules:list) are correctly implemented
  • One real starter stub (fastapi-best-practices/SKILL.md) bundled in stubs/

Minor (non-blocking): _parse_frontmatter is exported from __init__.py despite its underscore naming convention suggesting it's internal. Not a blocker but may want to remove from __all__ to avoid users accidentally depending on it.

This PR is approved. Merge once PR #114 is fixed and rebased against the new layout.

@bedus-creation

Copy link
Copy Markdown
Contributor Author

Architecture Note — Rules layout should nest inside skills (pre-merge change)

Updating my approval to flag one design issue before merge: the rules/ layout diverges from the Laravel Boost convention and should be corrected now, not as a follow-up.

Current vs. correct

Current PR #113:

.ai/fastapi-startkit/skill/fastapi-best-practices/SKILL.md   ← correct
rules/http-client.md                                          ← wrong location
rules/orm-queries.md                                          ← wrong location

Should match Laravel Boost:

.ai/fastapi-startkit/skill/fastapi-best-practices/
    SKILL.md
    rules/
        http-client.md
        orm-queries.md
        ...

Why this matters before merge

  1. fastapi-best-practices/SKILL.md already references rules/http-client.md etc — they're one logical unit, they should be one physical unit
  2. Prune/lifecycle: removing a skill naturally removes its rules; with rules/ at project root, rules become orphans
  3. Single publish unit — artisan provider:publish --provider=skills delivers skill + rules together
  4. Consistency with Laravel Boost — users coming from the Laravel ecosystem will look in the right place

Required changes to RulesRegistry

# Change scan path from:
RULES_BASE_PATH = Path("rules")                          # project root

# To:
SKILLS_BASE_PATH = Path(".ai") / "fastapi-startkit" / "skill"
# scan .ai/fastapi-startkit/skill/*/rules/*.md

Stubs to move

# From:
stubs/rules/http-client.md
# To:
stubs/.ai/fastapi-startkit/skill/fastapi-best-practices/rules/http-client.md

Adapters: no change needed

ClaudeRulesAdapter still writes to .claude/rules/<name>.md — flat output is correct regardless of source location. Same for Gemini.


Withdrawing approval until this is fixed. The code quality is good; this is purely a layout correction that should land in the same PR rather than a follow-up.

bedus-creation and others added 2 commits June 12, 2026 16:25
…oost convention)

Rules now live co-located with the SKILL.md they document, matching the
Laravel Boost layout exactly:

    .ai/fastapi-startkit/skill/<skill-name>/
        SKILL.md
        rules/
            <rule-name>.md

Changes:
- RulesRegistry scans `.ai/fastapi-startkit/skill/*/rules/*.md` (was `rules/*.md`)
- Rule dataclass gains `skill_name` field (owning skill dir name)
- RulesRegistry.for_skill(name) helper for scoped lookups
- ClaudeRulesAdapter output: .claude/skills/<skill>/rules/<rule>.md
  (mirrors source nesting; pending final confirmation from reviewer 361)
- GeminiRulesAdapter groups rules under their skill heading in the
  <!-- rules:start/end --> block
- Prune lifecycle: ClaudeAdapter.prune() removes the whole skill dir
  (shutil.rmtree) which automatically takes its nested rules/ with it;
  ClaudeRulesAdapter.prune() handles stale individual rule files within
  still-present skills
- Stubs: stubs/rules/http-client.md moved to
  stubs/.ai/fastapi-startkit/skill/fastapi-best-practices/rules/http-client.md
- SkillsServiceProvider.publishes() updated to new nested paths
- tests/skills/test_commands.py updated throughout; added
  test_prune_removes_skill_dir_removes_its_rules_too

ruff check src/ → All checks passed.
pytest tests/skills/ → 49 passed, 0 failures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop `import pytest` from test_adapters.py, `from pathlib import Path`
and the inline `Skill` import from test_commands.py, and `import textwrap`
plus the unused `Skill` from test_registry.py's multi-import.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@bedus-creation

Copy link
Copy Markdown
Contributor Author

Code Review — PR #113 CHANGES REQUIRED

Pytest passes (34 new tests, no regressions) — the implementation is solid. However Ruff blocks with 5 F401 unused import errors in test files:

  1. tests/skills/test_adapters.py:7import pytest imported but unused — remove
  2. tests/skills/test_commands.py:5from pathlib import Path imported but unused — remove
  3. tests/skills/test_commands.py:185 — local from fastapi_startkit.skills.registry import Skill imported but unused — remove or use
  4. tests/skills/test_registry.py:5import textwrap imported but unused — remove
  5. tests/skills/test_registry.py:13Skill imported but unused — remove or use

All are auto-fixable: run uv run ruff check --fix tests/skills/ and re-push.

Verdict: BLOCKED — fix unused imports, then ready to approve

ClaudeRulesAdapter: output path corrected to .claude/rules/<skill>/<rule>.md
so rules are actually read by Claude Code (the old .claude/skills/<skill>/rules/
path was silently ignored). prune() now independently scans .claude/rules/
instead of .claude/skills/.

RulesListCommand: synced-status check updated to .claude/rules/ path.

Tests: all path assertions updated; test_prune_removes_skill_dir_removes_its_rules_too
replaced with test_prune_removes_stale_rule_skill_subdir which asserts that
ClaudeRulesAdapter.prune() correctly targets .claude/rules/.

Stubs: SKILL.md and http-client.md rewritten as terse actionable checklists
(no filler commentary, minimal code examples only where they disambiguate).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
bedus-creation added a commit that referenced this pull request Jun 13, 2026
…ions only

- Delete fastapi/, inertia-controllers/, laravel-best-practices/, orm-models/
  (all replaced by the single verbatim fastapi-startkit skill on PR #113)
- Revert fastapi_startkit/pyproject.toml to main (removes [skills] extra
  added in an earlier commit — not part of this PR's scope)
- Strip filler from broadcasting/SKILL.md: remove "When to use" section,
  intro paragraph, and docstring comment in channel callback example
- Remove placeholder `# ...do work` comment from console-commands/SKILL.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
bedus-creation and others added 3 commits June 12, 2026 18:25
…actices + core.html

Replace the placeholder fastapi-best-practices stub with the user-authored
fastapi-startkit SKILL.md covering routing, controller shape, resource()
shortcut, ORM model + controller usage, Pydantic request classes,
JsonApiResource responses, and the action pattern.

Delete skills/stubs/core.html (zero code references, leftover junk).

Update SkillsServiceProvider.publishes() to reference the new skill only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop the extra skill/ level from the discovery path.
Skills now live at .ai/fastapi-startkit/<skill-name>/SKILL.md
(was .ai/fastapi-startkit/skill/<skill-name>/SKILL.md).

- SKILLS_BASE_PATH updated in registry.py and rules/registry.py
- Stub file moved from stubs/.ai/.../skill/fastapi-startkit/ to
  stubs/.ai/.../fastapi-startkit/ accordingly
- SkillsServiceProvider.publishes() destination updated
- Test path assertion and docstrings updated

ruff + pytest: 49/49 green

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant