forge-orkes 0.62.0 → 0.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/bin/create-forge.js +250 -99
  2. package/experimental/conventions/README.md +87 -0
  3. package/experimental/conventions/install.sh +179 -0
  4. package/experimental/conventions/source/rules/code-quality.md +17 -0
  5. package/experimental/conventions/source/rules/laravel-http.md +21 -0
  6. package/experimental/conventions/source/rules/laravel-jobs.md +19 -0
  7. package/experimental/conventions/source/rules/laravel-migrations.md +19 -0
  8. package/experimental/conventions/source/rules/laravel-services.md +21 -0
  9. package/experimental/conventions/source/rules/python.md +14 -0
  10. package/experimental/conventions/source/rules/testing.md +15 -0
  11. package/experimental/conventions/source/rules/vue-inertia.md +24 -0
  12. package/experimental/conventions/source/skills/laravel-conventions/SKILL.md +124 -0
  13. package/experimental/conventions/source/skills/python-conventions/SKILL.md +100 -0
  14. package/experimental/conventions/source/skills/testing-standards/SKILL.md +142 -0
  15. package/experimental/conventions/source/skills/vue-conventions/SKILL.md +92 -0
  16. package/experimental/conventions/uninstall.sh +97 -0
  17. package/experimental/m10/README.md +130 -0
  18. package/experimental/m10/install.sh +198 -0
  19. package/experimental/m10/source/hooks/forge-branch-guard.sh +61 -0
  20. package/experimental/m10/source/hooks/forge-claim-check-doctor.sh +77 -0
  21. package/experimental/m10/source/hooks/forge-claim-check.sh +113 -0
  22. package/experimental/m10/source/hooks/forge-session-id.sh +22 -0
  23. package/experimental/m10/source/mcp-server/README.md +74 -0
  24. package/experimental/m10/source/mcp-server/example.mcp.json +8 -0
  25. package/experimental/m10/source/mcp-server/index.js +460 -0
  26. package/experimental/m10/source/mcp-server/package.json +17 -0
  27. package/experimental/m10/source/skills/orchestrating/SKILL.md +223 -0
  28. package/experimental/m10/source/skills/orchestrating/bootstrap-checks.md +70 -0
  29. package/experimental/m10/uninstall.sh +69 -0
  30. package/package.json +3 -2
  31. package/template/.claude/settings.json +1 -7
  32. package/template/.claude/skills/upgrading/SKILL.md +105 -176
  33. package/template/.forge/FORGE.md +2 -2
  34. package/template/.forge/migrations/0.63.0-origin-first-upgrading.md +49 -0
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env bash
2
+ # Forge conventions experimental installer — opt-in stack convention skills + path-scoped rules.
3
+ # Stack-agnostic core stays portable; this pack adds Laravel/PHP, Python, Vue/Inertia, and
4
+ # universal testing/code-quality conventions on demand. See ADR-018.
5
+ #
6
+ # Idempotent. Re-running skips anything already present and warns (never clobbers user files).
7
+ #
8
+ # Usage:
9
+ # bash experimental/conventions/install.sh [--dry-run] [TARGET] [stack ...]
10
+ #
11
+ # TARGET path to the Forge project (default: current directory)
12
+ # stack one or more of: laravel python vue testing universal
13
+ # no stacks given = install all
14
+ #
15
+ # Examples:
16
+ # bash experimental/conventions/install.sh # all stacks, into cwd
17
+ # bash experimental/conventions/install.sh laravel vue # only Laravel + Vue, into cwd
18
+ # bash experimental/conventions/install.sh /path/to/repo python
19
+ # bash experimental/conventions/install.sh --dry-run laravel
20
+
21
+ set -euo pipefail
22
+
23
+ DRY_RUN=0
24
+ TARGET=""
25
+ STACKS=()
26
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27
+ SOURCE_DIR="${SCRIPT_DIR}/source"
28
+
29
+ ALL_STACKS=(laravel python vue testing universal)
30
+
31
+ is_stack() {
32
+ local s="$1"
33
+ for known in "${ALL_STACKS[@]}"; do
34
+ [[ "$known" == "$s" ]] && return 0
35
+ done
36
+ return 1
37
+ }
38
+
39
+ while [[ $# -gt 0 ]]; do
40
+ case "$1" in
41
+ --dry-run) DRY_RUN=1; shift ;;
42
+ -h|--help)
43
+ sed -n '2,24p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
44
+ exit 0
45
+ ;;
46
+ *)
47
+ if is_stack "$1"; then
48
+ STACKS+=("$1")
49
+ elif [[ -z "$TARGET" ]]; then
50
+ TARGET="$1"
51
+ else
52
+ echo "ERROR: unknown argument '$1' (not a path and not a known stack: ${ALL_STACKS[*]})" >&2
53
+ exit 2
54
+ fi
55
+ shift
56
+ ;;
57
+ esac
58
+ done
59
+
60
+ # Defaults: target = cwd, stacks = all
61
+ [[ -z "$TARGET" ]] && TARGET="$(pwd)"
62
+ [[ ${#STACKS[@]} -eq 0 ]] && STACKS=("${ALL_STACKS[@]}")
63
+
64
+ [[ -d "$TARGET" ]] || { echo "ERROR: target '$TARGET' does not exist" >&2; exit 2; }
65
+ [[ -f "$TARGET/.forge/project.yml" ]] || { echo "ERROR: target is not a Forge project (.forge/project.yml missing)" >&2; exit 2; }
66
+
67
+ say() { echo " $*"; }
68
+
69
+ # Stack → skill names (space-separated)
70
+ skills_for() {
71
+ case "$1" in
72
+ laravel) echo "laravel-conventions" ;;
73
+ python) echo "python-conventions" ;;
74
+ vue) echo "vue-conventions" ;;
75
+ testing) echo "testing-standards" ;;
76
+ universal) echo "" ;;
77
+ esac
78
+ }
79
+
80
+ # Stack → rule filenames (space-separated)
81
+ rules_for() {
82
+ case "$1" in
83
+ laravel) echo "laravel-http.md laravel-services.md laravel-jobs.md laravel-migrations.md" ;;
84
+ python) echo "python.md" ;;
85
+ vue) echo "vue-inertia.md" ;;
86
+ testing) echo "testing.md" ;;
87
+ universal) echo "code-quality.md testing.md" ;;
88
+ esac
89
+ }
90
+
91
+ echo "==> Forge conventions installer"
92
+ echo " target: $TARGET"
93
+ echo " stacks: ${STACKS[*]}"
94
+ [[ $DRY_RUN -eq 1 ]] && echo " mode: dry-run (no changes)"
95
+ echo
96
+
97
+ installed=0
98
+ skipped=0
99
+
100
+ install_skill() {
101
+ local name="$1"
102
+ local src="$SOURCE_DIR/skills/$name"
103
+ local dst="$TARGET/.claude/skills/$name"
104
+ [[ -d "$src" ]] || { say "WARN: source skill '$name' missing — skipping"; return; }
105
+ if [[ -e "$dst" ]]; then
106
+ say "skip: skill '$name' already present (not overwriting)"
107
+ skipped=$((skipped + 1))
108
+ return
109
+ fi
110
+ if [[ $DRY_RUN -eq 1 ]]; then
111
+ say "[dry-run] would install skill '$name' → .claude/skills/$name/"
112
+ else
113
+ mkdir -p "$TARGET/.claude/skills"
114
+ cp -R "$src" "$dst"
115
+ say "installed skill '$name'"
116
+ fi
117
+ installed=$((installed + 1))
118
+ }
119
+
120
+ install_rule() {
121
+ local file="$1"
122
+ local src="$SOURCE_DIR/rules/$file"
123
+ local dst="$TARGET/.claude/rules/$file"
124
+ [[ -f "$src" ]] || { say "WARN: source rule '$file' missing — skipping"; return; }
125
+ if [[ -e "$dst" ]]; then
126
+ say "skip: rule '$file' already present (not overwriting)"
127
+ skipped=$((skipped + 1))
128
+ return
129
+ fi
130
+ if [[ $DRY_RUN -eq 1 ]]; then
131
+ say "[dry-run] would install rule '$file' → .claude/rules/$file"
132
+ else
133
+ mkdir -p "$TARGET/.claude/rules"
134
+ cp "$src" "$dst"
135
+ say "installed rule '$file'"
136
+ fi
137
+ installed=$((installed + 1))
138
+ }
139
+
140
+ # Collect targets, de-duplicate (universal + testing both ship testing.md).
141
+ # Bash 3.2-compatible: space-delimited strings, no namerefs.
142
+ SKILL_LIST=""
143
+ RULE_LIST=""
144
+ contains_word() { case " $1 " in *" $2 "*) return 0 ;; *) return 1 ;; esac; }
145
+
146
+ for stack in "${STACKS[@]}"; do
147
+ for s in $(skills_for "$stack"); do
148
+ contains_word "$SKILL_LIST" "$s" || SKILL_LIST="$SKILL_LIST $s"
149
+ done
150
+ for r in $(rules_for "$stack"); do
151
+ contains_word "$RULE_LIST" "$r" || RULE_LIST="$RULE_LIST $r"
152
+ done
153
+ done
154
+
155
+ if [[ -n "${SKILL_LIST// }" ]]; then
156
+ echo "==> Skills → .claude/skills/"
157
+ for s in $SKILL_LIST; do install_skill "$s"; done
158
+ echo
159
+ fi
160
+
161
+ if [[ -n "${RULE_LIST// }" ]]; then
162
+ echo "==> Rules → .claude/rules/"
163
+ for r in $RULE_LIST; do install_rule "$r"; done
164
+ echo
165
+ fi
166
+
167
+ cat <<EOF
168
+ ==> Done. installed: $installed skipped (already present): $skipped
169
+
170
+ Next steps:
171
+ 1. Restart Claude Code in the target repo so the new skills/rules load.
172
+ 2. Path-scoped rules auto-attach when you edit matching files (e.g. app/Http/**, **/*.vue, **/*.py).
173
+ 3. Convention skills load when a task description matches (writing/reviewing Laravel/Python/Vue code).
174
+
175
+ Anything already present was left untouched. To replace a stale skill/rule,
176
+ remove the existing file first, then re-run.
177
+
178
+ Uninstall: bash $(dirname "${BASH_SOURCE[0]}")/uninstall.sh '$TARGET' [stack ...]
179
+ EOF
@@ -0,0 +1,17 @@
1
+ # Code Quality
2
+
3
+ Always-on rule (additive to core's `agent-discipline.md` — that rule covers
4
+ agent failure modes like scope creep and phantom deps; this one covers code
5
+ *shape*: function size, magic values, nesting). Universal, stack-agnostic.
6
+
7
+ - Functions do one thing. If the description needs "and", split the function.
8
+ - No magic values. Extract numbers, strings, and configuration to named constants or config files.
9
+ - No dead code, no commented-out blocks. Git has the history.
10
+ - Handle errors at the boundary where you can add context. Do not catch-and-rethrow without adding context about the operation that failed.
11
+ - No premature abstractions. Three similar lines are easier to read than one helper used once.
12
+ - No scope creep. Don't add features, "improvements", logging, metrics, or fallbacks beyond what was asked.
13
+ - Composition over inheritance.
14
+ - Guard clauses over nested conditionals. No more than 3 levels of nesting; extract helpers when you need more.
15
+ - Descriptive names. `resolve_mechanism_to_ips` not `process`. Names describe what the function does, not that it does something.
16
+ - Files under 300 lines, functions under 50 lines. If something grows past that, it's a sign to split.
17
+ - Type hints / type declarations on every public function. Private helpers can skip docstrings but not types.
@@ -0,0 +1,21 @@
1
+ ---
2
+ paths:
3
+ - "app/Http/**"
4
+ - "app/*/*/Http/**"
5
+ ---
6
+
7
+ # Laravel HTTP (Controllers, Requests, Resources)
8
+
9
+ These files run on every inbound request. Auth, validation, and authorisation are the non-negotiables.
10
+
11
+ - **Every new route needs auth.** Default to an authenticated middleware group. Only mark a route public with explicit sign-off.
12
+ - **Tenant isolation is assumed, never skipped (multi-tenant apps).** Every query touching tenant-scoped data runs through the tenant scope/middleware. If you are writing raw queries, the tenant filter is your responsibility. (N/A for single-tenant apps.)
13
+ - **Validate via Form Request classes.** Never use `$request->all()`. Use `$request->validated()` — it is the only input the controller should see.
14
+ - **Authorise before acting.** Call `$this->authorize('action', $model)` (or equivalent policy check) before any state change. "Only admins use it" is not an authorisation strategy.
15
+ - **Mass assignment needs a whitelist.** Eloquent models must declare `$fillable`. Never use `$guarded = []`.
16
+ - **Thin controllers.** Controllers parse input, delegate to the module's Service or Action, return the response. No business logic, no multi-model orchestration, no direct DB calls.
17
+ - **Resource classes shape responses.** Do not return Eloquent models *or bare arrays* directly — not even a three-field payload. A raw `response()->json([...])` produces no generated TypeScript type and forces the frontend to hand-roll one. Every data endpoint returns an API Resource or a typed DTO so field exposure is explicit and the contract propagates to the generated types.
18
+ - **Consistent response envelope.** Follow the module's existing response shape. Do not invent a new one.
19
+ - **Rate limiting on sensitive endpoints.** Auth, password reset, token-issuing, and any public write endpoint need a rate limiter.
20
+ - **No business logic in middleware.** Middleware gates and transforms the request; it does not perform domain operations.
21
+ - **Feature flag checks belong in the Form Request or controller, not in route middleware groups.** Route middleware is for structural, cross-cutting concerns (auth, throttle, CORS). Feature access is domain authorization.
@@ -0,0 +1,19 @@
1
+ ---
2
+ paths:
3
+ - "app/Jobs/**"
4
+ - "app/*/*/Jobs/**"
5
+ ---
6
+
7
+ # Laravel Jobs
8
+
9
+ Jobs run detached from the request, often retry, and can run long after the payload was created. Assume nothing about ambient state.
10
+
11
+ - **Re-verify tenant context inside `handle()` (multi-tenant apps).** The queue worker does not carry the dispatching user's session. Resolve the tenant from the job payload, not from a request-scoped singleton.
12
+ - **Declare `$tries` and `$backoff` explicitly.** No job should rely on framework defaults for retry behaviour. State the retry budget and the backoff strategy.
13
+ - **`handle()` must be idempotent.** A retry or double-dispatch must not double-charge, double-email, or double-write. Use a ledger row, unique constraint, or idempotency key.
14
+ - **Small payloads.** Serialize identifiers, not full models. `SerializesModels` makes this easy but mutation between dispatch and handle can still surprise you — re-fetch inside `handle()`.
15
+ - **Set `$timeout` for anything that makes outbound I/O.** DNS lookups, HTTP calls, third-party APIs. A silent hang blocks the worker.
16
+ - **`failed()` is not optional for critical jobs.** Log with enough context to triage. Page the right channel if the failure is user-impacting.
17
+ - **No business logic in the job.** The job is a scheduler. It calls into a Service or Action that also works outside the queue. Keeps the logic testable without a queue runner.
18
+ - **Queue selection is explicit.** Assign `$queue` by priority (high / default / low) rather than defaulting everything onto the same worker pool.
19
+ - **Do not rescue broadly inside `handle()`.** Swallowing an exception defeats retry and failure handling. Let it bubble; translate at the edges only.
@@ -0,0 +1,19 @@
1
+ ---
2
+ paths:
3
+ - "database/migrations/**"
4
+ - "app/*/*/database/migrations/**"
5
+ ---
6
+
7
+ # Laravel Migrations
8
+
9
+ Migrations run against shared databases. A bad migration is hard to undo.
10
+
11
+ - **Never modify an existing migration.** Always create a new migration for the change. Existing migrations may have run in production.
12
+ - Every migration must be reversible. Implement both `up()` and `down()`.
13
+ - Test both directions locally before committing.
14
+ - Destructive operations (`dropColumn`, `dropTable`, `renameColumn`) require explicit confirmation the data is not needed. State what data is affected.
15
+ - Add indexes in their own migration, separate from the schema change. Easier to roll back independently and often faster.
16
+ - Prefer the schema builder over raw SQL. Only drop to raw SQL when the builder cannot express the operation, and document why.
17
+ - No production seed data in migration files. Use dedicated seeders.
18
+ - New `NOT NULL` columns on existing tables need a backfill strategy: nullable first → backfill → enforce NOT NULL in a second migration.
19
+ - Foreign keys: declare `onDelete` / `onUpdate` behaviour explicitly. Default to `restrict` unless there's a reason otherwise.
@@ -0,0 +1,21 @@
1
+ ---
2
+ paths:
3
+ - "app/Services/**"
4
+ - "app/*/*/Services/**"
5
+ - "app/Actions/**"
6
+ - "app/*/*/Actions/**"
7
+ ---
8
+
9
+ # Laravel Services & Actions
10
+
11
+ Services and Actions hold the business logic. They are the layer that must stay framework-lean and testable.
12
+
13
+ - **Constructor injection only.** No `app()`, no `resolve()`, no facades inside the class body. Type-hint dependencies in the constructor so tests can swap them.
14
+ - **Take DTOs or primitives, not Request objects.** A Service that accepts `Request $request` is a controller in disguise. Accept typed data so the Service can be called from jobs, commands, and tests without fabricating a request.
15
+ - **Return typed results — a DTO, model, or result object, not a bare array. Void returns are a code smell unless the operation is genuinely fire-and-forget. A `: array` return shaped as an associative `['key' => ...]` is an untyped blob in disguise: it carries no contract to the controller, the Resource layer, or the generated frontend types. If the shape has named fields, it is a DTO — declare one.**
16
+ - **Transactions wrap the Service call, not stray queries.** Open a `DB::transaction(...)` at the operation boundary. Do not sprinkle `beginTransaction`/`commit` through private helpers.
17
+ - **Resource classes shape responses.** Do not return Eloquent models *or bare arrays* directly — not even a three-field payload. A raw `response()->json([...])` produces no generated TypeScript type and forces the frontend to hand-roll one. Every data endpoint returns an API Resource or a typed DTO so field exposure is explicit and the contract propagates to the generated types.
18
+ - **Consistent response envelope.** Follow the module's existing response shape. Do not invent a new one.
19
+ - **No controller concerns.** Services do not build HTTP responses, do not call `response()`, do not `abort()`. Throw a domain exception and let the controller translate it.
20
+ - **Idempotency where the operation can repeat.** If a retry or duplicate call is possible (webhooks, jobs, user double-clicks), state how the Service handles it.
21
+ - **Tenant context is a parameter, never ambient (multi-tenant apps).** If the Service operates on tenant data, accept the tenant explicitly. Do not rely on a request-scoped singleton inside a Service that might also run from a job or command.
@@ -0,0 +1,14 @@
1
+ ---
2
+ paths:
3
+ - "**/*.py"
4
+ ---
5
+
6
+ # Python
7
+
8
+ - Type hints on every function signature (public and private), every method, and every module-level variable. Return type too, even when `None`.
9
+ - No mutable default arguments. Use `None` and assign inside the body.
10
+ - Pydantic v2 at boundaries. No raw dicts passed more than one function deep.
11
+ - Never block the event loop inside `async def`. No `requests`, no `time.sleep`, no sync DB drivers.
12
+ - Exceptions re-raised with context: `raise NewError("op failed") from e`, not bare `raise Exception(str(e))`.
13
+
14
+ Framework detail (Django ORM patterns, FastAPI dependencies, tooling, project structure, error types) lives in the `python-conventions` skill.
@@ -0,0 +1,15 @@
1
+ # Testing
2
+
3
+ Always-on rule. Cheap ambient nudges so the basics fire even when the heavier
4
+ `testing-standards` skill (in this pack) is not loaded for the current task.
5
+ Universal, stack-agnostic. The skill is the deep reference; this is the floor.
6
+
7
+ - Test behaviour, not implementation. If the test would still pass with a subtly wrong implementation, it's the wrong test.
8
+ - Run the specific test file after a change, not the full suite. Full suite is for commits and CI.
9
+ - Fix or delete flaky tests. Never retry to make a test pass.
10
+ - AAA structure: Arrange, Act, Assert. No `if`/`for` logic in the test body.
11
+ - One behaviour per test. If the name needs "and", split it.
12
+ - Test names read as specifications: `it_requires_auth_on_domain_create`, not `test1`.
13
+ - Prefer real implementations. Mock only at system boundaries (network, filesystem, clock, external APIs).
14
+ - Never `expect(true)` or assert only that a mock was called without checking its arguments.
15
+ - No tests for pure refactors, comment changes, file renames, or type-only changes.
@@ -0,0 +1,24 @@
1
+ ---
2
+ paths:
3
+ - "resources/js/pages/**"
4
+ - "resources/js/components/**"
5
+ - "**/*.vue"
6
+ ---
7
+
8
+ # Vue + Inertia
9
+
10
+ Inertia makes the Laravel controller the single source of truth for page state. Client state is a cache, not a database. (The Inertia-specific points below apply when the project uses Inertia; the Vue/TypeScript points apply universally.)
11
+
12
+ - **`<script setup lang="ts">` only.** Composition API. No Options API in new files. No `any` types — if you reach for `any`, type the shape properly or accept `unknown` and narrow.
13
+ - **Server is the source of truth.** Page data arrives as Inertia props. Do not fetch the same data with `axios` on mount just because it is convenient.
14
+ - **Never `as`-cast an HTTP/`useHttp`/`axios` response to assert its shape.** A loosely-typed response means the backend contract is undefined — fix the contract, do not override the compiler. The cast disables the one check that would catch backend drift.
15
+ - **Response shapes come from the generated types file (e.g. `types.gen.ts`), never hand-declared.** If the generated type you need does not exist, the backend is missing a Resource/DTO — stop and add it, then regenerate. Do not write a local `type` that mirrors a server payload.
16
+ - **No new Vuex.** Use Inertia shared props, `provide/inject`, or a scoped `ref`/`reactive`. A store for cross-page UI state needs explicit justification.
17
+ - **Form submission via `useForm`.** Do not hand-roll `axios.post` + manual validation state. `useForm` handles errors, processing flags, and the Inertia navigation cycle.
18
+ - **`:key` is a stable identifier, not the index.** Using array index as `:key` breaks component identity on reorder and causes stale form state.
19
+ - **Emit names are past tense.** `@updated`, `@created`, `@deleted`, `@selected`. Declare them in `defineEmits<{}>()` with types.
20
+ - **Props are typed and required by default.** `defineProps<{}>()` with a TypeScript interface. Default values via `withDefaults()`, not inline.
21
+ - **Computed for derivations, watch for side effects.** If a `watch` is updating a ref based on another ref, it should be a `computed`.
22
+ - **Components under 250 lines.** Past that, extract child components or a composable. The template getting hard to scan is the signal.
23
+ - **Accessibility is not optional.** Semantic elements, `aria-*` on interactive custom components, visible focus states, keyboard navigation.
24
+ - **Use design tokens, not arbitrary values.** Use the project's configured scale (e.g. Tailwind tokens). `w-[13px]` and `#3a7bc8` inline are code smells — add a token or use an existing one.
@@ -0,0 +1,124 @@
1
+ ---
2
+ name: laravel-conventions
3
+ description: Laravel and PHP coding standards. Use when writing, reviewing, refactoring, or modifying PHP code in Laravel applications. Covers strict types, architecture patterns, Eloquent usage, testing, and security.
4
+ ---
5
+
6
+ # Laravel / PHP Conventions
7
+
8
+ ## Language standards
9
+
10
+ - `declare(strict_types=1)` at the top of every PHP file
11
+ - PHP 8.2+ (use the project's actual minimum; prefer modern syntax throughout)
12
+ - Constructor property promotion for value objects and services
13
+ - Enums (not constants) for fixed sets of values
14
+ - Match expressions over switch statements
15
+ - Named arguments for functions with 3+ parameters
16
+ - Return types on all methods. Parameter types on all methods.
17
+ - Union types where appropriate: `string|int`
18
+
19
+ ## Architecture
20
+
21
+ - Slim controllers: max 5 public methods (index, show, store, update, destroy)
22
+ - Business logic in Service classes, not controllers or models
23
+ - Form Requests for all input validation
24
+ - API Resources for all response transformation
25
+ - Action classes for complex single-purpose operations
26
+ - One class per file, following PSR-4 autoloading
27
+
28
+ ```php
29
+ // Correct: thin controller, logic in service
30
+ class DomainController extends Controller
31
+ {
32
+ public function store(StoreDomainRequest $request, DomainService $service): DomainResource
33
+ {
34
+ $domain = $service->create($request->validated());
35
+ return new DomainResource($domain);
36
+ }
37
+ }
38
+
39
+ // Wrong: business logic in controller
40
+ class DomainController extends Controller
41
+ {
42
+ public function store(Request $request): JsonResponse
43
+ {
44
+ $validated = $request->validate([...]);
45
+ $domain = Domain::create($validated);
46
+ // 50 more lines of business logic...
47
+ }
48
+ }
49
+ ```
50
+
51
+ ## Eloquent
52
+
53
+ - Eager load relationships to prevent N+1 queries: `->with(['relation'])`
54
+ - Use query scopes for reusable query logic
55
+ - Soft deletes via `SoftDeletes` trait where appropriate
56
+ - Never use `DB::raw()` without parameterised bindings
57
+ - Prefer Eloquent methods over raw queries
58
+ - Use `$guarded` or `$fillable` on all models
59
+
60
+ ## Testing
61
+
62
+ - Pest PHP or PHPUnit for all tests -- check what the project already uses and follow that
63
+ - Feature tests for API endpoints (HTTP tests)
64
+ - Unit tests for Services and Actions
65
+ - Use factories and seeders for test data
66
+ - `RefreshDatabase` trait for database tests
67
+
68
+ ## Code style
69
+
70
+ - Laravel Pint where configured for formatting (PSR-12 based)
71
+ - PHPStan where configured at level 8 minimum
72
+ - PHP_CodeSniffer where configured (use the project's standards file)
73
+ - Follow Laravel naming conventions:
74
+ - Models: singular PascalCase (`User`, `DomainConfig`)
75
+ - Controllers: singular PascalCase + Controller (`UserController`)
76
+ - Migrations: `create_users_table`, `add_status_to_domains_table`
77
+ - Routes: plural kebab-case (`/domain-configs`)
78
+
79
+ ## Security
80
+
81
+ - Always use parameterised queries (Eloquent handles this)
82
+ - Validate all input via Form Requests
83
+ - Use policies for authorisation
84
+ - Never expose internal IDs without authorisation checks; prefer UUIDs for public identifiers when the model has them
85
+ - Hash sensitive data, encrypt at rest where required
86
+ - If the application is multi-tenant, treat tenant isolation as a hard requirement: every tenant-scoped query runs through the tenant scope, and at least one test per tenant-scoped endpoint proves cross-tenant reads are denied. (Skip this section for single-tenant apps.)
87
+
88
+ ## Project structure
89
+
90
+ Standard Laravel layout:
91
+
92
+ ```
93
+ app/
94
+ ├── Http/
95
+ │ ├── Controllers/
96
+ │ ├── Requests/
97
+ │ ├── Resources/
98
+ │ └── Middleware/
99
+ ├── Models/
100
+ ├── Services/
101
+ ├── Actions/
102
+ ├── Policies/
103
+ └── Observers/
104
+ ```
105
+
106
+ This skill also supports a **module layout** some projects adopt, where each
107
+ domain module nests its own `Http/`, `Services/`, `Actions/`, etc. under a
108
+ per-project, per-module path such as `app/<project>/<module>/Http/...`. The
109
+ path-scoped rules in this pack are dual-globbed so they fire on either layout.
110
+ Follow whichever layout the project already uses — do not introduce a new one.
111
+
112
+ ## Laravel Boost MCP
113
+
114
+ If the project has [Laravel Boost](https://github.com/laravel/boost) installed, use its MCP tools to inspect the database schema, routes, configuration, and logs rather than guessing from code. Boost provides runtime context about the application that static file reading cannot. Its generic Laravel guidelines supplement but do not override the conventions in this file. Where Boost's guidelines conflict with these standards, follow this file.
115
+
116
+ ## Common agent mistakes to avoid
117
+
118
+ - Don't put business logic in controllers
119
+ - Don't skip `declare(strict_types=1)`
120
+ - Don't use `DB::raw()` without bindings
121
+ - Don't forget eager loading (N+1 queries)
122
+ - Don't validate input inline in controllers — use Form Requests
123
+ - Don't return raw arrays from API endpoints — use Resources
124
+ - Don't skip authorisation checks (policies)
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: python-conventions
3
+ description: Python coding standards and conventions. Use when writing, reviewing, refactoring, or modifying Python code. Covers type hints, async patterns, Pydantic usage, dependency injection, tooling, and project structure.
4
+ ---
5
+
6
+ # Python Conventions
7
+
8
+ ## Language standards
9
+
10
+ - Python 3.11+ with modern syntax throughout
11
+ - `X | None` not `Optional[X]`
12
+ - `list[str]`, `dict[str, int]` not `List[str]`, `Dict[str, int]`
13
+ - Type hints on ALL functions (public and private), all method signatures, and all module-level variables
14
+ - Use `type Alias = ...` for type aliases where appropriate
15
+ - No mutable default arguments. `def f(x=[])` is a bug. Use `None` and assign inside the body.
16
+ - Context managers for all resources: files, connections, locks, temp dirs. `with` always.
17
+ - f-strings for formatting. Not `%` or `.format()`. Not `+` concatenation with variables.
18
+ - `pathlib.Path`, not `os.path`. New code uses `Path`.
19
+ - `enum.Enum` for closed sets of values. Not string constants.
20
+
21
+ ## Data validation
22
+
23
+ - Use Pydantic v2 (BaseModel) for any data crossing a boundary: API requests/responses, config, external data, DB row mapping
24
+ - Use dataclasses only for pure internal value objects with no validation needs
25
+ - Never use plain dicts for structured data
26
+
27
+ ## Async patterns
28
+
29
+ - `async def` for all I/O-bound operations (DNS, HTTP, database queries)
30
+ - `asyncio.TaskGroup` (3.11+) for concurrent operations, not `asyncio.gather`
31
+ - Sync `def` for CPU-bound and pure computation
32
+ - Never block the event loop inside `async def`. No `requests.get`, no `time.sleep`, no synchronous DB drivers. Use `httpx.AsyncClient`, `asyncio.sleep`, async DB drivers.
33
+ - Do not call `asyncio.run` inside a running loop. Use `await` or schedule on the existing loop.
34
+
35
+ ## Django
36
+
37
+ - Atomic updates use `F()` expressions: `obj.counter = F('counter') + 1` then `save(update_fields=['counter'])`. Never read-modify-write without a lock.
38
+ - Batches use `bulk_create` / `bulk_update`. Looping `.save()` over hundreds of rows is a performance bug.
39
+ - `select_related` for forward FKs, `prefetch_related` for reverse and many-to-many. Profile before guessing.
40
+ - Migrations are reversible. Data migrations and schema migrations live in separate files. Never edit a migration after it has run anywhere.
41
+ - No raw SQL unless the ORM cannot express it. When you must, parameterise. Never f-string values into SQL.
42
+
43
+ ## FastAPI
44
+
45
+ - Pydantic models on every request and response. `model_config = ConfigDict(extra="forbid")` on request models so unknown fields are rejected loudly.
46
+ - Auth, DB session, current user — all declared via `Depends()` so they are overridable in tests.
47
+ - Return a model or a `Response`. Never a raw dict. The response model enforces the contract.
48
+ - Background work goes to a queue, not `BackgroundTasks`, for anything the user would notice failing.
49
+
50
+ ## Dependency injection
51
+
52
+ - Protocol classes for interfaces. NOT ABC/abstract base classes.
53
+ - Constructor injection for all dependencies
54
+ - No module-level singletons or global state
55
+
56
+ ```python
57
+ # Correct: Protocol defines the contract
58
+ class DNSResolver(Protocol):
59
+ async def resolve(self, domain: str) -> list[str]: ...
60
+
61
+ class MyService:
62
+ def __init__(self, resolver: DNSResolver) -> None:
63
+ self._resolver = resolver
64
+
65
+ # Wrong: inheriting from ABC
66
+ class DNSResolver(ABC):
67
+ @abstractmethod
68
+ async def resolve(self, domain: str) -> list[str]: ...
69
+ ```
70
+
71
+ ## Tooling
72
+
73
+ - `uv` for dependency management. Never `pip install` directly.
74
+ - `ruff` for linting and formatting
75
+ - `mypy` in strict mode for type checking
76
+ - `pytest` with `pytest-asyncio` for testing
77
+ - `structlog` for logging. Never `print()`, never stdlib `logging` directly.
78
+
79
+ ## Error handling
80
+
81
+ - Typed exceptions for application-specific errors, not generic `Exception`
82
+ - Result types where appropriate for expected failure paths
83
+ - Never bare `except:` or `except Exception:`
84
+ - Log errors with context before re-raising
85
+
86
+ ## Project structure
87
+
88
+ - Source code in `src/{package_name}/`
89
+ - One module per concern. The parser parses. The resolver resolves.
90
+ - `__init__.py` exports only public interfaces
91
+ - Tests in `tests/unit/` (no network, no DB) and `tests/integration/`
92
+
93
+ ## Common agent mistakes to avoid
94
+
95
+ - Don't use `Optional[X]` — use `X | None`
96
+ - Don't use `asyncio.gather` — use `asyncio.TaskGroup`
97
+ - Don't create abstract base classes — use `Protocol`
98
+ - Don't use `print()` for logging
99
+ - Don't put business logic in `__init__.py`
100
+ - Don't skip type hints on private methods