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.
Files changed (82) hide show
  1. llmframe-0.1.0/.agents/skills/add-hexagonal-feature/SKILL.md +118 -0
  2. llmframe-0.1.0/.agents/skills/bootstrap-python-app/SKILL.md +165 -0
  3. llmframe-0.1.0/.agents/skills/format-python-code/SKILL.md +29 -0
  4. llmframe-0.1.0/.agents/skills/lint-python-code/SKILL.md +39 -0
  5. llmframe-0.1.0/.agents/skills/python-add-adapter/SKILL.md +85 -0
  6. llmframe-0.1.0/.agents/skills/python-add-port/SKILL.md +161 -0
  7. llmframe-0.1.0/.agents/skills/run-local-quality-gate/SKILL.md +35 -0
  8. llmframe-0.1.0/.agents/skills/run-python-tests/SKILL.md +40 -0
  9. llmframe-0.1.0/.agents/skills/write-adr/SKILL.md +116 -0
  10. llmframe-0.1.0/.clinerules/00-readme.md +165 -0
  11. llmframe-0.1.0/.clinerules/01-cline-operating-guidance.md +27 -0
  12. llmframe-0.1.0/.clinerules/02-core-standards.md +54 -0
  13. llmframe-0.1.0/.clinerules/03-architecture-guardrails.md +90 -0
  14. llmframe-0.1.0/.clinerules/04-testing-standards.md +48 -0
  15. llmframe-0.1.0/.clinerules/05-docs-and-adr.md +25 -0
  16. llmframe-0.1.0/.clinerules/06-module-structure.md +93 -0
  17. llmframe-0.1.0/.clinerules/07-performance-and-observability.md +33 -0
  18. llmframe-0.1.0/.clinerules/08-repo-navigation.md +104 -0
  19. llmframe-0.1.0/.clinerules/09-pr-and-commit-hygiene.md +27 -0
  20. llmframe-0.1.0/.clinerules/10-tooling-and-ci.md +49 -0
  21. llmframe-0.1.0/.clinerules/11-documentation-standards.md +47 -0
  22. llmframe-0.1.0/.clinerules/12-logging-conventions.md +44 -0
  23. llmframe-0.1.0/.clinerules/13-command-execution-safety.md +47 -0
  24. llmframe-0.1.0/.clinerules/workflows/improve.md +68 -0
  25. llmframe-0.1.0/.clinerules/workflows/update-repo-navigation.md +58 -0
  26. llmframe-0.1.0/.github/workflows/ci_cd.yaml +91 -0
  27. llmframe-0.1.0/.gitignore +207 -0
  28. llmframe-0.1.0/.pre-commit-config.yaml +32 -0
  29. llmframe-0.1.0/CHANGELOG.md +7 -0
  30. llmframe-0.1.0/LICENSE +21 -0
  31. llmframe-0.1.0/PKG-INFO +108 -0
  32. llmframe-0.1.0/README.md +91 -0
  33. llmframe-0.1.0/pyproject.toml +151 -0
  34. llmframe-0.1.0/scripts/sync_clinerules.sh +12 -0
  35. llmframe-0.1.0/src/llmframe/__init__.py +5 -0
  36. llmframe-0.1.0/src/llmframe/adapters/__init__.py +1 -0
  37. llmframe-0.1.0/src/llmframe/adapters/input/__init__.py +1 -0
  38. llmframe-0.1.0/src/llmframe/adapters/output/__init__.py +5 -0
  39. llmframe-0.1.0/src/llmframe/adapters/output/llm/__init__.py +55 -0
  40. llmframe-0.1.0/src/llmframe/adapters/output/llm/llm_adapter/__init__.py +18 -0
  41. llmframe-0.1.0/src/llmframe/adapters/output/llm/llm_adapter/adapter.py +301 -0
  42. llmframe-0.1.0/src/llmframe/adapters/output/llm/llm_adapter/dto.py +26 -0
  43. llmframe-0.1.0/src/llmframe/adapters/output/llm/llm_adapter/exceptions.py +25 -0
  44. llmframe-0.1.0/src/llmframe/adapters/output/llm/llm_adapter/logging_utils.py +27 -0
  45. llmframe-0.1.0/src/llmframe/adapters/output/llm/llm_adapter/protocols.py +14 -0
  46. llmframe-0.1.0/src/llmframe/adapters/output/llm/llm_adapter/response_parser.py +38 -0
  47. llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/__init__.py +47 -0
  48. llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/client.py +41 -0
  49. llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/dto.py +32 -0
  50. llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/parsing/__init__.py +6 -0
  51. llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/parsing/message_content.py +80 -0
  52. llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/parsing/usage.py +56 -0
  53. llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/transport/__init__.py +35 -0
  54. llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/transport/adapter.py +477 -0
  55. llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/transport/payload_builders.py +175 -0
  56. llmframe-0.1.0/src/llmframe/adapters/output/llm/openai_adapter/transport/protocols.py +117 -0
  57. llmframe-0.1.0/src/llmframe/adapters/output/llm/usage_tracker/__init__.py +6 -0
  58. llmframe-0.1.0/src/llmframe/adapters/output/llm/usage_tracker/adapter.py +305 -0
  59. llmframe-0.1.0/src/llmframe/adapters/output/llm/usage_tracker/dto.py +36 -0
  60. llmframe-0.1.0/src/llmframe/adapters/output/persistence/__init__.py +5 -0
  61. llmframe-0.1.0/src/llmframe/adapters/output/persistence/protocols.py +21 -0
  62. llmframe-0.1.0/src/llmframe/application/__init__.py +1 -0
  63. llmframe-0.1.0/src/llmframe/application/ports/__init__.py +1 -0
  64. llmframe-0.1.0/src/llmframe/domain/__init__.py +1 -0
  65. llmframe-0.1.0/src/llmframe/json_types.py +9 -0
  66. llmframe-0.1.0/tests/__init__.py +1 -0
  67. llmframe-0.1.0/tests/integration/__init__.py +1 -0
  68. llmframe-0.1.0/tests/unit/__init__.py +1 -0
  69. llmframe-0.1.0/tests/unit/adapters/output/llm/__init__.py +0 -0
  70. llmframe-0.1.0/tests/unit/adapters/output/llm/llm_adapter/__init__.py +1 -0
  71. llmframe-0.1.0/tests/unit/adapters/output/llm/llm_adapter/test_adapter.py +529 -0
  72. llmframe-0.1.0/tests/unit/adapters/output/llm/openai_adapter/__init__.py +0 -0
  73. llmframe-0.1.0/tests/unit/adapters/output/llm/openai_adapter/parsing/__init__.py +1 -0
  74. llmframe-0.1.0/tests/unit/adapters/output/llm/openai_adapter/parsing/test_message_content.py +125 -0
  75. llmframe-0.1.0/tests/unit/adapters/output/llm/openai_adapter/parsing/test_usage.py +73 -0
  76. llmframe-0.1.0/tests/unit/adapters/output/llm/openai_adapter/test_client.py +133 -0
  77. llmframe-0.1.0/tests/unit/adapters/output/llm/openai_adapter/transport/__init__.py +1 -0
  78. llmframe-0.1.0/tests/unit/adapters/output/llm/openai_adapter/transport/test_adapter.py +542 -0
  79. llmframe-0.1.0/tests/unit/adapters/output/llm/usage_tracker/__init__.py +1 -0
  80. llmframe-0.1.0/tests/unit/adapters/output/llm/usage_tracker/test_adapter.py +121 -0
  81. llmframe-0.1.0/tests/unit/test_scaffold.py +8 -0
  82. 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.