diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index cf46881..5137020 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -8,7 +8,7 @@ "name": "archipy", "source": ".", "description": "Cursor and Claude Code plugin: rules, skills, and commands for building apps with ArchiPy", - "version": "0.8.0" + "version": "0.9.0" } ] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 552cdc3..6dd9e03 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "archipy", - "version": "0.8.0", + "version": "0.9.0", "description": "Cursor and Claude Code plugin: rules, skills, and commands for building apps with ArchiPy", "author": { "name": "SyntaxArc" diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index cf46881..5137020 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -8,7 +8,7 @@ "name": "archipy", "source": ".", "description": "Cursor and Claude Code plugin: rules, skills, and commands for building apps with ArchiPy", - "version": "0.8.0" + "version": "0.9.0" } ] } diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 856448e..39b4334 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "archipy", - "version": "0.8.0", + "version": "0.9.0", "description": "Cursor and Claude Code plugin: rules, skills, and commands for building apps with ArchiPy", "author": { "name": "SyntaxArc" diff --git a/AGENTS.md b/AGENTS.md index d18a65f..a82b04c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,10 +22,15 @@ Consumer plugin for **apps that depend on** PyPI [`archipy`](https://pypi.org/pr | Helpers | `/scaffold-utils`, `/scaffold-decorator`, `/scaffold-interceptor` | | Redis search | `/redis-search` (full-text, vector, caching) | | Health checks | `/scaffold-health-checks`, `/docs-health-checks` | +| OpenTelemetry | `/scaffold-observability`, `/docs-observability` | | Docs | `/docs-quickstart`, `/docs-adapters`, `/docs-helpers`, `/docs-config`, `/docs-errors`, `/docs-testing`, `/docs-observability`, `/docs-health-checks` | Bundled cheat sheet: `skills/archipy-docs/reference.md`. +When a command names a skill, read that skill's `SKILL.md` in full before generating files. Resolve +`skills/*/reference/` templates from the plugin installation (`$CURSOR_PLUGIN_ROOT` or `$CLAUDE_PLUGIN_ROOT`), copy +them into the app, and never edit plugin templates in place. + Live docs: https://syntaxarc.github.io/ArchiPy/ ## Hard rules (apps) @@ -34,3 +39,17 @@ Live docs: https://syntaxarc.github.io/ArchiPy/ - Domain adapters under `repositories/{domain}/adapters/` — not a top-level app `adapters/` package. - FastAPI/uvicorn from `config.FASTAPI`; prefer `AppUtils.create_fastapi_app` / gRPC factories. - Specific exceptions; always `raise ... from e`. +- Python 3.14 typing, double quotes, Google docstrings, complete public annotations; follow app tooling when stricter. +- No hardcoded secrets or logged credentials/tokens; parameterize queries and justify narrow security suppressions. + +## Rule index + +- Ownership/precedence: `rules/rules-index-for-apps.mdc` +- Always-on: `architecture-for-apps.mdc`, `python-code-style-for-apps.mdc`, `security-for-apps.mdc`, + `tooling-for-apps.mdc`, `contributing-for-apps.mdc` +- Python typing: `typing-for-apps.mdc` +- Layers: `using-archipy-models.mdc`, `using-archipy-adapters.mdc`, `using-archipy-repositories.mdc`, + `using-archipy-logics.mdc`, `using-archipy-services.mdc` +- Support/config: `using-archipy-utils.mdc`, `using-archipy-decorators.mdc`, `using-archipy-interceptors.mdc`, + `config-and-di.mdc` +- Tests: `testing-bdd-for-apps.mdc` diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e0a7a9..79185dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.9.0] - 2026-09-07 + +### Added + +- `/scaffold-observability` command and `scaffold-archipy-observability` skill for ArchiPy 5.x OpenTelemetry setup, + stack-specific extras, bootstrap ordering, decorators, and test guidance +- Consumer app rules for Python style, strict typing, security, `uv` tooling, contribution workflow, and rule precedence +- Catalog guards for skill quality, command/skill prompt parity, referenced templates, required rules, rule frontmatter, + and ArchiPy 5.x API drift + +### Changed + +- Scaffolding skills now inspect existing projects first, ask only unresolved choices, preserve files, and require + focused verification +- Slash commands now read their complete skills and follow the same inspect-first workflow +- Bundled reference and observability guidance now target ArchiPy 5.1 OpenTelemetry APIs and `otel*` extras +- Layer rules now align DTOs, adapter/repository error boundaries, AppUtils error mapping, config sources, BDD workflow, + and Claude Code rule injection with current ArchiPy conventions +- Plugin-template paths are resolved from Cursor/Claude plugin roots and copied into consumer apps + +### Fixed + +- Removed stale ArchiPy 4.x `TracingUtils`, Prometheus, Sentry, Elastic APM, and capture-decorator recommendations +- Removed redundant direct `grpcio-health-checking` installation when `archipy[grpc]` already provides it +- Corrected service helper documentation link and DTO examples to use ArchiPy `BaseDTO` + +### Tests + +- Added regression coverage for verification loops, inspect-first commands, template references, ArchiPy API drift, + and injection of all always-on consumer rules + ## [0.8.0] - 2026-08-24 ### Added diff --git a/README.md b/README.md index 95c462f..2601755 100644 --- a/README.md +++ b/README.md @@ -72,11 +72,17 @@ Then restart Claude Code or run `/reload-plugins`. ## What’s included -### Rules (11) +### Rules (17) | Rule file | Applies when | |----------------------------------|--------------------------------------------------| +| `rules-index-for-apps.mdc` | Always | | `architecture-for-apps.mdc` | Always | +| `python-code-style-for-apps.mdc` | Always | +| `security-for-apps.mdc` | Always | +| `tooling-for-apps.mdc` | Always | +| `contributing-for-apps.mdc` | Always | +| `typing-for-apps.mdc` | `**/*.py` | | `using-archipy-adapters.mdc` | `**/repositories/**/adapters/**/*.py` | | `using-archipy-utils.mdc` | `**/helpers/utils/**/*.py` | | `using-archipy-decorators.mdc` | `**/helpers/decorators/**/*.py` | @@ -88,7 +94,7 @@ Then restart Claude Code or run `/reload-plugins`. | `using-archipy-services.mdc` | `**/services/**/*.py`, `**/manage.py` | | `testing-bdd-for-apps.mdc` | `**/features/**/*` | -### Skills (12) +### Skills (13) | Skill | When to use | |----------------------------------|---------------------------------------------------------------------------| @@ -102,10 +108,11 @@ Then restart Claude Code or run `/reload-plugins`. | `scaffold-archipy-decorator` | Wire or create `helpers/decorators` | | `scaffold-archipy-interceptor` | Wire or create `helpers/interceptors` | | `scaffold-archipy-health-checks` | Scaffold FastAPI/gRPC health checks and optional K8s probe YAML | +| `scaffold-archipy-observability` | Configure ArchiPy 5.x OpenTelemetry and stack instrumentation | | `redis-search` | RediSearch full-text, vector search, and search-cache adapters | | `archipy-docs` | Answer “how do I… with ArchiPy?” using bundled `reference.md` + live docs | -### Commands (19) +### Commands (20) | Command | Action | |---------------------------|------------------------------------------------------| @@ -119,6 +126,7 @@ Then restart Claude Code or run `/reload-plugins`. | `/scaffold-decorator` | Run scaffold-archipy-decorator | | `/scaffold-interceptor` | Run scaffold-archipy-interceptor | | `/scaffold-health-checks` | Scaffold FastAPI/gRPC health + K8s probe YAML | +| `/scaffold-observability` | Configure OpenTelemetry traces, metrics, and logs | | `/redis-search` | Scaffold Redis full-text / vector / search-cache | | `/docs-quickstart` | Quickstart + bundled reference | | `/docs-adapters` | Adapter patterns + docs links | @@ -189,7 +197,7 @@ There is **no** `/scaffold-helper` — use the three helper-specific commands. ### `/scaffold-decorator` - **Purpose:** Prefer ArchiPy decorators (`ttl_cache_decorator`, `postgres_sqlalchemy_atomic_decorator`, - `capture_span` / `capture_transaction`, …). + `trace_span` / `trace_root`, `measure_duration` / `count_calls`, …). - **Asks:** Purpose; sync/async; built-in vs custom. - **Outcome:** Usage snippet or `helpers/decorators/.py` with example. @@ -207,6 +215,13 @@ There is **no** `/scaffold-helper` — use the three helper-specific commands. - **Outcome:** Shared check helpers + `health_service.py` and/or `health_grpc_service.py` (`grpc.health.v1`) + optional `deploy/k8s-probes.yaml` (`httpGet` / `grpc`). +### `/scaffold-observability` + +- **Purpose:** Configure ArchiPy 5.x OpenTelemetry traces, metrics, logs, and stack-specific instrumentation. +- **Asks:** Only unresolved signals, OTLP endpoint/protocol, service name, sampling, and log-level choices. +- **Outcome:** Matching `otel*` extras, `OTEL__*` config documentation, early `OtelUtils` bootstrap, and focused + verification. + ### `/redis-search` - **Purpose:** Add Redis full-text (RediSearch), vector, or search-cache adapters under a domain repository. @@ -217,8 +232,8 @@ There is **no** `/scaffold-helper` — use the three helper-specific commands. `/docs-observability` / `/docs-health-checks` -- **Purpose:** Orient the agent on the matching topic using `skills/archipy-docs/reference.md` first, then live docs - URLs. +- **Purpose:** Orient the agent using the bundled ArchiPy 5.x reference and live docs; version-sensitive topics use live + docs first. - **Outcome:** Short guidance + links; may suggest a `/scaffold-*` follow-up. ## Rules deep dive @@ -230,6 +245,13 @@ Always-on layer map, call flow (`services → logics → repositories → adapte - **Do:** Keep models free of I/O; UoW on logics; cross-domain via logics only. - **Don’t:** Import repositories/adapters from `models/`; invent a top-level app `adapters/` package. +### Cross-cutting app rules + +`python-code-style-for-apps`, `typing-for-apps`, `security-for-apps`, `tooling-for-apps`, and +`contributing-for-apps` carry the consumer-safe parts of ArchiPy's core standards: Python 3.14 style, complete typing, +runtime-evaluated type safety, secrets/logging controls, parameterized queries, `uv`, and focused PR workflow. App +tooling wins when stricter; library-only Make targets and per-file exemptions are not copied. + ### `using-archipy-adapters` Prefer ArchiPy extras and thin domain wrappers under `repositories/{domain}/adapters/`. @@ -338,7 +360,7 @@ always-on rule text, and the post-tool hook injects glob-matched rules. | `/docs-config` | [Config](https://syntaxarc.github.io/ArchiPy/tutorials/config_management/), [DI](https://syntaxarc.github.io/ArchiPy/tutorials/dependency_injection/) | | `/docs-errors` | [Error handling](https://syntaxarc.github.io/ArchiPy/tutorials/error_handling/) | | `/docs-testing`, `scaffold-archipy-bdd` | [Testing strategy](https://syntaxarc.github.io/ArchiPy/tutorials/testing_strategy/) | -| `/docs-observability` | [Observability](https://syntaxarc.github.io/ArchiPy/tutorials/observability/) | +| `/docs-observability`, OTel scaffold | [Observability](https://syntaxarc.github.io/ArchiPy/tutorials/observability/) + `/scaffold-observability` | | `/docs-health-checks`, health scaffold | Bundled `reference.md` Health checks + `/scaffold-health-checks` | | `archipy-docs` / `reference.md` | [Docs home](https://syntaxarc.github.io/ArchiPy/), [API reference](https://syntaxarc.github.io/ArchiPy/api_reference/) | diff --git a/commands/docs-observability.md b/commands/docs-observability.md index ad30318..146188b 100644 --- a/commands/docs-observability.md +++ b/commands/docs-observability.md @@ -1,6 +1,6 @@ --- name: docs-observability -description: Open ArchiPy observability guidance (tracing, metrics, APM) +description: Open ArchiPy OpenTelemetry guidance (traces, metrics, logs) --- # /docs-observability @@ -9,9 +9,10 @@ Use the **archipy-docs** skill and the Observability section of `skills/archipy- Cover: -- Tracing: `TracingUtils`, `capture_span` / `capture_transaction` -- Metrics: `PrometheusUtils`, metric interceptors (`archipy[prometheus]` required to import) -- APM / Sentry extras and AppUtils config flags +- OpenTelemetry: `BaseConfig.OTEL` + `OtelUtils` +- Tracing: `trace_root` / `trace_span` (+ async twins) +- Metrics: `measure_duration` / `count_calls` (+ async twins) +- AppUtils instrumentation through `archipy[otel-fastapi]` / `archipy[otel-grpc]` - Timing: `timing_decorator` Live docs: diff --git a/commands/redis-search.md b/commands/redis-search.md index bc12cb3..2b3e744 100644 --- a/commands/redis-search.md +++ b/commands/redis-search.md @@ -5,10 +5,10 @@ description: Scaffold Redis search adapters (RediSearch full-text, vector, searc # /redis-search -Follow the **redis-search** skill. +Read and follow the **redis-search** skill in full. Inspect the workspace as directed there; ask only for unresolved +choices. -1. Ask for search type (full-text / vector / caching), domain name, data structure, sync/async, and patterns needed. -2. Prefer `uv add "archipy[redis]"` and a thin domain wrapper under `repositories//adapters/`. -3. Generate the matching adapter stub (s) + repository orchestrator stub if missing; wire via DI. +1. Prefer `uv add "archipy[redis]"` and a thin domain wrapper under `repositories//adapters/`. +2. Generate the matching adapter stub (s) + repository orchestrator stub if missing; wire via DI. Docs: https://syntaxarc.github.io/ArchiPy/tutorials/adapters/ · bundled `skills/archipy-docs/reference.md` diff --git a/commands/scaffold-adapter.md b/commands/scaffold-adapter.md index 7a0fe02..d42027a 100644 --- a/commands/scaffold-adapter.md +++ b/commands/scaffold-adapter.md @@ -5,11 +5,11 @@ description: Scaffold a domain adapter under repositories/{domain}/adapters/ # /scaffold-adapter -Follow the **scaffold-archipy-adapter** skill. +Read and follow the **scaffold-archipy-adapter** skill in full. Inspect the workspace as directed there; ask only for +unresolved choices. -1. Ask for domain name, adapter purpose, sync/async, and whether mocks are needed. -2. Prefer wrapping an existing ArchiPy adapter via extras when possible. -3. Generate a thin wrapper under `repositories//adapters/` (e.g. `user_db_adapter.py`). Create +1. Prefer wrapping an existing ArchiPy adapter via extras when possible. +2. Generate a thin wrapper under `repositories//adapters/` (e.g. `user_db_adapter.py`). Create `repositories//_repository.py` stub if missing. Docs: https://syntaxarc.github.io/ArchiPy/getting-started/project_structure/ diff --git a/commands/scaffold-app.md b/commands/scaffold-app.md index 0ad16d5..8e2380f 100644 --- a/commands/scaffold-app.md +++ b/commands/scaffold-app.md @@ -5,12 +5,12 @@ description: Scaffold a minimal ArchiPy application package layout # /scaffold-app -Follow the **scaffold-archipy-app** skill. +Read and follow the **scaffold-archipy-app** skill in full. Inspect the workspace as directed there; ask only for +unresolved choices. -1. Ask for package name, ArchiPy extras, and optional first domain. -2. Generate config, models stub, `repositories//` (adapters + repository stub), optional helpers tree, and +1. Generate config, models stub, `repositories//` (adapters + repository stub), optional helpers tree, and `.env.example`. -3. Point the user to `/docs-quickstart` and `/scaffold-adapter` for domain wrappers under +2. Point the user to `/docs-quickstart` and `/scaffold-adapter` for domain wrappers under `repositories//adapters/`. Docs: https://syntaxarc.github.io/ArchiPy/getting-started/quickstart/ diff --git a/commands/scaffold-bdd.md b/commands/scaffold-bdd.md index 59d7720..01c6edd 100644 --- a/commands/scaffold-bdd.md +++ b/commands/scaffold-bdd.md @@ -5,17 +5,17 @@ description: Scaffold Behave BDD layout with ScenarioContext, pool manager, envi # /scaffold-bdd -Follow the **scaffold-archipy-bdd** skill. +Read and follow the **scaffold-archipy-bdd** skill in full. Inspect the workspace as directed there; ask only for +unresolved choices. -1. Ask for feature name and **mocks** vs **infra** (`@needs-*` + testcontainers). -2. Always generate (if missing), modeled on ArchiPy `features/`: +1. Always generate (if missing), modeled on ArchiPy `features/`: - `features/scenario_context.py` - `features/scenario_context_pool_manager.py` - `features/test_helpers.py` (`get_current_scenario_context`) - `features/environment.py` (behave hooks + `TestConfig`) - `features/.feature` + `features/steps/_steps.py` -3. Infra mode: also `features/test_containers.py` (`ContainerManager` + only needed containers), `.env.test` image vars, +2. Infra mode: also `features/test_containers.py` (`ContainerManager` + only needed containers), `.env.test` image vars, `uv add "archipy[testcontainers]"`. -4. Do **not** copy ArchiPy-core gRPC/Temporal server bootstrap unless the app tests those. +3. Do **not** copy ArchiPy-core gRPC/Temporal server bootstrap unless the app tests those. Docs: https://syntaxarc.github.io/ArchiPy/tutorials/testing_strategy/ diff --git a/commands/scaffold-decorator.md b/commands/scaffold-decorator.md index d5cd0df..2410e46 100644 --- a/commands/scaffold-decorator.md +++ b/commands/scaffold-decorator.md @@ -5,10 +5,11 @@ description: Scaffold or wire a helpers/decorators module (prefer ArchiPy decora # /scaffold-decorator -Follow the **scaffold-archipy-decorator** skill. +Read and follow the **scaffold-archipy-decorator** skill in full. Inspect the workspace as directed there; ask only for +unresolved choices. -1. Ask for purpose, sync/async, and ArchiPy vs custom. -2. Prefer `ttl_cache_decorator`, `postgres_sqlalchemy_atomic_decorator`, `capture_span` / `capture_transaction`, etc. -3. Custom decorators: no adapter imports at module level; include usage example. +1. Prefer `ttl_cache_decorator`, `postgres_sqlalchemy_atomic_decorator`, `trace_span` / `trace_root`, + `measure_duration` / `count_calls`, etc. +2. Custom decorators: no adapter imports at module level; include usage example. Reference: skills/archipy-docs/reference.md (Decorators). Docs: https://syntaxarc.github.io/ArchiPy/tutorials/helpers/ diff --git a/commands/scaffold-domain.md b/commands/scaffold-domain.md index 698ebde..c55724b 100644 --- a/commands/scaffold-domain.md +++ b/commands/scaffold-domain.md @@ -5,10 +5,10 @@ description: Scaffold a full ArchiPy domain slice (models, repo, logic, service) # /scaffold-domain -Follow the **scaffold-archipy-domain** skill. +Read and follow the **scaffold-archipy-domain** skill in full. Inspect the workspace as directed there; ask only for +unresolved choices. -1. Ask for domain name, extras, and transport (FastAPI default). -2. Compose adapter + models + logic + service skills for one domain. -3. Note DI wiring: ports → adapters → repository → logic → service. +1. Compose adapter + models + logic + service skills for one domain. +2. Note DI wiring: ports → adapters → repository → logic → service. Docs: https://syntaxarc.github.io/ArchiPy/getting-started/project_structure/ diff --git a/commands/scaffold-health-checks.md b/commands/scaffold-health-checks.md index fa5b9c3..be50413 100644 --- a/commands/scaffold-health-checks.md +++ b/commands/scaffold-health-checks.md @@ -5,14 +5,13 @@ description: Scaffold ArchiPy FastAPI and/or gRPC health checks and optional Kub # /scaffold-health-checks -Follow the **scaffold-archipy-health-checks** skill. +Read and follow the **scaffold-archipy-health-checks** skill in full. Inspect the workspace as directed there; ask only +for unresolved choices. -1. Ask for package name, transport (FastAPI / gRPC / both), readiness dependencies, optional heartbeat liveness, and - optional K8s probe YAML. -2. Generate shared check helpers plus: +1. Generate shared check helpers plus: - FastAPI: `services/health/v1/health_service.py` (`/health/live`, `/health/ready`) - gRPC: `services/health/v1/health_grpc_service.py` (`grpc.health.v1` with `liveness` / `readiness` service names) -3. Wire via `AppUtils.create_fastapi_app()` / `create_grpc_app` / `create_async_grpc_app`; emit `deploy/k8s-probes.yaml` +2. Wire via `AppUtils.create_fastapi_app()` / `create_grpc_app` / `create_async_grpc_app`; emit `deploy/k8s-probes.yaml` when requested (`httpGet` and/or `grpc` probes). Docs: https://syntaxarc.github.io/ArchiPy/getting-started/project_structure/ diff --git a/commands/scaffold-interceptor.md b/commands/scaffold-interceptor.md index a9a1f89..80a04f7 100644 --- a/commands/scaffold-interceptor.md +++ b/commands/scaffold-interceptor.md @@ -5,10 +5,10 @@ description: Scaffold or wire a helpers/interceptors module (prefer ArchiPy inte # /scaffold-interceptor -Follow the **scaffold-archipy-interceptor** skill. +Read and follow the **scaffold-archipy-interceptor** skill in full. Inspect the workspace as directed there; ask only +for unresolved choices. -1. Ask for framework (FastAPI/gRPC), sync/async, and ArchiPy vs custom. -2. Keep cross-cutting only — no domain use-case logic. -3. Wire via DI or framework registration. +1. Keep cross-cutting only — no domain use-case logic. +2. Wire via DI or framework registration. Reference: skills/archipy-docs/reference.md (Interceptors). Docs: https://syntaxarc.github.io/ArchiPy/tutorials/helpers/ diff --git a/commands/scaffold-logic.md b/commands/scaffold-logic.md index dc36672..6f46bd8 100644 --- a/commands/scaffold-logic.md +++ b/commands/scaffold-logic.md @@ -5,11 +5,11 @@ description: Scaffold an ArchiPy logic class with unit-of-work decorator # /scaffold-logic -Follow the **scaffold-archipy-logic** skill. +Read and follow the **scaffold-archipy-logic** skill in full. Inspect the workspace as directed there; ask only for +unresolved choices. -1. Ask for domain, logic name, and sync vs async atomic. -2. Generate `logics//_logic.py` with domain DTO I/O and `@postgres_sqlalchemy_atomic_decorator` (or async +1. Generate `logics//_logic.py` with domain DTO I/O and `@postgres_sqlalchemy_atomic_decorator` (or async twin). -3. Inject repository via constructor; no FastAPI/gRPC imports. +2. Inject repository via constructor; no FastAPI/gRPC imports. Docs: https://syntaxarc.github.io/ArchiPy/getting-started/concepts/ diff --git a/commands/scaffold-observability.md b/commands/scaffold-observability.md new file mode 100644 index 0000000..6d4d242 --- /dev/null +++ b/commands/scaffold-observability.md @@ -0,0 +1,16 @@ +--- +name: scaffold-observability +description: Configure ArchiPy 5.x OpenTelemetry traces, metrics, logs, and instrumentation +--- + +# /scaffold-observability + +Read and follow the **scaffold-archipy-observability** skill in full. Inspect the workspace as directed there; ask only +for unresolved choices. + +1. Install only the `otel*` extras matching the app stack. +2. Configure `BaseConfig.OTEL` through documented `OTEL__*` keys. +3. Initialize `OtelUtils` before DI constructs adapters, then use AppUtils/decorators for instrumentation. +4. Verify with test exporters; never commit OTLP credentials. + +Docs: https://syntaxarc.github.io/ArchiPy/tutorials/observability/ diff --git a/commands/scaffold-service.md b/commands/scaffold-service.md index 7ce5f40..deb5f72 100644 --- a/commands/scaffold-service.md +++ b/commands/scaffold-service.md @@ -5,10 +5,10 @@ description: Scaffold a thin ArchiPy FastAPI or gRPC service under services/{dom # /scaffold-service -Follow the **scaffold-archipy-service** skill. +Read and follow the **scaffold-archipy-service** skill in full. Inspect the workspace as directed there; ask only for +unresolved choices. -1. Ask for domain, API version, and FastAPI vs gRPC (sync/async). -2. Generate `services//v{n}/_service.py` calling logic with domain DTOs. -3. Bootstrap via `AppUtils`; uvicorn from `config.FASTAPI`. +1. Generate `services//v{n}/_service.py` calling logic with domain DTOs. +2. Bootstrap via `AppUtils`; uvicorn from `config.FASTAPI`. Docs: https://syntaxarc.github.io/ArchiPy/getting-started/project_structure/ diff --git a/commands/scaffold-utils.md b/commands/scaffold-utils.md index 88b88f8..781355d 100644 --- a/commands/scaffold-utils.md +++ b/commands/scaffold-utils.md @@ -5,10 +5,10 @@ description: Scaffold or wire a helpers/utils module (prefer ArchiPy utils) # /scaffold-utils -Follow the **scaffold-archipy-utils** skill. +Read and follow the **scaffold-archipy-utils** skill in full. Inspect the workspace as directed there; ask only for +unresolved choices. -1. Ask for purpose and whether an ArchiPy util already fits. -2. If ArchiPy provides it, show import + usage only. -3. Otherwise scaffold a pure util under `helpers/utils/`. +1. If ArchiPy provides it, show import + usage only. +2. Otherwise scaffold a pure util under `helpers/utils/`. Reference: skills/archipy-docs/reference.md (Utils). Docs: https://syntaxarc.github.io/ArchiPy/tutorials/helpers/ diff --git a/rules/architecture-for-apps.mdc b/rules/architecture-for-apps.mdc index 2d4aa3e..feb3ce3 100644 --- a/rules/architecture-for-apps.mdc +++ b/rules/architecture-for-apps.mdc @@ -44,8 +44,8 @@ Layer details live in the matching `using-archipy-*.mdc` / `config-and-di.mdc` / those files are open. This always-on rule keeps only the global map: - **models/** — data only (no I/O, no business rules). -- **repositories/** — domain data access; adapters under `repositories/{domain}/adapters/`; no cross-domain repo calls; - map driver errors with `raise ... from e`. +- **repositories/** — domain data access; adapters under `repositories/{domain}/adapters/`; no cross-domain repo calls. + Domain adapters map driver errors with `raise ... from e`; repository orchestrators consume domain-facing errors. - **logics/** — business rules + UoW (`*_sqlalchemy_atomic_decorator`); may call other logics; never other domains' repositories; no FastAPI/gRPC. - **services/** — thin transport; versioned `v{n}/`; no UoW / business rules. @@ -63,7 +63,5 @@ configs ← models ← helpers ← repositories / logics / services ## Python Conventions (apps) -- Python 3.14+: `X | Y`, `list[str]`, `dict[str, int]`. -- Double quotes for strings. -- Google-style docstrings on public functions/classes. -- Specific exceptions; always `raise ... from e`. +Canonical formatting, imports, errors, and complexity: `python-code-style-for-apps.mdc`. Strict typing: +`typing-for-apps.mdc`. Security and secrets: `security-for-apps.mdc`. Precedence: `rules-index-for-apps.mdc`. diff --git a/rules/config-and-di.mdc b/rules/config-and-di.mdc index 43068c0..03abac1 100644 --- a/rules/config-and-di.mdc +++ b/rules/config-and-di.mdc @@ -36,6 +36,10 @@ logger.info("Config loaded for environment: %s", config.ENVIRONMENT) - Read elsewhere via `BaseConfig.global_config()`. - Document required env vars in `.env.example`; never commit secrets. - Env overrides use nested delimiter: `FASTAPI__PROJECT_NAME`, `FASTAPI__SERVE_PORT`, … +- Preserve ArchiPy's settings-source priority: secret files → Vault → pyproject → TOML → environment → `.env` → + defaults. Do not add scattered `os.environ` reads that bypass it. +- Raise `ConfigurationError` / `InvalidArgumentError` from `archipy.models.errors` for config validation failures; + do not invent ad-hoc config exceptions. ## FastAPIConfig (`config.FASTAPI`) @@ -61,6 +65,8 @@ uv add "archipy[dependency-injection]" - Wire in `configs/containers.py` as providers: **ports → adapters → repositories → logics → services**. - Depend on **ports** in business code; override providers in tests with mocks. - Import `app_config` inside the container module so `set_global` runs before wiring. +- When OTel is enabled, call `OtelUtils.init_otel_if_needed(config)` after `set_global` and before the container creates + SQLAlchemy engines, Kafka clients, ScyllaDB sessions, or other instrumented adapters. Live: https://syntaxarc.github.io/ArchiPy/tutorials/config_management/ · https://syntaxarc.github.io/ArchiPy/tutorials/dependency_injection/ diff --git a/rules/contributing-for-apps.mdc b/rules/contributing-for-apps.mdc new file mode 100644 index 0000000..38ef713 --- /dev/null +++ b/rules/contributing-for-apps.mdc @@ -0,0 +1,28 @@ +--- +description: ArchiPy app contribution workflow — focused changes, conventional commits, branches, and PR checks +alwaysApply: true +--- + +# Contributing to ArchiPy Apps + +## Changes and Commits + +- Keep changes focused on one purpose; do not mix unrelated refactors or lockfile churn. +- Preserve pre-existing user changes. +- Use conventional commit types: `feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `perf`, `ci`. +- Write concise imperative subjects; explain non-obvious reasons in the body. +- Reference the app's issue tracker when applicable. + +## Branches and Pull Requests + +- Prefer short single-purpose branches such as `feat/`, `fix/`, or `chore/`. +- Keep PRs/MRs reviewable. Describe what changed, why, and how it was tested. +- Keep the description current when behavior changes during review. +- Never include secrets, generated credentials, large artifacts, or unrelated changes. + +## Before Merge + +Run the app's configured formatter, linter/type checker, targeted Behave/unit tests, and security checks appropriate to +the change. Network adapters, auth, cryptography, uploads, and secret/config changes require security validation. + +Do not update changelogs, versions, tags, or releases unless the app's policy or user explicitly requires it. diff --git a/rules/python-code-style-for-apps.mdc b/rules/python-code-style-for-apps.mdc new file mode 100644 index 0000000..6ce78c7 --- /dev/null +++ b/rules/python-code-style-for-apps.mdc @@ -0,0 +1,41 @@ +--- +description: Python 3.14 code style for ArchiPy apps — formatting, imports, docstrings, errors, complexity +alwaysApply: true +--- + +# Python Code Style for ArchiPy Apps + +Follow the app's configured formatter/linter when stricter. Otherwise use these ArchiPy defaults: + +- Python 3.14+ syntax: `X | Y`, `X | None`, `list[str]`, `dict[str, int]`. +- Double quotes for all strings. +- Four-space indentation, 120-character maximum lines, final newline. +- Google-style docstrings on public functions, classes, and methods (`__init__` may omit one). +- Complete parameter and return annotations on public APIs. + +## Imports + +Order sections: + +1. `from __future__ import ...` +2. Standard library +3. Third-party packages +4. `archipy` +5. App-local imports + +Keep a blank line between sections. Move type-only imports under `TYPE_CHECKING` only when runtime frameworks do not +evaluate them; Pydantic field types must remain importable at runtime. + +## Errors + +- Catch specific exceptions. Never use bare `except:`. +- Preserve context with `raise DomainError(...) from e`. +- Broad `except Exception` belongs only at an outer infrastructure boundary that maps failures to domain errors. +- Never leak driver exceptions or internal error details through HTTP/gRPC responses. + +## Maintainability + +- Keep McCabe complexity below 10 when the app uses ArchiPy's Ruff defaults. +- Prefer at most five function arguments; group cohesive inputs in DTOs/config objects. +- Run the repository's formatter, linter, and type checker after edits. Use its configured commands; do not assume the + ArchiPy library's `make` targets exist in consumer apps. diff --git a/rules/rules-index-for-apps.mdc b/rules/rules-index-for-apps.mdc new file mode 100644 index 0000000..8eebfd1 --- /dev/null +++ b/rules/rules-index-for-apps.mdc @@ -0,0 +1,36 @@ +--- +description: ArchiPy app rule ownership and precedence +alwaysApply: true +--- + +# ArchiPy App Rules Index + +## Canonical Owners + +| Topic | Rule | +|-------|------| +| Layers, call flow, import direction | `architecture-for-apps.mdc` | +| Python formatting, imports, errors, complexity | `python-code-style-for-apps.mdc` | +| Strict typing | `typing-for-apps.mdc` | +| Security, secrets, query safety, suppressions | `security-for-apps.mdc` | +| Dependencies and local checks | `tooling-for-apps.mdc` | +| Commits, branches, PRs/MRs | `contributing-for-apps.mdc` | +| Config and dependency injection | `config-and-di.mdc` | +| Models and domain errors | `using-archipy-models.mdc` | +| Domain adapters and infrastructure boundaries | `using-archipy-adapters.mdc` | +| Repositories | `using-archipy-repositories.mdc` | +| Logics and unit of work | `using-archipy-logics.mdc` | +| Services and AppUtils | `using-archipy-services.mdc` | +| Utils, decorators, interceptors | matching `using-archipy-*.mdc` | +| Behave BDD | `testing-bdd-for-apps.mdc` | + +## Precedence + +1. App `pyproject.toml`, formatter, linter, and type-checker configuration wins when stricter or project-specific. +2. A scoped rule wins over the always-on architecture summary for its layer. +3. `python-code-style-for-apps.mdc` owns general error handling; `using-archipy-adapters.mdc` owns the narrow + outer-boundary `except Exception` exception. +4. `config-and-di.mdc` owns secret/settings resolution; `security-for-apps.mdc` owns whether values are safe to store, + log, trace, or expose. +5. Consumer-app rules never import ArchiPy maintainer workflows such as graphify, library release/changelog policy, or + core monorepo Make targets. diff --git a/rules/security-for-apps.mdc b/rules/security-for-apps.mdc new file mode 100644 index 0000000..ca1871c --- /dev/null +++ b/rules/security-for-apps.mdc @@ -0,0 +1,34 @@ +--- +description: Security rules for ArchiPy apps — secrets, queries, logging, adapter boundaries, suppressions +alwaysApply: true +--- + +# Security for ArchiPy Apps + +## Secrets and Logging + +- Never hardcode tokens, passwords, private keys, connection credentials, or OTLP authorization headers. +- Resolve secrets through `BaseConfig` settings sources (environment, secret files, or Vault) and document names only + in `.env.example`. +- Never put secrets in committed `.env` / `.env.test` files; keep committed test values non-sensitive and prefer + `.env.example` for required-variable documentation. +- Do not log credentials, session/JWT tokens, cookies, full authorization headers, or unrestricted request bodies. +- Trace attributes may include non-sensitive identifiers only; do not capture free-form payloads or secret-like values. + +## Inputs and Infrastructure + +- Validate external input at service/port boundaries. +- Use parameterized SQL/CQL and bound query values; never concatenate untrusted values into query text. +- Map infrastructure failures to domain errors without exposing driver messages, stack traces, hosts, or credentials. +- Add dependencies with `uv add`; prefer the smallest matching `archipy[...]` extras. +- Treat network-facing adapters, authentication, cryptography, uploads, and deserialization as security-sensitive. + +## Suppressions + +- Fix findings before suppressing them. +- Keep `# nosec`, Ruff `# noqa: S...`, and type suppressions to the smallest line/file scope. +- Name the rule and explain the false positive beside the suppression. +- Never blanket-disable Bandit or Ruff security rules for a new package. + +Run the app's configured security scan after security-sensitive changes. If none exists, recommend adding Bandit/Ruff +security checks rather than assuming ArchiPy's library `make security` target is available. diff --git a/rules/testing-bdd-for-apps.mdc b/rules/testing-bdd-for-apps.mdc index cdc812b..b1db2a5 100644 --- a/rules/testing-bdd-for-apps.mdc +++ b/rules/testing-bdd-for-apps.mdc @@ -25,6 +25,30 @@ features/ Extras: `uv add "archipy[behave]"`; infra also `uv add "archipy[testcontainers]"`. +## Behavior-First Loop + +Name the public seam first (service endpoint, logic API, repository/adapter port, or public helper). Test observable +behavior, not private methods or driver call counts. + +Work in vertical slices: + +1. Add one scenario and run it red for the expected reason. +2. Implement only enough behavior to make it green. +3. Add the next scenario; refactor after green. + +Expected values must be independent literals/examples, not values recomputed with the production algorithm. + +```gherkin +# Good: observable app behavior +Scenario: Register a new user + When I register "user@example.com" + Then registration succeeds with status "pending" + +# Bad: implementation coupling +Scenario: Repository calls insert once + Then the private database adapter insert method was called once +``` + ## Lifecycle (environment.py) 1. `before_all` — `TestConfig` / `set_global`, `ScenarioContextPoolManager`, optional `ContainerManager` @@ -39,6 +63,7 @@ Prefer `/scaffold-bdd` templates over copying ArchiPy-core gRPC/Temporal hooks. - Inject mocks (e.g. `RedisMock`) or real adapters via scenario context. - Never share mutable globals across scenarios. +- Mocks and real adapters must satisfy the same port; assert through that public port. ## Infrastructure Tags @@ -49,12 +74,41 @@ Feature: ... `ContainerManager.extract_containers_from_tags` → `start_containers`. Register only containers the app tests. +Standard tag/container keys: + +| Tag | Container | +|-----|-----------| +| `@needs-postgres` | postgres | +| `@needs-mysql` | mysql | +| `@needs-kafka` | kafka | +| `@needs-elasticsearch` | elasticsearch | +| `@needs-minio` | minio | +| `@needs-keycloak` | keycloak | +| `@needs-redis` | redis | +| `@needs-redis-cluster` | redis-cluster | +| `@needs-scylladb` | scylladb | +| `@needs-starrocks` | starrocks | +| `@needs-temporal` | temporal | +| `@needs-vault` | vault | + ```bash uv run behave --tags=~@needs-redis ``` +Do not invent a `@needs-*` tag without adding its container mapping. + +## Async Steps + +Behave 1.3.3 supports native `async def` steps. Await async APIs directly; never call `asyncio.run()` inside a step. +Use `asyncio.run()` only in synchronous lifecycle/cleanup callbacks that cannot be async. + +Step functions may remain unannotated when `features/` is excluded from app lint/type checks. Typical Ruff exemptions +for Behave steps are `ANN001`, `ANN201`, `ARG001`, `PLR0913`, and `F811`; production helpers remain fully typed. + ## Do Not - Share adapter/DB state across scenarios without cleanup. - Use pytest as the primary ArchiPy app test style. - Paste the full ArchiPy library `test_containers.py` catalogue into an app. +- Spy on private methods or duplicate production calculations in assertions. +- Write a complete feature horizontally before making one scenario pass. diff --git a/rules/tooling-for-apps.mdc b/rules/tooling-for-apps.mdc new file mode 100644 index 0000000..7b9fad8 --- /dev/null +++ b/rules/tooling-for-apps.mdc @@ -0,0 +1,41 @@ +--- +description: ArchiPy app tooling — uv dependencies, project checks, Behave, Ruff, ty, and pre-commit +alwaysApply: true +--- + +# Tooling for ArchiPy Apps + +## Package Management + +Use `uv`; never install project packages with bare `pip`. + +```bash +uv sync +uv add "archipy[redis,fastapi]" +uv add --group dev ruff ty pre-commit +``` + +- Inspect `pyproject.toml` before adding dependencies; do not duplicate an existing direct/transitive requirement. +- Install only ArchiPy extras required by the app. +- Commit `uv.lock` changes when dependency changes are intentional. + +## Project Commands + +Prefer commands already defined by the app's `Makefile`, `pyproject.toml`, task runner, or CI. Do not assume ArchiPy +library Make targets exist. When no wrapper exists, typical direct checks are: + +```bash +uv run ruff format . +uv run ruff check . +uv run ty check +uv run behave features/example.feature +uv run pre-commit run --all-files +``` + +Run the narrowest relevant test during iteration, then the app's complete required gate before handoff. Use Behave as +the primary ArchiPy acceptance-test style; follow project-specific unit-test tooling when present. + +## Hooks + +If the app configures pre-commit, install/run it through `uv`. Do not add or rewrite hook configuration unless the task +requires tooling changes. Fix generated formatting/lint changes before reporting completion. diff --git a/rules/typing-for-apps.mdc b/rules/typing-for-apps.mdc new file mode 100644 index 0000000..3e1fcd0 --- /dev/null +++ b/rules/typing-for-apps.mdc @@ -0,0 +1,42 @@ +--- +description: Strict typing for ArchiPy app Python — complete annotations, Any discipline, runtime-evaluated types +globs: **/*.py +alwaysApply: false +--- + +# Strict Typing for ArchiPy Apps + +Follow the app's configured type checker. For new code: + +- Annotate every public function/method parameter and return type. +- Use Python 3.14 syntax (`X | None`, builtin generics) instead of legacy `Optional` / `List`. +- Avoid `Any`. Use precise protocols, DTOs, generics, or driver types. +- Add a narrow `# noqa: ANN401` / type-checker suppression only when unavoidable and include the reason. +- Never blanket-disable typing rules for a package. +- Keep shared per-file exemptions in the app's `pyproject.toml`; treat that configuration as source of truth. + +## Runtime-Evaluated Types + +`TYPE_CHECKING` guards are valid only for imports used purely by static annotations. Keep types imported at runtime +when Pydantic, SQLAlchemy, dependency-injector, FastAPI, or another framework evaluates them. + +```python +from __future__ import annotations + +from archipy.models.dtos.base_dtos import BaseDTO + +from my_app.models.entities import UserEntity + + +class UserOutputDTO(BaseDTO): + """User payload.""" + + user: UserEntity +``` + +## Behave Exception + +Step functions under `features/steps/` follow Behave signatures and may remain unannotated when the app excludes them +from Ruff/type checking. Production helpers used by steps remain fully typed. + +Run the app's configured type checker after changes (`uv run ty check` when the project uses `ty`). diff --git a/rules/using-archipy-adapters.mdc b/rules/using-archipy-adapters.mdc index 29d3c7a..83f25c4 100644 --- a/rules/using-archipy-adapters.mdc +++ b/rules/using-archipy-adapters.mdc @@ -57,8 +57,9 @@ Implementations and mocks must satisfy the same port. ## Boundary Errors -Catch **specific** client/driver errors and map to domain errors with `raise ... from e`. Never bare -`except Exception` unless the client exposes no typed root error: +Catch **specific** client/driver errors and map to domain errors with `raise ... from e`. A broad `except Exception` +is allowed only at the outermost infrastructure boundary when the client exposes no typed root error; add a narrow +`# noqa: BLE001` reason. Never use bare `except:`: ```python try: @@ -69,6 +70,12 @@ except KafkaError as e: Never leak raw driver exceptions into logics/services. +## Optional Dependencies + +Install the matching ArchiPy extra and import adapter dependencies normally. Do not hide missing extras behind lazy +imports inside adapter methods; a missing integration dependency should fail clearly at import/bootstrap time. +Function-scoped imports are reserved for a documented circular-import or cross-extra exception. + ## Sync vs Async - Sync and async are separate classes — do not mix `async def` into a sync adapter class. diff --git a/rules/using-archipy-decorators.mdc b/rules/using-archipy-decorators.mdc index 359f71a..8dd3c1b 100644 --- a/rules/using-archipy-decorators.mdc +++ b/rules/using-archipy-decorators.mdc @@ -17,7 +17,9 @@ Before writing a custom decorator, prefer ArchiPy under `archipy.helpers.decorat - `postgres_sqlalchemy_atomic_decorator` - `async_postgres_sqlalchemy_atomic_decorator` - also `sqlite_` / `starrocks_` / generic `sqlalchemy_atomic_decorator` twins -- Observability: `capture_span`, `capture_transaction` (+ async twins); `timing_decorator` +- Tracing: `trace_span`, `trace_root`, `async_trace_span`, `async_trace_root`, `trace_class` +- Metrics: `measure_duration`, `async_measure_duration`, `count_calls`, `async_count_calls` +- Local timing only: `timing_decorator` - Resilience: `retry_decorator`, `timeout_decorator` - Other: `singleton_decorator`, `grpc_rate_limit_decorator` (gRPC only) @@ -26,6 +28,8 @@ Before writing a custom decorator, prefer ArchiPy under `archipy.helpers.decorat - Live under `helpers/decorators/`. - Preserve wrapped function signatures and type hints (`functools.wraps`). - Document usage with a short example in the Google-style docstring. +- Raise `archipy.models.errors` types such as `ConfigurationError` or `InvalidArgumentError` for helper-level + configuration/validation failures; do not invent ad-hoc exception classes. - Do **not** import concrete adapter implementations at module level. If a decorator needs a port, accept it as an argument or resolve via injected callback/config. ## Do Not diff --git a/rules/using-archipy-interceptors.mdc b/rules/using-archipy-interceptors.mdc index 8ca86a7..273fabe 100644 --- a/rules/using-archipy-interceptors.mdc +++ b/rules/using-archipy-interceptors.mdc @@ -6,7 +6,7 @@ alwaysApply: false # Using ArchiPy Interceptors -Helpers stay **pure**: no database calls, no constructing adapters, no domain business rules. +Helpers stay **pure**: no database/network I/O, no constructing adapters, no domain business rules. ## Role @@ -18,8 +18,11 @@ Use ArchiPy interceptors under `archipy.helpers.interceptors` (FastAPI / gRPC) b Prefer **AppUtils** auto-registration for stock interceptors/middleware: -- FastAPI: `AppUtils.create_fastapi_app` wires CORS, GZip, TrustedHost, HTTPS redirect, metrics, APM from `FASTAPI` config. -- gRPC: `create_grpc_app` / `create_async_grpc_app` append exception + optional trace/metric/rate-limit interceptors from config. +- FastAPI: `AppUtils.create_fastapi_app` wires middleware/exception handlers and instruments through + `archipy[otel-fastapi]` when `OTEL.IS_ENABLED`. +- gRPC: `create_grpc_app` / `create_async_grpc_app` append exception, optional OTel contrib, and gRPC rate-limit + interceptors. Metric/trace interceptor packages from ArchiPy 4.x no longer exist. +- FastAPI rate limiting is not shipped by ArchiPy 5.x; use `fastapi-redis-sdk` when required. Do not manually re-wire stock ArchiPy interceptors unless customizing beyond config flags. @@ -31,9 +34,11 @@ Live: https://syntaxarc.github.io/ArchiPy/tutorials/helpers/interceptors/ - Keep them framework-aware but business-logic-free. - Wire via DI container, framework registration, or `customized_interceptors=` on gRPC AppUtils — do not have adapters import interceptors at module level to register themselves. - Sync and async variants stay separate when both exist. +- Raise/map through `archipy.models.errors`; do not invent ad-hoc exception classes or leak framework/driver errors. ## Do Not - Embed use-case logic inside interceptors. +- Call databases or downstream services directly; inject context already resolved at the appropriate boundary. - Perform multi-step domain writes here (that belongs in `logics/` with atomic boundaries). - Leak infrastructure exceptions — map to domain/HTTP/gRPC errors at the appropriate boundary. diff --git a/rules/using-archipy-models.mdc b/rules/using-archipy-models.mdc index 97464c8..ba5f334 100644 --- a/rules/using-archipy-models.mdc +++ b/rules/using-archipy-models.mdc @@ -32,13 +32,25 @@ models/ | Repository query | `{Action}QueryDTO` | `GetUserByIdQueryDTO` | | Repository response | `{Domain}ResponseDTO` | `UserResponseDTO` | -DTOs use Pydantic `BaseModel`. Prefer ArchiPy base entities where applicable. +DTOs should extend `archipy.models.dtos.base_dtos.BaseDTO` for ArchiPy's shared Pydantic configuration. Use plain +Pydantic `BaseModel` only when the app needs deliberately different model behavior. Prefer ArchiPy base entities where +applicable. + +`BaseDTO` is frozen, strips surrounding string whitespace, validates defaults, and supports `from_attributes`. Do not +mutate DTO instances after validation. ## Errors - Subclass ArchiPy errors from `archipy.models.errors` (e.g. `AlreadyExistsError`, `NotFoundError`). +- Prefer an existing exported error over creating a near-duplicate. +- Custom `BaseError` subclasses define stable `code`, localized `message_en` / `message_fa`, and transport + `http_status` / `grpc_status` class attributes. +- Pass context through constructor arguments / `additional_data`; do not mutate error codes or messages per instance. - Raise with context: `raise NotFoundError(...) from e`. - Never raise bare `Exception` for domain failures. +- Group app-specific errors by concern under `models/errors/` (for example `validation_errors.py`, + `resource_errors.py`, and `business_errors.py`) and subclass the closest ArchiPy error. +- Export public app errors from `models/errors/__init__.py` when other layers consume them. - With `AppUtils.create_fastapi_app` exception handlers, ArchiPy errors map to HTTP status automatically — see https://syntaxarc.github.io/ArchiPy/tutorials/error_handling/ ## Do Not diff --git a/rules/using-archipy-repositories.mdc b/rules/using-archipy-repositories.mdc index 5b24edf..e9f4507 100644 --- a/rules/using-archipy-repositories.mdc +++ b/rules/using-archipy-repositories.mdc @@ -23,7 +23,8 @@ repositories/{domain}/ - `adapters/` — entity construction, query building, thin wrappers; prefer ArchiPy extras (`uv add "archipy[redis]"`) before inventing clients. - `{domain}_repository.py` — orchestrates adapters, cache-aside patterns, maps results to repository DTOs (`*CommandDTO` / `*QueryDTO` / `*ResponseDTO`). -- Catch infrastructure exceptions at the adapter/repository boundary; re-raise as domain errors with `raise ... from e`. +- Raw driver exceptions are mapped inside domain adapters. Repositories receive domain-facing errors and may wrap a + specific error only to add domain context, preserving the cause with `raise ... from e`. ## Domain Isolation diff --git a/rules/using-archipy-services.mdc b/rules/using-archipy-services.mdc index 8b92bc1..66dd952 100644 --- a/rules/using-archipy-services.mdc +++ b/rules/using-archipy-services.mdc @@ -14,7 +14,8 @@ Live: https://syntaxarc.github.io/ArchiPy/getting-started/project_structure/ - Validate and translate HTTP/gRPC requests into domain `*InputDTO`. - Call the appropriate logic class; return domain `*OutputDTO`. -- Map domain errors → HTTP/gRPC status codes. +- Let AppUtils exception handlers/interceptors map ArchiPy `BaseError` status metadata. Add transport-specific mapping + only when the domain error has no suitable mapping; never expose internal exception text. - Version in path: `services/{domain}/v{n}/` — not in business logic. ## Bootstrap via AppUtils + FastAPIConfig @@ -63,3 +64,4 @@ CLI flags may override for local runs; defaults always come from `FASTAPI`. - Put business rules or `@postgres_sqlalchemy_atomic_decorator` in services. - Hand-roll bare `FastAPI()` / `grpc.server()` when AppUtils extras are installed. - Hardcode uvicorn host/port — use `FastAPIConfig`. +- Catch every domain error in each route/servicer when AppUtils already provides centralized mapping. diff --git a/rules/using-archipy-utils.mdc b/rules/using-archipy-utils.mdc index 3f226be..dd21d68 100644 --- a/rules/using-archipy-utils.mdc +++ b/rules/using-archipy-utils.mdc @@ -12,9 +12,9 @@ Helpers stay **pure**: no database calls, no network I/O, no constructing adapte Before writing a custom util, check whether ArchiPy already provides it under `archipy.helpers.utils`: -- `TracingUtils`, `RateLimitUtils` +- `OtelUtils`, `RateLimitUtils` - `DatetimeUtils`, `StringUtils`, `JWTUtils`, `PasswordUtils`, `FileUtils`, `ErrorUtils` -- Also: `BaseUtils`, `TOTPUtils`, `KeycloakUtils`, `PrometheusUtils` +- Also: `BaseUtils`, `TOTPUtils`, `KeycloakUtils` - Import from concrete submodules (e.g. `archipy.helpers.utils.jwt_utils`) — package `__init__` does not re-export - `AppUtils` — FastAPI / gRPC app factories (see below) @@ -29,16 +29,17 @@ Prefer ArchiPy factories — do **not** hand-roll bare `FastAPI()` / `grpc.serve | Method | Use | |--------|-----| -| `AppUtils.create_fastapi_app(config?)` | HTTP app; wires CORS/GZip/TrustedHost/HTTPS redirect, metrics, APM, exception handlers from `BaseConfig.FASTAPI` | -| `AppUtils.create_grpc_app(config)` | Sync gRPC server; exception + optional trace/metric/rate-limit interceptors | -| `AppUtils.create_async_grpc_app(config)` | Async gRPC (`grpc.aio`); same interceptor auto-registration | +| `AppUtils.create_fastapi_app(config?)` | HTTP app; middleware/exception handlers plus OTel instrumentation through `archipy[otel-fastapi]` | +| `AppUtils.create_grpc_app(config)` | Sync gRPC server; exception, optional OTel, and gRPC rate-limit interceptors | +| `AppUtils.create_async_grpc_app(config)` | Async gRPC (`grpc.aio`); equivalent async interceptor setup | Rules: - Pass `BaseConfig.global_config()` (or injected config); do not duplicate middleware/interceptor setup AppUtils already does. - **FastAPI + uvicorn always driven by `config.FASTAPI` (`FastAPIConfig`)** — never hardcode host/port/reload/proxy settings. - Sync servicers → `create_grpc_app`; async servicers → `create_async_grpc_app` — do not mix. -- Enable features via config flags (e.g. `FASTAPI.GZIP_MIDDLEWARE_IS_ENABLED`, `GRPC_RATE_LIMIT.IS_ENABLED`) rather than manually appending stock ArchiPy middleware/interceptors. +- Enable features through config (for example `OTEL.IS_ENABLED`, `FASTAPI.GZIP_MIDDLEWARE_IS_ENABLED`, and + `GRPC_RATE_LIMIT.IS_ENABLED`) rather than manually appending stock ArchiPy middleware/interceptors. - Custom gRPC interceptors: pass `customized_interceptors=`; do not bypass the exception interceptor. Live: https://syntaxarc.github.io/ArchiPy/tutorials/helpers/ @@ -47,6 +48,8 @@ Live: https://syntaxarc.github.io/ArchiPy/tutorials/helpers/ - Live under `helpers/utils/`. - Pure functions or small classes with clear Google-style docstrings and full type hints. +- Raise `archipy.models.errors` types such as `ConfigurationError` or `InvalidArgumentError` for helper-level + configuration/validation failures; do not invent ad-hoc exception classes. - Lazy-import optional third-party deps **inside** functions, never at module level, when the dep is optional: ```python diff --git a/scripts/check_catalog.py b/scripts/check_catalog.py index 6b86748..96bb04b 100755 --- a/scripts/check_catalog.py +++ b/scripts/check_catalog.py @@ -28,10 +28,34 @@ r"^description:\s*(?:>-\s*)?(.*?)(?=\n[a-zA-Z_]+\s*:|\n---)", re.MULTILINE | re.DOTALL, ) +SKILL_NAME_RE = re.compile(r"^[a-z0-9-]{1,64}$") +REFERENCE_FILE_RE = re.compile(r"`(reference/[A-Za-z0-9_.\-/]+)`") +ARCHIPY_REFERENCE_VERSION_RE = re.compile(r"Verified against `archipy` (\d+)\.(\d+)\.x") COMMAND_SKILL_RE = re.compile(r"Follow the \*\*([a-z0-9-]+)\*\* skill", re.IGNORECASE) DOCS_SKILL_RE = re.compile(r"Use the \*\*([a-z0-9-]+)\*\* skill", re.IGNORECASE) CHANGELOG_VERSION_RE = re.compile(r"^## \[(\d+\.\d+\.\d+)\]", re.MULTILINE) +ARCHIPY_5_REMOVED_GUIDANCE = ( + "`elastic-apm`", + "`prometheus`", + "`sentry`", + "`TracingUtils`", + "`PrometheusUtils`", + "`capture_span`", + "`capture_transaction`", + "`FastAPIRateLimitConfig`", +) +ARCHIPY_5_REQUIRED_GUIDANCE = ("`OtelUtils`", "`trace_span`", "`otel-fastapi`", "`otel-grpc`") +REQUIRED_APP_RULES = { + "architecture-for-apps.mdc", + "contributing-for-apps.mdc", + "python-code-style-for-apps.mdc", + "rules-index-for-apps.mdc", + "security-for-apps.mdc", + "tooling-for-apps.mdc", + "typing-for-apps.mdc", +} + def _fail(message: str) -> None: print(f"ERROR: {message}", file=sys.stderr) @@ -128,6 +152,29 @@ def check_changelog_version() -> list[str]: return errors +def _check_archipy_reference_text(text: str) -> list[str]: + errors: list[str] = [] + version = ARCHIPY_REFERENCE_VERSION_RE.search(text) + if not version: + errors.append("archipy-docs/reference.md missing `Verified against archipy X.Y.x` version") + elif int(version.group(1)) < 5: + errors.append("archipy-docs/reference.md must target ArchiPy 5.x or newer") + for removed in ARCHIPY_5_REMOVED_GUIDANCE: + if removed in text: + errors.append(f"archipy-docs/reference.md contains removed ArchiPy 5.x guidance: {removed}") + for required in ARCHIPY_5_REQUIRED_GUIDANCE: + if required not in text: + errors.append(f"archipy-docs/reference.md missing ArchiPy 5.x guidance: {required}") + return errors + + +def check_archipy_reference() -> list[str]: + reference = ROOT / "skills" / "archipy-docs" / "reference.md" + if not reference.is_file(): + return ["missing skills/archipy-docs/reference.md"] + return _check_archipy_reference_text(reference.read_text(encoding="utf-8")) + + def check_agents_commands() -> list[str]: errors: list[str] = [] agents = (ROOT / "AGENTS.md").read_text(encoding="utf-8") @@ -155,9 +202,24 @@ def check_skills() -> list[str]: name = match.group(1).strip().strip("\"'") if name != skill_dir.name: errors.append(f"skill folder `{skill_dir.name}` != frontmatter name `{name}`") + if not SKILL_NAME_RE.fullmatch(name): + errors.append(f"{skill_dir.name}/SKILL.md has invalid skill name `{name}`") desc = FRONTMATTER_DESC_RE.search(text) - if not desc or len(desc.group(1).strip()) < 20: + description = desc.group(1).strip() if desc else "" + if len(description) < 20: errors.append(f"{skill_dir.name}/SKILL.md missing or trivial description") + elif len(description) > 1024: + errors.append(f"{skill_dir.name}/SKILL.md description exceeds 1024 characters") + if len(text.splitlines()) > 500: + errors.append(f"{skill_dir.name}/SKILL.md exceeds 500 lines; move details to reference files") + for reference in REFERENCE_FILE_RE.findall(text): + if not (skill_dir / reference).is_file(): + errors.append(f"{skill_dir.name}/SKILL.md references missing `{reference}`") + if name.startswith("scaffold-") or name == "redis-search": + if "## Before writing files" not in text: + errors.append(f"{skill_dir.name}/SKILL.md missing `## Before writing files` workflow") + if "## Verify" not in text: + errors.append(f"{skill_dir.name}/SKILL.md missing `## Verify` feedback loop") return errors @@ -169,12 +231,23 @@ def check_rules() -> list[str]: rule_files = sorted(rules_root.glob("*.mdc")) if not rule_files: errors.append("no rules/*.mdc files") + missing_required = sorted(REQUIRED_APP_RULES - {rule.name for rule in rule_files}) + if missing_required: + errors.append(f"missing required app rules: {', '.join(missing_required)}") for rule in rule_files: text = rule.read_text(encoding="utf-8") - if "description:" not in text.split("---", 2)[1] if text.startswith("---") else text[:200]: - # crude: require description in frontmatter - if not re.search(r"^description:\s*\S", text, re.MULTILINE): - errors.append(f"{rule.name} missing frontmatter description") + parts = text.split("---", 2) + if not text.startswith("---\n") or len(parts) < 3: + errors.append(f"{rule.name} missing valid frontmatter") + continue + frontmatter = parts[1] + if not re.search(r"^description:\s*\S", frontmatter, re.MULTILINE): + errors.append(f"{rule.name} missing frontmatter description") + always_apply = re.search(r"^alwaysApply:\s*(true|false)\s*$", frontmatter, re.MULTILINE) + if not always_apply: + errors.append(f"{rule.name} missing boolean frontmatter `alwaysApply`") + elif always_apply.group(1) == "false" and not re.search(r"^globs:\s*\S", frontmatter, re.MULTILINE): + errors.append(f"{rule.name} requires frontmatter globs when alwaysApply is false") return errors @@ -192,6 +265,11 @@ def check_command_skill_refs() -> list[str]: for skill in refs: if skill not in skills_on_disk: errors.append(f"commands/{command.name} references missing skill `{skill}`") + if command.stem.startswith("scaffold-") or command.stem == "redis-search": + if "in full" not in text or "Inspect the workspace" not in text: + errors.append( + f"commands/{command.name} must read its skill in full and inspect the workspace" + ) return errors @@ -257,6 +335,7 @@ def main() -> int: errors.extend(check_versions()) errors.extend(check_manifest_parity()) errors.extend(check_changelog_version()) + errors.extend(check_archipy_reference()) errors.extend(check_agents_commands()) errors.extend(check_skills()) errors.extend(check_rules()) diff --git a/skills/archipy-docs/SKILL.md b/skills/archipy-docs/SKILL.md index 2971753..c3394c1 100644 --- a/skills/archipy-docs/SKILL.md +++ b/skills/archipy-docs/SKILL.md @@ -1,18 +1,20 @@ --- name: archipy-docs description: >- - Look up ArchiPy patterns for app teams. Use bundled reference.md first, then - live docs URLs. Trigger when the user asks how to use ArchiPy (config, adapters, - helpers, errors, DI, project layout). + Look up ArchiPy patterns for app teams using the bundled 5.x reference and live + docs. Trigger when the user asks how to use ArchiPy (config, adapters, helpers, + errors, DI, project layout, observability). --- # ArchiPy Docs Lookup ## Procedure -1. Read `reference.md` in this skill directory. -2. Answer from the reference when possible (cite section names). -3. If the topic is missing or version-sensitive, point to the matching live doc: +1. Determine the app's installed ArchiPy major version from `pyproject.toml` or `uv.lock` when available. +2. For version-sensitive topics (extras, observability, decorators, interceptors), consult the matching live doc first. +3. Read `reference.md` in this skill directory; it targets ArchiPy 5.x. For older apps, explain migration differences + instead of recommending removed APIs. +4. Answer concisely and link the matching live doc: | Topic | URL | |-------------------|-------------------------------------------------------------------------------------------------------| @@ -30,7 +32,7 @@ description: >- | Redis search | Bundled `reference.md` + `/redis-search` | | API reference | https://syntaxarc.github.io/ArchiPy/api_reference/ | -4. Prefer PyPI `archipy` APIs — do not assume the ArchiPy monorepo is on disk. +5. Prefer PyPI `archipy` APIs — do not assume the ArchiPy monorepo is on disk. ## Output diff --git a/skills/archipy-docs/reference.md b/skills/archipy-docs/reference.md index da19cc7..f97ddcb 100644 --- a/skills/archipy-docs/reference.md +++ b/skills/archipy-docs/reference.md @@ -4,9 +4,12 @@ Condensed patterns for **apps that depend on** PyPI `archipy`. Prefer live docs https://syntaxarc.github.io/ArchiPy/ -Verified against `archipy` 4.17.x. Import symbols from their **full submodule paths** — `archipy.helpers.utils` +Verified against `archipy` 5.1.x. Import symbols from their **full submodule paths** — `archipy.helpers.utils` and `archipy.configs` package `__init__` files do not re-export symbols. +> **ArchiPy 5.x:** OpenTelemetry replaces the removed Sentry, Elastic APM, and Prometheus integrations. Use +> `BaseConfig.OTEL`, `OtelUtils`, `trace_root` / `trace_span`, and the `otel*` extras described below. + ## Install ```bash @@ -38,15 +41,21 @@ uv add "archipy[grpc]" # gRPC + AppUtils create_*_grpc_app | `keycloak` | Keycloak auth adapter + `KeycloakUtils` | | `elasticsearch` | Elasticsearch adapter | | `elasticsearch-async` | Async Elasticsearch adapter | -| `elastic-apm` | Elastic APM integration | | `fastapi` | FastAPI + `AppUtils.create_fastapi_app` | | `grpc` | gRPC + `create_grpc_app` / `create_async_grpc_app` (+ `grpcio-health-checking`) | | `dependency-injection` | `dependency-injector` container helpers | | `behave` | Behave BDD helpers for apps | | `testcontainers` | Testcontainers for `@needs-*` infra BDD | | `temporalio` | Temporal adapter, worker, runtime | -| `prometheus` | Prometheus metrics (+ metric interceptors) | -| `sentry` | Sentry integration | +| `otel` | OpenTelemetry SDK + OTLP traces, metrics, and logs | +| `otel-fastapi` | FastAPI OpenTelemetry instrumentation | +| `otel-grpc` | gRPC OpenTelemetry instrumentation | +| `otel-sqlalchemy` | SQLAlchemy OpenTelemetry instrumentation | +| `otel-redis` | Redis OpenTelemetry instrumentation | +| `otel-elasticsearch` | Elasticsearch OpenTelemetry instrumentation | +| `otel-kafka` | Kafka OpenTelemetry instrumentation | +| `otel-scylladb` | ScyllaDB OpenTelemetry instrumentation | +| `otel-minio` | MinIO/botocore OpenTelemetry instrumentation | | `jwt` | JWT encode/decode (`JWTUtils`) | | `cache` | Cache helpers | | `scheduler` | Scheduler helpers | @@ -145,8 +154,8 @@ uvicorn.run( gRPC bind (parallel): `config.GRPC.SERVE_HOST`, `config.GRPC.SERVE_PORT`. -Also: `FastAPIRateLimitConfig` for FastAPI rate-limit settings; enable gRPC rate-limit via -`GRPC_RATE_LIMIT.IS_ENABLED`. +ArchiPy 5.x does not ship FastAPI rate limiting; use `fastapi-redis-sdk` when needed. gRPC rate limiting remains +available through `GRPC_RATE_LIMIT.IS_ENABLED`. Live: https://syntaxarc.github.io/ArchiPy/tutorials/config_management/ @@ -386,7 +395,7 @@ Prefer ArchiPy utils. Import from the **concrete submodule** (package `__init__` |-------------------|------------------------------------------|---------------------------------| | `AppUtils` | `archipy.helpers.utils.app_utils` | FastAPI / gRPC app factories | | `BaseUtils` | `archipy.helpers.utils.base_utils` | Shared facade helpers | -| `TracingUtils` | `archipy.helpers.utils.tracing_utils` | tracing helpers | +| `OtelUtils` | `archipy.helpers.utils.otel_utils` | OpenTelemetry provider lifecycle | | `RateLimitUtils` | `archipy.helpers.utils.rate_limit_utils` | rate limiting | | `DatetimeUtils` | `archipy.helpers.utils.datetime_utils` | datetime helpers | | `StringUtils` | `archipy.helpers.utils.string_utils` | string helpers | @@ -396,7 +405,6 @@ Prefer ArchiPy utils. Import from the **concrete submodule** (package `__init__` | `ErrorUtils` | `archipy.helpers.utils.error_utils` | error helpers | | `TOTPUtils` | `archipy.helpers.utils.totp_utils` | TOTP | | `KeycloakUtils` | `archipy.helpers.utils.keycloak_utils` | Keycloak helpers | -| `PrometheusUtils` | `archipy.helpers.utils.prometheus_utils` | Prometheus helpers | Custom utils: pure only — no DB/network/adapter construction. @@ -408,7 +416,8 @@ Prefer ArchiPy under `archipy.helpers.decorators`: |------------------------------|---------------------------------------------------------------------------------------------------------------------------| | Cache | `ttl_cache_decorator` (`archipy.helpers.decorators.cache`) | | Transactions (on **logics**) | `postgres_sqlalchemy_atomic_decorator`, `async_postgres_sqlalchemy_atomic_decorator`, plus sqlite/starrocks/generic twins | -| Observability | `capture_span`, `capture_transaction` (+ `async_capture_span`, `async_capture_transaction`); `timing_decorator` | +| Observability | `trace_span`, `trace_root`, `async_trace_span`, `async_trace_root`, `trace_class`; `timing_decorator` | +| Metrics | `measure_duration`, `async_measure_duration`, `count_calls`, `async_count_calls` | | Resilience | `retry_decorator`, `timeout_decorator` | | Other | `singleton_decorator`, `grpc_rate_limit_decorator` (gRPC only) | @@ -419,12 +428,12 @@ No concrete adapter imports at module level in custom decorators. Prefer ArchiPy under `archipy.helpers.interceptors` (FastAPI / gRPC). Cross-cutting only. Prefer AppUtils auto-registration for stock hooks; wire custom via DI / `customized_interceptors=` — not business logic. -### Rate-limit and metric interceptors +### Instrumentation and rate limiting -- FastAPI rate-limit: config via `FastAPIRateLimitConfig`; prefer AppUtils / config flags over manual wiring. +- FastAPI: `AppUtils.create_fastapi_app` auto-instruments through `archipy[otel-fastapi]` when `OTEL.IS_ENABLED`. +- gRPC: AppUtils factories insert OTel contrib server interceptors through `archipy[otel-grpc]`. - gRPC rate-limit: `GRPC_RATE_LIMIT.IS_ENABLED` + `grpc_rate_limit_decorator` / stock interceptors. -- **Metric interceptors require `archipy[prometheus]`** — they import `prometheus_client` at module import time. Without - the extra, importing those modules fails. +- FastAPI rate-limit handlers were removed in 5.0; use `fastapi-redis-sdk`. Live helpers overview: https://syntaxarc.github.io/ArchiPy/tutorials/helpers/ @@ -432,15 +441,34 @@ Live helpers overview: https://syntaxarc.github.io/ArchiPy/tutorials/helpers/ Combine library pieces rather than inventing a parallel stack: -| Concern | ArchiPy pieces | Extra | -|-----------------|---------------------------------------------------------------------------|---------------| -| Tracing / APM | `TracingUtils`, `capture_span` / `capture_transaction`, Elastic APM hooks | `elastic-apm` | -| Metrics | `PrometheusUtils`, metric interceptors | `prometheus` | -| Errors / events | Sentry integration | `sentry` | -| Timing | `timing_decorator` | — | +| Concern | ArchiPy pieces | Extra | +|-----------------|-------------------------------------------------------------------------------------------|--------------------------------| +| Traces | `OtelUtils`, `trace_root` / `trace_span` (+ async twins), AppUtils auto-instrumentation | `otel` + `otel-fastapi`/`otel-grpc` | +| Metrics | `measure_duration` / `count_calls` (+ async twins), OTLP export | `otel` + stack-specific extra | +| Logs | OTLP logging configured through `BaseConfig.OTEL` | `otel` | +| Errors | `BaseUtils.capture_exception` records on the current span | `otel` | +| Timing only | `timing_decorator` | — | + +Configure through nested settings, not SDK autoconfiguration: + +```bash +OTEL__IS_ENABLED=true +OTEL__SERVICE_NAME=my-service +OTEL__OTLP_ENDPOINT=http://localhost:4317 +OTEL__PROTOCOL=grpc +OTEL__TRACES_ENABLED=true +OTEL__METRICS_ENABLED=true +OTEL__LOGS_ENABLED=true +OTEL__TRACES_SAMPLE_RATIO=0.1 +OTEL__LOGS_LEVEL=WARNING +``` + +Call `OtelUtils.init_otel_if_needed(config)` after `BaseConfig.set_global(config)` and **before** DI constructs +SQLAlchemy engines, Kafka clients, or ScyllaDB sessions. AppUtils repeats initialization safely and auto-instruments +FastAPI/gRPC when their matching extras are installed. Prefer `WARNING` or higher for exported production logs. -Wire via `AppUtils` + config flags when possible. Health probes (above) are complementary but separate — probes answer -infra routing; observability answers product/ops insight. +Health probes (above) are complementary but separate — probes answer infra routing; observability answers product/ops +insight. Use `/scaffold-observability` for a repository-aware setup. Live: https://syntaxarc.github.io/ArchiPy/tutorials/observability/ diff --git a/skills/redis-search/SKILL.md b/skills/redis-search/SKILL.md index 43e93e2..83460b4 100644 --- a/skills/redis-search/SKILL.md +++ b/skills/redis-search/SKILL.md @@ -19,15 +19,16 @@ Redis search via ArchiPy (`archipy[redis]`): Canonical layout and ArchiPy constraints: `../archipy-docs/reference.md` (Adapters + Redis Search). Templates: `reference/fulltext_adapter.py`, `reference/vector_adapter.py`, `reference/search_cache_adapter.py`. -## Before writing files +Resolve files under `reference/` relative to this `SKILL.md` in the plugin installation +(`$CURSOR_PLUGIN_ROOT/skills/redis-search/` or `$CLAUDE_PLUGIN_ROOT/skills/redis-search/`). These are plugin templates, +not app-relative paths. Copy and adapt them into the app; never edit the plugin copies. -Ask the user for: +## Before writing files -1. Search type: full-text, vector, or caching -2. Domain name (e.g. `product`, `document`) -3. Data structure to index (HASH vs JSON) -4. Search patterns needed (autocomplete, faceted search, …) -5. Sync or async (or both as separate classes) +1. Inspect the domain, Redis config/adapters, DTOs, index naming, key prefixes, DI wiring, and tests. +2. Infer data structure, sync/async style, and existing search conventions. +3. Ask only for unresolved choices: search type, domain, query behavior, and vector dimension/metric when applicable. +4. Preserve existing indexes and adapters. Treat schema or prefix changes as migrations; do not silently replace them. ## Prefer ArchiPy @@ -84,6 +85,13 @@ logics own invalidation rules. - Prefer ArchiPy search handle API; only drop to raw Redis for operations the handle does not cover. - Use specific exceptions; always `raise ... from e`. +## Verify + +1. Run formatter/linter and focused tests for index creation, query mapping, empty results, and mapped failures. +2. For vector search, verify encoded vector dimension and distance metric match the schema. +3. Run live Redis tests only when the project already provides tagged/containerized infrastructure. +4. Report files, schema assumptions, dependency changes, and commands run. + ## Docs - https://syntaxarc.github.io/ArchiPy/tutorials/adapters/ (Redis Search section) diff --git a/skills/scaffold-archipy-adapter/SKILL.md b/skills/scaffold-archipy-adapter/SKILL.md index 00169da..3d653df 100644 --- a/skills/scaffold-archipy-adapter/SKILL.md +++ b/skills/scaffold-archipy-adapter/SKILL.md @@ -10,13 +10,14 @@ description: >- ## Before writing files -Ask the user for: - -1. Domain name (e.g. `user`, `order`) -2. Adapter purpose / file stem (e.g. `db`, `cache` → `user_db_adapter.py`) -3. Sync or async (or both as separate classes) -4. Whether an in-memory mock is needed for BDD -5. Whether this wraps an existing ArchiPy adapter or a new external client +1. Inspect `pyproject.toml`, the target domain, neighboring adapters, ports, DI wiring, and tests. +2. Infer installed extras, naming, sync/async style, and existing ArchiPy integration. +3. Ask only for unresolved choices: + - Domain and adapter purpose + - Sync or async when the repository does not establish one + - In-memory mock when testing requirements are unclear + - New external client when ArchiPy has no matching adapter +4. Preserve existing adapters and contracts. Extend compatible code; do not overwrite. ## Prefer ArchiPy @@ -50,6 +51,13 @@ repositories// - Do **not** create a top-level `adapters//` package — domain adapters live under repositories. - Wire via DI in `configs/containers.py`. +## Verify + +1. Run the repository's formatter and linter on generated Python. +2. Run focused adapter/repository tests with mocks; do not require live infrastructure unless the project already does. +3. Confirm imports, port conformance, exception chaining, and DI wiring. +4. Report files, dependency changes, and commands run. + ## Docs - https://syntaxarc.github.io/ArchiPy/getting-started/project_structure/ diff --git a/skills/scaffold-archipy-app/SKILL.md b/skills/scaffold-archipy-app/SKILL.md index e394d36..0a97a89 100644 --- a/skills/scaffold-archipy-app/SKILL.md +++ b/skills/scaffold-archipy-app/SKILL.md @@ -10,11 +10,14 @@ description: >- ## Before writing files -Ask the user for: - -1. Python package name (e.g. `my_app`) -2. ArchiPy extras to install (e.g. `redis`, `dependency-injection`, `postgres`, `sqlalchemy`, `fastapi`) -3. Optional: first domain name (default `user`) +1. Inspect the workspace for `pyproject.toml`, existing packages, config, and source layout. +2. Infer the package name and installed extras when they already exist. +3. Ask only for unresolved choices: + - Python package name + - ArchiPy extras to install + - Optional first domain name (default `user`) +4. If files already exist, merge compatible additions and preserve project conventions. Never replace an existing + application tree without explicit approval. ## Steps @@ -69,6 +72,13 @@ features/ # optional — /scaffold-bdd - No secrets in code; list env keys in `.env.example`. - Do not copy ArchiPy library maintainer tooling (graphify, library BDD internals). +## Verify + +1. Run the repository's formatter and linter on generated Python. +2. Import the package and app factory without starting network services. +3. Run existing targeted tests, if present. +4. Report created/updated files, installed extras, and commands run. + ## Docs - https://syntaxarc.github.io/ArchiPy/getting-started/quickstart/ diff --git a/skills/scaffold-archipy-bdd/SKILL.md b/skills/scaffold-archipy-bdd/SKILL.md index 1be1b1a..0483e75 100644 --- a/skills/scaffold-archipy-bdd/SKILL.md +++ b/skills/scaffold-archipy-bdd/SKILL.md @@ -10,10 +10,10 @@ description: >- ## Before writing files -Ask the user for: - -1. Feature name / file stem (e.g. `user_registration`) -2. Mode: **mocks only** vs **infra** (`@needs-*` + testcontainers) +1. Inspect existing `features/`, Behave config, scenario context, tags, containers, and the behavior being tested. +2. Infer naming and reuse shared support files. Ask only for an unresolved feature name or whether real infrastructure is + required; default to mocks. +3. Never overwrite shared support files. Merge missing hooks/registrations while preserving project-specific behavior. ## Prefer ArchiPy @@ -43,6 +43,10 @@ features/ Model after ArchiPy `features/` + docs tutorial (consumer-slim — no library gRPC/Temporal special cases unless the app needs them). +Resolve files under `reference/` relative to this `SKILL.md` in the plugin installation +(`$CURSOR_PLUGIN_ROOT/skills/scaffold-archipy-bdd/` or `$CLAUDE_PLUGIN_ROOT/skills/scaffold-archipy-bdd/`). These are +plugin templates, not app-relative paths. Copy and adapt them into the app; never edit the plugin copies. + **Copy templates from `reference/`, adapt package imports, create only if missing:** | Destination | Template | @@ -79,6 +83,13 @@ Do **not** paste ArchiPy’s entire container catalogue or gRPC test servers. - Dispose context after each scenario; `stop_all` after feature/all when using containers. - Consumer apps: slim templates — library `environment.py` gRPC/Temporal blocks are ArchiPy-core specific. +## Verify + +1. Run the generated feature in mocks mode. +2. For infra mode, run the narrow tagged scenario only when its required container runtime is available. +3. Confirm scenario context is isolated and disposed on both success and failure. +4. Report files, tags, dependency changes, and commands run. + ## Docs - https://syntaxarc.github.io/ArchiPy/tutorials/testing_strategy/ diff --git a/skills/scaffold-archipy-decorator/SKILL.md b/skills/scaffold-archipy-decorator/SKILL.md index ef491d9..9b0d701 100644 --- a/skills/scaffold-archipy-decorator/SKILL.md +++ b/skills/scaffold-archipy-decorator/SKILL.md @@ -3,7 +3,7 @@ name: scaffold-archipy-decorator description: >- Scaffold or wire a helpers/decorators module for an ArchiPy app. Prefer ArchiPy decorators (ttl_cache_decorator, postgres_sqlalchemy_atomic_decorator, - capture_span / capture_transaction, …) before custom ones. + trace_span / trace_root, measure_duration / count_calls, …) before custom ones. --- # Scaffold ArchiPy Decorator @@ -14,11 +14,10 @@ description: >- ## Before writing files -Ask: - -1. Decorator purpose (cache, atomic, retry, timing, …) -2. Prefer ArchiPy built-in vs custom -3. Sync, async, or both +1. Inspect existing decorators, call sites, and the installed ArchiPy version for a matching decorator. +2. Infer sync/async style and project naming from the target call site. +3. Ask only for unresolved behavior. Prefer an ArchiPy decorator whenever it fits. +4. Preserve existing decorator modules; do not overwrite. ## Prefer ArchiPy @@ -27,8 +26,10 @@ Examples: - `from archipy.helpers.decorators.cache import ttl_cache_decorator` - `from archipy.helpers.decorators.sqlalchemy_atomic import postgres_sqlalchemy_atomic_decorator` - `from archipy.helpers.decorators.sqlalchemy_atomic import async_postgres_sqlalchemy_atomic_decorator` -- `capture_span` / `capture_transaction`, `timeout_decorator`, `retry_decorator`, `singleton_decorator`, - `timing_decorator`, `grpc_rate_limit_decorator` under `archipy.helpers.decorators` +- `trace_span` / `trace_root` (+ async twins) from `archipy.helpers.decorators.tracing` +- `measure_duration` / `count_calls` (+ async twins) from `archipy.helpers.decorators.metrics` +- `timeout_decorator`, `retry_decorator`, `singleton_decorator`, `timing_decorator`, and + `grpc_rate_limit_decorator` under `archipy.helpers.decorators` Show correct usage on a sample function; do not reimplement. UoW decorators belong on **logics**, not services/repositories. @@ -85,6 +86,11 @@ def timed(func: Callable[P, R]) -> Callable[P, R]: - **No** concrete adapter imports at module level - Separate sync/async wrappers if both needed +## Verify + +Run formatter/linter and focused tests for return values, exceptions, and metadata/signature preservation. Report the +reused ArchiPy API or files created, plus commands run. + ## Docs - https://syntaxarc.github.io/ArchiPy/tutorials/helpers/ diff --git a/skills/scaffold-archipy-domain/SKILL.md b/skills/scaffold-archipy-domain/SKILL.md index 3ab4949..fc97147 100644 --- a/skills/scaffold-archipy-domain/SKILL.md +++ b/skills/scaffold-archipy-domain/SKILL.md @@ -9,15 +9,23 @@ description: >- ## Before writing files -Ask the user for: - -1. Domain name (e.g. `order`) -2. ArchiPy extras to install/use (e.g. `redis`, `postgres`, `sqlalchemy`, `fastapi`) -3. Transport: FastAPI (default) or gRPC +1. Inspect `pyproject.toml`, the package tree, neighboring domains, DI containers, and existing tests. +2. Infer package name, installed extras, naming, transport, and sync/async style from the repository. +3. Ask only for unresolved choices that materially change the generated slice: + - Domain name + - Missing infrastructure/extras + - Transport when the app does not already establish one (default FastAPI) +4. Preserve existing files. Extend compatible modules; stop and explain conflicts instead of overwriting them. ## Compose — do not fork templates -Apply existing skills in order (reuse their constraints and file layouts): +Before generating files, read these plugin skills in full: + +- `../scaffold-archipy-adapter/SKILL.md` +- `../scaffold-archipy-logic/SKILL.md` +- `../scaffold-archipy-service/SKILL.md` + +Apply their constraints and `Verify` sections in order; do not replace them with summaries: 1. **Models** — stubs below, then flesh via `using-archipy-models` rule 2. **scaffold-archipy-adapter** — thin wrapper under `repositories//adapters/` + `_repository.py` @@ -46,10 +54,12 @@ Example shapes (`order` → rename): ```python # models/dtos/order/domain/v1/order_create_input_dto.py -from pydantic import BaseModel, Field +from pydantic import Field +from archipy.models.dtos.base_dtos import BaseDTO -class OrderCreateInputDTO(BaseModel): + +class OrderCreateInputDTO(BaseDTO): """Domain input crossing the service → logic boundary.""" customer_id: str = Field(min_length=1) @@ -59,10 +69,10 @@ class OrderCreateInputDTO(BaseModel): ```python # models/dtos/order/domain/v1/order_create_output_dto.py -from pydantic import BaseModel +from archipy.models.dtos.base_dtos import BaseDTO -class OrderCreateOutputDTO(BaseModel): +class OrderCreateOutputDTO(BaseDTO): """Domain output returned to the service layer.""" order_id: str @@ -71,10 +81,10 @@ class OrderCreateOutputDTO(BaseModel): ```python # models/dtos/order/repository/order_create_command_dto.py -from pydantic import BaseModel +from archipy.models.dtos.base_dtos import BaseDTO -class OrderCreateCommandDTO(BaseModel): +class OrderCreateCommandDTO(BaseDTO): """Repository write command — mapped from domain input inside the logic.""" customer_id: str @@ -95,8 +105,8 @@ class OrderInvalidArgumentError(InvalidArgumentError): """Raised when order input fails domain validation.""" ``` -Naming: `*InputDTO` / `*OutputDTO` for domain; `*CommandDTO` / `*QueryDTO` for repository. Prefer ArchiPy `BaseError` -hierarchy — adjust base classes to what the installed `archipy` version exports. +Naming: `*InputDTO` / `*OutputDTO` for domain; `*CommandDTO` / `*QueryDTO` for repository. Prefer ArchiPy `BaseDTO` +and the exported `BaseError` hierarchy. Verify imports against the app's installed ArchiPy version before writing. ## Outcome checklist @@ -114,6 +124,13 @@ hierarchy — adjust base classes to what the installed `archipy` version export - Double quotes, Google-style docstrings, Python 3.14+ typing. - Do **not** invent a top-level app `adapters/` package. +## Verify + +1. Run the repository's formatter and linter on generated Python. +2. Run targeted domain tests; add a focused test when behavior, mapping, or error handling was added. +3. Confirm imports and DI wiring resolve without constructing production infrastructure. +4. Report created/updated files, dependency changes, and commands run. + ## Docs - https://syntaxarc.github.io/ArchiPy/getting-started/project_structure/ diff --git a/skills/scaffold-archipy-health-checks/SKILL.md b/skills/scaffold-archipy-health-checks/SKILL.md index b1ba056..3ef5c01 100644 --- a/skills/scaffold-archipy-health-checks/SKILL.md +++ b/skills/scaffold-archipy-health-checks/SKILL.md @@ -10,13 +10,11 @@ description: >- ## Before writing files -Ask the user for: - -1. Python package name -2. Transport: FastAPI (HTTP), gRPC (sync or async), or both -3. Dependencies to check in readiness (`postgres`, `redis`, downstream HTTP/gRPC, custom) -4. Optional: heartbeat-based liveness deadlock detection for HTTP (`yes` / `no`) -5. Optional: emit `deploy/k8s-probes.yaml` (`yes` / `no`) +1. Inspect package/config, current transports, app lifecycle, dependency adapters, DI wiring, deployment manifests, and + existing health endpoints. +2. Infer package name, transport, ports, and readiness dependencies from the repository. +3. Ask only for unresolved choices: dependencies to include, heartbeat deadlock detection, or Kubernetes YAML. +4. Preserve existing health routes and manifests; merge compatible additions instead of overwriting. ## Prefer ArchiPy @@ -28,13 +26,7 @@ duplicate endpoint sketches here. ```bash uv add "archipy[fastapi]" # HTTP probes -uv add "archipy[grpc]" # gRPC server; also need grpcio-health-checking -``` - -For gRPC health protocol: - -```bash -uv add grpcio-health-checking +uv add "archipy[grpc]" # gRPC server + grpcio-health-checking ``` Prefer existing app bootstrap: @@ -61,6 +53,11 @@ styles on one server. ## Generate +Resolve files under `reference/` relative to this `SKILL.md` in the plugin installation +(`$CURSOR_PLUGIN_ROOT/skills/scaffold-archipy-health-checks/` or +`$CLAUDE_PLUGIN_ROOT/skills/scaffold-archipy-health-checks/`). These are plugin templates, not app-relative paths. Copy +and adapt them into the app; never edit the plugin copies. + ```text /services/health/v1/ ├── health_checks.py # shared readiness helpers (deps, warm-up, shutdown) @@ -160,6 +157,13 @@ See `../archipy-docs/reference.md` § Health checks → Common mistakes. Also av - Prefer FastAPI `JSONResponse` for HTTP probes; prefer `grpcio-health-checking` for gRPC - Suggest `/docs-health-checks` for explanation and `/docs-observability` for metrics / tracing follow-up +## Verify + +1. Run formatter/linter and focused health tests without starting a long-lived server. +2. Test healthy, dependency failure, timeout, warm-up, and shutdown states. +3. Validate generated Kubernetes YAML and confirm probe ports/paths/service names match app config. +4. Report files, dependencies, and commands run. + ## Beyond Kubernetes Same pattern applies to ALB, Consul, Nginx, or gRPC load balancers: expose health (HTTP and/or `grpc.health.v1`), define diff --git a/skills/scaffold-archipy-interceptor/SKILL.md b/skills/scaffold-archipy-interceptor/SKILL.md index edc5df9..e670e29 100644 --- a/skills/scaffold-archipy-interceptor/SKILL.md +++ b/skills/scaffold-archipy-interceptor/SKILL.md @@ -13,12 +13,11 @@ description: >- ## Before writing files -Ask: - -1. Framework: FastAPI, gRPC, or other -2. Sync or async -3. Prefer ArchiPy built-in vs custom -4. Cross-cutting concern (metrics, auth context, logging) — not a use-case +1. Inspect app bootstrap, existing interceptors/middleware, DI wiring, and the installed ArchiPy version. +2. Infer framework and sync/async style from the repository. +3. Ask only for an unresolved cross-cutting concern or framework choice. Prefer an ArchiPy interceptor whenever it + fits. +4. Preserve existing registration order and modules; do not overwrite. ## Prefer ArchiPy @@ -72,6 +71,11 @@ class RequestIdMiddleware(BaseHTTPMiddleware): - Wire through `configs/containers.py`, `AppUtils`, or framework middleware registration - Map errors at the boundary; do not leak raw exceptions +## Verify + +Run formatter/linter and focused transport tests for ordering, success, and mapped failure behavior. Confirm registration +occurs once. Report the reused ArchiPy API or files created, plus commands run. + ## Docs - https://syntaxarc.github.io/ArchiPy/tutorials/helpers/ diff --git a/skills/scaffold-archipy-logic/SKILL.md b/skills/scaffold-archipy-logic/SKILL.md index a20ab6a..35e9341 100644 --- a/skills/scaffold-archipy-logic/SKILL.md +++ b/skills/scaffold-archipy-logic/SKILL.md @@ -9,11 +9,11 @@ description: >- ## Before writing files -Ask the user for: - -1. Domain name (e.g. `user`) -2. Logic name / file stem (e.g. `user_registration` → `user_registration_logic.py`) -3. Sync or async atomic (default sync if `postgres`+`sqlalchemy`; async if `postgres`+`sqlalchemy-async`) +1. Inspect the target domain's DTOs, repository contract, neighboring logics, DI wiring, and tests. +2. Infer naming and sync/async style from existing code and installed extras. +3. Ask only for an unresolved domain/use-case name or transaction choice. Default to sync for + `postgres` + `sqlalchemy` and async for `postgres` + `sqlalchemy-async`. +4. Preserve existing use cases; do not overwrite logic or DTO files. ## Prefer ArchiPy @@ -68,6 +68,13 @@ Create missing domain DTO stubs under `models/dtos//domain/v1/` if they - No atomic / UoW decorators on repositories or services — only logics. - Wire via DI in `configs/containers.py`. +## Verify + +1. Run the repository's formatter and linter on generated Python. +2. Add or run focused tests for success, business-rule failure, and rollback-relevant failure. +3. Confirm DTO boundaries, repository injection, decorator choice, and DI wiring. +4. Report files and commands run. + ## Docs - https://syntaxarc.github.io/ArchiPy/getting-started/concepts/ diff --git a/skills/scaffold-archipy-observability/SKILL.md b/skills/scaffold-archipy-observability/SKILL.md new file mode 100644 index 0000000..639f7cb --- /dev/null +++ b/skills/scaffold-archipy-observability/SKILL.md @@ -0,0 +1,107 @@ +--- +name: scaffold-archipy-observability +description: >- + Configure ArchiPy 5.x OpenTelemetry traces, metrics, logs, AppUtils + instrumentation, and decorators. Use when adding OTLP observability or + migrating removed Sentry, Elastic APM, or Prometheus integrations. +--- + +# Scaffold ArchiPy OpenTelemetry + +## Before writing files + +1. Inspect `pyproject.toml`/`uv.lock`, `AppConfig`, bootstrap order, app transports, adapters, and `.env.example`. +2. Infer required instrumentation extras from the installed stack. +3. Ask only for unresolved choices: enabled signals, OTLP endpoint/protocol, service name, sampling ratio, and log level. +4. Preserve existing config/bootstrap. Never write collector credentials or OTLP headers with secret values. + +## Install only matching extras + +| Stack | Extra | +|-------|-------| +| Core OTLP traces, metrics, logs | `otel` | +| FastAPI | `otel-fastapi` | +| gRPC | `otel-grpc` | +| SQLAlchemy | `otel-sqlalchemy` | +| Redis | `otel-redis` | +| Elasticsearch | `otel-elasticsearch` | +| Kafka | `otel-kafka` | +| ScyllaDB | `otel-scylladb` | +| MinIO/botocore | `otel-minio` | + +Combine required extras, for example: + +```bash +uv add "archipy[otel-fastapi,otel-sqlalchemy,otel-redis]" +``` + +Do not use removed ArchiPy 4.x extras: `prometheus`, `sentry`, or `elastic-apm`. + +## Configure + +Add non-secret defaults/documentation to `.env.example`: + +```bash +OTEL__IS_ENABLED=true +OTEL__SERVICE_NAME=my-service +OTEL__OTLP_ENDPOINT=http://localhost:4317 +OTEL__PROTOCOL=grpc +OTEL__TRACES_ENABLED=true +OTEL__METRICS_ENABLED=true +OTEL__LOGS_ENABLED=true +OTEL__TRACES_SAMPLE_RATIO=0.1 +OTEL__LOGS_LEVEL=WARNING +OTEL__FASTAPI_EXCLUDED_URLS=health,docs,redoc,openapi.json +``` + +Use `BaseConfig.OTEL`; ArchiPy builds providers programmatically. Do not rely on OpenTelemetry SDK `OTEL_*` +autoconfiguration. For `http/protobuf`, use port 4318; ArchiPy appends `/v1/{signal}` when the base endpoint has no path. + +## Initialize before adapters + +After `BaseConfig.set_global(config)` and before constructing the DI container/adapters: + +```python +from archipy.configs.base_config import BaseConfig +from archipy.helpers.utils.otel_utils import OtelUtils + +config = AppConfig() +BaseConfig.set_global(config) +OtelUtils.init_otel_if_needed(config) +container = ApplicationContainer() +``` + +Initialization is idempotent. AppUtils calls it again safely. Early initialization matters for SQLAlchemy engines, +Kafka clients, ScyllaDB sessions, and logs created during bootstrap. + +## Instrument application code + +- FastAPI: `AppUtils.create_fastapi_app(config)` with `archipy[otel-fastapi]`. +- gRPC server: `create_grpc_app` / `create_async_grpc_app` with `archipy[otel-grpc]`. +- gRPC clients: `OtelUtils.grpc_client_interceptors()` / `async_grpc_client_interceptors()`. +- Traces: `trace_root`, `trace_span`, `async_trace_root`, `async_trace_span`, `trace_class`. +- Metrics: `measure_duration`, `async_measure_duration`, `count_calls`, `async_count_calls`. +- Exceptions: `BaseUtils.capture_exception` records on the current span. + +Import decorators from `archipy.helpers.decorators`. Sync decorators reject coroutine functions; use their async twins. +Only capture non-sensitive argument names in trace attributes. + +## ArchiPy 4.x migration + +- `TracingUtils` → `OtelUtils` +- `capture_transaction` / `capture_span` → `trace_root` / `trace_span` (+ async twins) +- Prometheus/Sentry/Elastic APM config → `BaseConfig.OTEL` +- Removed FastAPI rate-limit handler → `fastapi-redis-sdk` + +## Verify + +1. Run the repository formatter/linter and import the bootstrap without starting long-lived servers. +2. Confirm OTel initializes before adapter construction and AppUtils receives the same global/injected config. +3. Test with in-memory exporters or `OtelUtils.configure_for_testing`; do not require a production collector. +4. Confirm disabled signals no-op and no credentials appear in source, logs, or committed env files. +5. Report extras, config keys, bootstrap changes, and commands run. + +## Docs + +- https://syntaxarc.github.io/ArchiPy/tutorials/observability/ +- https://syntaxarc.github.io/ArchiPy/tutorials/helpers/interceptors/ diff --git a/skills/scaffold-archipy-service/SKILL.md b/skills/scaffold-archipy-service/SKILL.md index 2d56676..24351e2 100644 --- a/skills/scaffold-archipy-service/SKILL.md +++ b/skills/scaffold-archipy-service/SKILL.md @@ -10,11 +10,10 @@ description: >- ## Before writing files -Ask the user for: - -1. Domain name (e.g. `user`) -2. API version (default `v1`) -3. Transport: FastAPI (default) or gRPC (sync vs async) +1. Inspect existing transports, domain DTOs/logics, app bootstrap, DI wiring, and service tests. +2. Infer domain, API version, framework, and sync/async style when established by the repository. +3. Ask only for unresolved choices. Default to `v1` and FastAPI only when no project convention exists. +4. Preserve existing routes, servicers, and bootstrap code; integrate without overwriting. ## Prefer ArchiPy @@ -62,7 +61,14 @@ uvicorn in `manage.py` from `config.FASTAPI` (`SERVE_HOST`, `SERVE_PORT`, `RELOA - Do not re-implement CORS/exception handlers/stock gRPC interceptors AppUtils already wires. - Wire logic via DI container. +## Verify + +1. Run the repository's formatter and linter on generated Python. +2. Run focused transport tests without starting a long-lived server. +3. Confirm route/servicer registration, DTO mapping, domain-error mapping, and DI resolution. +4. Report files, dependency changes, and commands run. + ## Docs - https://syntaxarc.github.io/ArchiPy/getting-started/project_structure/ -- https://syntaxarc.github.io/ArchiPy/tutorials/helpers/interceptors/ +- https://syntaxarc.github.io/ArchiPy/tutorials/helpers/ diff --git a/skills/scaffold-archipy-utils/SKILL.md b/skills/scaffold-archipy-utils/SKILL.md index dd637be..094b8b1 100644 --- a/skills/scaffold-archipy-utils/SKILL.md +++ b/skills/scaffold-archipy-utils/SKILL.md @@ -13,11 +13,10 @@ description: >- ## Before writing files -Ask: - -1. Util name / purpose -2. Prefer existing ArchiPy util vs custom (`TracingUtils`, `RateLimitUtils`, `DatetimeUtils`, `StringUtils`, `JWTUtils`, - `PasswordUtils`, `FileUtils`, `ErrorUtils`, `AppUtils`, …) +1. Inspect existing helpers and installed ArchiPy version for a matching utility. +2. Infer project naming and function/class style. +3. Ask only for an unresolved purpose or behavior. Prefer an ArchiPy utility whenever it fits. +4. Preserve existing helper modules; do not overwrite. ## Prefer ArchiPy @@ -56,6 +55,11 @@ class SlugUtils: - Lazy-import optional deps inside functions when needed - Brief usage snippet in the reply +## Verify + +Run the repository's formatter, linter, and focused unit tests. Report the reused ArchiPy API or files created, plus +commands run. + ## Docs - https://syntaxarc.github.io/ArchiPy/tutorials/helpers/ diff --git a/tests/test_scripts.py b/tests/test_scripts.py index 9356c11..ebfbb23 100644 --- a/tests/test_scripts.py +++ b/tests/test_scripts.py @@ -6,8 +6,12 @@ import os import subprocess import sys +import tempfile import unittest from pathlib import Path +from unittest import mock + +from scripts import check_catalog ROOT = Path(__file__).resolve().parents[1] SCRIPTS = ROOT / "scripts" @@ -25,6 +29,89 @@ def test_check_catalog_passes(self) -> None: self.assertEqual(result.returncode, 0, result.stderr) self.assertIn("OK: version=", result.stdout) + def test_scaffold_skill_requires_verification_loop(self) -> None: + with tempfile.TemporaryDirectory() as tmp_dir: + root = Path(tmp_dir) + skill_dir = root / "skills" / "scaffold-example" + skill_dir.mkdir(parents=True) + (skill_dir / "SKILL.md").write_text( + "---\n" + "name: scaffold-example\n" + "description: Scaffold an example component when requested by the user.\n" + "---\n" + "# Example\n" + "## Before writing files\n" + "Inspect the repository.\n", + encoding="utf-8", + ) + + with mock.patch.object(check_catalog, "ROOT", root): + errors = check_catalog.check_skills() + + self.assertIn( + "scaffold-example/SKILL.md missing `## Verify` feedback loop", + errors, + ) + + def test_scaffold_command_requires_inspect_first_prompt(self) -> None: + with tempfile.TemporaryDirectory() as tmp_dir: + root = Path(tmp_dir) + skill_dir = root / "skills" / "scaffold-archipy-example" + skill_dir.mkdir(parents=True) + (skill_dir / "SKILL.md").write_text("# Example\n", encoding="utf-8") + commands_dir = root / "commands" + commands_dir.mkdir() + (commands_dir / "scaffold-example.md").write_text( + "Follow the **scaffold-archipy-example** skill.\n", + encoding="utf-8", + ) + + with mock.patch.object(check_catalog, "ROOT", root): + errors = check_catalog.check_command_skill_refs() + + self.assertIn( + "commands/scaffold-example.md must read its skill in full and inspect the workspace", + errors, + ) + + def test_skill_reference_must_exist(self) -> None: + with tempfile.TemporaryDirectory() as tmp_dir: + root = Path(tmp_dir) + skill_dir = root / "skills" / "example" + skill_dir.mkdir(parents=True) + (skill_dir / "SKILL.md").write_text( + "---\n" + "name: example\n" + "description: Read an example template when handling example requests.\n" + "---\n" + "# Example\n" + "Use `reference/missing.py`.\n", + encoding="utf-8", + ) + + with mock.patch.object(check_catalog, "ROOT", root): + errors = check_catalog.check_skills() + + self.assertIn( + "example/SKILL.md references missing `reference/missing.py`", + errors, + ) + + def test_archipy_reference_rejects_pre_5_guidance(self) -> None: + errors = check_catalog._check_archipy_reference_text( + "Verified against `archipy` 4.17.x.\n" + "Use `TracingUtils` and `capture_span`.\n", + ) + + self.assertIn( + "archipy-docs/reference.md must target ArchiPy 5.x or newer", + errors, + ) + self.assertIn( + "archipy-docs/reference.md contains removed ArchiPy 5.x guidance: `TracingUtils`", + errors, + ) + class HygieneTests(unittest.TestCase): def test_session_start(self) -> None: @@ -83,7 +170,12 @@ def test_claude_session_start_injects_architecture_rule(self) -> None: self.assertEqual(result.returncode, 0, result.stderr) payload = json.loads(result.stdout) context = payload["additional_context"] + self.assertIn("# ArchiPy App Rules Index", context) self.assertIn("# Architecture for ArchiPy Apps", context) + self.assertIn("# Contributing to ArchiPy Apps", context) + self.assertIn("# Python Code Style for ArchiPy Apps", context) + self.assertIn("# Security for ArchiPy Apps", context) + self.assertIn("# Tooling for ArchiPy Apps", context) self.assertIn("services → logics", context) def test_claude_post_tool_use_injects_glob_rule(self) -> None: @@ -101,6 +193,7 @@ def test_claude_post_tool_use_injects_glob_rule(self) -> None: self.assertEqual(result.returncode, 0, result.stderr) payload = json.loads(result.stdout) self.assertIn("Unit of Work", payload["additional_context"]) + self.assertIn("# Strict Typing for ArchiPy Apps", payload["additional_context"]) if __name__ == "__main__":