关于 starter-kit-upgrade
Laravel Starter Kit Upgrade 是面向使用 Laravel Starter Kit 的项目的升级技能,可从上游版本中逐项挑选功能变更。它通过独立分支、逐项提交和行为验证来保护自定义代码,不会自动合并冲突或覆盖清单与锁文件。
主要功能
- 01识别 Vue、React、Svelte 或 Livewire kit
- 02列出上游新增功能供选择
- 03比较文件并标记自定义改动
- 04每项功能单独提交到专用分支
- 05应用后重新运行测试与构建
适用场景
- 01将上游通知功能移植到定制项目
- 02审查上游改动对自定义文件的影响
- 03安全挑选 starter kit 的单项功能
- 04验证更新后测试和构建仍通过
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ laravel/ agent-skills --skill starter-kit-upgrade安装后在 Claude Code 中输入/starter-kit-upgrade确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
scripts/apply_new_files.shclassify_feature.shfetch_kit.shlater_edits.shpreflight.shreconcile_manifests.shrun_tests.shscan_transitive_imports.sh详细介绍
Laravel Starter Kit 升级
- 用户从
laravel/vue-starter-kit、react-starter-kit、svelte-starter-kit或livewire-starter-kit初始化项目,然后进行自定义。他们拥有这些代码。 - 我们从上游挑选特定功能(例如“toast 通知”“修复双重身份验证自动聚焦问题”),而不是“版本升级”。
- 用户的 Git 历史与 Starter Kit 无关。两者没有共同祖先。我们逐字节比较用户当前代码与上游当前代码。
- 我们绝不会自动合并经过自定义的文件。我们会列出自定义内容,由用户决定。
- 保持行为不变是我们的约定:用户当前通过的测试、类型检查和构建,在应用更新后仍必须通过。
安全约定:不可妥协
在执行任何副作用之前,先向用户说明以下内容,并始终遵守:
- 工作树必须干净。如果
git status --porcelain输出非空,则拒绝操作,并告知用户提交更改或将其暂存。不要“替他们暂存”。 - 所有工作都在专用分支(
starter-kit-upgrade/<short-id>)上进行。绝不修改用户当前所在的分支。 - 每项已应用的功能都单独提交。这是确保可还原的方式。
- 绝不自动解决冲突。触及自定义代码的更改必须列出;默认操作是跳过该文件。
- 绝不静默覆盖清单或锁文件(
composer.json、package.json、*-lock.*)。显示差异;由用户决定。 - 验证行为保持不变。应用更新后,重新运行用户的测试、类型检查和构建。如果之前通过的检查现在失败,就是回归。停止操作,说明情况,并建议还原。
- 根据明确无误的信号进行检测;存在歧义时应询问。具体证据(例如存在
config/fortify.php)可以作为依据。当信号混杂或缺失时,不要自行猜测可能的答案。
如果违反以上任何约定,请中止操作,并清楚说明出了什么问题以及如何恢复。
必需工具
git(位于用户的项目中)gh(已完成身份验证;gh auth status返回 OK)jq(由run_tests.sh使用)bash(用于随附脚本)
如果缺少任何工具,请在第 4 阶段停止,并告知用户如何安装。
注意事项
如果不提前告知,Agent 就会弄错的特定环境行为。在开始工作流前阅读,并在整个过程中遵循。
-
并行实现。 当某项功能包含
new文件,并且在调用处标记为differs时,用户可能已经有内部等效实现(例如自己的 toast 辅助工具、验证规则等)。应整体呈现该功能;不要单独应用new文件,仿佛它们“安全”一样。默认操作是跳过整个功能;用户之后可以选择采用上游版本并移除自己的实现。 -
重命名的路径。 如果某个
new路径的基本名称或类名已存在于用户仓库的其他位置,用户很可能已重命名或移动它。应提示用户,不要自动应用,否则会创建重复项。向用户展示上游更改,让他们手动将其应用到重命名后的文件,或等待用户确认。 -
上游后续编辑。 复制上游 HEAD 会拉取该功能以来的所有提交,而不仅仅是该功能自身的更改。应用前始终执行第 5 阶段第 2 步的检查。如果存在后续编辑,请使用
<sha>:<path>,而不是HEAD:<path>。 -
传递导入。 新文件经常会从同一功能提交中未包含的辅助工具导入内容(Vue/React/Svelte:
@/lib/...、@/components/...;Livewire:@include、<x-...>、<livewire:...>)。第 5 阶段第 4 步会进行扫描;完成此步骤前,绝不要宣告功能已应用。未覆盖的导入会导致运行时或编译错误。 -
锁文件偏差。 清单由用户自行维护。切勿覆盖。引导用户查看上游差异并进行合并,然后通过包管理器重新生成锁文件(第 6 阶段)。
-
主要版本升级后
node_modules过时。 Vite v7 → v8、React 18 → 19 等升级后,npm install经常会因ERESOLVE失败。请清理并重新安装(第 6 阶段)。 -
新增迁移。 上游新增迁移时(例如“将迁移追赶至 Skeleton”),应单独提示。建议先运行
php artisan migrate:status;在已有数据的数据库上应用新迁移可能会直接失败。 -
将框架主要版本升级视为功能。 Laravel 12 → 13、Livewire 3 → 4 或 Inertia v2 → v3 这类升级规模太大,且破坏性太强,不适合按功能逐项处理。不要通过此技能尝试升级。应提示用户先运行相应的 Laravel Boost MCP 斜杠命令,然后返回并针对生成的(干净工作树)仓库重新运行此技能。如果尚未安装 Boost:
composer require laravel/boost --dev && php artisan boost:install(需要 Boost^2.0)。斜杠命令:- Laravel 12 → 13:
/upgrade-laravel-v13 - Livewire 3 → 4:
/upgrade-livewire-v4 - Inertia v2 → v3:
/upgrade-inertia-v3
- Laravel 12 → 13:
-
已存在的功能。 如果第 2 阶段的预筛选未能识别,而第 5 阶段的分类器报告所有文件均为
already-present,则跳过该功能,并注明:“每个文件都与上游当前版本一致;继续处理。”不要提交空提交。 -
超过约 50 个
differs。 逐个文件讲解在这种规模下过于繁琐,没有实际用处。停止处理,并建议手动升级该功能。
工作流
共分八个阶段,按顺序执行。每个阶段都会建立下一阶段所依赖的不变条件。
阶段 1:识别套件和分支变体
检查用户的项目:
| vue | react | svelte | livewire | |
|---|---|---|---|---|
| 线索 | resources/js/components/ui/ 中的 .vue 文件 | resources/js/components/ui/ 中的 .tsx 文件 | resources/js/components/ui/ 中的 .svelte 文件 | 没有 resources/js/components/ui/ 目录 |
package.json 中包含 | "vue" + "@inertiajs/vue3" | "react" + "@inertiajs/react" | "svelte" + "@inertiajs/svelte" | n/a |
composer.json 中包含 | n/a | n/a | n/a | "livewire/livewire" + "livewire/flux" |
明确告知检测到的套件。如果只有一列匹配,继续操作。如果两列部分匹配(例如,同时存在 .vue 和 .tsx 文件,或 package.json 同时列出了 vue 和 react),则停止并询问用户。
然后确定分支变体。每个套件有四个分支,由两个相互独立的维度组成:
- Auth 维度(读取
composer.json):- 如果
composer.json中包含laravel/fortify,或存在config/fortify.php,或存在app/Actions/Fortify/,或存在app/Providers/FortifyServiceProvider.php,则为 Fortify。 - 如果
composer.json中包含laravel/workos且不存在任何 Fortify 标记,则为 WorkOS。
- 如果
- Teams 维度(检查是否存在团队脚手架):
- 如果存在
app/Models/Team.php,则为 Teams(通常还会有Membership.php、TeamInvitation.php和一个..._create_teams_table.php迁移)。 - 否则为非 Teams。
- 如果存在
组合两个维度,得到分支名称:
| Auth | Teams | Branch |
|---|---|---|
| Fortify | 否 | main |
| Fortify | 是 | teams |
| WorkOS | 否 | workos |
| WorkOS | 是 | workos-teams |
明确告知检测到的分支。只有在信号相互矛盾时才询问用户(例如,同时存在 Fortify 标记和 composer 中的 laravel/workos,或存在 Team.php 模型但没有团队迁移);这表明用户进行了你无法安全猜测的自定义修改。
阶段 2:枚举上游提供的功能
用户无法可靠地告知你“他们使用的是哪个版本”(我们也不尝试判断)。检查当前上游内容,并展示功能目录。
获取原始数据。默认范围是最近 100 个提交 / 已合并 PR;请事先告知用户这一点,以便他们了解目录不会列出更早的功能。如果他们很久以前就创建了项目,则使用 &page=2、&page=3 等向前追溯,或增加 --limit。
gh api "repos/laravel/<kit>/commits?sha=<branch>&per_page=100" \
-q '.[] | {sha: .sha[0:7], date: .commit.author.date[0:10], msg: .commit.message | split("\n")[0]}'
gh pr list --repo "laravel/<kit>" --state merged --base "<branch>" --limit 100 \
--json number,title,mergeCommit,mergedAt
将提交 / PR 归类为面向用户的功能。用户能够理解的示例:
- “所有套件均支持 Toast 通知”(1 个提交,涉及多个文件)
- “认证表单中可切换密码可见性”(1 个提交,涉及 3 个文件)
- “修复 2FA 自动聚焦问题”(1 个提交,涉及 1 个文件)
- “支持 Teams”(1 个 PR,涉及许多文件;标记为大型改动)
- “升级 Inertia 3”(涉及大量锁文件改动;标记为需要审核)
- “维护:格式化 / lint 配置”(若干小型提交归为一组)
将内部提交 / 重构提交归入单个“维护”条目。用户通常会跳过此项。
预筛选:针对每个候选功能,对其提交运行 scripts/classify_feature.sh。如果所有文件均为 already-present,则标记为 [!] 已存在,并默认跳过。
阶段 3:展示目录并获取明确选择
Available upstream features (vue-starter-kit, branch: main):
[ ] Toast notifications · PR #142, 4 files, 1 lockfile
[ ] Password visibility toggle · PR #131, 3 files
[ ] 2FA autofocus fix · commit 78fda0c, 1 file
[ ] Teams support · PR #98, 23 files (LARGE)
[~] Inertia 3 upgrade · PR #110, lockfile-heavy (review carefully)
[!] Already present: Vite font plugin
Which would you like to pull in?
等待用户选择。总结所选项目和受影响的文件数量。在产生任何副作用之前,再进行最后一次确认。
阶段 4:预检、基线和工作区设置
运行预检:
scripts/preflight.sh <user_repo>
它会检查仓库是否为 git 仓库、工作区是否干净,以及 gh(已通过身份验证)和 jq 是否可用。如果退出码非零,请原样显示消息并停止。
记录验证基线,以便阶段 7 区分回归问题和已有失败。使用 mktemp,避免并发运行相互覆盖:
baseline=$(mktemp -t skup-baseline.XXXXXX.json)
scripts/run_tests.sh <user_repo> --baseline "$baseline"
保留 $baseline;阶段 7 需要用到它。
获取上游工具包并记录其路径:
kit_dir=$(scripts/fetch_kit.sh <kit> <branch>)
保留 $kit_dir;阶段 5 需要用到它。该脚本具有幂等性:使用相同参数重新运行时,会获取最新的分支提交,而不会重新克隆。
创建升级分支:
git -C <user_repo> checkout -b "starter-kit-upgrade/$(date +%Y%m%d-%H%M)-<first-slug>"
如果用户当前已在 starter-kit-upgrade/... 分支上(之前运行后未清理),且新名称发生冲突,checkout -b 会拒绝创建。不要自动处理:询问用户是要在该分支上继续(跳过 checkout -b,从当前状态接着进行)、重新开始(新的带时间戳名称通常会有所不同,直接重试即可;如果时间戳仍在同一分钟内,则改用 +%Y%m%d-%H%M%S),还是中止,由他们手动清理。切勿代替用户删除现有分支。
从现在开始,所有写入操作都在此分支上进行。
阶段 5:应用选定的功能
按顺序处理每项选定的功能:
- 分类。运行
scripts/classify_feature.sh <kit_dir> <sha> <user_repo>。状态如下:
new:用户仓库中不存在该文件,而上游 HEAD 中存在。可以安全添加。already-present:用户的文件与上游 HEAD 完全相同。跳过。differs:用户有该文件,且内容与上游 HEAD 不同。提示用户处理。deleted-upstream:上游 HEAD 中没有该文件,但用户有。提示用户处理;默认保留用户的文件。lockfile:清单或锁文件。提示用户处理;绝不自动合并。
分类器只与上游 HEAD 比较。用户的 Git 历史无法追溯到工具包的历史,因此没有可供合并的“修改前版本”基线;我们不会尝试合并。功能提交只会列出需要检查的路径。
- 检查后续修改。找出上游较晚的提交也修改过的功能路径:
scripts/later_edits.sh <kit_dir> <sha> <user_repo>
脚本打印的每个路径,都是复制上游 HEAD 内容时也会带入后续修改的路径。比较 <sha>:<path> 与 HEAD:<path>;如果存在非空白字符差异,将范围限定为功能提交(git -C <kit_dir> show <sha>:<path>),并在报告中注明。
3. 应用 new 文件。 对每个 new 路径,脚本都会写入上游 HEAD 的内容并将其暂存;其他所有内容留待步骤 4–5 处理:
scripts/apply_new_files.sh <kit_dir> <sha> <user_repo>
每写入一个文件,脚本都会打印 applied <path>,以便你收集这些路径,用于功能的提交消息和报告。
运行脚本前,检查重命名问题(参见“注意事项”→“重命名路径”)。如果用户仓库中的其他位置已存在某个 new 路径的基本文件名,请先告知用户,再进行应用。
4. 检查传递依赖导入。 新文件通常会导入不属于同一功能提交的辅助文件。脚本会为对应工具包选择合适的正则表达式(Vue/React/Svelte 会处理 TS/JS 导入;Livewire 会处理 Blade 包含项、x- 组件和 livewire: 标签):
scripts/scan_transitive_imports.sh <kit> <new_files...>
输出格式为每条导入项一行:<file>:<line>:<match>。对于每个匹配项,请确认相应的辅助文件存在于用户仓库中。如果不存在,新文件将无法编译或渲染;请将缺失目标标记为用户需要获取的后续依赖(按处理 differs 的相同步骤进行)。
5. 引导用户逐个处理 differs、deleted-upstream 和 lockfile 文件。 一次处理一个文件:
- 展示上游文件内容:
git -C <kit_dir> show HEAD:<path>(如果later_edits.sh标记了此路径,则使用<sha>:<path>)。 - 展示用户当前的文件。
- 展示两者之间的差异。
- 请用户选择:完全采用上游版本(会丢失内容;须先确认)、保留用户版本,或手动合并(你生成统一差异供其参考;由用户写入结果)。
- 如果用户拿不定主意,请再次询问,并向其展示差异。如果仍不确定,则保留用户版本并继续。不要默默替用户做决定。
- 暂存用户选择的内容:
git -C <user_repo> add <path>。
对于 lockfile:绝不要覆盖清单。展示 composer.json / package.json 的上游差异,引导用户处理相关更改,并让用户自行编辑清单。锁文件将在阶段 6 重新生成。
6. 将该功能作为一个可单独还原的单元提交:
git -C <user_repo> commit -m "starter-kit-upgrade: <feature name>
Upstream: laravel/<kit>@<sha>
Files added: <list>
Files updated (took upstream): <list>
Files updated (manual merge): <list>
Files kept as-is: <list>"
如果用户想在任何时候退出,请保持分支原样。他们可以用 git branch -D 删除该分支。
阶段 6:必要时协调清单
如果任何功能修改了清单,锁文件就会不同步。征得用户同意后,运行:
scripts/reconcile_manifests.sh <user_repo>
脚本会运行 composer install(当 composer.json 和 composer.lock 同时存在时),根据现有锁文件自动检测 JS 包管理器,运行 <pm> install;如果失败(通常是在 Vite v7 → v8 或 React 18 → 19 这类主版本升级后遇到 ERESOLVE),则会删除 node_modules 和锁文件,并重试一次。
将锁文件更新作为单独的 starter-kit-upgrade: dependency lockfiles 提交,以便能够独立还原。
阶段 7:验证行为是否保持不变
与基线进行比较:
scripts/run_tests.sh <user_repo> --compare "$baseline"
比较模式会运行 PHP 测试、JS 类型检查、JS 构建(如果存在),并且只报告基线中通过、现在却失败的检查。已有的失败不是升级导致的,也不会阻碍升级。
如果报告了回归:
- 显示脚本指向的每项检查日志文件中的失败输出。
- 首先建议使用
git revert HEAD;如果问题仍未解决,再次回退。 - 如果无法确定是哪个功能导致问题,建议运行
git bisect start <upgrade-branch> <previous-branch>。 - 不要修改代码来让失败的检查通过;这违反了行为契约。
如果项目没有可发现的验证命令,请在报告中明确说明。不要假装已完成验证。
阶段 8:撰写报告
首先写入 /tmp/starter-kit-upgrade-report-<id>.md(其中 <id> 与升级分支的 starter-kit-upgrade/<id> 一致);切勿在用户的仓库中悄悄写入文件。为报告添加 ID 可避免并行运行和重新运行时相互覆盖。显示文件路径,并询问用户是否希望将其复制为 STARTER_KIT_UPGRADE.md,或将其保留在仓库目录之外。
# Starter Kit Upgrade Report
- Date: <date>
- Kit: laravel/<kit>
- Branch tracked: <branch>
- Upgrade branch: starter-kit-upgrade/<id>
## Features applied
- <feature name> · laravel/<kit>@<sha> · <N files>
- Applied: <list>
- Skipped: <list with reasons>
- Manual decisions: <if any, with reasoning>
- Later-edit drift avoided: <if any, with paths scoped manually>
## Lockfile updates
<which lock files were regenerated and how>
## Verification
- Baseline: <path or summary>
- Result: <PASS / REGRESSED:<list> / NO-CHECKS>
- Output: <relevant snippet>
## How to revert
- Drop a single feature: `git revert <commit-sha>`
- Discard everything: `git checkout <previous-branch> && git branch -D starter-kit-upgrade/<id>`
不在范围内
- 检测用户最初使用的是哪个“版本”的工具包。没有可靠的方法;我们不会假装能做到。
- 自动协调依赖版本约束。我们会展示相关信息;由用户决定。
- 入门工具包的分支。如果仓库结构无法识别为三种官方工具包之一,请拒绝并说明原因。
- 跨工具包迁移(例如 Vue → React)。
- 对已应用的文件运行代码检查器或格式化工具。用户应运行自己的工具。