This commit is contained in:
ВяткинАртём
2026-04-06 17:54:07 +03:00
parent 6d4e45d407
commit d00f858d98
15 changed files with 1319 additions and 0 deletions
View File
+61
View File
@@ -0,0 +1,61 @@
---
name: "TestLink Autotest Engineer"
description: "End-to-end autotest generation from a TestLink test case number. Use when: user provides a TestLink test case ID and wants autotests written, reviewed, and verified. Triggers: автоматизировать тест-кейс, написать автотест по TestLink, КМД в автотест, IDS в автотест, automate test case, generate autotests from TestLink, полный цикл автотестов, testlink-to-autotest."
tools: [read, edit, search, execute, todo, mcp_testlink_get_test_case, mcp_testlink_get_projects, mcp_io_github_ups_resolve-library-id, mcp_io_github_ups_get-library-docs]
argument-hint: "TestLink test case ID (e.g. КМД-1831)"
---
You are a senior test automation engineer. Your only job is to take a TestLink test case ID and produce reviewed, passing autotests by running the full `testlink-to-autotest` pipeline.
You do not answer general programming questions. You do not write code outside of test files. You do not modify production source code.
## Responsibilities
1. Receive a TestLink test case ID from the user
2. Execute the full pipeline defined in the `testlink-to-autotest` skill:
- **Stage 1** — `testlink-decompose`: fetch and decompose the test case into mini-tests
- **Stage 2** — `autotest-writer`: write autotests matching the project's conventions
- **Stage 3** — `python-review`: review and fix code quality (🔴🟡 auto-apply)
- **Stage 4** — `test-runner`: run, diagnose, and fix failing tests
3. Report the final pipeline summary
## Constraints
- DO NOT modify production source code — only test files
- DO NOT skip the review stage (Stage 3) even if code looks clean
- DO NOT run more than 3 fix-and-retry iterations in Stage 4 without asking the user
- DO NOT proceed past Stage 1 if the test case is not found — ask for a correct ID
- ONLY write tests that correspond to the decomposed mini-tests — do not invent scenarios
## Checkpoints
Stop and wait for user confirmation at:
1. **After Stage 1**: show mini-test table, confirm which mini-tests to automate
2. **After Stage 3**: show 🟢 suggestions, ask if any should be applied
Continue automatically (no confirmation needed) between Stage 2→3 and after applying fixes.
## Tool Usage
- Use `mcp_testlink_get_test_case` to fetch the test case (try `external_id` first, then `test_case_id`)
- Use `mcp_testlink_get_projects` only if the prefix is unknown
- Use `mcp_io_github_ups_resolve-library-id` + `mcp_io_github_ups_get-library-docs` when the code under test uses a third-party library not covered by existing project tests
- Use `execute` to run `mypy`, `ruff`, and `pytest` — always read config from `pyproject.toml` first
- Use `todo` to track pipeline stages visibly
## Output Format
At the end of the pipeline, always produce:
```
## Pipeline Complete: {ID} → Autotests
| Stage | Result |
|-------|--------|
| 1. Decompose | {N} mini-tests |
| 2. Write | {N} test functions in {filepath} |
| 3. Review | {N} fixes applied |
| 4. Run | {passed}/{total} tests passing |
```
If any stage fails unrecoverably, explain what is blocking and what the user needs to provide.
View File
+101
View File
@@ -0,0 +1,101 @@
---
name: autotest-writer
description: "Write autotests that match the project's existing test style, framework, fixtures, and conventions. Use when: user asks to write tests, generate autotests, cover code with tests, add unit tests, add integration tests, написать тесты, покрыть тестами, написать автотесты, сгенерировать тесты, добавить тесты. Discovers patterns from existing tests first. Uses MCP context7 for library docs when needed. Applies python-review skill rules for type annotations."
argument-hint: "File or function to cover with tests (e.g. src/auth.py or src/auth.py::login)"
---
# Autotest Writer
Discovers how the project writes tests, then generates autotests that look like they were written by the same developer.
## When to Use
- Cover a new function or module with tests
- Generate tests from a TestLink mini-test decomposition (pairs with `testlink-decompose`)
- Add missing edge-case or negative tests
- Scaffold a test file for a new module
## Procedure
### Step 1 — Understand What to Test
From the user's request, identify:
- **Target**: specific file, class, function, or mini-test ID (e.g. `КМД-1831-MT1`)
- **Scope**: unit / integration / end-to-end
- **Source**: existing code to read, or mini-test structure from `testlink-decompose`
If the target is a mini-test ID — treat its steps and expected results as the test specification.
If the target is a source file — read it fully before proceeding.
### Step 2 — Discover Project Test Patterns
Follow the full discovery process in
[references/discover-patterns.md](./references/discover-patterns.md).
Output a one-line summary before continuing:
> Detected: **{framework}** · fixtures via **{conftest/class/factory}** · assertions: **{assert/custom}** · async: **{yes/no}** · mocks: **{unittest.mock / pytest-mock / none}**
### Step 3 — Resolve Library Documentation (if needed)
When the code under test uses a third-party library AND the project has no existing tests covering it:
1. Call `mcp_io_github_ups_resolve-library-id` with the library name to get the context7 ID
2. Call `mcp_io_github_ups_get-library-docs` with that ID to retrieve relevant docs
3. Extract: how to instantiate, patch, or assert against the library's objects
4. If context7 returns nothing — read the library's source from `site-packages/` in the virtualenv
Do NOT fetch docs for libraries already exercised in existing project tests.
### Step 4 — Plan the Tests
Before writing code, output a test plan as a table:
| # | Test name | Scenario type | Target | Fixtures needed |
|---|-----------|--------------|--------|-----------------|
| 1 | `test_login_valid_credentials` | happy path | `auth.login()` | `db_session`, `user_factory` |
| 2 | `test_login_wrong_password` | negative | `auth.login()` | `db_session`, `user_factory` |
| 3 | `test_login_user_not_found` | negative | `auth.login()` | `db_session` |
Coverage target: at minimum cover happy path + all negative/edge cases visible in the source.
Ask: "Продолжить? Или изменить план?" — wait for confirmation before writing code if the plan has more than 5 tests.
For ≤ 5 tests, proceed immediately.
### Step 5 — Write the Tests
Follow all rules in [references/write-rules.md](./references/write-rules.md).
Key constraints (never violate):
- Match the exact file naming convention discovered in Step 2
- Use the same fixture sources (conftest.py / inline / factory) as existing tests
- Use the same assertion style (plain `assert` vs custom matchers)
- Use the same import order and grouping
- Use Python 3.13+ type annotations on all new functions
- Match async/sync style of the surrounding test suite
### Step 6 — Place the File
Determine the output path:
- If a parallel test file exists (e.g. `src/auth.py``tests/test_auth.py`) — append to it
- If no test file exists — create one at the location matching the project layout
- Never create a test file in the source tree unless that is the project's convention
Output the full file path before writing.
### Step 7 — Validate
After writing, run:
```bash
# Syntax and type check
mypy --strict <test_file>
# Lint
ruff check <test_file>
# Execute tests (dry run — collect only)
pytest --collect-only <test_file>
```
Fix any collection errors or mypy/ruff violations silently.
If tests actually run — report pass/fail counts.
@@ -0,0 +1,112 @@
# Discover Project Test Patterns
Run this discovery process once at the start of every autotest-writer session.
Cache results — do not repeat searches for the same project.
## 1. Framework Detection
Search for test dependencies in `pyproject.toml` or `requirements*.txt`:
| Found | Framework |
|-------|-----------|
| `pytest` | pytest (default assumption) |
| `unittest` only | stdlib unittest |
| `anyio`, `pytest-asyncio` | async pytest |
| `hypothesis` | property-based testing present |
If none found — default to **pytest**.
## 2. Test File Layout
Run a glob search for `test_*.py` and `*_test.py` files.
Determine:
- **Root test folder**: `tests/`, `test/`, inline next to source, or mixed
- **Naming convention**: `test_{module}.py` or `{module}_test.py`
- **Mirrors source tree?** e.g. `src/auth/login.py``tests/auth/test_login.py`
## 3. conftest.py Analysis
Read all `conftest.py` files found (root + subdirectories).
Extract:
- All fixture names and their scopes (`function`, `session`, `module`)
- Factory fixtures (return a callable/class)
- Common parametrize patterns
- Any custom pytest plugins or hooks registered
## 4. Read Sample Tests
Read **23 representative test files** (pick files that seem most complete, not trivial ones).
Extract the following patterns:
### Import style
```python
# Note: stdlib first? third-party second? local last?
# Note: are there "from __future__ import annotations"?
```
### Class vs function tests
- Are tests in classes (`class TestLogin:`) or bare functions?
- If classes — do they inherit from anything?
### Assertion style
- Plain `assert a == b`
- `assert a == b, "message"`
- Custom matchers: `assert_that(a).equals(b)`, `expect(a).to.equal(b)`
### Mock / patch style
- `unittest.mock.patch` as decorator
- `unittest.mock.patch` as context manager
- `pytest-mock` via `mocker` fixture
- `MagicMock` / `AsyncMock` / `create_autospec`
### Async style
- `@pytest.mark.asyncio` + `async def test_`
- `anyio` backend marker
- Synchronous wrappers around async code
### Parametrize style
```python
@pytest.mark.parametrize("input,expected", [...])
```
or inline in class docstring (hypothesis)?
## 5. Factory / Builder Patterns
Search for:
- `factory_boy``UserFactory`, `PostFactory` classes
- `pytest-factoryboy``register()` usage
- Custom builder fixtures that return objects
- `faker` usage for test data generation
## 6. Database / External Service Patterns
- Does the project use `pytest-django`, `pytest-sqlalchemy`, or custom DB fixtures?
- Are there transaction rollback fixtures (`db`, `transactional_db`)?
- Are HTTP calls mocked via `responses`, `httpretty`, `respx`, or `pytest-httpx`?
- Is there a test database URL in `.env.test` or `pytest.ini`?
## 7. Markers and Config
Read `pytest.ini`, `setup.cfg`, or `[tool.pytest.ini_options]` in `pyproject.toml`.
Note:
- Custom markers (`@pytest.mark.integration`, `@pytest.mark.slow`)
- `asyncio_mode` setting (`auto` or `strict`)
- `testpaths` — where pytest looks for tests
- Any `filterwarnings` that should be preserved
## Output Format
Summarize findings as:
```
Framework: pytest 8.x
Async: pytest-asyncio, asyncio_mode=auto
Test root: tests/
Naming: test_{module}.py mirroring src/
Classes: no (bare functions)
Fixtures: conftest.py at root; session-scoped db_engine, function-scoped db_session
Factories: factory_boy (UserFactory, ProjectFactory)
Mocks: pytest-mock (mocker fixture)
HTTP mocks: respx
Markers: integration, slow, unit
Assertions: plain assert
```
@@ -0,0 +1,168 @@
# Test Writing Rules
Mandatory rules when generating test code. Never violate without explicit user approval.
## File Structure
```python
# 1. stdlib imports
# 2. third-party imports
# 3. local imports (blank line between groups — match project convention)
# 4. Module-level constants or type aliases (if needed)
# 5. Fixtures local to this file (if not in conftest)
# 6. Test functions / class
```
Always match the import grouping style found in existing tests.
## Naming
| Target | Convention | Example |
|--------|-----------|---------|
| Test file | `test_{module}.py` or `{module}_test.py` (match project) | `test_auth.py` |
| Test function | `test_{what}_{condition}` | `test_login_wrong_password` |
| Test class | `Test{Subject}` | `TestLoginService` |
| Fixture | `{noun}` or `{noun}_{qualifier}` | `db_session`, `admin_user` |
| Parametrize ID | descriptive string | `"valid"`, `"empty_email"` |
## Type Annotations — Python 3.13+
All test functions must have return type `-> None`.
Fixture functions must have explicit return type annotation.
```python
# Correct
def test_login_valid(db_session: AsyncSession, user: User) -> None:
...
@pytest.fixture
def admin_user(db_session: AsyncSession) -> User:
...
```
Never use `Optional[X]` — use `X | None`.
Never use `List[X]`, `Dict[K, V]` — use `list[X]`, `dict[K, V]`.
## Assertions
Use the same assertion style as the project (discovered in Step 2).
For plain `assert` style:
- Always compare with `==`, not `is`, for value equality
- Use `assert x is None` / `assert x is not None` for None checks
- Use `assert isinstance(x, SomeClass)` for type checks
- Include a failure message for non-obvious assertions:
`assert result.status == 200, f"Expected 200, got {result.status}"`
For exception assertions:
```python
with pytest.raises(ValueError, match="invalid email"):
service.create_user(email="bad")
```
Never assert `True` or `False` directly from a function that returns bool:
```python
# Bad
assert service.is_valid(x) == True
# Good
assert service.is_valid(x)
```
## Fixtures
- Prefer existing fixtures from conftest.py over creating new ones
- Create a new fixture only if it will be reused in ≥ 2 tests in this file
- One-off setup → inline in the test body
- Scope: use `function` by default; `module`/`session` only for read-only shared resources
- Never use `autouse=True` for fixtures defined inside a test file
## Parametrize
Use `@pytest.mark.parametrize` when the same logic is tested with ≥ 3 data variants:
```python
@pytest.mark.parametrize(
("email", "expected_error"),
[
("", "Email cannot be empty"),
("not-an-email", "Invalid email format"),
("a" * 256 + "@x.com", "Email too long"),
],
)
def test_create_user_invalid_email(
email: str,
expected_error: str,
db_session: AsyncSession,
) -> None:
with pytest.raises(ValueError, match=expected_error):
UserService(db_session).create_user(email=email)
```
## Mocking
Match the project's mock style (discovered in Step 2).
With `pytest-mock`:
```python
def test_sends_email(mocker: MockerFixture) -> None:
send = mocker.patch("myapp.notifications.send_email")
service.register(email="u@example.com")
send.assert_called_once_with("u@example.com", subject=mocker.ANY)
```
With `unittest.mock`:
```python
from unittest.mock import AsyncMock, patch
@patch("myapp.notifications.send_email", new_callable=AsyncMock)
async def test_sends_email(mock_send: AsyncMock) -> None:
await service.register(email="u@example.com")
mock_send.assert_awaited_once()
```
Use `create_autospec` when the interface must be enforced:
```python
mock_repo = create_autospec(UserRepository)
```
Never use `MagicMock()` without speccing — it silently accepts any attribute.
## Async Tests
Match `asyncio_mode` from `pyproject.toml`:
- `asyncio_mode = "auto"` → no decorator needed, just `async def test_`
- `asyncio_mode = "strict"` → must add `@pytest.mark.asyncio`
Never mix sync and async in the same test — use `AsyncMock` for async dependencies.
## Test Isolation Rules
- Each test must be independent — no shared mutable state between tests
- Never rely on test execution order
- Clean up side effects: files, DB rows, environment variables
- Use `monkeypatch` for environment variable overrides (not `os.environ` direct)
- Use `tmp_path` fixture for temporary file I/O
## Scenario Coverage per Function
At minimum, cover:
| Scenario | Must have |
|----------|-----------|
| Happy path | ✅ Always |
| Empty / zero input | ✅ If applicable |
| Boundary values | ✅ If numeric limits exist |
| Invalid type or format | ✅ If input is validated |
| Not found / missing resource | ✅ For DB/file lookups |
| Permission denied | ✅ If auth is involved |
| External service failure | ✅ If network/DB call exists |
## What NOT to Test
- Internal private methods (`_method`) directly — test via public API
- Framework internals (Django ORM, SQLAlchemy internals)
- Third-party library behaviour (mock at the boundary, don't re-test the lib)
- Trivial getters/setters with zero logic
+104
View File
@@ -0,0 +1,104 @@
---
name: python-review
description: "Python 3.13+ code review with mypy strict and ruff. Use when: user asks to review Python code, check types, lint, fix type errors, review pull request, code quality check, mypy errors, ruff violations, optimize Python, улучши код, ревью кода, проверь типы, код ревью питон, исправь mypy, исправь ruff."
argument-hint: "File or folder to review (e.g. src/module.py or src/)"
---
# Python Code Review (3.13+ · mypy strict · ruff)
Two-phase workflow: first **propose** improvements, then **apply** only what the user accepts.
## When to Use
- Review Python files before commit or PR
- Fix mypy strict or ruff violations
- Optimize performance, readability, or architecture
- Prepare code for Python 3.13+ compatibility
## Procedure
### Step 1 — Discover Project Config
Before running any tool, check if `pyproject.toml` exists in the workspace root.
Read it and extract:
- `[tool.mypy]` — use these settings instead of defaults; if `strict = true` is absent, treat it as enabled anyway
- `[tool.ruff]` and `[tool.ruff.lint]` — use `select`, `ignore`, `line-length`, `target-version`
- `[project]``requires-python` — confirm target is 3.13+
If `pyproject.toml` is absent — use the baseline config from
[references/default-config.md](./references/default-config.md).
### Step 2 — Run Static Analysis
Run both tools on the target file(s) and collect all findings:
```bash
# mypy
mypy --strict <target>
# ruff (check only, no fixes yet)
ruff check <target>
```
Also read the source file(s) to perform a manual review pass.
### Step 3 — Categorize Findings
Group all findings into three tiers (see full criteria in
[references/review-checklist.md](./references/review-checklist.md)):
| Tier | Label | Examples |
|------|-------|---------|
| 🔴 Must fix | Correctness / type safety | mypy errors, `Any` leaks, logic bugs, security issues |
| 🟡 Should fix | Code quality | ruff warnings, missing return types, dead code, mutable defaults |
| 🟢 Consider | Optimization / style | performance hints, readability, modern Python 3.13+ idioms |
### Step 4 — Present Proposals (DO NOT EDIT YET)
Output a structured review report:
```
## Code Review: <filename>
### 🔴 Must Fix ({N})
1. **[MYPY/RUFF code]** `symbol` — explanation
```python
# before
# after
```
### 🟡 Should Fix ({N})
...
### 🟢 Consider ({N})
...
---
Accept all fixes? Or specify which tiers/items to apply: "apply 🔴", "apply all", "apply 1,3,5"
```
**Do not modify any files until the user explicitly accepts.**
### Step 5 — Apply Accepted Fixes
Wait for the user's response. Parse their intent:
| User says | Action |
|-----------|--------|
| "apply all" / "применить всё" | Apply all tiers |
| "apply 🔴" / "применить красные" | Apply Must Fix only |
| "apply 🔴🟡" | Apply Must Fix + Should Fix |
| "apply 1,3,5" / "пункты 1,3,5" | Apply specific numbered items |
| "skip" / "отмена" | Do nothing |
Apply fixes following the rules in [references/fix-rules.md](./references/fix-rules.md).
After applying:
1. Re-run `mypy --strict` and `ruff check` to confirm zero remaining violations
2. Report: "Applied N fixes. Remaining issues: X" (or "Clean ✅")
### Step 6 — Final Check
If any 🔴 items were skipped — warn the user:
> ⚠️ {N} critical issue(s) not applied. The code may have type errors or bugs at runtime.
@@ -0,0 +1,56 @@
# Default Config
Baseline mypy and ruff settings used when `pyproject.toml` is absent or incomplete.
## mypy (strict mode)
```ini
[mypy]
python_version = 3.13
strict = true
warn_return_any = true
warn_unused_ignores = true
warn_redundant_casts = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_any_generics = true
no_implicit_reexport = true
```
## ruff
```toml
[tool.ruff]
target-version = "py313"
line-length = 88
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"UP", # pyupgrade
"B", # flake8-bugbear
"SIM", # flake8-simplify
"I", # isort
"RUF", # ruff-specific
"ANN", # flake8-annotations (public API must be typed)
]
ignore = [
"ANN101", # missing type for self
"ANN102", # missing type for cls
]
```
## pyproject.toml Priority Rules
When `pyproject.toml` exists, always prefer its values over these defaults:
| Config key | Behaviour |
|---|---|
| `[tool.mypy] strict = false` | Still enforce strict — mention the deviation to the user |
| `[tool.mypy] python_version` | Use as-is; warn if < 3.13 |
| `[tool.ruff.lint] ignore` | Respect fully — do not flag ignored rules |
| `[tool.ruff] line-length` | Use for E501; default 88 if absent |
| `[tool.ruff.lint] select` | Merge with defaults — do not drop user selections |
@@ -0,0 +1,97 @@
# Fix Application Rules
Rules the agent must follow when applying accepted review fixes.
## General Rules
1. **One file at a time** — edit files sequentially, not in parallel, to avoid conflicts
2. **Minimal diff** — change only what is needed; do not reformat unrelated code
3. **Preserve comments** — keep existing comments unless they are part of the fix
4. **No behaviour changes** — fixes must not alter runtime behaviour unless the original was a bug
5. **Add imports sparingly** — only add imports that are strictly required by the fix
## Type Annotation Fixes
- Add annotations using Python 3.13+ syntax: `X | None` not `Optional[X]`
- Use `list[str]` not `List[str]`, `dict[str, int]` not `Dict[str, int]`
- For functions with no return: annotate as `-> None`
- For `*args` / `**kwargs`: annotate properly, e.g. `*args: str`, `**kwargs: int`
- Do not add `from __future__ import annotations` — use native syntax directly
## mypy `# type: ignore` Usage
- Never add bare `# type: ignore` — always use specific code: `# type: ignore[assignment]`
- Only use `type: ignore` as last resort when fixing the root cause is not feasible
- Add a comment explaining why: `# type: ignore[misc] # third-party stub incomplete`
## ruff Auto-fix Rules
- Run `ruff check --fix <file>` only for safe auto-fixable rules (F401, UP*, I*)
- Do NOT use `--unsafe-fixes` without explicit user confirmation
- After auto-fix, re-read the file to verify no logic was altered
## Mutable Default Arguments
```python
# Before
def process(items: list[str] = []) -> None: ...
# After
def process(items: list[str] | None = None) -> None:
if items is None:
items = []
```
## Union Type Modernization (Python 3.13+)
```python
# Before
from typing import Optional, Union
def f(x: Optional[str]) -> Union[int, str]: ...
# After
def f(x: str | None) -> int | str: ...
```
Remove now-unused `Optional` / `Union` imports after rewriting.
## TypeAlias Modernization (PEP 695)
```python
# Before
from typing import TypeAlias
Vector: TypeAlias = list[float]
# After
type Vector = list[float]
```
## Broad Exception Handling
```python
# Before
try:
...
except Exception:
pass
# After — option A: log and re-raise
except Exception:
logger.exception("Unexpected error")
raise
# After — option B: specific exception (preferred when possible)
except ValueError as exc:
logger.warning("Invalid value: %s", exc)
```
## Post-Fix Validation Sequence
After applying all accepted fixes to a file:
1. `mypy --strict <file>` — must return exit code 0
2. `ruff check <file>` — must return exit code 0
3. If any new violations appear that were NOT in the original report — fix them silently and note in the summary
4. Report final status:
- ✅ Clean — zero violations remaining
- ⚠️ Remaining — list unresolved items with reason
@@ -0,0 +1,73 @@
# Review Checklist
Full criteria for categorizing findings in the Python review skill.
## 🔴 Must Fix — Correctness & Type Safety
These block merge. Apply by default.
### mypy strict violations
- `error:` any mypy error (all are must-fix under strict mode)
- Implicit `Any` — untyped function parameters or return values
- `type: ignore` without a specific error code (e.g. bare `# type: ignore`)
- Incompatible types in assignment or return
- Missing `--strict` compatibility: missing `py.typed`, missing stubs
### Logic & Security (manual review)
- Mutable default arguments: `def f(x: list = [])` → use `None` + guard
- Shadowing built-ins: `list`, `dict`, `id`, `type`, `input`, etc.
- Uncaught exceptions from external I/O without explicit handling
- SQL/shell injection risks (string formatting into queries/commands)
- Hardcoded secrets or credentials
- `assert` used for runtime validation (stripped with `-O`)
---
## 🟡 Should Fix — Code Quality
These hurt maintainability. Fix unless explicitly skipped.
### ruff violations (common)
- `E501` — line too long (respect `line-length` from pyproject.toml)
- `F401` — unused import
- `F841` — local variable assigned but never used
- `B006` — mutable argument default
- `B007` — loop variable unused
- `UP` prefix — pyupgrade rules (use modern Python syntax)
- `SIM` prefix — simplifiable conditions
- `RUF` prefix — Ruff-specific best practices
### Manual quality
- Missing return type annotation on public functions/methods
- Missing docstring on public API (classes, public methods, module)
- `pass` in `except` block without a comment explaining why
- `print()` left in production code (use `logging`)
- Overly broad `except Exception` without re-raise or specific handling
- Dead code: unreachable branches, commented-out code blocks
---
## 🟢 Consider — Optimization & Modern Python 3.13+
Suggestions only. Offer but don't apply without confirmation.
### Python 3.13+ idioms
- Use `type X = ...` (PEP 695) instead of `TypeAlias`
- Use `X | Y` union syntax instead of `Union[X, Y]` or `Optional[X]`
- Use `TypeVar` with `type` statement (PEP 695) instead of old `TypeVar()`
- `@override` decorator (PEP 698) for overriding methods
- `tomllib` (stdlib) instead of third-party TOML parsers
- `pathlib.Path` instead of `os.path` string operations
### Performance
- Generator expressions instead of list comprehensions when result is iterated once
- `__slots__` on data-heavy classes to reduce memory
- `functools.cache` / `functools.lru_cache` for pure deterministic functions
- Avoid repeated attribute lookup in tight loops (`local = self.attr`)
- Prefer `collections.deque` over `list` for O(1) pop from front
### Readability
- Replace long `if/elif` chains with `match/case` (structural pattern matching)
- Extract deeply nested logic into named helper functions
- Use `dataclasses.dataclass` or `pydantic.BaseModel` for plain data containers
- Named tuples or `TypedDict` instead of untyped `dict` returns
+127
View File
@@ -0,0 +1,127 @@
---
name: test-runner
description: "Run pytest tests, diagnose failures, escalate verbosity when output is insufficient, and fix broken tests. Use when: run tests, tests are failing, fix failing tests, pytest errors, запустить тесты, тесты падают, исправить тесты, почему падает тест, debug test failure, test errors, починить тесты, pytest fail, assertion error in test."
argument-hint: "Test file or folder to run (e.g. tests/test_auth.py or tests/). Leave empty to run all."
---
# Test Runner & Fixer
Runs pytest, diagnoses failures with escalating verbosity, and fixes broken tests automatically.
## When to Use
- Tests were just written and need to be verified
- User reports that tests are failing
- CI is broken and the cause is unknown
- Need to understand why a specific test fails
## Procedure
### Step 1 — Read pytest Config
Check `pyproject.toml` (or `pytest.ini`, `setup.cfg`) for `[tool.pytest.ini_options]`.
Extract and respect:
- `testpaths` — default path(s) to run
- `addopts` — default flags (e.g. `-x`, `--tb=short`, `-q`)
- `asyncio_mode``auto` or `strict`
- `filterwarnings` — do not suppress these
- Custom markers and their meanings
- `env` or `env_files` (pytest-dotenv, pytest-env)
Store the effective base command — all runs in this session build on it.
### Step 2 — First Run (Standard)
Run pytest with the base config flags plus minimal output:
```bash
pytest {target} {base_addopts} --tb=short -q
```
Parse the output:
- ✅ All passed → report summary and stop
- ⚠️ Warnings only → report warnings, ask if user wants to address them
- ❌ Failures → proceed to Step 3
### Step 3 — Triage Failures
For each failing test, classify the error:
| Error type | Signal |
|---|---|
| `ImportError` / `ModuleNotFoundError` | Missing dependency or wrong path |
| `fixture not found` | Fixture name mismatch or missing conftest import |
| `AssertionError` | Wrong expected value or broken logic |
| `TypeError` / `AttributeError` | Signature mismatch, wrong mock, API change |
| `asyncio` / `RuntimeError: coroutine` | Async test misconfiguration |
| `ERRORS` (collection error) | Syntax error or import-time crash |
| Timeout | Missing `await`, infinite loop, or slow test |
If ≤ 3 failures — read the full test source for each failed test immediately.
If > 3 failures — read the source of the first 3 and look for a common root cause first.
### Step 4 — Escalate Verbosity (if needed)
If the failure message from Step 2 is insufficient to diagnose, re-run with more detail.
Follow the escalation ladder in
[references/diagnose-failures.md](./references/diagnose-failures.md).
Escalation stops as soon as the root cause is identified — do not run all levels blindly.
### Step 5 — Propose Fixes
For each failure, show the diagnosis and proposed fix **before editing**:
```
### ❌ test_login_wrong_password
**Cause:** `UserService.__init__` signature changed — now requires `settings` argument.
**Fix:** Update fixture `user_service` in conftest.py to pass `settings`.
Before:
user_service = UserService(db_session)
After:
user_service = UserService(db_session, settings=app_settings)
```
If multiple tests share the same root cause — group them under one fix proposal.
Ask: "Apply fixes?" — unless there is only one trivial fix, in which case apply immediately.
### Step 6 — Apply Fixes
Apply only accepted fixes following the rules in the `python-review` skill's
`references/fix-rules.md` (minimal diff, no behaviour changes, preserve comments).
After applying:
1. Re-run the previously failing tests in isolation:
```bash
pytest {fixed_test_ids} --tb=short -q
```
2. If still failing → repeat from Step 3 (max 3 iterations before asking user)
3. If passing → run the full suite to check for regressions:
```bash
pytest {target} --tb=short -q
```
### Step 7 — Report
Output a final summary:
```
## Test Run Summary
| Status | Count |
|--------|-------|
| ✅ Passed | N |
| ❌ Failed | N |
| ⚠️ Warnings | N |
| 🔧 Fixed | N |
{List of fixed tests}
{List of remaining failures if any, with reason}
```
If any failures remain after 3 fix iterations — explain what is needed from the user
(e.g. missing env vars, external service not running, DB migration needed).
@@ -0,0 +1,131 @@
# Diagnose Test Failures — Escalation Ladder
When the default `--tb=short -q` output is not enough to identify a root cause,
escalate through these levels in order. Stop as soon as the cause is clear.
## Level 1 — Full Traceback
Use when: short traceback cuts off the relevant frame.
```bash
pytest {failing_tests} --tb=long -v
```
What to look for:
- The exact line that raises, with full local variable context
- Which fixture or conftest call is involved
- Whether the error originates in test code or production code
---
## Level 2 — Show Local Variables
Use when: you see the line but not the values involved.
```bash
pytest {failing_tests} --tb=long -v --showlocals
```
What to look for:
- Values of `self`, `result`, `response`, `exc` at the point of failure
- Unexpected `None` where an object was expected
- Wrong type (e.g. `str` instead of `int`)
---
## Level 3 — Verbose + Full Diff
Use when: `AssertionError` with truncated diff output.
```bash
pytest {failing_tests} -vv --tb=long --showlocals
```
`-vv` forces pytest to print the full comparison diff without truncation.
What to look for:
- Exact difference between actual and expected dicts/lists/objects
- Whitespace or encoding differences in string comparisons
- Extra or missing keys in dicts
---
## Level 4 — Capture Disabled (see print/log output)
Use when: test uses `print()` or `logging` for debug output, but it's suppressed.
```bash
pytest {failing_tests} -s --tb=long -v
```
`-s` disables stdout/stderr capture — all print and logging output becomes visible.
What to look for:
- Debug prints left in test or production code
- Log messages from libraries (SQLAlchemy queries, httpx requests)
- Side effects happening before the assertion
---
## Level 5 — Log Level Override
Use when: need structured log output, not just prints.
```bash
pytest {failing_tests} --log-cli-level=DEBUG --tb=long -v
```
What to look for:
- DB query output (sqlalchemy echo)
- HTTP request/response details
- Background task or worker lifecycle events
---
## Level 6 — Single Test Isolation
Use when: a test passes alone but fails in suite (ordering issue or shared state).
```bash
# Run the failing test in complete isolation
pytest {single_failing_test_id} --tb=long -v --forked
# or without pytest-forked:
pytest {single_failing_test_id} --tb=long -v -p no:randomly
```
Also try running with `--randomly-seed=0` to fix the order and reproduce reliably.
What to look for:
- Global state mutated by a previous test
- Singleton or module-level cache not reset between tests
- `autouse` fixture with session scope polluting state
---
## Level 7 — Collection Errors
Use when: pytest crashes before running any test (`ERROR collecting ...`).
```bash
pytest {target} --collect-only --tb=long
```
What to look for:
- `SyntaxError` in test file
- Import-time crash (`ModuleNotFoundError`, circular import)
- Missing fixture at discovery time
- Plugin conflict (`conftest.py` error)
---
## Common Root Causes Quick Reference
| Symptom | Likely Cause | Fix Hint |
|---------|-------------|----------|
| `fixture 'X' not found` | Fixture not in scope or conftest not loaded | Check conftest path; add `conftest.py` to package |
| `coroutine was never awaited` | `async def` called without `await` in sync context | Add `@pytest.mark.asyncio` or set `asyncio_mode=auto` |
| `TypeError: __init__() missing argument` | Production code signature changed | Update fixture to pass new required arg |
| `AssertionError` with None | Mock not set up / fixture returns None | Check mock `return_value`; check fixture creates object |
| `ImportError` on test file | Wrong `PYTHONPATH` or missing `__init__.py` | Check `pythonpath` in `[tool.pytest.ini_options]` |
| `RuntimeError: Event loop closed` | Async fixture scope mismatch | Match fixture scope to `asyncio_mode`; use `loop_scope` |
| All tests suddenly fail | Broken conftest or missing env var | Run `--collect-only` first; check `.env.test` |
+102
View File
@@ -0,0 +1,102 @@
---
name: testlink-decompose
description: "Decompose a TestLink test case into autonomous mini-tests for AI processing and autotest generation. Use when: user provides a TestLink test case number or ID and wants to split it into independent end-to-end mini-tests. Triggers: 'decompose test', 'split test case', 'mini-tests', 'generate autotests from test case', 'TestLink test number', 'break down test', 'разбей тест', 'декомпозируй тест-кейс', 'мини-тесты из TestLink'."
argument-hint: "TestLink test case ID — numeric (e.g. 99) or external (e.g. PRJ-5)"
---
# TestLink Test Case Decomposer
Fetches a test case from TestLink via MCP and breaks it down into autonomous mini-tests,
each ready for AI processing and automated test generation.
## When to Use
- User provides a TestLink test case number
- A large test needs to be split into independent scenarios
- Preparing a test case for autotest generation
- Analyzing scenario coverage within a single test case
## Procedure
### Step 1 — Fetch the Test Case from TestLink (MCP)
Call `mcp_testlink_get_test_case` to retrieve the full test case.
Determine the correct parameter from what the user provided:
- **Plain number** (e.g. `99`, `1234`) → pass as `test_case_id` (integer)
- **PREFIX-NUMBER** (e.g. `PRJ-5`, `KMD-42`) → pass as `external_id` (string)
If the user's input is ambiguous (no prefix, but looks like it could be external):
1. First try `test_case_id`
2. If not found, ask the user for the full external ID (e.g. `PRJ-99`)
Fields to extract from the response:
- `id` and `external_id` — identifiers (use `external_id` as the display ID in output)
- `name` — test case title
- `summary` — description / objective
- `preconditions` — preconditions
- `steps[]` — array of steps: `step_number`, `actions`, `expected_results`
- `importance` — priority (1=Low, 2=Medium, 3=High)
- `status` — design status
If MCP returns an error or the test case is not found — report to the user and stop.
### Step 2 — Analyze the Content
Analyze the test case steps and identify logical groups using these criteria:
1. **Change of tested function** — steps test a different feature, API, or screen
2. **Change of scenario** — steps describe an independent user journey
3. **Execution independence** — the group can run without the other steps
Each logical group becomes one mini-test.
Rules:
- Setup/Teardown steps (data creation, login) → `preconditions` of the mini-test
- If all steps are inseparable → one mini-test equals the original test case
- Minimum number of mini-tests: 1
### Step 3 — Decompose into Mini-Tests
For each logical group, create a mini-test strictly following the format in
[references/mini-test-format.md](./references/mini-test-format.md).
Numbering and reference rules:
- Mini-test ID: `{ORIGINAL_ID}-MT{N}` — e.g. `TC-1234-MT1`
- `source_steps` — step numbers from the original that are included in this mini-test
- Step numbering inside a mini-test starts from 1 (do not preserve original numbers)
### Step 4 — Output the Structure
Output in the following order:
```
# Decomposition of Test Case {ORIGINAL_ID}: {ORIGINAL_TITLE}
> Original test split into {N} mini-test(s).
> Priority: {High/Medium/Low} | Status: {status}
> Original preconditions: {preconditions or "None"}
---
{mini-test 1 per format from references/mini-test-format.md}
---
{mini-test 2 per format from references/mini-test-format.md}
---
```
### Step 5 — Summary Table
After all mini-tests, append a summary table:
| Mini-Test ID | Title | Original Steps | Ready for Autotest |
|---|---|---|---|
| TC-XXXX-MT1 | ... | 13 | ✅ |
| TC-XXXX-MT2 | ... | 46 | ⚠️ Needs clarification |
Readiness markers:
- ✅ — steps are atomic, expected results are measurable
- ⚠️ — ambiguous steps or vague expected results
@@ -0,0 +1,64 @@
# Mini-Test Format
Every mini-test must strictly follow this structure.
## Template
**ID:** `{original_id}-MT{N}`
**Title:** {short title — what exactly is being tested}
**Description:** {what this mini-test verifies, 13 sentences}
**Priority:** {High / Medium / Low}
**Preconditions:**
- {system state required before execution}
- {or "None" if no preconditions}
**Steps:**
| # | Action | Expected Result |
|---|--------|-----------------|
| 1 | {user or system action} | {expected behavior} |
| 2 | {action} | {expected result} |
**Final Expected Result:**
{overall outcome after all steps of this mini-test complete}
**Link to Original Test Case:**
- Original test case: `{original_id}` — {original_title}
- Original steps covered: `{e.g. 13, 5}`
- Check type: `functional` | `boundary` | `negative` | `scenario`
---
## Field Rules
### ID
Format: `{ORIGINAL_ID}-MT{N}`, where N is a sequential number starting from 1.
Examples: `TC-1234-MT1`, `TC-1234-MT2`.
### Title
- Must answer: "What is being tested?"
- Format: verb + object. Example: "Login with valid credentials"
### Description
- 13 sentences about the goal of this mini-test
- Do not repeat steps — describe the essence of the check
### Preconditions
- List the system, user, and data state required before execution
- Include setup steps from the original that are not part of the verification logic
### Steps
- Each step = one atomic action
- Action: specific ("Click the 'Login' button"), not abstract ("Log in")
- Expected result: measurable ("A 'Welcome' message is displayed")
### Check Type
- `functional` — verification of core functionality
- `boundary` — boundary/edge values
- `negative` — invalid input or error scenarios
- `scenario` — end-to-end user journey
## Autotest Readiness Markers
-`Ready` — all steps are atomic, expected results are unambiguous and measurable
- ⚠️ `Needs clarification` — vague wording or dependency on external/dynamic data
+123
View File
@@ -0,0 +1,123 @@
---
name: testlink-to-autotest
description: "Full pipeline: fetch TestLink test case → decompose into mini-tests → write autotests → review code quality → run and fix tests. Use when: user wants end-to-end test automation from a TestLink test case number, полный цикл по тест-кейсу, сгенерируй автотесты по тест-кейсу TestLink, автоматизировать тест-кейс, full pipeline test case to autotest, КМД в автотест, TestLink в код."
argument-hint: "TestLink test case ID (e.g. КМД-1831 or 1831)"
---
# TestLink → Autotest Full Pipeline
Orchestrates four skills in sequence to go from a TestLink test case ID
to reviewed, passing autotests — with checkpoints at each stage.
## Pipeline Overview
```
[1] testlink-decompose → Mini-test specifications
[2] autotest-writer → Test code files
[3] python-review → Code quality fixes (🔴 auto-applied, 🟡🟢 proposed)
[4] test-runner → Passing test suite
```
## Procedure
### Stage 1 — Decompose Test Case
Apply the full `testlink-decompose` skill procedure using the test case ID provided by the user.
On completion, display the mini-test table and ask:
> **Checkpoint 1/4** — Found {N} mini-test(s). Proceed to write autotests for all?
> Or specify which mini-test IDs to cover (e.g. `MT1, MT3`).
Wait for confirmation. If the user selects a subset — carry only those mini-tests forward.
---
### Stage 2 — Write Autotests
Apply the full `autotest-writer` skill procedure for each accepted mini-test.
Pass the mini-test structure (id, title, description, steps, expected result)
directly as the test specification — skip `autotest-writer` Step 1 discovery of "what to test"
since the spec is already known.
Still execute:
- `autotest-writer` Step 2 — discover project test patterns
- `autotest-writer` Step 3 — resolve library docs via context7 if needed
- `autotest-writer` Step 4 — plan tests (one plan entry per mini-test step group)
- `autotest-writer` Step 57 — write, place, and collect-validate
On completion:
> **Checkpoint 2/4** — Written {N} test function(s) in `{filepath}`.
> Proceeding to code review...
No confirmation needed — continue automatically.
---
### Stage 3 — Code Review
Apply the full `python-review` skill procedure on the test file(s) written in Stage 2.
Auto-apply rules:
- 🔴 Must Fix — **apply immediately without asking**
- 🟡 Should Fix — **apply immediately without asking**
- 🟢 Consider — **show as suggestions, do not apply**
After applying 🔴🟡 fixes, display 🟢 suggestions (if any) and ask:
> **Checkpoint 3/4** — Review complete. Applied {N} fixes.
> 🟢 Suggestions: {list or "none"}
> Apply any suggestions before running tests? (`yes` / `no` / list numbers)
Wait for response, then apply selected suggestions.
---
### Stage 4 — Run Tests
Apply the full `test-runner` skill procedure on the test file(s) from Stage 2.
If tests pass on first run:
> **Checkpoint 4/4 ✅** — All {N} tests passing.
If tests fail — follow the full `test-runner` diagnosis and fix loop (up to 3 iterations).
After fixing, re-run the full suite for regression check.
---
### Final Report
Output a structured pipeline summary:
```
## Pipeline Complete: {ORIGINAL_ID} → Autotests
| Stage | Result |
|-------|--------|
| 1. Decompose | {N} mini-tests from {ORIGINAL_ID} |
| 2. Write | {N} test functions in {filepath} |
| 3. Review | {N} fixes applied; {N} suggestions |
| 4. Run | {passed}/{total} tests passing |
### Test Functions Written
- `test_{name_1}` — КМД-XXXX-MT1
- `test_{name_2}` — КМД-XXXX-MT2
### Remaining Issues (if any)
{description or "None"}
```
## Error Handling
| Failure point | Action |
|---|---|
| Stage 1: test case not found | Stop, report to user, ask for correct ID |
| Stage 2: no test patterns found | Ask user to point to an existing test file as reference |
| Stage 3: mypy/ruff not installed | Skip review, warn user, continue to Stage 4 |
| Stage 4: tests fail after 3 fix attempts | Report remaining failures with diagnosis; do not loop further |