starter-kit-upgrade
Selectively pull upstream improvements from a Laravel starter kit (laravel/vue-starter-kit, laravel/react-starter-kit, laravel/svelte-starter-kit, laravel/livewire-starter-kit) into a project bootstrapped from one. Use when the user wants to update, sync, or migrate features from their starter kit.
By laravel · 383 installs
npx skills add laravel/agent-skills --skill starter-kit-upgrade
Source repository · Upstream listing
Laravel Starter Kit Upgrade
Users bootstrap from laravel/vue starter kit , react starter kit , svelte starter kit , or livewire starter kit , then customize. They own the code.
We pick specific features from upstream (e.g. "toast notifications", "2FA autofocus fix"), not "version upgrades."
The user's git history is unrelated to the kit's. There is no common ancestor. We compare user now vs upstream now, byte by byte.
We never auto merge a customized file. Customizations are surfaced; the user decides.
Behavior preservation is the contract: the user's currently passing tests/typecheck/build must still pass after.
Safety contract: non negotiable
Read these to the user before any side effects, and live by them throughout:
1. Working tree must be clean. If git status porcelain is non empty, refuse and tell the user to commit or stash. Do not "stash for them."
2. All work happens on a dedicated branch ( starter kit upgrade/<short id ). The user's current branch is never modified.
3. Each applied feature is its own commit. That is how revertability works.
4. Never auto resolve conflicts. A change touching customized code is surfaced; default action is to skip the file.
5. Never silently overwrite manifests or lockfiles ( composer.json , package.json , lock. ). Show diffs; let the user decide.
6. Verify behavior preservation. Re run the user's tests/typecheck/build after applying. A previously passing check that now fails is a regression. Stop, surface, recommend revert.
7. Detect from unambiguous signals; ask when ambiguous. Concrete evidence (e.g. config/fortify.php exists) is fine. Picking a likely answer when signals are mixed or absent is not.
If any of these is violated, abort with a clear message about what went wrong and how to recover.
Required tools
git (in the user's project)
gh (authenticated; gh auth status returns OK)
jq (used by run tests.sh )
bash (for the bundled scripts)
If any is missing, stop in Phase 4 and tell the user how to install.
Gotchas
Environment specific behavior the agent will get wrong without being told. Read these before starting the workflow and apply throughout.
Parallel implementations. When a feature has new files plus differs to call sites, the user may already have an in house equivalent (their own toast helper, validation rule, etc.). Surface as a whole; don't apply the new files in isolation as if they're "safe." Default action is to skip the entire feature; the user can opt to adopt upstream's version and remove theirs later.
Renamed paths. If a new path's basename or class name already exists elsewhere in the user's repo, the user has likely renamed/moved it. Surface, don't auto apply, or you'll create a duplicate. Show them the upstream change and let them apply it to their renamed file by hand or wait for user confirmation.
Later upstream edits. Copying upstream HEAD pulls in every commit since the feature, not just the feature's own changes. Always run the Phase 5 step 2 check before applying. When later edits exist, scope to <sha :<path instead of HEAD:<path .
Transitive imports. New files often import from helpers that are NOT in the same feature commit (Vue/React/Svelte: @/lib/... , @/components/... ; Livewire: @include , <x ... , <livewire:... ). Phase 5 step 4 covers the scan; never declare a feature applied without it. Uncovered imports show up as runtime/compile errors.
Lockfile drift. Manifests are user curated. Never overwrite. Walk the user through the upstream diff, let them merge, then regenerate lockfiles via the package manager (Phase 6).
Stale node modules after major bumps. After Vite v7 → v8, React 18 → 19, etc., npm install often fails with ERESOLVE . Clean and reinstall (Phase 6).
New migrations. When upstream adds migrations (e.g. "Catch migrations up to Skeleton"), surface them separately. Recommend php artisan migrate:status first; applying a new migration on a populated DB can fail loudly.
Major framework bumps as features. Things like Laravel 12 → 13, Livewire 3 → 4, or Inertia v2 → v3 are too large and too breaking for the feature by feature flow. Do not attempt them through this skill. Instead, prompt the user to run the corresponding [Laravel Boost](https://github.com/laravel/boost) MCP slash command first, then come back and re run this skill against the resulting (clean tree) repo. If Boost is not yet installed: composer require laravel/boost dev && php artisan boost:install (requires Boost ^2.0 ). Slash commands:
Laravel 12 → 13: /upgrade laravel v13
Livewire 3 → 4: /upgrade livewire v4
Inertia v2 → v3: /upgrade inertia v3
Already present features. If Phase 2's pre filter missed it and Phase 5's classifier reports every file as already present , skip the feature with a note: "every file matches upstream's current; moving on." Don't commit an empty commit.
More than ~50 differs . The per file walkthrough is too tedious to be useful at that scale. Stop, recommend manual upgrade for that feature.
Workflow
Eight phases, in order. Each phase establishes invariants the next relies on.
Phase 1: Identify the kit and branch variant
Inspect the user's project:
vue react svelte livewire
Cue .vue files in resources/js/components/ui/ .tsx files in resources/js/components/ui/ .svelte files in resources/js/components/ui/ no resources/js/components/ui/ dir
package.json has "vue" + "@inertiajs/vue3" "react" + "@inertiajs/react" "svelte" + "@inertiajs/svelte" n/a
composer.json has n/a n/a n/a "livewire/livewire" + "livewire/flux"
State the detected kit out loud. If only one column matches, proceed. If two columns partially match (e.g. both .vue and .tsx present, or package.json lists vue and react ), stop and ask.
Then determine the branch variant. There are four branches per kit, formed by two independent axes:
Auth axis (read composer.json ):
Fortify if composer.json has laravel/fortify , or config/fortify.php exists, or app/Actions/Fortify/ exists, or app/Providers/FortifyServiceProvider.php exists.
WorkOS if composer.json has laravel/workos and none of the Fortify markers are present.
Teams axis (check whether team scaffolding is present):
Teams if app/Models/Team.php exists (usually accompanied by Membership.php , TeamInvitation.php , and a ... create teams table.php migration).
Non teams otherwise.
Combine the two axes to get the branch name:
Auth Teams Branch
Fortify no main
Fortify yes teams
WorkOS no workos
WorkOS yes workos teams
State the detected branch out loud. Only ask if signals are contradictory (e.g. Fortify markers present and laravel/workos in composer, or a Team.php model with no teams migration); that means user customization you can't safely guess at.
Phase 2: Enumerate available upstream features
The user can't tell you "what version they're on" reliably (and we don't try). Inspect upstream as it exists today and present a feature catalog.
Fetch raw data. The default window is the last 100 commits / merged PRs ; tell the user that up front so they know features older than that won't appear in the catalog. If they bootstrapped well before that window, walk back with &page=2 , &page=3 , etc. or raise limit .
Cluster commits/PRs into user facing features. Examples a user would recognize:
"Toast notifications across all kits" (1 commit, several files)
"Password visibility toggle in auth forms" (1 commit, 3 files)
"2FA autofocus fix" (1 commit, 1 file)
"Teams support" (1 PR, many files; flag as large)
"Inertia 3 upgrade" (lockfile heavy; flag as needing review)
"Maintenance: formatting / lint config" (bucket of small commits)
Bucket internal/refactor commits as a single "Maintenance" entry. The user usually skips it.
Pre filter: for each candidate feature, run scripts/classify feature.sh against its commit. If every file is already present , mark [!] Already present and skip by default.
Phase 3: Present the catalog and get explicit selection
Wait for the selection. Recap the picks and the affected file counts. Ask one final time before any side effects.
Phase 4: Preflight, baseline, and workspace setup
Run preflight:
It checks the repo is a git repo, the tree is clean, and that gh (authenticated) and jq are available. If it exits non zero, surface the message verbatim and stop.
Record a verification baseline so Phase 7 can distinguish regressions from pre existing failures. Use mktemp so concurrent runs don't clobber each other:
Hold onto $baseline ; Phase 7 needs it.
Fetch the upstream kit and capture its path:
Hold onto $kit dir ; Phase 5 needs it. The script is idempotent: re running with the same args fetches the latest branch tip rather than re cloning.
Create the upgrade branch:
If the user is already on a starter kit upgrade/... branch (a previous run that didn't get cleaned up), checkout b will refuse if the new name collides. Don't auto resolve: ask whether they want to resume on that branch (skip the checkout b , keep going from where they were), start fresh (the new timestamped name will already differ by minute, so just retry — or bump to +%Y%m%d %H%M%S if it's the same minute), or abort so they can clean up manually. Never delete the existing branch on their behalf.
From this point on, every write goes to this branch.
Phase 5: Apply each selected feature
For each selected feature, in order:
1. Classify. Run scripts/classify feature.sh <kit dir <sha <user repo . Statuses:
new : file does not exist in user repo, exists at upstream HEAD. Safe to add.
already present : user's file is byte identical to upstream HEAD. Skip.
differs : user has the file and bytes differ from upstream HEAD. Surface.
deleted upstream : upstream HEAD lacks the file but the user has it. Surface; default is keep theirs.
lockfile : manifest or lock file. Surface; never auto merge.
The classifier compares only against upstream HEAD. The user's git history doesn't trace back to the kit's, so there's no "before image" baseline to merge against; we don't try. The feature commit just enumerates which paths to look at.
2. Later edits check. Find which feature paths later upstream commits also modified:
Each path the script prints is a path where copying upstream HEAD's content pulls later changes in too. Diff <sha :<path against HEAD:<path ; if a non whitespace hunk differs, scope to the feature commit ( git C <kit dir show <sha :<path ) and note it in the report.
3. Apply new files. The script writes upstream HEAD's content for each new path and stages it; everything else is left for steps 4–5:
It prints applied <path for each file written so you can collect the list for the feature's commit message and the report.
Before letting the script run, check for the rename gotcha (see Gotchas → "Renamed paths"). If a new path's basename already exists at a different location in the user's repo, surface to the user before applying.
4. Transitive imports check. New files often import helpers that aren't in the same feature commit. The script picks the right regex for the kit (Vue/React/Svelte handle TS/JS impor