Skip to content

Commit 613832d

Browse files
committed
chore(claude): port skills + rules from shipping-platform
Sub-PR 1 of the 2026.5.0 umbrella release (PRDs/RELEASE_2026_5_PLATFORM_UPGRADE.md). Skills added (new): - create-extension-module — scaffold new modules/<name>/ extension - django-graphql — schema layout + auto-discovery patterns - django-rest-api — view/serializer/router patterns - run-tests — decision table mapping changed files to test commands Skills updated: - review-implementation — expanded N+1 check, extension-pattern gate, security check Rules added (new): - commit-conventions.md — type(scope) format, karrio scopes, no AI footer lines - extension-patterns.md — golden rule, namespace-package caveats, core hook points Rules updated: - code-style.md — merge JTL's i18n/Django-models/serializers guidance with karrio specifics - testing.md — imports-at-top rule + mocking guidance + Django/SDK patterns Also: - AGENTS.md — expand Context Priority with .claude/{rules,skills} load order - CLAUDE.md — update rules/skills index to match new set All ports adapt JTL-specific references (modules/entitlements, karrio subtree framing) to karrio equivalents (modules/orders, modules/events, etc.). Karrio- specific rules preserved: import karrio.lib as lib, no pytest, 4-method carrier test pattern, no legacy DP/SF/NF/DF/XP utilities.
1 parent 0a93585 commit 613832d

11 files changed

Lines changed: 1574 additions & 74 deletions

File tree

.claude/rules/code-style.md

Lines changed: 59 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,63 @@
11
# Code Style & Naming Conventions
22

3-
## General
4-
- Write code as if the same person authored the entire codebase
5-
- Favor functional, declarative style: use `map`, `reduce`, `filter`, list comprehensions
6-
- Concise but readable: no unnecessary verbosity, no cryptic one-liners
7-
- Never reinvent the wheel — always search for existing utilities first
3+
## General Principles
4+
5+
- Write code as if the same person authored the entire codebase — consistency is paramount.
6+
- Favor functional, declarative style: list comprehensions, `map` / `filter` / `reduce`.
7+
- `list(dict.fromkeys(items))` for order-preserving deduplication.
8+
- Concise but readable — no unnecessary verbosity, no cryptic one-liners.
9+
- Never reinvent the wheel — always search for existing utilities before writing new code.
810

