llmframe 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- llmframe-0.1.0/.agents/skills/add-hexagonal-feature/SKILL.md +118 -0
- llmframe-0.1.0/.agents/skills/bootstrap-python-app/SKILL.md +165 -0
- llmframe-0.1.0/.agents/skills/format-python-code/SKILL.md +29 -0
- llmframe-0.1.0/.agents/skills/lint-python-code/SKILL.md +39 -0
- llmframe-0.1.0/.agents/skills/python-add-adapter/SKILL.md +85 -0
- llmframe-0.1.0/.agents/skills/python-add-port/SKILL.md +161 -0
- llmframe-0.1.0/.agents/skills/run-local-quality-gate/SKILL.md +35 -0
- llmframe-0.1.0/.agents/skills/run-python-tests/SKILL.md +40 -0
- llmframe-0.1.0/.agents/skills/write-adr/SKILL.md +116 -0
- llmframe-0.1.0/.clinerules/00-readme.md +165 -0
- llmframe-0.1.0/.clinerules/01-cline-operating-guidance.md +27 -0
- llmframe-0.1.0/.clinerules/02-core-standards.md +54 -0
- llmframe-0.1.0/.clinerules/03-architecture-guardrails.md +90 -0
- llmframe-0.1.0/.clinerules/04-testing-standards.md +48 -0
- llmframe-0.1.0/.clinerules/05-docs-and-adr.md +25 -0
- llmframe-0.1.0/.clinerules/06-module-structure.md +93 -0
- llmframe-0.1.0/.clinerules/07-performance-and-observability.md +33 -0
- llmframe-0.1.0/.clinerules/08-repo-navigation.md +104 -0
- llmframe-0.1.0/.clinerules/09-pr-and-commit-hygiene.md +27 -0
- llmframe-0.1.0/.clinerules/10-tooling-and-ci.md +49 -0
- llmframe-0.1.0/.clinerules/11-documentation-standards.md +47 -0
- llmframe-0.1.0/.clinerules/12-logging-conventions.md +44 -0
- llmframe-0.1.0/.clinerules/13-command-execution-safety.md +47 -0
- llmframe-0.1.0/.clinerules/workflows/improve.md +68 -0
- llmframe-0.1.0/.clinerules/workflows/update-repo-navigation.md +58 -0
- llmframe-0.1.0/.github/workflows/ci_cd.yaml +91 -0
- llmframe-0.1.0/.gitignore +207 -0
- llmframe-0.1.0/.pre-commit-config.yaml +32 -0
- llmframe-0.1.0/CHANGELOG.md +7 -0
- llmframe-0.1.0/LICENSE +21 -0
- llmframe-0.1.0/PKG-INFO +108 -0
- llmframe-0.1.0/README.md +91 -0
- llmframe-0.1.0/pyproject.toml +151 -0
- llmframe-0.1.0/scripts/sync_clinerules.sh +12 -0
- llmframe-0.1.0/src/llmframe/__init__.py +5 -0
- llmframe-0.1.0/src/llmframe/adapters/__init__.py +1 -0
- llmframe-0.1.0/src/llmframe/adapters/input/__init__.py +1 -0
- llmframe-0.1.0/src/llmframe/adapters/output/__init__.py +5 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/__init__.py +55 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/llm_adapter/__init__.py +18 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/llm_adapter/adapter.py +301 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/llm_adapter/dto.py +26 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/llm_adapter/exceptions.py +25 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/llm_adapter/logging_utils.py +27 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/llm_adapter/protocols.py +14 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/llm_adapter/response_parser.py +38 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/__init__.py +47 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/client.py +41 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/dto.py +32 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/parsing/__init__.py +6 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/parsing/message_content.py +80 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/parsing/usage.py +56 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/transport/__init__.py +35 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/transport/adapter.py +477 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/transport/payload_builders.py +175 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/transport/protocols.py +117 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/usage_tracker/__init__.py +6 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/usage_tracker/adapter.py +305 -0
- llmframe-0.1.0/src/llmframe/adapters/output/llm/usage_tracker/dto.py +36 -0
- llmframe-0.1.0/src/llmframe/adapters/output/persistence/__init__.py +5 -0
- llmframe-0.1.0/src/llmframe/adapters/output/persistence/protocols.py +21 -0
- llmframe-0.1.0/src/llmframe/application/__init__.py +1 -0
- llmframe-0.1.0/src/llmframe/application/ports/__init__.py +1 -0
- llmframe-0.1.0/src/llmframe/domain/__init__.py +1 -0
- llmframe-0.1.0/src/llmframe/json_types.py +9 -0
- llmframe-0.1.0/tests/__init__.py +1 -0
- llmframe-0.1.0/tests/integration/__init__.py +1 -0
- llmframe-0.1.0/tests/unit/__init__.py +1 -0
- llmframe-0.1.0/tests/unit/adapters/output/llm/__init__.py +0 -0
- llmframe-0.1.0/tests/unit/adapters/output/llm/llm_adapter/__init__.py +1 -0
- llmframe-0.1.0/tests/unit/adapters/output/llm/llm_adapter/test_adapter.py +529 -0
- llmframe-0.1.0/tests/unit/adapters/output/llm/openai_adapter/__init__.py +0 -0
- llmframe-0.1.0/tests/unit/adapters/output/llm/openai_adapter/parsing/__init__.py +1 -0
- llmframe-0.1.0/tests/unit/adapters/output/llm/openai_adapter/parsing/test_message_content.py +125 -0
- llmframe-0.1.0/tests/unit/adapters/output/llm/openai_adapter/parsing/test_usage.py +73 -0
- llmframe-0.1.0/tests/unit/adapters/output/llm/openai_adapter/test_client.py +133 -0
- llmframe-0.1.0/tests/unit/adapters/output/llm/openai_adapter/transport/__init__.py +1 -0
- llmframe-0.1.0/tests/unit/adapters/output/llm/openai_adapter/transport/test_adapter.py +542 -0
- llmframe-0.1.0/tests/unit/adapters/output/llm/usage_tracker/__init__.py +1 -0
- llmframe-0.1.0/tests/unit/adapters/output/llm/usage_tracker/test_adapter.py +121 -0
- llmframe-0.1.0/tests/unit/test_scaffold.py +8 -0
- llmframe-0.1.0/uv.lock +1240 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-hexagonal-feature
|
|
3
|
+
description: Implement a new feature or use case in a Python hexagonal project, including domain modeling, ports, application service, and tests.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: Add a Hexagonal Feature
|
|
7
|
+
|
|
8
|
+
Use this skill to implement a new feature, use case, or business capability in
|
|
9
|
+
a Python hexagonal project.
|
|
10
|
+
|
|
11
|
+
This skill focuses on the application and domain changes needed to add a use
|
|
12
|
+
case cleanly. It owns feature-level orchestration across domain,
|
|
13
|
+
application, and tests. When the change requires a new port or adapter, use
|
|
14
|
+
the specialized skill for that procedure instead of duplicating it here.
|
|
15
|
+
|
|
16
|
+
## Prerequisites
|
|
17
|
+
|
|
18
|
+
- The project already has the standard hexagonal `src/` layout.
|
|
19
|
+
- The feature is clear enough that you understand its inputs, outputs, and core
|
|
20
|
+
business rules.
|
|
21
|
+
|
|
22
|
+
## Steps
|
|
23
|
+
|
|
24
|
+
### 1. Name the use case
|
|
25
|
+
|
|
26
|
+
Choose a clear verb-noun name for the use case, for example `PlaceOrder`,
|
|
27
|
+
`RegisterUser`, or `SendNotification`. Use that name consistently for the
|
|
28
|
+
related files and classes.
|
|
29
|
+
|
|
30
|
+
### 2. Model the domain if needed
|
|
31
|
+
|
|
32
|
+
Create or update files under `src/<app_name>/domain/`:
|
|
33
|
+
|
|
34
|
+
- **Entity** — an object with identity that changes over time.
|
|
35
|
+
- **Value object** — an immutable descriptor (e.g. `EmailAddress`, `Money`).
|
|
36
|
+
- **Domain event** — something that happened (e.g. `OrderPlaced`).
|
|
37
|
+
|
|
38
|
+
Rules:
|
|
39
|
+
|
|
40
|
+
- Domain objects must be pure Python with no framework imports or I/O.
|
|
41
|
+
- Use `@dataclass(frozen=True)` for value objects.
|
|
42
|
+
- Raise domain-specific exceptions, not HTTP or database errors.
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
# src/<app_name>/domain/<entity>.py
|
|
46
|
+
from dataclasses import dataclass
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class <Entity>:
|
|
50
|
+
id: str
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 3. Define or confirm the required ports
|
|
54
|
+
|
|
55
|
+
Identify the application boundaries the feature needs:
|
|
56
|
+
|
|
57
|
+
- an input port when an external caller invokes a new use case
|
|
58
|
+
- one or more output ports when the application needs infrastructure
|
|
59
|
+
dependencies such as repositories, publishers, or gateways
|
|
60
|
+
|
|
61
|
+
If a required port does not exist yet, use `python-add-port` for the detailed
|
|
62
|
+
procedure. In this skill, keep the focus on deciding which boundaries the
|
|
63
|
+
feature needs and making sure the application service depends only on those
|
|
64
|
+
port contracts.
|
|
65
|
+
|
|
66
|
+
### 4. Implement the application service
|
|
67
|
+
|
|
68
|
+
Create the use case implementation under `src/<app_name>/application/`:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
class <UseCaseName>:
|
|
72
|
+
def __init__(self, repository: <EntityRepositoryPort>) -> None:
|
|
73
|
+
self._repository = repository
|
|
74
|
+
|
|
75
|
+
def execute(self, command: <Command>) -> <Result>:
|
|
76
|
+
...
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Rules:
|
|
80
|
+
|
|
81
|
+
- The application service depends only on domain objects and port interfaces.
|
|
82
|
+
- It must not import from `adapters/`.
|
|
83
|
+
- It must not perform I/O directly, including `open()`, HTTP calls, or database
|
|
84
|
+
access.
|
|
85
|
+
- If the feature needs a new adapter implementation for an existing or new
|
|
86
|
+
port, use `python-add-adapter` for that procedure.
|
|
87
|
+
|
|
88
|
+
### 5. Write unit tests
|
|
89
|
+
|
|
90
|
+
Create tests under `tests/unit/`:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from unittest.mock import MagicMock
|
|
94
|
+
|
|
95
|
+
def test_<use_case_name>_happy_path() -> None:
|
|
96
|
+
repo = MagicMock()
|
|
97
|
+
use_case = <UseCaseName>(repository=repo)
|
|
98
|
+
use_case.execute(<Command>(...))
|
|
99
|
+
repo.save.assert_called_once()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
TDD is encouraged when it fits the change. Writing tests before the
|
|
103
|
+
implementation is fine and often preferable.
|
|
104
|
+
|
|
105
|
+
- Use `MagicMock` or a hand-written fake for outbound ports, never real
|
|
106
|
+
infrastructure.
|
|
107
|
+
- Cover the happy path and at least one failure or edge case.
|
|
108
|
+
|
|
109
|
+
## Dependency direction reminder
|
|
110
|
+
|
|
111
|
+
The canonical dependency rules are in `03-architecture-guardrails.md`. This diagram is a quick reference only.
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
adapters/input → application → domain
|
|
115
|
+
adapters/output → (implements application/ports)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Never let an arrow point in the opposite direction.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bootstrap-python-app
|
|
3
|
+
description: Initialize a new Python project with a hexagonal architecture layout, core tooling, and quality checks.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Bootstrap a Python Hexagonal Application
|
|
7
|
+
|
|
8
|
+
Use this skill to initialize a new Python project with a hexagonal
|
|
9
|
+
(ports-and-adapters) architecture.
|
|
10
|
+
|
|
11
|
+
## Prerequisites
|
|
12
|
+
|
|
13
|
+
- `uv` is installed (`brew install uv` / `pip install uv`).
|
|
14
|
+
- `<app_name>` — the project and package name.
|
|
15
|
+
- `<python_version>` — for example `3.13`.
|
|
16
|
+
|
|
17
|
+
## Steps
|
|
18
|
+
|
|
19
|
+
### 1. Initialize the project with uv
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
uv init <app_name> --python <python_version>
|
|
23
|
+
cd <app_name>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Run the remaining steps from the project root.
|
|
27
|
+
|
|
28
|
+
### 2. Create the hexagonal `src/` layout
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
src/
|
|
32
|
+
└── <app_name>/
|
|
33
|
+
├── __init__.py
|
|
34
|
+
├── domain/ # Pure domain: entities, value objects, domain events
|
|
35
|
+
│ └── __init__.py
|
|
36
|
+
├── application/ # Use cases, application services, port interfaces
|
|
37
|
+
│ ├── __init__.py
|
|
38
|
+
│ └── ports/
|
|
39
|
+
│ └── __init__.py
|
|
40
|
+
└── adapters/ # Input & output adapter implementations
|
|
41
|
+
├── __init__.py
|
|
42
|
+
├── input/
|
|
43
|
+
│ └── __init__.py
|
|
44
|
+
└── output/
|
|
45
|
+
└── __init__.py
|
|
46
|
+
tests/
|
|
47
|
+
├── __init__.py
|
|
48
|
+
├── unit/
|
|
49
|
+
│ └── __init__.py
|
|
50
|
+
└── integration/
|
|
51
|
+
└── __init__.py
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Create every directory and `__init__.py` file listed above.
|
|
55
|
+
|
|
56
|
+
### 3. Configure pyproject.toml
|
|
57
|
+
|
|
58
|
+
Update the `pyproject.toml` generated by `uv init` to follow this structure.
|
|
59
|
+
Keep the values `uv init` already set for `name`, `version`, and
|
|
60
|
+
`requires-python` unless the user asked for something else.
|
|
61
|
+
|
|
62
|
+
```toml
|
|
63
|
+
[build-system]
|
|
64
|
+
requires = ["hatchling"]
|
|
65
|
+
build-backend = "hatchling.build"
|
|
66
|
+
|
|
67
|
+
[project]
|
|
68
|
+
name = "<app_name>"
|
|
69
|
+
version = "0.1.0"
|
|
70
|
+
requires-python = ">= <python_version>"
|
|
71
|
+
dependencies = []
|
|
72
|
+
|
|
73
|
+
[project.optional-dependencies]
|
|
74
|
+
dev = [
|
|
75
|
+
"pytest>=8",
|
|
76
|
+
"pytest-cov>=6",
|
|
77
|
+
"mypy>=1.10",
|
|
78
|
+
"ruff>=0.4",
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
[tool.hatch.build.targets.wheel]
|
|
82
|
+
packages = ["src/<app_name>"]
|
|
83
|
+
|
|
84
|
+
[tool.ruff]
|
|
85
|
+
line-length = 120
|
|
86
|
+
target-version = "py<python_version_nodot>" # e.g. py313 (version without the dot)
|
|
87
|
+
|
|
88
|
+
[tool.ruff.lint]
|
|
89
|
+
select = ["ALL"]
|
|
90
|
+
ignore = [
|
|
91
|
+
"COM812", # trailing comma — conflicts with ruff formatter
|
|
92
|
+
"ISC001", # implicit string concat — conflicts with ruff formatter
|
|
93
|
+
"D1", # missing docstrings — too noisy for early development
|
|
94
|
+
"ANN", # type annotations — redundant with mypy strict
|
|
95
|
+
"TD", # TODO comment format
|
|
96
|
+
"FIX", # fixme comments
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
[tool.mypy]
|
|
100
|
+
python_version = "<python_version>"
|
|
101
|
+
strict = true
|
|
102
|
+
files = ["src"]
|
|
103
|
+
|
|
104
|
+
[tool.pytest.ini_options]
|
|
105
|
+
testpaths = ["tests"]
|
|
106
|
+
addopts = "--cov=src --cov-report=term-missing"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 4. Install dev dependencies
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
uv sync --all-extras
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 5. Set up pre-commit (optional but recommended)
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
uv add --dev pre-commit
|
|
119
|
+
uv run pre-commit install
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Add a minimal `.pre-commit-config.yaml`:
|
|
123
|
+
|
|
124
|
+
```yaml
|
|
125
|
+
repos:
|
|
126
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
127
|
+
rev: <pinned_version>
|
|
128
|
+
hooks:
|
|
129
|
+
- id: ruff
|
|
130
|
+
args: ["--fix"]
|
|
131
|
+
- id: ruff-format
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Replace `<pinned_version>` with an appropriate pinned release.
|
|
135
|
+
|
|
136
|
+
### 6. Verify the setup
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
uv run ruff check .
|
|
140
|
+
uv run mypy .
|
|
141
|
+
uv run pytest
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
All three commands must exit with code 0.
|
|
145
|
+
|
|
146
|
+
### 7. Write a minimal README
|
|
147
|
+
|
|
148
|
+
Write a `README.md` that includes:
|
|
149
|
+
|
|
150
|
+
- What the application does.
|
|
151
|
+
- How to install dependencies (`uv sync --all-extras`).
|
|
152
|
+
- How to run quality checks (`ruff`, `mypy`, `pytest`).
|
|
153
|
+
- A high-level architecture overview (domain / application / adapters).
|
|
154
|
+
|
|
155
|
+
## Hexagonal architecture conventions
|
|
156
|
+
|
|
157
|
+
| Layer | Directory | Rule |
|
|
158
|
+
| ----------------- | --------------------------------- | ------------------------------------------------------------------------------------ |
|
|
159
|
+
| Domain | `src/<app_name>/domain/` | No imports from `application` or `adapters`. Pure Python only. |
|
|
160
|
+
| Application | `src/<app_name>/application/` | Depends only on `domain`. Defines port interfaces as ABCs or Protocols. |
|
|
161
|
+
| Adapters (input) | `src/<app_name>/adapters/input/` | Calls into `application`. Uses domain types only through application ports and DTOs. |
|
|
162
|
+
| Adapters (output) | `src/<app_name>/adapters/output/` | Implements port interfaces from `application`. |
|
|
163
|
+
|
|
164
|
+
If appropriate for the project, enforce these rules with an import linter such
|
|
165
|
+
as `import-linter`, or document them in a root-level `ARCHITECTURE.md`.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: format-python-code
|
|
3
|
+
description: Formats Python code using ruff and applies safe auto-fixes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: Format Python Code
|
|
7
|
+
|
|
8
|
+
Use this skill to format Python code and apply safe auto-fixes using `ruff`.
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
|
|
12
|
+
- `uv` is installed and configured for the project.
|
|
13
|
+
- `ruff` is installed as a development dependency and configured in `pyproject.toml`.
|
|
14
|
+
|
|
15
|
+
## Steps
|
|
16
|
+
|
|
17
|
+
### 1. Apply auto-fixes and format
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv run ruff check . --fix
|
|
21
|
+
uv run ruff format .
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
These commands will automatically reformat Python code and apply any safe linting auto-fixes.
|
|
25
|
+
|
|
26
|
+
## When formatting fails
|
|
27
|
+
|
|
28
|
+
- If `ruff format .` produces unexpected changes, verify that the project's `pyproject.toml` ruff configuration is correct before overriding the formatter.
|
|
29
|
+
- Unfixable lint violations reported by `ruff check . --fix` will be caught and addressed during the `lint-python-code` step.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lint-python-code
|
|
3
|
+
description: Lints Python code using ruff and mypy for type checking.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: Lint Python Code
|
|
7
|
+
|
|
8
|
+
Use this skill to lint Python code using `ruff` and perform type checking with `mypy`.
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
|
|
12
|
+
- `uv` is installed and configured for the project.
|
|
13
|
+
- `ruff` and `mypy` are installed as development dependencies and configured in `pyproject.toml`.
|
|
14
|
+
|
|
15
|
+
## Steps
|
|
16
|
+
|
|
17
|
+
### 1. Run ruff linting
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv run ruff check .
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This command checks for linting errors without applying auto-fixes.
|
|
24
|
+
|
|
25
|
+
### 2. Run mypy type checking
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
uv run mypy .
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This command performs static type checking on the Python codebase.
|
|
32
|
+
|
|
33
|
+
## When linting or type checking fails
|
|
34
|
+
|
|
35
|
+
- Read the `ruff check` output carefully. Each violation includes a rule code, file path, and line number. Fix the underlying code rather than adding `# noqa` comments unless the suppression is explicitly justified.
|
|
36
|
+
- Do not disable lint rules to silence violations. Prefer refactoring the code to satisfy the rule.
|
|
37
|
+
- Read `mypy` errors and fix the type annotations or logic that caused them. Use `# type: ignore[<code>]` only at narrowly scoped boundaries to genuinely untyped third-party code, and document the reason.
|
|
38
|
+
- If a `mypy` error reflects a genuine design issue (wrong return type, missing protocol method), fix the design rather than suppressing the error.
|
|
39
|
+
- After fixes, re-run both commands to confirm the codebase is clean before proceeding.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: python-add-adapter
|
|
3
|
+
description: Add an input or output adapter to a Python hexagonal project while keeping business logic in the application layer.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Add an Adapter
|
|
7
|
+
|
|
8
|
+
Add an input or output adapter to a Python hexagonal project while keeping business logic in the application layer.
|
|
9
|
+
|
|
10
|
+
This skill owns adapter implementation. If the required application boundary
|
|
11
|
+
does not exist yet, define the port first with `python-add-port`.
|
|
12
|
+
|
|
13
|
+
## Prerequisites
|
|
14
|
+
|
|
15
|
+
- The relevant port interface exists in `src/<app_name>/application/ports/`.
|
|
16
|
+
- The adapter technology has been chosen and any required library is installed (for example with `uv add <library>`).
|
|
17
|
+
|
|
18
|
+
If the port does not exist yet, use `python-add-port` before implementing the
|
|
19
|
+
adapter.
|
|
20
|
+
|
|
21
|
+
## Input adapter
|
|
22
|
+
|
|
23
|
+
An input adapter receives external input and calls the application through an input port or service entry point.
|
|
24
|
+
|
|
25
|
+
### 1. Create the module
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
src/<app_name>/adapters/input/<adapter_name>/
|
|
29
|
+
__init__.py
|
|
30
|
+
adapter.py
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Keep `__init__.py` lightweight. Re-export the public symbol only when you want a stable package-level API, and declare `__all__` when it adds clarity:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from .adapter import router
|
|
37
|
+
|
|
38
|
+
__all__ = ["router"]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. Implement
|
|
42
|
+
|
|
43
|
+
- Accept external input and map it to an application command or query DTO.
|
|
44
|
+
- Call the application through its input port or service entry point.
|
|
45
|
+
- Map the result or exception back to the external format.
|
|
46
|
+
- Map domain exceptions to adapter-level error responses.
|
|
47
|
+
- Do not import from `domain/` directly.
|
|
48
|
+
- Keep all business logic in the application service.
|
|
49
|
+
|
|
50
|
+
### 3. Test
|
|
51
|
+
|
|
52
|
+
Place tests under `tests/integration/<adapter_name>/`. Test through the framework test client or transport boundary, injecting a fake or stubbed application service to keep tests focused on adapter behavior.
|
|
53
|
+
|
|
54
|
+
Add unit tests only if the adapter contains meaningful mapping or serialization helpers that warrant direct, framework-free verification.
|
|
55
|
+
|
|
56
|
+
## Output adapter
|
|
57
|
+
|
|
58
|
+
An output adapter implements a port interface and talks to external infrastructure.
|
|
59
|
+
|
|
60
|
+
### 1. Create the module
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
src/<app_name>/adapters/output/<adapter_name>/
|
|
64
|
+
__init__.py
|
|
65
|
+
adapter.py
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Keep `__init__.py` lightweight. Re-export the public symbol only when you want a stable package-level API, and declare `__all__` when it adds clarity:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from .adapter import <AdapterName>
|
|
72
|
+
|
|
73
|
+
__all__ = ["<AdapterName>"]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 2. Implement
|
|
77
|
+
|
|
78
|
+
- Implement all port interface methods.
|
|
79
|
+
- Map infrastructure types to domain objects inside the adapter. Do not expose infrastructure types beyond the adapter boundary.
|
|
80
|
+
- Raise domain exceptions, not infrastructure exceptions.
|
|
81
|
+
- Keep framework clients, ORM models, serializers, and transport-specific configuration inside the adapter package.
|
|
82
|
+
|
|
83
|
+
### 3. Test
|
|
84
|
+
|
|
85
|
+
Write unit tests using fakes, stubs, or mocks around the infrastructure boundary. Follow with integration tests against the real infrastructure when adapter behavior depends on actual driver, network, or persistence integration.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: python-add-port
|
|
3
|
+
description: Add a technology-agnostic application port interface to a Python hexagonal project for a new use case or dependency.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Add a Port
|
|
7
|
+
|
|
8
|
+
Use this skill to add a port interface to a Python hexagonal project.
|
|
9
|
+
|
|
10
|
+
A port defines an application-layer boundary. It describes either how the
|
|
11
|
+
outside world uses the application or what the application needs from external
|
|
12
|
+
systems. A port is not an adapter and must remain technology-agnostic.
|
|
13
|
+
|
|
14
|
+
This skill owns the detailed procedure for defining new application ports.
|
|
15
|
+
Keep feature orchestration and adapter implementation in related skills.
|
|
16
|
+
|
|
17
|
+
## When to use this skill
|
|
18
|
+
|
|
19
|
+
Use this skill when you need to:
|
|
20
|
+
|
|
21
|
+
- expose a new use case through an input port
|
|
22
|
+
- define a new dependency the application needs through an output port
|
|
23
|
+
- extract an application-facing interface so adapters depend on an application
|
|
24
|
+
contract rather than concrete implementation details
|
|
25
|
+
|
|
26
|
+
## Prerequisites
|
|
27
|
+
|
|
28
|
+
- The project follows a hexagonal structure under `src/<app_name>/`.
|
|
29
|
+
- You understand the use case or dependency the port represents.
|
|
30
|
+
- Any command, query, or result DTOs used by the port already exist or are part
|
|
31
|
+
of the same change.
|
|
32
|
+
|
|
33
|
+
## Port types
|
|
34
|
+
|
|
35
|
+
### Input port
|
|
36
|
+
|
|
37
|
+
An input port defines how the outside world invokes an application use case.
|
|
38
|
+
It is called by an input adapter and implemented by an application service.
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
|
|
42
|
+
- `CreateOrderPort`
|
|
43
|
+
- `RegisterUserPort`
|
|
44
|
+
- `GenerateReportPort`
|
|
45
|
+
|
|
46
|
+
### Output port
|
|
47
|
+
|
|
48
|
+
An output port defines a dependency the application needs from external
|
|
49
|
+
infrastructure. It is declared by the application and implemented by an output
|
|
50
|
+
adapter.
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
|
|
54
|
+
- `OrderRepositoryPort`
|
|
55
|
+
- `EmailSenderPort`
|
|
56
|
+
- `PaymentGatewayPort`
|
|
57
|
+
|
|
58
|
+
## Steps
|
|
59
|
+
|
|
60
|
+
### 1. Choose the file and name
|
|
61
|
+
|
|
62
|
+
Create the interface under:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
src/<app_name>/application/ports/
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Use a focused file name that matches the responsibility, for example:
|
|
69
|
+
|
|
70
|
+
- `<use_case_name>_port.py`
|
|
71
|
+
- `<dependency_name>_port.py`
|
|
72
|
+
|
|
73
|
+
Name both the file and the class by business capability or dependency, not by
|
|
74
|
+
technology.
|
|
75
|
+
|
|
76
|
+
Good: `CreateInvoicePort`, `CustomerRepositoryPort`
|
|
77
|
+
|
|
78
|
+
Avoid: `FastAPIPort`, `PostgresPort`, `S3AdapterPort`
|
|
79
|
+
|
|
80
|
+
### 2. Define the interface
|
|
81
|
+
|
|
82
|
+
Use `Protocol` by default for lightweight structural typing. Use `ABC` only
|
|
83
|
+
when you need shared abstract behavior or stricter inheritance semantics.
|
|
84
|
+
|
|
85
|
+
Example:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from typing import Protocol
|
|
89
|
+
|
|
90
|
+
from <app_name>.application.commands.create_invoice import CreateInvoiceCommand
|
|
91
|
+
from <app_name>.application.results.create_invoice import CreateInvoiceResult
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class CreateInvoicePort(Protocol):
|
|
95
|
+
def execute(self, command: CreateInvoiceCommand) -> CreateInvoiceResult:
|
|
96
|
+
...
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
For output ports, follow the same pattern using domain objects or application
|
|
100
|
+
DTOs in the signature.
|
|
101
|
+
|
|
102
|
+
### 3. Keep the port clean
|
|
103
|
+
|
|
104
|
+
- Keep ports in the application layer.
|
|
105
|
+
- Do not import from `adapters/` or infrastructure libraries.
|
|
106
|
+
- Do not embed framework request or response types in port method signatures.
|
|
107
|
+
- Prefer domain objects and application DTOs in method signatures.
|
|
108
|
+
- Keep each port narrowly focused on one use case or one dependency role.
|
|
109
|
+
- Name methods by business intent, not transport or storage mechanics.
|
|
110
|
+
|
|
111
|
+
For input ports, a single `execute(...)` method is often enough.
|
|
112
|
+
|
|
113
|
+
For output ports, define only the operations the application needs. Do not
|
|
114
|
+
mirror a full ORM, SDK, or driver API.
|
|
115
|
+
|
|
116
|
+
### 4. Wire dependencies in the right direction
|
|
117
|
+
|
|
118
|
+
The arrows below show call/invocation flow (who calls whom), not import dependency direction. For dependency direction rules, see `03-architecture-guardrails.md`.
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
input adapters -> input ports -> application service
|
|
122
|
+
application service -> output ports -> output adapters
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
In practice:
|
|
126
|
+
|
|
127
|
+
- input adapters depend on input port contracts
|
|
128
|
+
- application services implement input ports
|
|
129
|
+
- application services depend on output port contracts
|
|
130
|
+
- output adapters implement output ports
|
|
131
|
+
|
|
132
|
+
Never let a port import an adapter or mention a specific framework.
|
|
133
|
+
|
|
134
|
+
### 5. Test appropriately
|
|
135
|
+
|
|
136
|
+
Ports are interfaces, so they usually need little or no direct testing.
|
|
137
|
+
|
|
138
|
+
Test surrounding behavior instead:
|
|
139
|
+
|
|
140
|
+
- unit test that the application service honors the input port contract
|
|
141
|
+
- unit test that application services call output ports as expected
|
|
142
|
+
- test adapter implementations separately in adapter-focused tests
|
|
143
|
+
|
|
144
|
+
If the project uses runtime-checkable protocols or shared contract fixtures, add
|
|
145
|
+
small targeted tests only when they provide clear value.
|
|
146
|
+
|
|
147
|
+
### 6. Review related changes
|
|
148
|
+
|
|
149
|
+
When adding a new port, check whether the same change needs:
|
|
150
|
+
|
|
151
|
+
- a new application service implementing the input port
|
|
152
|
+
- a new output adapter implementing the output port
|
|
153
|
+
- new command, query, or result DTOs
|
|
154
|
+
- dependency injection or composition-root wiring updates
|
|
155
|
+
|
|
156
|
+
If a required adapter does not exist yet, use `python-add-adapter` for that
|
|
157
|
+
follow-up work instead of embedding adapter logic into the port.
|
|
158
|
+
|
|
159
|
+
If the overall change is a complete end-to-end use case spanning domain,
|
|
160
|
+
application service, and tests, use `add-hexagonal-feature` as the primary
|
|
161
|
+
feature workflow and use this skill only for the port-definition part.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: run-local-quality-gate
|
|
3
|
+
description: Orchestrates the execution of Python code formatting, linting, type checking, and testing.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: Run Local Quality Gate
|
|
7
|
+
|
|
8
|
+
Use this skill to run the full local quality gate for a Python project, including formatting, linting, type checking, and automated tests.
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
|
|
12
|
+
- `uv` is installed and configured for the project.
|
|
13
|
+
- `ruff`, `mypy`, and `pytest` are installed as development dependencies and configured in `pyproject.toml`.
|
|
14
|
+
- The `format-python-code`, `lint-python-code`, and `run-python-tests` skills are available.
|
|
15
|
+
|
|
16
|
+
## Steps
|
|
17
|
+
|
|
18
|
+
### 1. Auto-fix and format code
|
|
19
|
+
|
|
20
|
+
Use the `format-python-code` skill to apply auto-fixes and format the codebase.
|
|
21
|
+
|
|
22
|
+
### 2. Lint and type check code
|
|
23
|
+
|
|
24
|
+
Use the `lint-python-code` skill to perform linting and type checking.
|
|
25
|
+
|
|
26
|
+
### 3. Run automated tests
|
|
27
|
+
|
|
28
|
+
Use the `run-python-tests` skill to execute all automated tests.
|
|
29
|
+
|
|
30
|
+
## When a step fails
|
|
31
|
+
|
|
32
|
+
- If any step fails, stop and fix the underlying issue before proceeding to the next step.
|
|
33
|
+
- Do not bypass `pyproject.toml`-backed tool configuration with ad hoc flags in the final validation run.
|
|
34
|
+
- Run the full quality gate before handoff, even if only a single file changed.
|
|
35
|
+
- Pre-commit hooks provide helpful fast feedback, but they do not replace the full local quality gate.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: run-python-tests
|
|
3
|
+
description: Runs automated tests for a Python project using pytest.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: Run Python Tests
|
|
7
|
+
|
|
8
|
+
Use this skill to run automated tests for a Python project using `pytest`.
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
|
|
12
|
+
- `uv` is installed and configured for the project.
|
|
13
|
+
- `pytest` is installed as a development dependency and configured in `pyproject.toml`.
|
|
14
|
+
|
|
15
|
+
## Steps
|
|
16
|
+
|
|
17
|
+
### 1. Run all tests
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv run pytest
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This command executes all tests defined in the project.
|
|
24
|
+
|
|
25
|
+
### 2. Run focused tests (optional)
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
uv run pytest tests/<path_to_test_file>
|
|
29
|
+
uv run pytest -k "<pattern>"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Use these commands to run a subset of tests during development.
|
|
33
|
+
|
|
34
|
+
## When tests fail
|
|
35
|
+
|
|
36
|
+
- Read the test output to identify the failing test and the assertion or exception that caused the failure.
|
|
37
|
+
- Fix the root cause in the source code or test rather than skipping or deleting the test.
|
|
38
|
+
- Use `uv run pytest -x` to stop at the first failure during iterative debugging.
|
|
39
|
+
- For flaky or slow tests, document the reason and mitigation in the handoff notes rather than silently ignoring them.
|
|
40
|
+
- After fixes, re-run the full test suite with `uv run pytest` before handoff.
|