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.
- package/bin/create-forge.js +250 -99
- package/experimental/conventions/README.md +87 -0
- package/experimental/conventions/install.sh +179 -0
- package/experimental/conventions/source/rules/code-quality.md +17 -0
- package/experimental/conventions/source/rules/laravel-http.md +21 -0
- package/experimental/conventions/source/rules/laravel-jobs.md +19 -0
- package/experimental/conventions/source/rules/laravel-migrations.md +19 -0
- package/experimental/conventions/source/rules/laravel-services.md +21 -0
- package/experimental/conventions/source/rules/python.md +14 -0
- package/experimental/conventions/source/rules/testing.md +15 -0
- package/experimental/conventions/source/rules/vue-inertia.md +24 -0
- package/experimental/conventions/source/skills/laravel-conventions/SKILL.md +124 -0
- package/experimental/conventions/source/skills/python-conventions/SKILL.md +100 -0
- package/experimental/conventions/source/skills/testing-standards/SKILL.md +142 -0
- package/experimental/conventions/source/skills/vue-conventions/SKILL.md +92 -0
- package/experimental/conventions/uninstall.sh +97 -0
- package/experimental/m10/README.md +130 -0
- package/experimental/m10/install.sh +198 -0
- package/experimental/m10/source/hooks/forge-branch-guard.sh +61 -0
- package/experimental/m10/source/hooks/forge-claim-check-doctor.sh +77 -0
- package/experimental/m10/source/hooks/forge-claim-check.sh +113 -0
- package/experimental/m10/source/hooks/forge-session-id.sh +22 -0
- package/experimental/m10/source/mcp-server/README.md +74 -0
- package/experimental/m10/source/mcp-server/example.mcp.json +8 -0
- package/experimental/m10/source/mcp-server/index.js +460 -0
- package/experimental/m10/source/mcp-server/package.json +17 -0
- package/experimental/m10/source/skills/orchestrating/SKILL.md +223 -0
- package/experimental/m10/source/skills/orchestrating/bootstrap-checks.md +70 -0
- package/experimental/m10/uninstall.sh +69 -0
- package/package.json +3 -2
- package/template/.claude/settings.json +1 -7
- package/template/.claude/skills/upgrading/SKILL.md +105 -176
- package/template/.forge/FORGE.md +2 -2
- 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
|