Shared AI assets for webhub GmbH — guidelines, Skills, and slash-commands used across our Laravel and Craft CMS projects, and uploaded to our claude.ai Team workspace.
cd into your dev directory first (~/reps, ~/code, whatever you use), then run:
[ -d ai ] || git clone https://github.com/webhubworks/ai.git && cd ai && git pull && ./bootstrap.shClones webhubworks/ai into the current directory if it isn't already there, pulls the latest, then runs bootstrap.sh to wire commands and guidelines into ~/.claude. Safe to re-run any time — it's the update path too.
ai/
├── bootstrap.sh # One-shot installer that links this repo into ~/.claude
├── hooks/
│ └── session-start.sh # Claude Code SessionStart hook: detects the stack,
│ # injects the matching guideline(s), throttled auto-pull
├── guidelines/ # Plain-markdown guidelines
│ ├── README.md
│ ├── webhub.md # stack-agnostic webhub baseline
│ ├── webhub-laravel.md # Laravel/PHP
│ ├── webhub-laravel.digest.md # the small subset the hook injects
│ ├── webhub-craft-cms.md # Craft CMS (scaffold)
│ ├── webhub-nativephp.digest.md # NativePHP always-on deviations (no full guideline - see the nativephp skill)
│ └── laravel-php-guidelines.md # Spatie's Laravel/PHP standards, mirrored from spatie.be
├── skills/ # Skills — linked into ~/.claude/skills by bootstrap.sh,
│ │ # and each subfolder also zips up into one claude.ai Skill
│ ├── laravel/
│ │ ├── SKILL.md # thin wrapper - the rules live in the guideline
│ │ └── reference.md # → ../../guidelines/webhub-laravel.md (symlink)
│ ├── craft-cms/
│ │ ├── SKILL.md # thin wrapper - the rules live in the guideline
│ │ └── reference.md # → ../../guidelines/webhub-craft-cms.md (symlink)
│ ├── craft-upgrade/ # Craft major-version upgrade playbook
│ │ ├── SKILL.md # orchestrator (only its description loads per session)
│ │ ├── upgrading-across-multiple-major-versions.md
│ │ ├── v2-to-v3/ v3-to-v4/ v4-to-v5/ # per-leg docs, read on demand
│ │ └── v5-to-v6/ # placeholder until the first 5→6 upgrade
│ ├── laravel-browser-testing/ # self-contained playbook (Pest browser tests + ddev)
│ │ └── SKILL.md
│ ├── pest-v5-upgrade/ # self-contained playbook (Pest 5 upgrade + PCOV/Tia)
│ │ └── SKILL.md
│ └── nativephp/ # self-contained playbook (NativePHP desktop apps)
│ └── SKILL.md
└── commands/ # Slash-commands for Claude Code
├── laravel/ # → /webhub:laravel:<name>
│ ├── commit-and-simplify.md
│ └── simplify.md
├── plan-issue.md # → /webhub:plan-issue
└── security-review.md # → /webhub:security-review
Everything is served from your local clone of this repo, so a single git pull updates every project on your machine — no composer update, nothing committed into project repos.
There are two layers, both wired up by bootstrap.sh:
- Stack-agnostic baseline —
guidelines/webhub.mdis@-imported into~/.claude/CLAUDE.md. The import points at the live file in this clone, so it's in context for every session in every project, and updates the moment you pull. - Stack-specific guidelines — a Claude Code SessionStart hook (
hooks/session-start.sh) runs at the start of every session. It:- detects the project type from the working directory (
composer.json→craftcms/cms= Craft,nativephp/*= NativePHP desktop,laravel/framework= Laravel; falls back toartisan/craftmarker files). NativePHP is tested before Laravel, since those projects requirelaravel/frameworktoo and would otherwise be told to use ddev, which this stack cannot use, - reads the matching guideline(s) straight from this clone (
webhub-laravel.digest.mdfor Laravel; that pluswebhub-nativephp.digest.mdfor NativePHP;webhub-craft-cms.mdfor Craft) and injects them as session context, prefixed with the detected project type and an instruction naming the skill(s) to invoke for the full standards, - runs a throttled background
git pullof this clone (at most once/hour, fully detached, offline-safe) so edits you push here reach every developer's next session automatically.
- detects the project type from the working directory (
So Claude opens every session already knowing the stack and with the right rules loaded — in any repo, including Craft and non-PHP repos — and guideline edits propagate on their own.
One-time per developer: the hook can't install itself —
bootstrap.shis what wires it into~/.claude/settings.json. After that singlegit pull && ./bootstrap.sh, it's hands-off: the hook keeps the clone fresh and serves the latest guidelines with no further action. You only need to re-runbootstrap.shif the wiring itself changes (a new globally-imported guideline, or a change to how the hook is registered) — never for ordinary guideline or hook-script edits.
See the one-liner above. What bootstrap.sh does, idempotently:
- Symlinks
commands/into~/.claude/commands/webhub/, so every slash-command in this repo is available as/webhub:<name>(subdirs add namespace segments — e.g./webhub:laravel:simplify). - Symlinks each
skills/<name>/folder into~/.claude/skills/<name>/, so every skill is discoverable in Claude Code — available as/<name>and auto-invoked from itsdescription. Only the description loads each session; a skill's body and any bundled reference docs are read on demand. A pre-existing non-symlink~/.claude/skills/<name>is left untouched (your personal skills are safe). - Appends an
@<path>import for the stack-agnosticwebhub.mdto~/.claude/CLAUDE.md, so the webhub baseline applies to every session regardless of stack. Existing content in~/.claude/CLAUDE.mdis left untouched. - Registers the SessionStart hook (
hooks/session-start.sh) in~/.claude/settings.jsonundermatcher: "*"(fires on startup, resume, clear, compact). Wiring is idempotent and relocation-safe (a moved clone replaces the old entry instead of stacking), and any unrelated SessionStart hooks you have are left intact.
To update: re-run the one-liner from the directory above the clone, or cd into the clone and run git pull && ./bootstrap.sh. In practice the hook's auto-pull keeps the clone fresh on its own; re-running bootstrap.sh is only required when the wiring changes (see the note above).
From inside your clone of this repo, the same effect by hand:
ln -sfn "$PWD/commands" ~/.claude/commands/webhub
echo "@$PWD/guidelines/webhub.md" >> ~/.claude/CLAUDE.md
chmod +x "$PWD/hooks/session-start.sh"
# Then register the SessionStart hook in ~/.claude/settings.json (jq):
jq --arg cmd "$PWD/hooks/session-start.sh" \
'.hooks.SessionStart = ((.hooks.SessionStart // []) + [{"matcher":"*","hooks":[{"type":"command","command":$cmd,"timeout":30}]}])' \
~/.claude/settings.json > /tmp/s.json && mv /tmp/s.json ~/.claude/settings.json(bootstrap.sh does all of this idempotently and is the recommended path.)
Plain markdown files served to Claude from this clone (never copied into project repos):
- Stack-agnostic baseline —
webhub.mdis@-imported into~/.claude/CLAUDE.mdbybootstrap.sh, so it's in context for every session everywhere. - Stack-specific —
webhub-laravel.md(+ the mirroredlaravel-php-guidelines.md) andwebhub-craft-cms.mdare injected by the SessionStart hook only when it detects that project type. Claude sees Laravel rules in Laravel work, Craft rules in Craft work, nothing extra elsewhere. - Because everything resolves to the live files in this clone, a
git pull(the hook auto-pulls hourly) updates every project at once. Nothing is committed into consuming repos anymore.
laravel-php-guidelines.md is mirrored from Spatie (https://spatie.be/laravel-php-ai-guidelines.md). It's vendored here so the hook can serve it locally with no network dependency. Refresh it occasionally with:
curl -fsSL -o guidelines/laravel-php-guidelines.md https://spatie.be/laravel-php-ai-guidelines.mdTo add a new guideline file, drop it into guidelines/ and:
- Add it to the
GUIDELINES=( … )array inbootstrap.shif it should apply user-level to every session (stack-agnostic). - Wire it into
hooks/session-start.sh'scase "$stack"mapping if it should load for a specific stack (and add detection there if it's a new stack).
Each subfolder under skills/ is one Skill, consumed in two places:
- Claude Code (local):
bootstrap.shsymlinks every skill into~/.claude/skills/, so they're discoverable automatically — available as/<name>and auto-invoked from theirdescription. Nothing else to do. - claude.ai org-wide: upload each as a
.zipwithSKILL.md(and any bundled files) at the root of the zip — no wrapper directory.
cd skills/laravel && zip ../laravel.zip SKILL.md reference.md && cd - # zip follows the symlink, so the guideline content lands as reference.md
cd skills/craft-cms && zip ../craft-cms.zip SKILL.md reference.md && cd - # same — keeps the upload self-contained
cd skills/craft-upgrade && zip -r ../craft-upgrade.zip . && cd - # bundles SKILL.md + reference docs
cd skills/laravel-browser-testing && zip ../laravel-browser-testing.zip SKILL.md && cd - # self-contained, single file
cd skills/pest-v5-upgrade && zip ../pest-v5-upgrade.zip SKILL.md && cd - # self-contained, single file
cd skills/nativephp && zip ../nativephp.zip SKILL.md && cd - # self-contained, single fileThe
laravelandcraft-cmsskills are thin wrappers: theirSKILL.mdjust points to a bundledreference.md, which is a symlink to the correspondingguidelines/webhub-*.md(the single source of truth). Editing the guideline updates the skill everywhere - locally the symlink resolves live; for claude.ai thezipabove dereferences it into the archive. Never duplicate guideline content intoSKILL.md.
Then in claude.ai: Settings → Skills → Organization skills → + Add → pick the .zip. Only the org owner can publish org-wide skills.
When a skill grows extra files (examples, scripts, reference docs — like craft-upgrade's per-leg docs), zip the contents of its folder so siblings of SKILL.md sit at the zip root.
Both layers exist for different shapes of knowledge. Picking wrong costs either context on every session or knowledge that never gets loaded.
Guideline (guidelines/) |
Skill (skills/) |
|
|---|---|---|
| Shape | House style - how we write code. Normative, applies to every line. | Playbook - interlocking failure modes, where the symptom presents as an unrelated bug. |
| Examples | webhub-laravel.md: Actions + DTOs, up() only, #[Scope], no $fillable |
craft-upgrade, laravel-browser-testing, nativephp |
| Loading | Auto-injected by the SessionStart hook for the detected stack. Costs context every session. | Only the description loads per session; the body is read on demand. Costs ~nothing until invoked. |
| Trigger | Stack detection (deterministic) | Claude matching the description (probabilistic) - so write the description well, see below |
Rules of thumb:
- Don't promote a playbook to a guideline just because it's important. Put the ruinous upfront facts in a small
*.digest.mdthe hook injects, and the diagnosis in the skill. That's thenativephpsplit: ~2KB digest, ~14KB skill. - Don't create a guideline for a stack we've used once. A skill is nearly free when uninvoked; a guideline taxes every session.
nativephpis deliberately a skill (n=1 project) even though it holds a lot of hard-won detail. - Write descriptions as symptoms, not topics. The
descriptionis the entire trigger surface. Name the literal strings a developer will paste:"Playwright is outdated",spawn .../php EACCES, AppleScript error-1743.laravel-browser-testingandnativephpboth do this on purpose. - Back the description with a hook instruction when the stack is detectable. The injected context leading with "invoke the
Xskill" turns a judgement call into an instruction - far more reliable than description-matching alone. - Push project knowledge up. When a project
CLAUDE.mdaccumulates stack-generic traps, move them into the skill and leave only what is genuinely project-specific behind. Otherwise the next project rediscovers them.
craft-upgrade bundles large per-leg reference docs (Craft's official upgrade docs, plus webhub's pitfalls/protocols). These are not loaded every session: only SKILL.md's description loads at session start. Claude reads the body when an upgrade task begins, and then reads only the version-specific docs matching the from→to versions. That's why a Craft upgrade playbook can live here without bloating every unrelated session.
- Edit the file here.
- Commit + push.
- It propagates automatically:
- Guidelines, the hook script, commands, skills: nothing to do. Each developer's SessionStart hook auto-pulls this clone (≤ 1h), and everything resolves to live files/symlinks — so the next session already has the change. A developer who wants it now can
git -C ~/reps/ai pull. - Wiring changes only (a new globally-imported guideline added to
bootstrap.sh, or a change to how the hook is registered): developers re-rungit pull && ./bootstrap.shonce. - claude.ai Skills: re-zip and re-upload via the admin UI.
- Guidelines, the hook script, commands, skills: nothing to do. Each developer's SessionStart hook auto-pulls this clone (≤ 1h), and everything resolves to live files/symlinks — so the next session already has the change. A developer who wants it now can
Note:
webhubworks/laravel-starterno longer publishes guidelines into project repos. The SessionStart hook is the single source for Claude Code; itswebhub:post-autoloadnow removes any stale.ai/guidelines/copies it finds (and re-runsboost:update) so Boost stops composing outdated rules.