911
## Python
10-
- PEP 8 with 4-space indentation
11-
- Format: `black`, type check: `mypy`
12-
- snake_case for modules/functions, PascalCase for classes
13-
- Always use `import karrio.lib as lib` — never the legacy `DP`, `SF`, `NF`, `DF`, `XP` utilities
14-
- Import order: stdlib → third-party → karrio core → local/relative
15-
16-
## TypeScript/React
17-
- 2-space indentation, format with Prettier
18-
- PascalCase for components, camelCase for functions/variables
19-
- Functional components only (no class components)
20-
- Import order: React/Next → third-party → @karrio packages → local
21-
- Always import types from `@karrio/types` (never define inline)
22-
- Use existing hooks from `@karrio/hooks/*` (never raw fetch/axios)
23-
- Use existing UI components from `@karrio/ui` (never duplicate patterns)
24-
25-
## Anti-Patterns
26-
- Never use `pytest` — we use `unittest` and Django tests
27-
- Never use raw SQL in Django migrations — use Django operations only
28-
- Never catch bare `Exception` — be specific
29-
- Never use mutable default arguments
30-
- Never add features not explicitly requested
31-
- Never use `any` type in TypeScript
12+
13+
- PEP 8, 4-space indentation, format with `ruff format` (previously `black`), type-check with `mypy`.
14+
- `snake_case` for modules/functions, `PascalCase` for classes.
15+
- Import order: stdlib → third-party → karrio core → local/relative.
16+
- **Always use `import karrio.lib as lib`** — NEVER the legacy `DP`, `SF`, `NF`, `DF`, `XP` utilities, and NEVER create new utility functions that duplicate `lib.*`.
17+
- Use specific exceptions; never bare `except:` or `except Exception:`.
18+
- No mutable default arguments — use `functools.partial` or sentinel pattern.
19+
20+
### Localization (i18n)
21+
22+
- **All user-facing strings** must use `django.utils.translation.gettext` (or `gettext_lazy`) — never hardcode messages.
23+
- This includes: error messages, validation messages, notification text, and any string returned in API responses that users see.
24+
- Import as `from django.utils.translation import gettext as _` and wrap strings: `_("Shipment created successfully")`.
25+
- Internal log messages and developer-facing strings (e.g., exception names, debug logs) do NOT need `gettext`.
26+
27+
## TypeScript / React
28+
29+
- 2-space indentation, format with Prettier.
30+
- `PascalCase` for components, `camelCase` for functions/variables.
31+
- Functional components only — no class components; use hooks.
32+
- Import order: React/Next → third-party → `@karrio/*` packages → local.
33+
- Always import types from `@karrio/types` — never define inline.
34+
- Use existing hooks from `@karrio/hooks/*` — never raw fetch/axios.
35+
- Use existing UI components from `@karrio/ui` — never duplicate patterns.
36+
- Never use `any` — use proper types, or `unknown` with type guards.
37+
- Use regenerate scripts (`./bin/run-generate-on`, Next codegen) for type generation — never manually edit generated files.
38+
39+
## Django Models
40+
41+
- Tenant-scoped models inherit from `OwnedEntity` and are filtered via `Model.access_by(request)` (or `Model.objects.filter(org=request.user.org)` at the ORM level).
42+
- System models: plain `models.Model` (no tenant scoping).
43+
- Register with `@core.register_model` decorator when applicable.
44+
- Use `functools.partial(core.uuid, prefix="xxx_")` for ID generation.
45+
- JSONField defaults: `functools.partial(karrio.server.core.models._identity, value=[])` (never bare `[]`).
46+
47+
## Serializers
48+
49+
- Use mixin classes for shared logic between Account and System serializers.
50+
- `@owned_model_serializer` for tenant-scoped serializers (handles `created_by` + `link_org()`).
51+
- Plain serializers for system-scoped models (manual `created_by` in `create()`).
52+
- Always use `Serializer.map()` pattern for validation + save.
53+
54+
## Anti-Patterns (NEVER DO)
55+
56+
- `pytest` — always `unittest` for SDK, `karrio test` for Django server.
57+
- Raw SQL in migrations — use Django migration operations (`AddField`, `RemoveField`, `RenameField`, `AlterField`, `RunPython`) only.
58+
- `RunSQL` — must work across SQLite, PostgreSQL, MySQL.
59+
- New utility functions duplicating `karrio.lib` — check `lib.*` reference first.
60+
- Bare exceptions, mutable defaults, `any` types.
61+
- Class components in React — use function components + hooks.
62+
- Manual CSS files — use Tailwind classes.
63+
- Features not explicitly requested.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Commit & PR Conventions
2+
3+
## Commit Messages
4+
5+
Format: `type(scope): summary` or `type: summary`
6+
7+
**Types**: `feat`, `fix`, `chore`, `refactor`, `test`, `docs`, `release`
8+
9+
**Scopes**: carrier name (e.g. `ups`, `fedex`, `dhl_express`, `smartkargo`, `canadapost`), or module (`admin`, `graph`, `manager`, `core`, `cli`, `sdk`, `dashboard`, `mcp`, `tracing`, `documents`, `data`, `events`, `orders`, `proxy`, `pricing`, `connectors`).
10+
11+
Examples:
12+
13+
```
14+
feat(smartkargo): add rate + shipment support
15+
fix(dhl_parcel_de): make package dimensions optional in shipment request
16+
chore: update requirements
17+
refactor(graph): auto-discover schemas via pkgutil.iter_modules
18+
test(admin): add system rate sheet CRUD tests
19+
docs(subtree): document SUBTREE_SYNC_WORKFLOW
20+
release: 2026.5.0
21+
```
22+
23+
## Rules
24+
25+
- **CRITICAL**: NEVER add `Co-Authored-By: <AI model>` or any AI attribution lines.
26+
- **CRITICAL**: NEVER add "Generated with Claude Code" or similar AI footers to commits or PR bodies.
27+
- **CRITICAL**: NEVER commit without explicit user permission.
28+
- Max 72-character subject line, imperative mood ("add", not "added").
29+
- Reference issues: `refs #123` or `fixes #123`.
30+
- Keep commits focused — one logical change per commit.
31+
- Run tests before pushing: `./bin/run-sdk-tests` and/or `./bin/run-server-tests`.
32+
33+
## Pull Requests
34+
35+
- Title under 70 characters, same format as commits.
36+
- Body follows [`git-workflow.md`](./git-workflow.md) — Feature / Fix / Release templates.
37+
- No AI-generated boilerplate footers.
38+
- Group related changes by `### Feat`, `### Fix`, `### Docs`, `### Chore` sections in release PRs.
39+
- Use tables for file-level change summaries and audit matrices.
40+
- Include test/build evidence in the Verification section.
41+
42+
## Branch Naming
43+
44+
Format: `type/description` — examples:
45+
46+
- `feat/2026.5-huey-http`
47+
- `fix/dhl-parcel-de-dimensions-optional`
48+
- `hotfix/2026.1.29-idempotent-archiving-migrations`
49+
- `chore/2026.5-ruff-precommit`
50+
- `sync/shipping-platform-2026-04-19` (upstream subtree sync; see `PRDs/SUBTREE_SYNC_WORKFLOW.md`)
51+
52+
## Release Commits
53+
54+
- Format: `release: YYYY.M.PATCH` (calendar versioning).
55+
- Central version file: `apps/api/karrio/server/VERSION`.
56+
- Version bump process: `./bin/update-version``./bin/update-package-versions``./bin/update-version-freeze``./bin/update-source-version-freeze`, or use `./bin/release [version]` for the full workflow.
57+
58+
## Subtree Sync Commits
59+
60+
When pulling changes from or pushing to `jtlshipping/shipping-platform` per `PRDs/SUBTREE_SYNC_WORKFLOW.md`:
61+
62+
- Prefix with `fix: sync shipping-platform patches (<brief description>)`.
63+
- Branch name: `sync/shipping-platform-YYYY-MM-DD`.
64+
- Isolate sync to a single commit; no mixing with semantic changes.
65+
- Document conflict-resolution decisions in the PR body (the SUBTREE_SYNC_WORKFLOW matrix is the authority).
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
# Extension Patterns — "Extend, Don't Modify Core"
2+
3+
## The Golden Rule
4+
5+
Karrio is modular by design: `modules/core`, `modules/graph`, `modules/admin` form the server core; `modules/connectors/*` are plugins; everything else (`modules/orders`, `modules/data`, `modules/documents`, `modules/events`, `modules/pricing`, `modules/manager`, ...) is an auto-discovered extension module.
6+
7+
**Prefer adding a new extension module over modifying an existing one.** Modify a core module only when the feature is genuinely generic and benefits all karrio deployments.
8+
9+
## Decision Tree
10+
11+
```
12+
Is this feature specific to one domain (orders, documents, pricing, …)?
13+
YES -> Extend or create the relevant modules/<name>/ package
14+
NO -> Is it a bug fix in an existing module?
15+
YES -> Fix in that module, isolate to a clean commit
16+
NO -> Is it a new generic capability the server always needs?
17+
YES -> Add to modules/core or modules/graph
18+
NO -> Create a new extension module in modules/<name>/
19+
```
20+
21+
## The Extension Module Pattern
22+
23+
### Architecture
24+
25+
```
26+
┌────────────────────────────────────────┐
27+
│ karrio/ │
28+
│ │
29+
│ modules/ │
30+
│ ├── core/ (OSS core) │
31+
│ ├── graph/ (OSS graph) │
32+
│ ├── admin/ (OSS admin) │
33+
│ ├── manager/ │
34+
│ ├── orders/ │
35+
│ ├── data/ │
36+
│ ├── documents/ │
37+
│ ├── events/ │
38+
│ ├── pricing/ │
39+
│ ├── connectors/*/ (carrier plugins) │
40+
│ └── <your-module>/ │
41+
│ │ │
42+
│ │ hooks via │
43+
│ │ AppConfig.ready() │
44+
│ ▼ │
45+
│ ┌────────────────────┐ │
46+
│ │ core hook points │ │
47+
│ │ @pre_processing │ │
48+
│ │ pkgutil discover │ │
49+
│ │ INSTALLED_APPS │ │
50+
│ └────────────────────┘ │
51+
└────────────────────────────────────────┘
52+
```
53+
54+
### Canonical Examples
55+
56+
Study these before creating new extensions:
57+
58+
- `modules/orders/karrio/server/orders/` — domain module with REST + GraphQL + signals
59+
- `modules/events/karrio/server/events/` — webhook/event delivery module with Huey task registration
60+
- `modules/documents/karrio/server/documents/` — module that registers its own auto-discovered URLs
61+
- `modules/graph/karrio/server/graph/schemas/base/` — reference GraphQL module layout (base schemas for the whole server)
62+
63+
### Module File Organization
64+
65+
Keep `__init__.py` as a **thin interface definition** — just `Query` field declarations and `Mutation` one-liner delegations. All resolver logic belongs in `types.py` and `mutations.py`.
66+
67+
**Canonical reference:** `modules/graph/karrio/server/graph/schemas/base/__init__.py`
68+
69+
- **`__init__.py`** — Interface only. `Query` uses `strawberry.field(resolver=types.XType.resolve)`. `Mutation` methods are one-liners that delegate to `mutations.XMutation.mutate()`. No business logic, no imports of domain modules.
70+
- **`types.py`** — Strawberry types with `resolve` / `resolve_list` static methods containing query logic.
71+
- **`inputs.py`** — Strawberry input types and filters.
72+
- **`mutations.py`** — Mutation classes with `mutate()` static methods containing mutation logic.
73+
- **`datatypes.py`**`@attr.s(auto_attribs=True)` dataclasses for structured data flowing through the module. Prefer typed attributes over raw dicts.
74+
- **`utils.py`** — Reusable helper functions (payload builders, transformers, formatters, availability decorators). **Must not import from `types.py` or `mutations.py`** — see dependency rule below.
75+
76+
```
77+
modules/<name>/karrio/server/graph/schemas/<name>/
78+
├── __init__.py # Thin interface: Query fields + Mutation delegators
79+
├── types.py # Strawberry types + resolve/resolve_list methods
80+
├── inputs.py # Strawberry input types and filters
81+
├── mutations.py # Mutation classes + mutate() methods
82+
├── datatypes.py # @attr.s dataclasses for typed data
83+
└── utils.py # Business logic, payload builders, decorators
84+
```
85+
86+
### Dependency Direction (one-way only)
87+
88+
Imports between schema files must flow in one direction. Circular imports between these files cause silent schema registration failures (`schema.py` catches the error and skips the module).
89+
90+
```
91+
__init__.py ──→ types.py ──→ utils.py
92+
──→ mutations.py ──→ utils.py
93+
──→ inputs.py
94+
```
95+
96+
- **`utils.py`** must never import `types.py` or `mutations.py`.
97+
- **`types.py`** must never import `mutations.py` (or vice versa).
98+
- Factory methods that construct a GraphQL type belong as **static methods on the type itself** (e.g., `ShipmentType.parse(...)`), not in `utils.py`.
99+
100+
```python
101+
# ✅ Good — type knows how to construct itself
102+
@strawberry.type
103+
class ItemType:
104+
id: int
105+
name: str
106+
107+
@staticmethod
108+
def parse(raw: dict) -> "ItemType":
109+
return ItemType(id=raw["id"], name=raw.get("name", ""))
110+
111+
# ❌ Bad — utils.py imports types.py, creating circular dependency
112+
# utils.py
113+
import karrio.server.graph.schemas.items.types as types
114+
def enrich_item(raw: dict) -> types.ItemType: # circular!
115+
return types.ItemType(...)
116+
```
117+
118+
```python
119+
# ✅ Good __init__.py — thin interface
120+
@strawberry.type
121+
class Query:
122+
items: typing.List[types.ItemType] = strawberry.field(
123+
resolver=types.ItemType.resolve_list
124+
)
125+
item: typing.Optional[types.ItemType] = strawberry.field(
126+
resolver=types.ItemType.resolve
127+
)
128+
129+
@strawberry.type
130+
class Mutation:
131+
@strawberry.mutation
132+
def create_item(self, info: Info, input: inputs.CreateItemInput) -> mutations.CreateItemMutation:
133+
return mutations.CreateItemMutation.mutate(info, **input.__dict__)
134+
135+
# ❌ Bad __init__.py — inline resolver logic
136+
@strawberry.type
137+
class Query:
138+
@strawberry.field
139+
@staticmethod
140+
def items(info: Info) -> typing.List[types.ItemType]:
141+
# 50 lines of business logic...
142+
```
143+
144+
### Hook Registration Pattern
145+
146+
```python
147+
# apps.py
148+
from django.apps import AppConfig
149+
150+
class OrdersConfig(AppConfig):
151+
name = "karrio.server.orders"
152+
153+
def ready(self):
154+
from karrio.server.orders import signals # noqa: registers signal handlers
155+
# Append validation hooks to a core serializer's pre_process_functions:
156+
# from karrio.server.manager.serializers import ShipmentSerializer
157+
# ShipmentSerializer.pre_process_functions.append(validators.validate_order_link)
158+
```
159+
160+
### Settings Auto-Discovery
161+
162+
```python
163+
# modules/<name>/karrio/server/settings/<name>.py
164+
from karrio.server.settings.base import * # noqa
165+
166+
INSTALLED_APPS += ["karrio.server.<name>"]
167+
KARRIO_URLS += ["karrio.server.<name>.urls"] # if module has REST endpoints
168+
```
169+
170+
Karrio discovers settings modules via `importlib.util.find_spec()` at startup. Your settings file runs only if the module is installed via `-e ./modules/<name>` in `requirements.build.txt`.
171+
172+
### GraphQL Extension (Auto-Discovery)
173+
174+
```python
175+
# modules/<name>/karrio/server/graph/schemas/<name>/__init__.py
176+
import strawberry
177+
import typing
178+
from strawberry.types import Info
179+
180+
@strawberry.type
181+
class Query:
182+
# Fields auto-merged into root Query via pkgutil.iter_modules()
183+
...
184+
185+
@strawberry.type
186+
class Mutation:
187+
# Fields auto-merged into root Mutation
188+
...
189+
190+
extra_types: typing.List = [] # required even if empty
191+
```
192+
193+
### Namespace Packages — NEVER Add `__init__.py` to Shared Paths
194+
195+
Karrio uses **implicit namespace packages** (`pkgutil.extend_path`) so that multiple installed packages can contribute to the same Python namespace (e.g., `karrio.server.graph.schemas`). The core packages (`modules/graph/`, `modules/admin/`, etc.) already define the namespace roots.
196+
197+
**NEVER add `__init__.py` files to namespace paths that are owned by another package.** Doing so converts the implicit namespace package into a regular package, which shadows the core package and breaks `pkgutil.iter_modules()` discovery.
198+
199+
```
200+
# ❌ WRONG — adding __init__.py to a namespace owned by core graph
201+
modules/<yourmod>/karrio/server/graph/__init__.py # breaks karrio.server.graph
202+
modules/<yourmod>/karrio/server/graph/schemas/__init__.py # breaks schema discovery
203+
204+
# ✅ CORRECT — only add __init__.py inside your module's own leaf directory
205+
modules/<yourmod>/karrio/server/graph/schemas/<yourmod>/__init__.py # leaf: your schema module
206+
modules/<yourmod>/karrio/server/<yourmod>/__init__.py # leaf: your module root
207+
```
208+
209+
**Rule of thumb:** if a directory path already exists in another installed module (e.g., `modules/graph/karrio/server/graph/`), do NOT create an `__init__.py` at that same path in your extension module. Only the **leaf directory** unique to your module gets `__init__.py`.
210+
211+
### Core Hook Points
212+
213+
| Hook | Location | Purpose |
214+
|------|----------|---------|
215+
| `@pre_processing` | `karrio.server.core.utils` | Append validators to serializer pipelines |
216+
| `AppConfig.ready()` | Django app startup | Register hooks, signal handlers |
217+
| `pkgutil.iter_modules()` | `graph/schema.py`, `admin/schema.py` | Auto-discover GraphQL schemas |
218+
| `importlib.util.find_spec()` | `settings/base.py` | Auto-discover settings modules |
219+
| `KARRIO_URLS` | `settings/base.py` | Register REST URL patterns |
220+
| `huey` task registry | `karrio.server.events.task_definitions` | Auto-discovered background tasks |
221+
222+
## Creating a New Extension Module
223+
224+
1. Create directory: `modules/<name>/karrio/server/<name>/`.
225+
2. Add `apps.py` with `AppConfig` and optional `ready()` hook.
226+
3. Add `karrio/server/settings/<name>.py` for auto-discovery.
227+
4. Add GraphQL schemas under `karrio/server/graph/schemas/<name>/` or `karrio/server/admin/schemas/<name>/` (admin is OSS-side; NOT `ee/insiders/modules/admin`).
228+
5. **Do NOT add `__init__.py` to shared namespace paths** (e.g., `karrio/server/graph/`, `karrio/server/admin/`) — only the leaf directory unique to your module gets `__init__.py` (see "Namespace Packages" above).
229+
6. Add tests under `modules/<name>/karrio/server/<name>/tests/`.
230+
7. **Add `karrio.server.<name>.tests` to `bin/run-server-tests`** — without this, tests pass locally but never run in CI.
231+
8. **Add `-e ./modules/<name>` to `requirements.build.txt`** — without this the module is not installed in Docker images and schema discovery silently skips it.
232+
9. Use the `create-extension-module` skill for scaffolding.
233+
234+
## Connectors vs Extension Modules
235+
236+
Carrier connectors under `modules/connectors/*/` follow a separate structure documented in [`carrier-integration.md`](./carrier-integration.md) — use `./bin/cli sdk add-extension` to scaffold. Do NOT use the extension-module pattern for carriers.

0 commit comments

Comments
 (0)