Compare commits

...

7 Commits

Author SHA1 Message Date
John Lancaster 098a2418ee shims 2026-06-20 20:03:54 -05:00
John Lancaster 7f672b9c8f pytest naming convention 2026-06-20 20:02:03 -05:00
John Lancaster 3c5efc6018 prune link 2026-06-20 19:44:11 -05:00
John Lancaster 0b2d45d419 pytest add 2026-06-20 19:44:02 -05:00
John Lancaster 406fd63a07 better copilot integration 2026-06-20 19:40:43 -05:00
John Lancaster 323f02102d step6 2026-06-20 19:30:37 -05:00
John Lancaster 906bba427b step 6 update 2026-06-20 18:20:43 -05:00
14 changed files with 486 additions and 89 deletions
@@ -0,0 +1,16 @@
---
name: New Skill Configuration
description: Route docs/skills edits to the Personal MCP new-skill resource.
applyTo: 'docs/skills/**/*.md'
---
When editing files under `docs/skills/`, use `resource://skills/new-skill/document` as the primary guidance source for skill structure and authoring decisions.
Execution pattern:
1. Load `resource://skills/new-skill/document` first.
2. Apply only the portions relevant to the file being edited (`SKILL.md` or `references/*.md`).
3. Keep edits minimal and aligned with repository skill conventions.
4. Include source-document links for any feature-level recommendation.
If task intent is ambiguous, ask one clarifying question before editing.
@@ -0,0 +1,16 @@
---
name: Pytest Scaffolding Guidance
description: Route tests edits to the Personal MCP pytest-scaffolding resource.
applyTo: 'tests/**'
---
When editing files under `tests/`, use `resource://skills/pytest-scaffolding/document` as the primary guidance source for test scaffolding and pytest authoring decisions.
Execution pattern:
1. Load `resource://skills/pytest-scaffolding/document` first.
2. Apply only the portions relevant to the file being edited.
3. Keep tests focused, deterministic, and aligned with repository conventions.
4. Include source-document links for any feature-level recommendation.
If task intent is ambiguous, ask one clarifying question before editing.
@@ -0,0 +1,19 @@
---
name: VS Code Configuration
description: Route .vscode edits to the Personal MCP VS Code configuration skill resource.
applyTo: '.vscode/**'
---
When editing files under `.vscode/`, use `resource://skills/vscode-configuration/document` as the primary guidance source.
Execution pattern:
1. Load `resource://skills/vscode-configuration/document` first.
2. Select only the matching reference page for the current file type:
- `launch.json` -> debug launch configurations.
- `tasks.json` -> tasks.json project tasks.
- `mcp.json` -> mcp.json MCP server configuration.
3. Prefer the smallest safe config change and keep settings explicit.
4. Include source-document links for any feature-level recommendation.
If task intent is ambiguous, ask one clarifying question before editing.
@@ -8,7 +8,7 @@ Create a docs-first FastMCP architecture where all Markdown remains in docs/ as
3. Phase 1: Define URI contract with explicit break-and-replace policy. Recommend resource://catalog/skills_index, resource://catalog/skills/{skill_id}, resource://skills/{skill_id}/document, resource://skills/{skill_id}/references/{ref_id}, and resource://docs/{path*}. Evolving URIs and reference ids requires direct replacement, with no aliases or compatibility shims. Depends on steps 1-2. Deliverable: update the current docs/ directory with the finalized URI contract and break-and-replace policy from this step. 3. Phase 1: Define URI contract with explicit break-and-replace policy. Recommend resource://catalog/skills_index, resource://catalog/skills/{skill_id}, resource://skills/{skill_id}/document, resource://skills/{skill_id}/references/{ref_id}, and resource://docs/{path*}. Evolving URIs and reference ids requires direct replacement, with no aliases or compatibility shims. Depends on steps 1-2. Deliverable: update the current docs/ directory with the finalized URI contract and break-and-replace policy from this step.
4. Phase 2: Build a docs registry loader that reads packaged docs via importlib.resources.files(...) Traversable APIs, parses SKILL.md frontmatter, validates schema, and creates an in-memory registry keyed by skill_id. Fail fast for duplicate ids, missing files, broken reference mappings, or invalid depends_on. Depends on steps 2-3. 4. Phase 2: Build a docs registry loader that reads packaged docs via importlib.resources.files(...) Traversable APIs, parses SKILL.md frontmatter, validates schema, and creates an in-memory registry keyed by skill_id. Fail fast for duplicate ids, missing files, broken reference mappings, or invalid depends_on. Depends on steps 2-3.
5. Phase 2: Register FastMCP resources from the registry using RFC6570 templates (including wildcard paths where appropriate), read-only/idempotent annotations, explicit mime types, and on_duplicate_resources="error" for startup safety. Depends on step 4. 5. Phase 2: Register FastMCP resources from the registry using RFC6570 templates (including wildcard paths where appropriate), read-only/idempotent annotations, explicit mime types, and on_duplicate_resources="error" for startup safety. Depends on step 4.
6. Phase 2: Add discovery surfaces as resources first, then tool fallback. Keep catalog discovery in resources, then add ResourcesAsTools for tool-only clients. Add thin discovery tools only for parity and optional BM25/regex tool search when catalog/tool volume grows enough to affect token efficiency. Depends on step 5. 6. Phase 2: Add discovery surfaces as resources first, then tool fallback. Keep catalog discovery in resources, then add ResourcesAsTools for tool-only clients. Add thin discovery tools only for parity and optional BM25/regex tool search when catalog/tool volume grows enough to affect token efficiency. Define canonical fallback tool names (`list_resources`, `read_resource`, `search_patterns`, `get_pattern_by_id`, `get_skill_document_by_id`), research host-specific naming behavior for GitHub Copilot, Cursor, Claude Desktop, and generic MCP clients, and require client-side name mapping or intentionally documented aliases when providers expose namespaced wrappers. Depends on step 5.
7. Phase 3: Implement packaging so docs/ is copied into package resource space at build time (wheel + sdist) while docs/ remains canonical in source control. Use importlib.resources at runtime only; avoid direct filesystem assumptions. Depends on steps 4-6. 7. Phase 3: Implement packaging so docs/ is copied into package resource space at build time (wheel + sdist) while docs/ remains canonical in source control. Use importlib.resources at runtime only; avoid direct filesystem assumptions. Depends on steps 4-6.
8. Phase 3: Remove materialization coupling between skill source modules and docs. The website build reads docs/ directly, while MCP reads packaged docs resources from the installed package. This preserves one authored source with two distribution surfaces. Depends on step 7. 8. Phase 3: Remove materialization coupling between skill source modules and docs. The website build reads docs/ directly, while MCP reads packaged docs resources from the installed package. This preserves one authored source with two distribution surfaces. Depends on step 7.
9. Phase 4: Add validation and CI gates: frontmatter schema checks, URI uniqueness checks, reference integrity checks, docs build check, package content check, and stdio smoke checks that read representative skill/document resources from an installed wheel. Depends on steps 5-8. 9. Phase 4: Add validation and CI gates: frontmatter schema checks, URI uniqueness checks, reference integrity checks, docs build check, package content check, and stdio smoke checks that read representative skill/document resources from an installed wheel. Depends on steps 5-8.
@@ -31,7 +31,7 @@ Create a docs-first FastMCP architecture where all Markdown remains in docs/ as
2. Run uv run pytest -q with tests that validate frontmatter parsing, URI generation, reference mapping, and catalog responses. 2. Run uv run pytest -q with tests that validate frontmatter parsing, URI generation, reference mapping, and catalog responses.
3. Run a packaging integrity check using importlib.resources.files(...) to confirm packaged docs resources exist and are readable from an installed wheel. 3. Run a packaging integrity check using importlib.resources.files(...) to confirm packaged docs resources exist and are readable from an installed wheel.
4. Run a stdio MCP smoke test that lists resources and reads at least one skill document and one reference document. 4. Run a stdio MCP smoke test that lists resources and reads at least one skill document and one reference document.
5. Run fallback-client smoke tests verifying list_resources/read_resource tools work and return expected metadata for both static and templated resources. 5. Run fallback-client smoke tests verifying list_resources/read_resource tools work and return expected metadata for both static and templated resources, and that GitHub Copilot, Cursor, Claude Desktop, and protocol-level SDK tests use canonical tool names or documented mapped aliases.
**Decisions** **Decisions**
- Anthropic compatibility: strict skill directory pattern with SKILL.md and references subtree. - Anthropic compatibility: strict skill directory pattern with SKILL.md and references subtree.
+82
View File
@@ -23,6 +23,41 @@ Normative conclusions from those sources:
3. Resources and tools must resolve to the same canonical authored markdown. 3. Resources and tools must resolve to the same canonical authored markdown.
4. Fallback behavior should keep context bounded and deterministic. 4. Fallback behavior should keep context bounded and deterministic.
### FastMCP Source Baseline (Authoritative References)
Step 6 fallback behavior and compatibility-layer expectations align with:
1. [FastMCP server concepts](https://gofastmcp.com/servers/server)
2. [FastMCP resources and resource templates](https://gofastmcp.com/servers/resources)
3. [FastMCP resources-as-tools transform](https://gofastmcp.com/servers/transforms/resources-as-tools)
4. [MCP specification: resources](https://modelcontextprotocol.io/specification/latest/server/resources)
Applied conclusions for this step:
1. Resource contracts remain canonical and should be surfaced directly when clients support resource attachment.
2. Tool-first compatibility layers should wrap canonical resource reads rather than creating alternate authored-content stores.
3. URI-template-backed resource identity remains stable across direct-resource and tool-compatibility access paths.
### Client Tool-Naming Research Baseline
Authoritative and client-specific references to verify during implementation:
1. [MCP specification: tools](https://modelcontextprotocol.io/specification/latest/server/tools)
2. [MCP client concepts](https://modelcontextprotocol.io/docs/learn/client-concepts)
3. [FastMCP tools](https://gofastmcp.com/servers/tools)
4. [FastMCP resources-as-tools transform](https://gofastmcp.com/servers/transforms/resources-as-tools)
5. [VS Code MCP servers](https://code.visualstudio.com/docs/agent-customization/mcp-servers)
6. [VS Code MCP configuration reference](https://code.visualstudio.com/docs/agents/reference/mcp-configuration)
7. [Cursor MCP documentation](https://docs.cursor.com/context/model-context-protocol)
8. [Claude Desktop local MCP server setup](https://support.anthropic.com/en/articles/10949351-getting-started-with-local-mcp-servers-on-claude-desktop)
Baseline naming conclusions:
1. MCP protocol tool identity is the server-advertised `name` returned by `tools/list` and used in `tools/call`.
2. FastMCP tool identity should be treated as the canonical server contract unless a tool is intentionally registered with an explicit alternate name.
3. Clients and host integrations may display, namespace, or internally route tool names with provider-specific prefixes, but those wrappers are not canonical server tool names.
4. Compatibility should be validated by observed `tools/list` and successful `tools/call` behavior in each target client rather than by assuming one global host naming convention.
### Discovery Priority Contract (Normative) ### Discovery Priority Contract (Normative)
Preferred sequence for skill discovery and loading: Preferred sequence for skill discovery and loading:
@@ -58,6 +93,19 @@ The fallback tool surface includes:
4. `get_pattern_by_id` 4. `get_pattern_by_id`
5. `get_skill_document_by_id` 5. `get_skill_document_by_id`
Canonical naming rule:
1. The server-level tool contract uses the exact registered FastMCP tool names above.
2. Clients that expose provider-prefixed names (for example, namespaced wrappers) must map those names to the canonical server tool name before invocation.
3. `catalog_get_skill_document_by_id` is not a canonical server tool name for this contract unless an explicit alias is intentionally registered.
Compatibility alias policy:
1. Prefer canonical server tool names over aliases.
2. Add server-side aliases only when a major client cannot reliably map its wrapper name back to the canonical name.
3. Any alias must be read-only, delegate to the same payload builder as the canonical tool, and be documented as compatibility-only.
4. If aliases are added, canonical and alias tools must return byte-for-byte equivalent payloads for the same input.
Fallback order: Fallback order:
1. call `list_resources` to inspect canonical static/template resource surfaces 1. call `list_resources` to inspect canonical static/template resource surfaces
@@ -70,6 +118,40 @@ Tool behavior requirements:
2. deterministic ordering and bounded pagination 2. deterministic ordering and bounded pagination
3. explicit not-found responses (`found: false` style) where applicable 3. explicit not-found responses (`found: false` style) where applicable
4. payloads remain schema-aligned with catalog resources 4. payloads remain schema-aligned with catalog resources
5. tool invocation examples and Copilot guidance must use canonical server tool names to avoid unknown-tool errors
### Major Client Compatibility Plan
Target clients and expected validation:
1. GitHub Copilot in VS Code
- primary path: attach MCP resources when `MCP Resources...` is available
- fallback path: call `list_resources`, `read_resource`, then canonical thin tools only when needed
- validation: confirm Copilot-visible tool inventory includes or can invoke `list_resources`, `read_resource`, `search_patterns`, `get_pattern_by_id`, and `get_skill_document_by_id`
- compatibility risk: host-generated wrapper names may differ from canonical FastMCP names; document any observed wrapper-to-canonical mapping
2. Cursor
- primary path: use the client MCP server configuration and resource/tool surfaces supported by the active Cursor version
- fallback path: prefer resource-backed tools first, then canonical thin tools
- validation: capture Cursor `tools/list` equivalent behavior and verify the canonical tool names or required host mappings
- compatibility risk: Cursor may present MCP tools through its own UI labels or internal routing names
3. Claude Desktop
- primary path: configure the local MCP server and inspect advertised tools/resources in Claude Desktop
- fallback path: invoke canonical server tool names exactly as returned by `tools/list`
- validation: run a local smoke prompt that reads `resource://catalog/skills_index` and loads one skill document through `read_resource` or `get_skill_document_by_id`
- compatibility risk: local server configuration and transport setup may fail before tool-name compatibility is tested
4. Generic MCP clients and SDK-based tests
- primary path: protocol-level `resources/list`, `resources/read`, `tools/list`, and `tools/call`
- fallback path: none beyond the canonical tool contract
- validation: automated smoke tests assert exact tool names returned by `tools/list` and successful calls for canonical names
- compatibility risk: SDK/client libraries may expose helper names that differ from raw protocol names
Implementation checklist:
1. Capture each target client's advertised tool names before adding aliases.
2. Prefer fixing documentation or client-side mapping when the server already advertises canonical names correctly.
3. Add a server-side alias only for a confirmed major-client incompatibility.
4. Add regression tests for canonical names, resource-backed tools, and any intentionally supported aliases.
5. Keep public examples centered on `list_resources`/`read_resource` and canonical thin tool names.
### Resources-As-Tools Compatibility Layer ### Resources-As-Tools Compatibility Layer
+9
View File
@@ -69,6 +69,15 @@ When resource attachment is unavailable in the active session, use ResourcesAsTo
4. `get_pattern_by_id` 4. `get_pattern_by_id`
5. `get_skill_document_by_id` 5. `get_skill_document_by_id`
Canonical naming policy:
1. Prefer the five canonical tool names above in prompts and instructions.
2. For compatibility with clients that emit `catalog_*` naming, the server also exposes:
- `catalog_search_patterns`
- `catalog_get_pattern_by_id`
- `catalog_get_skill_document_by_id`
3. Canonical and compatibility alias tools return equivalent payloads for the same input.
The first two are generated from the canonical resource surface and should be preferred in tool-only clients. The first two are generated from the canonical resource surface and should be preferred in tool-only clients.
These should stay read-only, minimal, and schema-aligned with catalog resources. These should stay read-only, minimal, and schema-aligned with catalog resources.
+42 -86
View File
@@ -1,6 +1,6 @@
--- ---
name: pytest-scaffolding name: pytest-scaffolding
description: "Scaffold a maintainable, hierarchical pytest suite with fast defaults and clear escalation paths for FastAPI and SQLAlchemy tests. Use when creating or reorganizing tests, defining fixture/marker boundaries, or making test strategy progressively discoverable." description: "Reference hub for pytest suite structure, naming, markers, and stack-specific testing patterns. Use when you need best-practice guidance and source links for core pytest, FastAPI testing, and SQLAlchemy testing."
argument-hint: "Target scope plus stack details (pure Python, FastAPI, SQLAlchemy sync, SQLAlchemy async, or mixed)" argument-hint: "Target scope plus stack details (pure Python, FastAPI, SQLAlchemy sync, SQLAlchemy async, or mixed)"
x-personal-mcp: x-personal-mcp:
id: pytest-scaffolding id: pytest-scaffolding
@@ -16,111 +16,67 @@ x-personal-mcp:
# Pytest Scaffolding # Pytest Scaffolding
Create test scaffolding that stays fast for daily work and scales safely as dependencies increase. This skill is a collection of best-practice references and source-documentation links for building and maintaining pytest suites.
This skill is optimized for progressive discoverability: Use it to quickly find the right guidance for:
1. Start with the shortest path in this file. 1. Baseline pytest structure and marker strategy.
2. Load exactly one deeper reference only when a decision requires it. 2. Naming conventions and test hierarchy organization.
3. Continue only as far as needed for the current task. 3. FastAPI route, dependency override, and lifespan testing patterns.
4. SQLAlchemy transaction and session testing patterns.
Repository defaults: Repository defaults:
- `uv run pytest` is the canonical invocation. - `uv run pytest` is the canonical invocation.
- pytest settings live in `pyproject.toml` under `[tool.pytest.ini_options]`. - pytest settings live in `pyproject.toml` under `[tool.pytest.ini_options]`.
- strict marker checking is expected (`--strict-markers`). - strict marker checking is expected (`--strict-markers`).
## Discovery Ladder ## Reference Map
### Level 0: Scope And Stack Triage (always) Use this map to open only the reference that matches your immediate need.
Collect:
1. Target scope (repo, package, module).
2. Stack shape (pure Python, FastAPI, SQLAlchemy sync, SQLAlchemy async, or mixed).
3. Speed target (what must stay instant).
4. CI gate policy (which marker groups block merge).
If any are missing, ask concise clarifying questions before scaffolding. - Core pytest practices and command patterns: [pytest-docs.md](./references/pytest-docs.md)
- Naming conventions and hierarchy organization: [naming-and-organization.md](./references/naming-and-organization.md)
- FastAPI-specific testing patterns: [fastapi-testing.md](./references/fastapi-testing.md)
- SQLAlchemy-specific testing patterns: [sqlalchemy-testing.md](./references/sqlalchemy-testing.md)
### Level 1: Core pytest scaffold (default) ## Baseline Best Practices
Use this for all stacks first:
1. Mirror `src/` into `tests/` with one starter file per core module.
2. Classify test intent by cost:
- `unit`: no DB/network/filesystem side effects.
- `integration`: framework, DB, or multi-layer contracts.
- `smoke`: thin critical-path checks.
3. Scaffold each new module with:
- one happy-path test,
- one failure/edge test,
- TODO anchors for deeper assertions.
4. Keep fixtures layered:
- global lightweight fixtures in `tests/conftest.py`,
- domain fixtures in subtree `conftest.py` only when needed.
5. Register markers early: `unit`, `integration`, `smoke`, `slow`, `external`.
6. Validate in order:
- `uv run pytest --collect-only -q`
- `uv run pytest -m unit -q`
- `uv run pytest -q` when dependencies are available.
Load next reference only if needed: These are stable defaults regardless of stack:
- Baseline details and rationale: [pytest-docs.md](./references/pytest-docs.md)
### Level 2: FastAPI branch (only for HTTP/dependency/lifespan concerns) 1. Mirror `src/` into `tests/` so ownership and coverage are obvious.
Escalate here when testing API routes, dependency injection boundaries, or app lifespan behavior. 2. Keep fixtures explicit and layered (`tests/conftest.py` globally, subtree `conftest.py` for domain-specific fixtures).
3. Register markers up front (`unit`, `integration`, `smoke`, `slow`, `external`) and keep strict marker checks enabled.
4. Separate fast feedback (`-m unit`) from broader integration/external lanes.
5. Validate structure early with collection checks before expanding assertions.
Apply these defaults: ## Stack-Specific Guidance
1. Prefer `TestClient` with sync `def` tests for route behavior.
2. Use `AsyncClient` + `@pytest.mark.anyio` only when test logic must await other async work.
3. Prefer `app.dependency_overrides` over patching internals.
4. Reset dependency overrides in teardown after every test/fixture.
5. For startup/shutdown semantics:
- use `TestClient` as context manager, or
- use `LifespanManager` with async client.
Marker intent in FastAPI-heavy suites: - For FastAPI, prefer dependency overrides and clear lifecycle handling; see [fastapi-testing.md](./references/fastapi-testing.md).
- `unit`: service logic without HTTP/DB. - For SQLAlchemy, prefer transaction-safe session fixtures and explicit async loading strategy; see [sqlalchemy-testing.md](./references/sqlalchemy-testing.md).
- `integration`: route + DI + DB contract checks. - For naming and tree organization, use the conventions in [naming-and-organization.md](./references/naming-and-organization.md).
- `smoke`: one request per critical user path.
Reference: [fastapi-testing.md](./references/fastapi-testing.md) ## Source Documentation Entry Points
### Level 3: SQLAlchemy branch (only for DB transaction/session design) Primary upstream docs are curated in each reference page. Start with:
Escalate here when session lifecycle, transaction isolation, or async ORM behavior matters.
Apply these defaults: 1. Pytest good practices: [pytest docs](https://docs.pytest.org/en/stable/explanation/goodpractices.html)
1. Create engine once per test session. 2. Pytest fixtures: [fixture how-to](https://docs.pytest.org/en/stable/how-to/fixtures.html)
2. Open connection + outer transaction per test. 3. Pytest markers: [marker examples](https://docs.pytest.org/en/stable/example/markers.html)
3. Bind session with `join_transaction_mode="create_savepoint"`. 4. FastAPI testing: [FastAPI testing tutorial](https://fastapi.tiangolo.com/tutorial/testing/)
4. Allow code under test to call `commit()` safely; rollback outer transaction at test end. 5. SQLAlchemy transaction testing: [SQLAlchemy external transaction pattern](https://docs.sqlalchemy.org/en/20/orm/session_transaction.html#joining-a-session-into-an-external-transaction-such-as-for-test-suites)
5. Keep unit tests DB-free; DB tests belong under `integration`.
Async additions: ## Quick Validation Commands
- use async fixtures and `@pytest.mark.anyio`.
- set `expire_on_commit=False` for `AsyncSession`.
- avoid implicit lazy IO; use eager loading (`selectinload`) or explicit refresh.
SQLite in-memory with threaded test clients: Use these commands to check structure and execution lanes:
- use `StaticPool` when required by thread/connection sharing.
Reference: [sqlalchemy-testing.md](./references/sqlalchemy-testing.md) 1. `uv run pytest --collect-only -q`
2. `uv run pytest -m unit -q`
## Branching Logic Summary 3. `uv run pytest -m "not external" -q`
- If pure logic can be faked cleanly, keep in `unit`. 4. `uv run pytest -q`
- If framework/DB contract is the behavior under test, use `integration`.
- If external service credentials/network is required, gate behind `external`.
- If suite slows down, split by marker before broadening fixture scope.
- If async relationship access raises `MissingGreenlet`, switch to eager loading strategy.
## Completion Checks
A scaffold pass is complete when all are true:
1. Core source areas map to clear test modules.
2. Fast path (`-m unit`) is deterministic and quick.
3. Integration and external paths are isolated by fixtures and markers.
4. No unregistered-marker failures occur.
5. Structure is understandable without extra oral context.
6. Clear TODO extension points exist for deeper assertions.
## Output Contract ## Output Contract
When this skill is applied, return: When this skill is applied, return:
1. Proposed test tree diff. 1. Which references were consulted.
2. Marker and fixture plan. 2. Recommended structure, naming, fixture, and marker decisions.
3. Exact fast-path and full-path commands. 3. Exact validation commands.
4. Which reference level was loaded and why. 4. Relevant source-doc links for any non-trivial recommendation.
5. Risks or open questions before expanding assertions. 5. Risks, assumptions, or open questions.
@@ -0,0 +1,118 @@
# Pytest Naming Conventions and Test Organization
!!! info "Primary sources"
- [Good integration practices](https://docs.pytest.org/en/stable/explanation/goodpractices.html)
- [Changing standard (Python) test discovery](https://docs.pytest.org/en/stable/example/pythoncollection.html)
- [How to use fixtures](https://docs.pytest.org/en/stable/how-to/fixtures.html)
- [How to parametrize fixtures and test functions](https://docs.pytest.org/en/stable/how-to/parametrize.html)
- [Marker examples](https://docs.pytest.org/en/stable/example/markers.html)
## Agent Quick Path
Use this when creating or reorganizing test modules so naming and hierarchy stay predictable.
1. Mirror the product domain structure in `tests/` so ownership is obvious.
2. Encode broad context in module and class names (`test_*.py`, `Test*`).
3. Keep leaf test names short and behavior-focused (`test_*`).
4. Use `class Test<Subject>:` only for grouping related scenarios.
5. Place fixtures in the nearest `conftest.py` needed by scope.
6. Separate expensive tests with markers first, directories second.
## Naming Conventions
### File and directory naming
- Use lowercase snake_case for test file names: `test_user_service.py`.
- Keep directories domain-oriented and stable over time: `tests/orders/`, `tests/billing/`.
- Prefer descriptive test names over internal ticket numbers or implementation details.
### Test function naming
- Prefer hierarchical naming: put broad context in folder/module/class, and keep the function name focused on the final assertion.
- Keep pytest discovery prefixes intact:
- modules start with `test_`
- classes start with `Test`
- functions start with `test_`
- Start with user-visible behavior or contract, not private helper names.
Recommended pattern:
- module: `test_<subject>.py`
- class: `Test<Operation>` or `Test<Scenario>`
- function: `test_<expected_outcome>`
Examples:
- Flat (still valid): `test_create_order_rejects_invalid_currency`
- Class-context: `TestOrder -> TestCreate -> test_rejects_invalid_currency`
- Module-context: `test_order.py -> TestCreate -> test_rejects_invalid_currency`
- Module + class context can similarly shorten:
- `test_token.py -> TestRefresh -> test_rotates_session_id`
- `test_user_list.py -> TestListUsers -> test_returns_empty_for_new_tenant`
### Test class naming
- Use `class Test<SubjectOrScenario>:` for scenario grouping and context reduction.
- Keep class names noun-focused (`TestOrderService`) rather than action-focused.
- Avoid xUnit style setup inheritance when fixtures can express dependencies directly.
## Hierarchy and Organization Patterns
Two patterns work well; choose one and apply it consistently.
### Pattern A: Source-mirror hierarchy (default for product code ownership)
```text
src/
app/
orders/service.py
billing/invoice.py
tests/
app/
orders/test_service.py
billing/test_invoice.py
```
Use this when teams own modules by source path and want direct test-to-source mapping.
### Pattern B: Cost-lane hierarchy (default for CI policy clarity)
```text
tests/
unit/
orders/test_service.py
integration/
api/test_orders.py
persistence/test_order_repository.py
smoke/
test_health.py
```
Use this when CI gating is based on cost lanes and marker filtering.
### Hybrid rule (recommended)
- Keep a source-mirror tree for local ownership.
- Add markers (`unit`, `integration`, `smoke`, `external`) for runtime policy.
- Avoid duplicating both trees unless the repository already requires it.
## Fixture Placement Strategy
- Put universal lightweight fixtures in `tests/conftest.py`.
- Put domain fixtures in subtree `conftest.py` files close to where they are used.
- Keep fixtures composable and explicit; avoid large fixture "god objects".
- Use `yield` fixtures for teardown so cleanup is always paired with setup.
## Parametrize and ID Naming
- Use `pytest.mark.parametrize` for behavior matrices instead of copy/paste tests.
- Provide explicit `ids=` labels when case names are not obvious.
- Keep IDs business-meaningful (`"expired-token"`, `"zero-balance"`) so failures are readable.
## Collection and Structure Checks
Use these checks after introducing new test files or renaming modules:
- `uv run pytest --collect-only -q`
- `uv run pytest -m unit -q`
- `uv run pytest -m "not external" -q`
If collection surprises appear, verify file names, marker registration, and directory placement first.
## Common Anti-Patterns
- Mixed naming styles (`testFoo.py`, `test_foo.py`, `foo_test.py`) in one repository.
- Deep fixture chains that hide setup behavior.
- Test names that encode implementation details instead of behavior.
- Moving slow tests into `unit` directories without marker updates.
- Sharing mutable module-level state across tests.
@@ -19,6 +19,7 @@ Use this file when you need fast pytest scaffolding defaults without framework-s
Load other references only when needed: Load other references only when needed:
- FastAPI routes/dependency injection/lifespan: `fastapi-testing.md` - FastAPI routes/dependency injection/lifespan: `fastapi-testing.md`
- SQLAlchemy sessions/transactions/DB fixtures: `sqlalchemy-testing.md` - SQLAlchemy sessions/transactions/DB fixtures: `sqlalchemy-testing.md`
- Naming conventions and test hierarchy: `naming-and-organization.md`
## Practical Guidance For This Skill ## Practical Guidance For This Skill
- Use src-aligned test layout and keep test discovery conventional. - Use src-aligned test layout and keep test discovery conventional.
+8
View File
@@ -204,6 +204,14 @@ Preferred tool fallback order:
4. `get_pattern_by_id` 4. `get_pattern_by_id`
5. `get_skill_document_by_id` 5. `get_skill_document_by_id`
Compatibility aliases for clients that use `catalog_*` naming are also available:
1. `catalog_search_patterns`
2. `catalog_get_pattern_by_id`
3. `catalog_get_skill_document_by_id`
Use canonical names first; aliases exist only to preserve interoperability when a client emits non-canonical names.
If confidence is low after discovery, ask one clarifying question before loading more context. If confidence is low after discovery, ask one clarifying question before loading more context.
``` ```
+5
View File
@@ -26,3 +26,8 @@ dev = [
"pre-commit>=4.6.0", "pre-commit>=4.6.0",
"ruff>=0.15.18", "ruff>=0.15.18",
] ]
test = [
"pytest>=9.1.1",
"pytest-asyncio>=1.4.0",
"pytest-cov>=7.1.0",
]
+28
View File
@@ -185,4 +185,32 @@ def get_skill_document_by_id(skill_id: str) -> dict[str, Any]:
} }
@mcp.tool
def catalog_search_patterns(
query: str = "",
tags: list[str] | None = None,
skip: int = 0,
limit: int = 20,
) -> dict[str, Any]:
"""Compatibility alias for clients expecting catalog_* tool naming."""
return search_patterns(
query=query,
tags=tags,
skip=skip,
limit=limit,
)
@mcp.tool
def catalog_get_pattern_by_id(id: str) -> dict[str, Any]:
"""Compatibility alias for clients expecting catalog_* tool naming."""
return get_pattern_by_id(id)
@mcp.tool
def catalog_get_skill_document_by_id(skill_id: str) -> dict[str, Any]:
"""Compatibility alias for clients expecting catalog_* tool naming."""
return get_skill_document_by_id(skill_id)
_install_tool_fallback_transforms() _install_tool_fallback_transforms()
Generated
+140
View File
@@ -208,6 +208,75 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
] ]
[[package]]
name = "coverage"
version = "7.14.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/9c/a3/3834a5564fe8f32154cd7032400d3c2f9c565b2a373fa671f2bbdad6f634/coverage-7.14.2.tar.gz", hash = "sha256:7a2da3d81cfe17c18038c6d98e6592aa9147d596d056119b0ee612c3c8bd5230", size = 923982, upload-time = "2026-06-20T14:49:30.885Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d0/d9/bdd141aa2c605096a8ef63b8435fd4f5fec78946a3cb7b9145840ec78291/coverage-7.14.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:37c94712e533ea06f0b1e4d934811c520b1914ce0e4da3916220717aa7a86bc6", size = 220528, upload-time = "2026-06-20T14:47:49.652Z" },
{ url = "https://files.pythonhosted.org/packages/02/97/d24ae7d2afc62c54a36313d4dedb655c9afbba3003f0f7f1ae81e97af31f/coverage-7.14.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c050bbc7bba94c77e4ed7438f4fda1babe98ab145691d80aa6f60df934a1468b", size = 220883, upload-time = "2026-06-20T14:47:51.036Z" },
{ url = "https://files.pythonhosted.org/packages/f8/0e/d8f00efd3df0d63e6843ebcbade9e4119d60f5376753c9705d84b014c775/coverage-7.14.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a7af571767a2ee342a171c16fc1b1a07a0bf511606d381703fb7cf397fe49d46", size = 252395, upload-time = "2026-06-20T14:47:52.627Z" },
{ url = "https://files.pythonhosted.org/packages/1c/1c/ab9510dfe1a16a35a10f90efad0d9a9cf61b9876973752968f2ba882f73f/coverage-7.14.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8b4910cce599cd2438f8da65f5ef199a70a1cdb6ab314926df78271ca5954240", size = 255131, upload-time = "2026-06-20T14:47:54.235Z" },
{ url = "https://files.pythonhosted.org/packages/ba/dd/70171e9371003b33dc6b20f527ac216ff91bbe5c1088e754eb8950d79193/coverage-7.14.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c33e9e4878972f430b0cc06de3bf2a28d054a9efb4f8426d27de0d9cb81396ff", size = 256246, upload-time = "2026-06-20T14:47:55.61Z" },
{ url = "https://files.pythonhosted.org/packages/0f/80/a68b1dd81d5c011e17fd6ab0d707d33297df1d0c618114b9b750a2219c80/coverage-7.14.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e7967ea55c6dea6becba4d5870e2fa0aa4915a8be7ebff1bb79e6207aa75ce8d", size = 258504, upload-time = "2026-06-20T14:47:56.979Z" },
{ url = "https://files.pythonhosted.org/packages/8e/7b/40baaa946189f5317cd77d484e39b9b0727d02ebada0a12162374f2faee2/coverage-7.14.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d1322f237c2979b84096f4239c17828ff17fea6b3bbe96c44381c5f587c44c26", size = 252808, upload-time = "2026-06-20T14:47:58.418Z" },
{ url = "https://files.pythonhosted.org/packages/d5/05/b19517b09c43d1e8591de6c13178b0c03166c31e1adbebda378e64c66b9a/coverage-7.14.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:77849525340c99f516d793dddbcee16b18d50af892ac43c8de1a6f343d41e3b5", size = 254166, upload-time = "2026-06-20T14:48:00.004Z" },
{ url = "https://files.pythonhosted.org/packages/ae/f5/6e65da5957e041d2094a9b97736628dd80160f1cc007a50790bbb2668c1a/coverage-7.14.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ef11695493ec3f06f7b2678ca274bcabb4ca04057317df268ddbfd8b05f661a8", size = 252310, upload-time = "2026-06-20T14:48:01.458Z" },
{ url = "https://files.pythonhosted.org/packages/2d/de/01b5274f0db63175b04d9354eff68d2d268b8b57a1b2db7d3dcb1f2c9dbb/coverage-7.14.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8134f0e0723e080d1c27bbe8fc149f0162e429fa1852482150015d0fce83eaf1", size = 256379, upload-time = "2026-06-20T14:48:02.981Z" },
{ url = "https://files.pythonhosted.org/packages/71/d6/9a2ffbca41e2f8f86f61e8b78b86afa433ec8cdeac4908ace93a28fe3ff0/coverage-7.14.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:914eead2b843fc357f733b3fe39cc94f1b53d466e8cfe03080b1ed9d24ccfc73", size = 251880, upload-time = "2026-06-20T14:48:04.463Z" },
{ url = "https://files.pythonhosted.org/packages/e3/ff/20bd54a43c88c08f474e6cb355a97e024e38412873ef0a581629abe1e26f/coverage-7.14.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e4b2d5e847fb7958583b74910cc19e5ec4ece514487385677b26433b2546116e", size = 253753, upload-time = "2026-06-20T14:48:05.99Z" },
{ url = "https://files.pythonhosted.org/packages/35/2a/2b3482c30d8344f301d8df6ff232a321f2ab87d5ac97ba21891a68638131/coverage-7.14.2-cp312-cp312-win32.whl", hash = "sha256:e753db9e40dda7302e0ac3e1e6e1325fb7f7b4694f87a7314ab15dd5d57911a7", size = 222584, upload-time = "2026-06-20T14:48:07.361Z" },
{ url = "https://files.pythonhosted.org/packages/f6/5e/83934ffff147edd313fe925db426e8f7ccad9e4663262eb5c4db4e345658/coverage-7.14.2-cp312-cp312-win_amd64.whl", hash = "sha256:d32e5ca5f16dafb269ee50b60d32b00c704b3f6f78e238105f1d94a3a5f24bf5", size = 223118, upload-time = "2026-06-20T14:48:08.837Z" },
{ url = "https://files.pythonhosted.org/packages/bb/ee/616b4f38a34f076f3045d3eedfa764d34d82e6a6cc6b300acb0f1ff22a98/coverage-7.14.2-cp312-cp312-win_arm64.whl", hash = "sha256:dc366f158e2fb2add9d4e57338ca48f12611024278688ee657eb0b853fcb5de5", size = 222504, upload-time = "2026-06-20T14:48:10.436Z" },
{ url = "https://files.pythonhosted.org/packages/6d/09/b5b334c27960e7aac0003b96491bada7838dc641099fa64a1a598abf33cd/coverage-7.14.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e5f077641a6713ce9d38df9e85d4fb9e008677fc0775cbaeb32ddfc3b319d4ca", size = 220552, upload-time = "2026-06-20T14:48:11.847Z" },
{ url = "https://files.pythonhosted.org/packages/79/20/879a000c319b4df7b50e4d688c0f7c0f6b5ac9d7b18848cbc00eabf26efe/coverage-7.14.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0907f39b49ae818fe8af50aaa0f19afbc8ca164aea0865181ca7af17a3ac690b", size = 220919, upload-time = "2026-06-20T14:48:13.397Z" },
{ url = "https://files.pythonhosted.org/packages/f6/b7/326dded4371bab60f42215797944a356e4d81a3cee106121c7f7dd531604/coverage-7.14.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5734d47669118d75c28981e562d4530ceb77342d31ffef6def5edd5ad4f05d7b", size = 251917, upload-time = "2026-06-20T14:48:14.931Z" },
{ url = "https://files.pythonhosted.org/packages/eb/14/b3232ba218a0d1a70883d2675f18ff465de9e8e5e3346e81dc2b079838bd/coverage-7.14.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1d9a1b5813d00ea6151f6ccf64d1fa16892771dfdda12ba87162d15ec4ea3e1e", size = 254515, upload-time = "2026-06-20T14:48:16.545Z" },
{ url = "https://files.pythonhosted.org/packages/b7/7a/d77bcbee1cad71b42776574114b462225cc9125b4982f43da1b66adc850f/coverage-7.14.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f0a80f4c8ac3f774210b1cc1bc0e31e75502f2818dda9a144ff90e702c4d91d", size = 255749, upload-time = "2026-06-20T14:48:18.214Z" },
{ url = "https://files.pythonhosted.org/packages/86/86/97377937b29e9e44a1529bb20cb74dbcf80ed9006d87d7e742ff69e44b67/coverage-7.14.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e66f3f22d6c1515ce70f2e7c3e9c6f3ff0ff33480125c9f9c53e8f6508e30f", size = 257882, upload-time = "2026-06-20T14:48:19.7Z" },
{ url = "https://files.pythonhosted.org/packages/c1/a4/0fc8fe68bc505450bb068a2823ac7797bd8495240ccb8b4a5a1da1ee7e62/coverage-7.14.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6a2c37c3114f87ca7f10113756026eecb49656514debad600dcbec21f355ccea", size = 252144, upload-time = "2026-06-20T14:48:21.176Z" },
{ url = "https://files.pythonhosted.org/packages/8d/4a/450094ddc41ab0d2eb4a0457b3856400ea3329568d1303696e85de099ae6/coverage-7.14.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3b16a7959d04b1497281c062c180413565c3f3469211d78799ad5b9a75f67796", size = 253882, upload-time = "2026-06-20T14:48:22.701Z" },
{ url = "https://files.pythonhosted.org/packages/d0/28/2f6ae6d98265d9aa6bac311c4a93403675905b03aca95dc4373080279d75/coverage-7.14.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6466c6999545cf00c4c142dfcbbf2db396dc735f005dcf8f91d57e351a79472b", size = 251846, upload-time = "2026-06-20T14:48:24.295Z" },
{ url = "https://files.pythonhosted.org/packages/c2/6e/707281468400794d52874e8fb5e38ff7578a0ff32ed49fe4fe85f192d0fc/coverage-7.14.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c60915ebb8f562317ba5ff6b8c32e25c0882289b201a9f2fb2987f91efd95d8", size = 256002, upload-time = "2026-06-20T14:48:26.015Z" },
{ url = "https://files.pythonhosted.org/packages/c2/83/5e963120de4011257a950ce4cfb7fc833ddf3fee19db495268d3dec28154/coverage-7.14.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:33b830850488acbcd358c78a4fecfafe7031667b4da8ddff5546295dc962cdeb", size = 251665, upload-time = "2026-06-20T14:48:27.654Z" },
{ url = "https://files.pythonhosted.org/packages/e9/78/66b482cd525083bcc0bc894c16db79dabac37490065b53b07d6e8ab77202/coverage-7.14.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d0f845539230b8269aec902bc978b0cc403f52f002d18a04492efc943404d0bc", size = 253435, upload-time = "2026-06-20T14:48:29.354Z" },
{ url = "https://files.pythonhosted.org/packages/e6/61/0663fb8cb530c8b11819b920109694eee95a3b22960a9495be0200f657f1/coverage-7.14.2-cp313-cp313-win32.whl", hash = "sha256:a8ac51a2e441e9119b9395f4d893fbc4934c64c8ba58be9b9eaa85591249e548", size = 222591, upload-time = "2026-06-20T14:48:31.142Z" },
{ url = "https://files.pythonhosted.org/packages/a6/47/1536d2b009c2848c3682500f497053f4645e70911afe02f594000997831a/coverage-7.14.2-cp313-cp313-win_amd64.whl", hash = "sha256:039b264cdb31c44b48f9821e2afbf8f37df49e0fb837e24a942918b36c567e31", size = 223134, upload-time = "2026-06-20T14:48:32.696Z" },
{ url = "https://files.pythonhosted.org/packages/28/9a/33ba4f335dd60bb34350318283d784f46018070e67b7d4df7c910ec9d9a0/coverage-7.14.2-cp313-cp313-win_arm64.whl", hash = "sha256:7f2ef591e381cc36b8e53334e1b842c760c520c8a52d01e8626209400e93fe6a", size = 222529, upload-time = "2026-06-20T14:48:34.237Z" },
{ url = "https://files.pythonhosted.org/packages/fc/bc/120390669817ede714ab141ae0a2a73240fd7354aac992c41dc0bd19570f/coverage-7.14.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7a0d1f026b72d627fa5c8a57cbc86ad209b64aa2a65833c83b290ace5cbee126", size = 220593, upload-time = "2026-06-20T14:48:35.755Z" },
{ url = "https://files.pythonhosted.org/packages/4f/a3/7f1cfacd76af91e585f7ad689d7168002b444ed2a8ce59f2daaff10089b5/coverage-7.14.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4d2b86f81c1c9310a7e774e3cc9e927a3d0bf583ecbfa01498dd626930025428", size = 220925, upload-time = "2026-06-20T14:48:37.35Z" },
{ url = "https://files.pythonhosted.org/packages/e7/10/6514b2525bb672eb8b43703e46d061d694111db21efe7609db722df2233f/coverage-7.14.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d76bdc1f9396ae70a55d050cf9743d88141c62ce0a22a3f627fab1d11c2f8bc6", size = 251974, upload-time = "2026-06-20T14:48:39.109Z" },
{ url = "https://files.pythonhosted.org/packages/23/b4/4533091541c6620ecd68115bbfa1c61265b775618adef3a5fd137f4582e9/coverage-7.14.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cda36d8e7bfd63b3e44e75163265429caa5d935b672b00f71bccc8c010518c64", size = 254479, upload-time = "2026-06-20T14:48:40.871Z" },
{ url = "https://files.pythonhosted.org/packages/06/af/e251a143d5d106385dbca696c553afab6b69f7f6bc376a34e089cc0b8b32/coverage-7.14.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0904f3b79d7b845bef0715afe1900da634d12b97f05b9479cb472880ca07cb9c", size = 255824, upload-time = "2026-06-20T14:48:42.608Z" },
{ url = "https://files.pythonhosted.org/packages/9c/53/9e5876e60efbaa79d743d1948a5015ddc05b808db1cd62228acf83e87d43/coverage-7.14.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b6795ca4198d6cb7fc2c6163214f6555a6bc5f0ae1e268e76139dec4b37c4499", size = 258139, upload-time = "2026-06-20T14:48:44.263Z" },
{ url = "https://files.pythonhosted.org/packages/85/5a/d35a4f431fb594e46b81cad4a13b470b017e918f347c1c0b260f7494fa1e/coverage-7.14.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c41e9b60fc0fa57f5d73306417d2f9d668202cca6944f9435878c55a5e7ae213", size = 252002, upload-time = "2026-06-20T14:48:45.961Z" },
{ url = "https://files.pythonhosted.org/packages/0c/e2/f5b304c8139c606c4f1b230d3a257d0c88edfbbdf06c58364f07625dc45c/coverage-7.14.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:419d2aadd5746efc2e9df0f33c05570d8192e6f6a6098ab05acce586f44ce8a5", size = 253832, upload-time = "2026-06-20T14:48:47.582Z" },
{ url = "https://files.pythonhosted.org/packages/86/bc/bbbd283daa6be4f68aad4ad4066fd39ae98e4174db8c03ab26c5803d6234/coverage-7.14.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1c5d273c5f1411c0d26c4f066c398d4a434b1f97bb5fa409189bedce86d4add4", size = 251799, upload-time = "2026-06-20T14:48:49.42Z" },
{ url = "https://files.pythonhosted.org/packages/69/8d/0745fceb89c9e5f7dd8ed243d97dc8561b7a95545741e2409d2b34654824/coverage-7.14.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5fe465bc691264adce601527a972990c1174075d86bcbe9968fd20c95e0b1948", size = 256075, upload-time = "2026-06-20T14:48:51.065Z" },
{ url = "https://files.pythonhosted.org/packages/a2/a0/441d9a5255cf021ab41ee00c014a4607d1c72d5e5bef0a4fdaa5be86a907/coverage-7.14.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:6fbb61617af1c56f95d53170ae9fa6c9aef6de1abd02fcc50064bfc672efb18d", size = 251612, upload-time = "2026-06-20T14:48:52.653Z" },
{ url = "https://files.pythonhosted.org/packages/50/37/3d19c5e32d4a529c068eb296abfa3e455bd2c0f9311ecf26280f408ff8e0/coverage-7.14.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e1eff22b831dfd5694989cc1f0789980f18391f614ac67c851af9a8e6d25e9ba", size = 253270, upload-time = "2026-06-20T14:48:54.3Z" },
{ url = "https://files.pythonhosted.org/packages/3d/b0/54dd13937297518da6d092cc2c39d9340ec2194bdfa92e0a64694d643e23/coverage-7.14.2-cp314-cp314-win32.whl", hash = "sha256:58e91be0a233adef698d3e6be54f10401bb91fd7854c0d4c4d50e0d3711e72f1", size = 222796, upload-time = "2026-06-20T14:48:56.084Z" },
{ url = "https://files.pythonhosted.org/packages/51/45/7a10e0909919686e335fdd95869cfb222d55243ebff27dc5cf59ca259a1f/coverage-7.14.2-cp314-cp314-win_amd64.whl", hash = "sha256:d8429bf97906bfe6c61f9dbfb3342e0d88120da61939da8bd04f830cc3eab3b8", size = 223285, upload-time = "2026-06-20T14:48:57.729Z" },
{ url = "https://files.pythonhosted.org/packages/2e/03/9cb197eb4b3d1a2eccb2537c226a93c80522c5b8afc5dd93e1993d7bb021/coverage-7.14.2-cp314-cp314-win_arm64.whl", hash = "sha256:13609d9d77249447aa73357b14831b0f3b95f275026c9ff20dd105f981f53a0c", size = 222712, upload-time = "2026-06-20T14:48:59.413Z" },
{ url = "https://files.pythonhosted.org/packages/d6/3c/e59f498511080d20bf866b0af9eeab820feb91547dae2084cb9bb7fb0e58/coverage-7.14.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9818486c2bac88ae931df7e04905ee29bef49fd218c00f5f02bed4855254a101", size = 221325, upload-time = "2026-06-20T14:49:01.447Z" },
{ url = "https://files.pythonhosted.org/packages/d3/37/8d7955f7e701e69198bd0a0132ea76518c078a635b930a4924e2ccfa70f0/coverage-7.14.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:58055adffabfa243516a197aa9f85f0dd56d905b0fba1a10193269759c29ccb0", size = 221594, upload-time = "2026-06-20T14:49:03.13Z" },
{ url = "https://files.pythonhosted.org/packages/34/7a/6738e1e1533ce8ec4e2e472696eefdd4723864d7efaa140e433053bf576a/coverage-7.14.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:535747dbc200349d7fb434cffcb28e770f0290f69b225f56dc3803aa7210cdea", size = 262957, upload-time = "2026-06-20T14:49:04.829Z" },
{ url = "https://files.pythonhosted.org/packages/35/c4/d1be863cd39e0955904315fece67c5c23e046563f5eea0ceac16c547a759/coverage-7.14.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:420c66e35d85c0ca5dc6a38147d83ef239762542900e5921ebbdb89333c540ea", size = 265081, upload-time = "2026-06-20T14:49:07.018Z" },
{ url = "https://files.pythonhosted.org/packages/72/7f/412df3c3c251284a11834287fd6f7e3bb98c528c53e030589e9344a3ef80/coverage-7.14.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2cf17b33773be446a588551ea6a746b2d70dd0bc90dc31f1dd7648975a63c6b", size = 267500, upload-time = "2026-06-20T14:49:08.709Z" },
{ url = "https://files.pythonhosted.org/packages/54/68/7d0764e83459455384d5c04179ce2d2a837bef01b9ba463079c6e8b31361/coverage-7.14.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:adb4a5fef041f7179bb264203add873c147d169cf2f8d0adae89ff2e51271bac", size = 268619, upload-time = "2026-06-20T14:49:10.405Z" },
{ url = "https://files.pythonhosted.org/packages/14/68/1292164ac70cbcc86ac3982da31a6fbb42bb4bcebf6e5cf73c99cfcfd50d/coverage-7.14.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9c012ec357dec9408a83dad5541172a63c5cfa1421709f2e5811480d31ae1b28", size = 262066, upload-time = "2026-06-20T14:49:12.257Z" },
{ url = "https://files.pythonhosted.org/packages/20/44/fd6fdf3f63b6e00a1a9230022d072ded5189576001685706aa6524187c65/coverage-7.14.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:dacd0ecd08fda3cb2f85b60cabea7da326dcb2fc15fbb23a88830a80144cc9f2", size = 264953, upload-time = "2026-06-20T14:49:14.13Z" },
{ url = "https://files.pythonhosted.org/packages/39/29/e803fea3da89eaeb5b6b41b3ccd039fe9f3300a900e3803baac1a998529f/coverage-7.14.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f27e980f2feba5dfe7a32b22b125470de69c0bd113c75e16165de909a777f512", size = 262555, upload-time = "2026-06-20T14:49:15.803Z" },
{ url = "https://files.pythonhosted.org/packages/32/3c/b360e48ac68e3236c04cb83658382e7f5be7efbbec2e1faae3dcca432783/coverage-7.14.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:105c00efb65c863630b2b63cbf7b8267e4da2d44b62284efbb19a03b04c337d4", size = 266289, upload-time = "2026-06-20T14:49:17.962Z" },
{ url = "https://files.pythonhosted.org/packages/59/12/1ed6d9274d599c586e2d1aa9818765dcdae6bb52aa88afa2fcd868398191/coverage-7.14.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:571173fa04c8e8d6235ab32ae67fecca97777e2e1b4a1a30f3022c34e397c1c1", size = 261402, upload-time = "2026-06-20T14:49:19.708Z" },
{ url = "https://files.pythonhosted.org/packages/44/17/eb6cf12a4538cda937aefbeabb15377a8a30b377b484e63d31c9da790966/coverage-7.14.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e532f34d42d1a421fa00ed6b7735d14ac2e340256c1bad26a5e1dc1252b0bed7", size = 263715, upload-time = "2026-06-20T14:49:21.427Z" },
{ url = "https://files.pythonhosted.org/packages/8a/ca/4bafdb9d372ab05d6ed3a63e7f00d3195d169d0afea00f617c026e386c19/coverage-7.14.2-cp314-cp314t-win32.whl", hash = "sha256:243971550fb46c3039257f75e65610002d84304c505f609bbd9779e20a653a0a", size = 223103, upload-time = "2026-06-20T14:49:23.24Z" },
{ url = "https://files.pythonhosted.org/packages/35/cb/0765dbd9011d2e47315f1da31e62c5fe231f04a6ec8da213e64c4505896d/coverage-7.14.2-cp314-cp314t-win_amd64.whl", hash = "sha256:60fb0ca084a92da96474b8b405a7ea76dfecac3c68db54383e7934b6f3871169", size = 223934, upload-time = "2026-06-20T14:49:25.347Z" },
{ url = "https://files.pythonhosted.org/packages/4e/ce/373dde027ecd0ae54511430fe7569f838d3a0376b70333ba9fd20c76b836/coverage-7.14.2-cp314-cp314t-win_arm64.whl", hash = "sha256:36a0a3f42ed7dfdbca2a69a541519ffd5064a5692152fc0018109e74370d7345", size = 223249, upload-time = "2026-06-20T14:49:27.241Z" },
{ url = "https://files.pythonhosted.org/packages/a3/5e/a8ba14ceb014f39bd5e3f7077150718c7de61c01ce326bfe7e8eae9b19b2/coverage-7.14.2-py3-none-any.whl", hash = "sha256:04d92589e481a8b68a005a5a1e0646a91c76f322c397c4635298c57cf63699b5", size = 212325, upload-time = "2026-06-20T14:49:28.991Z" },
]
[[package]] [[package]]
name = "cryptography" name = "cryptography"
version = "49.0.0" version = "49.0.0"
@@ -531,6 +600,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
] ]
[[package]]
name = "iniconfig"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
]
[[package]] [[package]]
name = "jaraco-classes" name = "jaraco-classes"
version = "3.4.0" version = "3.4.0"
@@ -852,6 +930,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" },
] ]
[[package]]
name = "pluggy"
version = "1.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
]
[[package]] [[package]]
name = "pre-commit" name = "pre-commit"
version = "4.6.0" version = "4.6.0"
@@ -886,6 +973,11 @@ dev = [
{ name = "pre-commit" }, { name = "pre-commit" },
{ name = "ruff" }, { name = "ruff" },
] ]
test = [
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-cov" },
]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
@@ -902,6 +994,11 @@ dev = [
{ name = "pre-commit", specifier = ">=4.6.0" }, { name = "pre-commit", specifier = ">=4.6.0" },
{ name = "ruff", specifier = ">=0.15.18" }, { name = "ruff", specifier = ">=0.15.18" },
] ]
test = [
{ name = "pytest", specifier = ">=9.1.1" },
{ name = "pytest-asyncio", specifier = ">=1.4.0" },
{ name = "pytest-cov", specifier = ">=7.1.0" },
]
[[package]] [[package]]
name = "py-key-value-aio" name = "py-key-value-aio"
@@ -1091,6 +1188,49 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" }, { url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" },
] ]
[[package]]
name = "pytest"
version = "9.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "iniconfig" },
{ name = "packaging" },
{ name = "pluggy" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
]
[[package]]
name = "pytest-asyncio"
version = "1.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" },
]
[[package]]
name = "pytest-cov"
version = "7.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "coverage" },
{ name = "pluggy" },
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" },
]
[[package]] [[package]]
name = "python-discovery" name = "python-discovery"
version = "1.4.2" version = "1.4.2"
-1
View File
@@ -55,7 +55,6 @@ nav = [
{ "Copilot" = "copilot.md" }, { "Copilot" = "copilot.md" },
{ "Usage" = "usage.md" }, { "Usage" = "usage.md" },
{ "Future Work" = "future_work.md" }, { "Future Work" = "future_work.md" },
{ "New Skill" = "new_skill.md" },
{ "Security" = "securing.md" }, { "Security" = "securing.md" },
] }, ] },
{ "Skills" = [ { "Skills" = [