Init repository

This commit is contained in:
2026-08-08 11:24:44 +03:00
commit 36764b8ab0
10 changed files with 488 additions and 0 deletions

60
.gitignore vendored Normal file
View File

@@ -0,0 +1,60 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Virtual environments
.venv/
venv/
ENV/
env/
# Environment files (do not commit secrets)
.env
.env.local
.env.*.local
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Pytest
.pytest_cache/
.coverage
htmlcov/
# Mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Ruff
.ruff_cache/
# IDEs
.idea/
.vscode/
*.swp
*.swo
*~
# OS files
.DS_Store
Thumbs.db

26
README.md Normal file
View File

@@ -0,0 +1,26 @@
# AI Skills
This repository contains skills and a superpower used by AI agents when working on this codebase.
All documents are written in English. They describe technology choices, project structure, architecture, and coding conventions.
## Index
| File | Type | Description |
|------|------|-------------|
| [`skills/python-development/SKILL.md`](skills/python-development/SKILL.md) | Superpower | High-level philosophy, when to apply, and skill routing map |
| [`skills/python-development-stack/SKILL.md`](skills/python-development-stack/SKILL.md) | Skill | Technology stack: Python, FastAPI, SQLModel, facet, typer, loguru, uv |
| [`skills/python-development-layout/SKILL.md`](skills/python-development-layout/SKILL.md) | Skill | File and folder organization, service and adapter package layout |
| [`skills/python-development-architecture/SKILL.md`](skills/python-development-architecture/SKILL.md) | Skill | Ports and Adapters, Monolith-over-Microservices, facet hierarchy |
| [`skills/python-development-code-style/SKILL.md`](skills/python-development-code-style/SKILL.md) | Skill | Imports, `__init__.py`, wildcard bans, block grouping |
| [`skills/python-development-settings/SKILL.md`](skills/python-development-settings/SKILL.md) | Skill | Pydantic Settings, BaseModel, YAML/JSON loading, validators |
| [`skills/python-development-cli/SKILL.md`](skills/python-development-cli/SKILL.md) | Skill | Typer composition, callback boot, deferred imports, nesting |
## Usage
When an agent starts working on a task, it should load the superpower first.
The superpower contains a decision tree that points to the relevant granular skills.
## Customizing
Edit the files in `skills/` to match your actual preferences, frameworks, and team conventions.

View File

@@ -0,0 +1,13 @@
---
name: python-development-architecture
description: Architectural patterns, service boundaries, API design, resilience, and observability. Use when designing systems, reviewing integration patterns, or investigating production issues.
---
## Rules
- Follow Ports and Adapters (Hexagonal Architecture). Define abstract interfaces (ports) at the package level in `interfaces.py` or `base.py`. Place concrete implementations (adapters) in dedicated sub-packages named after the infrastructure type, e.g. `database/`, `filesystem/`, `memory/`.
- When a port has multiple adapter implementations, provide a factory module (conventionally `fabric.py` in the port's package) that selects and instantiates the concrete implementation based on a type discriminator in settings. The rest of the codebase imports only the port; it must never import a concrete adapter directly.
- Follow the Monolith-over-Microservices approach. Each top-level package inside the main project folder is a bounded context and a candidate for future extraction into a standalone service.
- A package becomes an independent runnable service when it contains a `service.py` module with an async service class. The class participates in the hierarchical service tree managed by the service orchestration library.
- The root service of the application aggregates child services through a declarative `dependencies` property. The orchestration library handles the full async lifecycle of the tree: concurrent start, run, and graceful shutdown in reverse dependency order.
- Define a single central composition root (e.g. a resolver or registry class). It reads configuration, lazily instantiates adapters and services, caches resolved instances, and injects them into consumers. Domain code must never construct its own dependencies.
- Package business logic into capability modules that expose operations to higher layers. A capability module declares what it needs (required capabilities) and what it can do (provided capabilities) through class-level declarations. The execution context and accessors are injected by the calling layer at runtime based on these declarations.

View File

@@ -0,0 +1,100 @@
---
name: python-development-cli
description: CLI design patterns using typer. Use when building, nesting, or extending command-line interfaces.
---
## Rules
- Every package that exposes CLI commands contains a `cli.py` module with a `get_cli() -> typer.Typer` factory function. Do not create the Typer instance at import time.
- The root package CLI (`cli.py` in the root package) is the composition point. It calls `cli.add_typer(get_subpackage_cli(), name="subpackage")` to mount sub-package CLI trees as subcommands.
- The root CLI defines a single `callback` function that runs before any command. It is responsible for shared bootstrap: load settings, configure logging, and store the settings object in `ctx.obj["settings"]` so every sub-command can access it.
Example root CLI `cli.py`:
```python
import pathlib
import typer
from loguru import logger
from .service import ApplicationService
from .settings import RootSettings
def callback(
ctx: typer.Context,
env_path: pathlib.Path | None = typer.Option(
None, "--env", "-e",
help="Environment variables file location",
),
config_path: pathlib.Path | None = typer.Option(
None, "--config", "-c",
help="Config file location",
),
):
ctx.obj = {}
settings = RootSettings.load(
env_prefix="APP__",
env_file=env_path,
config_file=config_path,
)
ctx.obj["settings"] = settings
logger.configure(handlers=[{"sink": sys.stderr, "level": settings.logging.level}])
def run(ctx: typer.Context):
settings: RootSettings = ctx.obj["settings"]
service = ApplicationService(settings=settings)
asyncio.run(service.run())
def get_cli() -> typer.Typer:
from .agents import get_cli as get_agents_cli
from .cron import get_cli as get_cron_cli
cli = typer.Typer()
cli.callback()(callback)
cli.command(name="run")(run)
cli.add_typer(get_agents_cli(), name="agents")
cli.add_typer(get_cron_cli(), name="cron")
return cli
```
- Sub-package CLI commands receive `ctx: typer.Context` as the first parameter and read settings via `ctx.obj["settings"]`.
Example sub-package `cli.py`:
```python
import asyncio
import typer
def run(
ctx: typer.Context,
name: str | None = typer.Argument(default=None),
debug: bool = typer.Option(False, "-d", "--debug"),
):
# Deferred imports — heavy project modules are imported inside the command body
from mypackage.resolver import DependencyResolver
from mypackage.settings import RootSettings
from mypackage.channels.tui import TUIChannel, TUIChannelSettings
settings: RootSettings = ctx.obj["settings"]
resolver = DependencyResolver(settings=settings)
channel = TUIChannel(settings=TUIChannelSettings(debug=debug), ...)
asyncio.run(channel.run())
def get_cli() -> typer.Typer:
cli = typer.Typer()
cli.command(name="run")(run)
return cli
```
- Each CLI command is a plain Python function decorated with `cli.command(name="...")`. Keep command functions small: parse arguments, create a resolver or service from settings, and run async code with `asyncio.run()`. No business logic inside the command function.
- The root package `__main__.py` calls `get_cli()` and runs it. Example:
```python
from .cli import get_cli
if __name__ == "__main__":
cli = get_cli()
cli()
```

View File

@@ -0,0 +1,56 @@
---
name: python-development-code-style
description: Code conventions, typing, testing habits, and engineering principles. Use when writing, reviewing, or refactoring Python code.
---
## Rules
- Follow PEP 8 for all Python code. Use `ruff` for automatic formatting and linting.
- Every package `__init__.py` must declare the public API explicitly with `__all__`. `__all__` is always a tuple, even when it contains a single element. Use inline comment group headers to separate imports by source module.
- Inside `__init__.py` import only from sibling modules. Do not place executable code.
- Group related names together in `__all__` and mirror the grouping with comment headers. Example:
```python
from .base import BaseService, ServiceMixin
from .dto import ServiceInput, ServiceOutput
from .exceptions import ServiceError, ValidationError
from .fabric import get_service
from .settings import ServiceSettings
__all__ = (
# base
"BaseService",
"ServiceMixin",
# dto
"ServiceInput",
"ServiceOutput",
# exceptions
"ServiceError",
"ValidationError",
# fabric
"get_service",
# settings
"ServiceSettings",
)
```
- Wildcard imports (`from module import *`) are forbidden in every module, including `__init__.py`. Always import explicit names.
- Within a package, import sibling modules using relative imports (`from .module import Name`). When importing from a different top-level package or from a parent package that is not a direct sibling, use an absolute import (`from project_name.module import Name`).
- Group imports in every module into three blocks separated by a blank line:
1. Standard library (`import pathlib`, `from typing import Self`, etc.).
2. Third-party packages (`import yaml`, `from pydantic import Field`, etc.).
3. Project-local imports (`from .services import MyService`, `from microclaw.settings import Settings`, etc.).
- Within each block, place `from ... import ...` lines first, then `import ...` lines. Sort all lines alphabetically. Example:
```python
import json
import pathlib
from typing import Self
import yaml
from pydantic import Field
from pydantic_settings import BaseSettings
from .services import MyService
from microclaw.settings import Settings
```
- Functions and methods must not return tuples of multiple values. Returning a tuple is allowed only in exceptional cases (e.g. unpacking a well-known pair such as `(key, value)`). When a function needs to return more than one piece of data, define a Pydantic `BaseModel` for the result or reuse an existing model. This makes the return type self-documenting, enables IDE autocomplete, and keeps field names stable during refactoring.
- Do not use `from __future__ import annotations`. Use explicit forward-reference strings (e.g. `"MyClass"`) when necessary.
- Do not use `if TYPE_CHECKING:` blocks. Import the types you need at the top level. Deferred imports inside `TYPE_CHECKING` hide dependencies and break runtime introspection.
- Do not abbreviate variable or parameter names. Write them out in full so the intent is obvious without context. Examples of forbidden abbreviations: `idx` (use `index`), `dep` (use `dependency`), `cfg` (use `config` or `configuration`), `msg` (use `message`), `err` (use `error`), `resp` (use `response`). The same rule applies to class attributes, function parameters, and local variables.

View File

@@ -0,0 +1,114 @@
---
name: python-development-fastapi
description: Patterns for building a FastAPI HTTP service inside the facet service tree. Use when creating REST endpoints, routers, or the api/rest service package.
---
## Rules
- The FastAPI service lives in a sub-package named `api/rest/` inside the root package. It is a regular facet service: it has `service.py`, `settings.py`, and `__init__.py`.
- Provide a custom `UvicornServer` that disables signal handlers so the parent facet orchestrator controls startup and shutdown. Example:
```python
import uvicorn
class UvicornServer(uvicorn.Server):
def install_signal_handlers(self):
pass
```
- The service class inherits from `facet.AsyncioServiceMixin`. Its constructor accepts the settings instance declared in the same package's `settings.py`. Keep the constructor minimal; heavy initialization happens in `start()`.
- The `start()` method builds the FastAPI application asynchronously, configures uvicorn, and adds the server coroutine to the facet task list with `self.add_task(server.serve())`.
- Extract app construction into an `async` `_build_app()` method so router registration and dependency wiring can use `await`.
- Register routers in `_setup_app()` or `_build_app()` using `app.include_router(...)`. Group related endpoints under routers with a `prefix`.
- The settings class inherits from `pydantic.BaseModel` (not `BaseSettings`). It declares the minimum required for the HTTP layer: `host`, `port`, and references to other subsystem settings when the REST layer wires them directly.
### Minimal complete example
`api/rest/settings.py`:
```python
from pydantic import BaseModel, conint
class RESTAPISettings(BaseModel):
host: str = "127.0.0.1"
port: conint(ge=1, le=65535) = 8000
```
`api/rest/service.py`:
```python
import facet
import fastapi
import uvicorn
from . import handlers, users
from .settings import RESTAPISettings
class UvicornServer(uvicorn.Server):
def install_signal_handlers(self):
pass
class RESTAPIService(facet.AsyncioServiceMixin):
def __init__(self, settings: RESTAPISettings):
self._settings = settings
async def start(self):
app = await self._build_app()
config = uvicorn.Config(
app=app,
host=self._settings.host,
port=self._settings.port,
)
server = UvicornServer(config)
self.add_task(server.serve())
async def _build_app(self) -> fastapi.FastAPI:
app = fastapi.FastAPI()
await self._setup_app(app)
return app
async def _setup_app(self, app: fastapi.FastAPI):
app.get("/health")(handlers.health)
app.include_router(users.get_users, prefix="/users")
```
### Router and handler structure
- Endpoints must be RESTful. The URL path structure maps directly to the package structure inside `api/rest/`.
- Each URL segment becomes a sub-package. For example `/users/{id}` lives in `api/rest/users/`.
- Every handler sub-package contains at minimum:
- `router.py` — defines `get_router() -> fastapi.APIRouter` and wires each route to a handler function.
- `handlers.py` — plain async functions that implement the endpoint logic.
- `schemas.py` — Pydantic request and response models shared by the handlers in this package.
- `__init__.py` — exports only `get_router`.
- Optional but common additional files:
- `dependencies.py` — FastAPI dependency functions reused across handlers in the package.
- `exceptions.py` — custom HTTP exceptions for this domain.
Example `api/rest/users/router.py`:
```python
import fastapi
from . import handlers
def get_router() -> fastapi.APIRouter:
router = fastapi.APIRouter()
router.add_api_route(path="/", methods=["GET"], endpoint=handlers.list_users)
router.add_api_route(path="/", methods=["POST"], endpoint=handlers.create_user)
router.add_api_route(path="/{id}", methods=["GET"], endpoint=handlers.get_user)
router.add_api_route(path="/{id}", methods=["PUT"], endpoint=handlers.update_user)
router.add_api_route(path="/{id}", methods=["DELETE"], endpoint=handlers.delete_user)
return router
```
Example `api/rest/users/__init__.py`:
```python
from .router import get_router
__all__ = (
# router
"get_router",
)
```
- Do not construct `fastapi.APIRouter` instances at import time outside `get_router()`. The router is created only when `get_router()` is called during `_setup_app()`.
```

View File

@@ -0,0 +1,45 @@
---
name: python-development-layout
description: Folder structure and file organization for Python services. Use when creating a new service, reorganizing modules, or reviewing project structure.
---
## Rules
- Every repository must contain a `README.md`.
- Every repository must contain a `.gitignore` that excludes `.venv/`, `*.pyc`, and `__pycache__/`.
- Every repository must contain a `.dockerignore` with content similar to `.gitignore`.
- Every repository must contain a `Dockerfile` for containerized deployment.
- Every repository must contain a `pyproject.toml` created automatically by `uv init`.
- The Python package source folder lives in the repository root (e.g. `my_package/`), not under a `src/` directory.
- `pyproject.toml` must explicitly name the root package folder so the build system knows where the code lives.
- All tool configuration (tests, linters, formatters, type checkers, coverage, etc.) must live inside `pyproject.toml` under the `[tool.*]` tables. Do not use separate dot-files such as `.pylintrc`, `.flake8`, `setup.cfg`, or `tox.ini` for tool settings.
- The repository root may also contain a `tests/` folder and a `docs/` folder alongside the package folder.
- Inside the root package every sub-package is either a service package, an adapter package, or a library package. The structure of each must follow the conventions below.
- The root package must contain a `__main__.py` module. It is the executable entry point when the package is run with `python -m package_name`. Keep it minimal: import only from sibling modules (relative imports), call the factory that builds the CLI or the root service, and invoke it under `if __name__ == "__main__":`. No business logic lives here. Example:
```python
from .cli import get_cli
if __name__ == "__main__":
cli = get_cli()
cli()
```
### Service package layout
A package that exposes a runnable service must contain at minimum:
- `service.py` — the service class. It inherits from the async service mixin and implements `start`/`stop` lifecycle methods. The constructor accepts exactly one settings instance — the class declared in the same package's `settings.py`. Example:
```python
import facet
from .settings import MyServiceSettings
class MyService(facet.AsyncioServiceMixin):
def __init__(self, settings: MyServiceSettings):
self._settings = settings
async def start(self):
# service startup logic
pass
```
- `settings.py` — a Pydantic Settings class that declares the configuration schema for the service.
- `interfaces.py` or `base.py` — abstract ports (interfaces) that the service depends on. Consumers import from here, never from adapters.
- `fabric.py` — a factory module that instantiates the service and its collaborators. It receives settings and returns fully constructed objects.
- `__init__.py` — public API of the package. Exports only the types and functions intended for external use.

View File

@@ -0,0 +1,38 @@
---
name: python-development-settings
description: Application configuration and settings management. Use when defining, loading, validating, or extending application settings.
---
## Rules
- The root settings class must inherit from `pydantic_settings.BaseSettings`. This enables automatic loading from environment variables and `.env` files.
- Every nested settings class (for individual services, adapters, or components) must inherit from `pydantic.BaseModel`, not from `BaseSettings`. Only the root class needs the env-loading behavior.
- The root settings class exposes one field per top-level service or subsystem. Provide sensible defaults as plain values for immutable types (str, int, bool, None) and use `Field(default_factory=...)` only when the default is a mutable object (dict, list, or model instance). Never use mutable literals as class-attribute defaults in Pydantic v2. Provide defaults so the application starts without a config file when reasonable.
- A settings class can accept either a settings object or a string key referencing another entry in the same config. The resolver (or a `@model_validator`) replaces string references with resolved instances after initial parsing. This is how services declare their dependencies declaratively.
- Use `@model_validator(mode="after")` on the root settings class to validate cross-references between subsystems, resolve string keys to actual instances, and enforce consistency across the configuration graph.
- Support loading from YAML config files with custom tags. Register `!include` for file inclusion and `!env` for environment variable interpolation inside YAML. The YAML loader builder must live as a `@staticmethod` inside the root settings class. Example:
```python
@staticmethod
def get_yaml_loader(base_path: pathlib.Path) -> yaml.BaseLoader:
loader = type("Loader", (yaml.SafeLoader,), {})
loader.add_constructor(
"!include",
yaml_include.Constructor(base_dir=str(base_path)),
)
loader.add_constructor("!env", construct_env_tag)
return loader
```
- Support loading from JSON config files using standard `json.load`. JSON does not support custom tags; use string interpolation or env var resolution handled by `BaseSettings` after load.
- Provide a single `load()` classmethod on the root settings class that reads the config file (YAML or JSON), then passes the parsed dict into the `BaseSettings` constructor along with optional `_env_prefix` and `_env_file`. Example:
```python
@classmethod
def load(cls, config_file: pathlib.Path | None = None) -> Self:
data = {}
if config_file is not None:
if config_file.suffix in (".yaml", ".yml"):
data.update(yaml.load(config_file.read_text(), Loader=get_loader()))
elif config_file.suffix == ".json":
data.update(json.loads(config_file.read_text()))
return cls(**data)
```
- Keep each subsystem's settings in its own `settings.py` module inside the subsystem package. Import and compose them into the root settings class. Do not inline subsystem schemas into the root file.
- Use `pydantic.Field(default_factory=...)` for mutable default values (dicts, lists, models). Never use mutable literals as class-attribute defaults in Pydantic v2.

View File

@@ -0,0 +1,16 @@
---
name: python-development-stack
description: Technology stack decisions for Python services. Use when adding dependencies, choosing infrastructure, bootstrapping projects, or reviewing tool choices.
---
## Rules
- Use `uv` as the package manager for all Python dependency operations.
- Use `FastAPI` for REST API and HTTP services.
- Use `SQLModel` and `SQLAlchemy` for database access. Async drivers: `asyncpg` for PostgreSQL, `aiosqlite` for SQLite.
- Use `alembic` for database migrations.
- Use `facet` for hierarchical async service composition with dependency management and graceful shutdown.
- Use `typer` for CLI interfaces.
- Use `pydantic` for DTOs and data validation; use `pydantic-settings` for application configuration.
- Use `loguru` for logging.
- Use `pyyaml` for YAML parsing; use `pyyaml-include` for `!include` tags and `pyyaml-env-tag` for `!env` interpolation inside YAML config files.

View File

@@ -0,0 +1,20 @@
---
name: python-development
description: Superpower for building robust, maintainable Python backend services. Use whenever you create, modify, review, or debug service code in this repository.
---
## Skill Map
| Situation | Primary Skill |
|---|---|
| Choosing frameworks, libraries, DBs, or infrastructure components | [`python-development-stack`](./python-development-stack/SKILL.md) |
| Bootstrapping a new service or reviewing folder structure | [`python-development-layout`](./python-development-layout/SKILL.md) |
| Designing service boundaries, APIs, data flow, or resilience patterns | [`python-development-architecture`](./python-development-architecture/SKILL.md) |
| Writing or reviewing code, tests, configs, or observability instrumentation | [`python-development-code-style`](./python-development-code-style/SKILL.md) |
| Defining, loading, validating, or extending application settings | [`python-development-settings`](./python-development-settings/SKILL.md) |
| Building, nesting, or extending command-line interfaces | [`python-development-cli`](./python-development-cli/SKILL.md) |
| Creating a FastAPI REST service inside the facet tree | [`python-development-fastapi`](./python-development-fastapi/SKILL.md) |
| Debugging production issues (logs, traces, metrics) | [`python-development-architecture`](../python-development-architecture/SKILL.md) + [`python-development-code-style`](../python-development-code-style/SKILL.md) |
## Rules
- Python interpreter versions are managed with `pyenv`.