维护 lt 栈(所有基础仓库)
七个基础仓库按依赖顺序进行维护和发布。权威来源是 github.com/lenneTech;发布需要在本地克隆每个仓库,并且检出路径因机器而异——请通过以下命令定位它,而不是假设一个固定路径:
find "$HOME" -maxdepth 5 -type d -name nest-server-starter -not -path '*/node_modules/*' 2>/dev/null
如果某个仓库没有在任何地方检出,请将其克隆到与其兄弟仓库相同的工作区目录中。最终目标状态:每个仓库都是最新的,check 在所有地方都是绿色的,npm 包已发布,模板已打标签——通过完整的 /lt-dev:fullstack:smoke-test 运行来证明。
依赖图(决定了顺序)
第一波(可并行): nuxt-extensions nest-server lt-monorepo cli
│ │
▼ (等待 npm 发布!)
第二波(可并行): nuxt-base-starter nest-server-starter
│
▼
验证: /lt-dev:fullstack:smoke-test (实际运行所有仓库)
│
▼
第三波(仅在有发现时): 补丁修复 → 重新发布受影响的仓库(nest-server-starter:仅推送)
规则: 只有当其 npm 包在 npm 上实际可解析时,starter 才会更新,而不是在 GitHub 发布存在时——publish.yml 操作需要几分钟。
请询问版本特定的端点,而不是 npm view。 npm view 和 https://registry.npmjs.org/<pkg> 读取相同的 CDN 缓存包清单(packument),并且它比发布滞后几分钟:
curl -s -o /dev/null -w '%{http_code}' https://registry.npmjs.org/<pkg>/<version>
# 200 = 现在可解析。替代方法:使用 -H 'Cache-Control: no-cache' 加上查询参数来破坏缓存。
2026-09-04 测量:一个会话轮询了 npm view 十分钟并读到“尚未发布”,而 @lenne.tech/[email protected] 一直在那里——版本特定的端点立即响应了 200。从调用方的角度来看,“还不在那里”和“仪器正在读取缓存”看起来是一样的,这就是为什么端点是要询问的对象。
跨并行会话运行各个波次
上面的波次说“可并行”,这是字面意思:每个仓库一个 Claude Code 会话,在其自己的终端中,这才是栈发布真正快速的方式。第一波包含四个独立的仓库,第二波包含另外两个,因此挂钟时间下限是一个仓库的发布时间,而不是六个顺序执行。
只有当会话在四件事上协调时,这才有效。所有这些都运行在 coordinating-peer-sessions 协议上,并且按仓库拆分正是该协议要求的边界:没有两个会话会触及同一个工作树。
1. 在开始之前认领一个仓库。 首先在账本中记录它,然后宣布它:
bash "${CLAUDE_PLUGIN_ROOT}/scripts/peer-ledger.sh" read # 谁已经拥有什么
bash "${CLAUDE_PLUGIN_ROOT}/scripts/peer-ledger.sh" claim "repo:nest-server" "Wave 1 release"
账本部分在这里比在任何地方都更重要:一轮发布运行数小时,会话来来去去,仅存在于消息中的认领在其终端关闭的那一刻就丢失了。claim 拒绝另一个存活会话持有的仓库并命名它;其会话已死亡的认领读作 [stale] 并且可以自由认领,因此崩溃永远不会使仓库搁浅。当仓库发布时,使用 peer-ledger.sh release "repo:nest-server" "<version>" 释放。没有这些,两个会话都会发布 nest-server,第二个会在第一个之上创建一个版本。
[CLAIM] nest-server — 承担此仓库的第一波发布。
涉及:栈发布;其他人不应对其进行版本控制或发布。
需要:选择一个不同的第一波仓库。
空闲:当我报告 READY 并带有已发布的版本时。
2. 使用 READY 信号通知第二波,不要让它轮询。 波次之间的等待是真正的 npm 传播延迟,等待它的会话除了反复询问注册表之外,无法知道它何时清除。发布会话确切地知道何时清除。它发送一个带有版本的 READY:
[READY] @lenne.tech/[email protected] 已发布并可在注册表上解析。
涉及:nuxt-base-starter — 第二波依赖升级可以开始了。
需要:pnpm add @lenne.tech/[email protected] 并继续。
第二波会话也可以订阅第一波会话的 notify_when_idle 而不是轮询。无论哪种方式,没有人会在一个 sleep 循环中对着注册表等待。
3. 为任何环境问题发送 SOLVED。 此技能中的经验性陷阱几乎都是机器范围的,而不是仓库特定的:一个默默地返回空值的远程读取、一个尚未传播的注册表、一个积压的 CI 运行器队列、一个损坏的工具链版本。第一个诊断出一个的会话已经为它付出了代价,而其他五个正在走向同一堵墙。一个 SOLVED 消息,它们就不会。
[SOLVED] `git ls-remote` 在 stderr 上失败并输出空 stdout,因此标签检查读作“无标签”。
涉及:此发布轮次中的每个仓库;您的标签验证以相同的方式撒谎。
需要:检查退出码,或通过 HTTPS 读取——参见推送通道规则。
4. 使用 LANDED 报告完成,以便冒烟测试只开始一次。 验证门控在每个仓库都发布后运行。最后完成的人启动它;其他人报告完成并停止。
不能通过消息传递的内容:存在哪些仓库以及它们的顺序是什么(此技能已说明),以及仓库在哪个版本上(注册表和标签已说明)。并且没有会话给另一个会话分配仓库。用户决定谁承担什么,或者每个会话认领一个空闲的并说明是哪个。
交叉规则(所有仓库)
-
关键依赖包必须在两个框架仓库中一次性移动。 某些依赖不是一个仓库的事——它们是一个协议的两个端点,而 nest-server 和 nuxt-extensions 各拥有一个端点。
better-auth就是教会我们这个的案例;将两个框架都导入的任何包视为相同。两者都将其声明为 peer,其范围在两个清单中是字节相同的,并且是狭窄的:
>=<已知最低良好版本> <<下一个次要版本>,永远不要用^。better-auth 在 MINOR 版本中会破坏,因此插入符号会在一个发布后邀请相同的分裂。从清单中读取当前范围,而不是从这里——它会移动,写在此技能中的版本是会过时的版本:node -e 'for (const p of ["nest-server","nuxt-extensions"]) console.log(p, require(`${process.env.HOME}/code/lenneTech/${p}/package.json`).peerDependencies["better-auth"])'升级它意味着:在 nest-server 和 nuxt-extensions 中提高范围,在 nest-server-starter 和 nuxt-base-starter 中固定新版本,发布所有四个。永远不要只做一半,即使是“只是为了解除前端的阻塞”。仅提高下限也是一种升级——它正是说明哪个补丁是已知良好的,而两个仓库之间不一致的下限与版本分裂是相同的缺陷,只是早一个发布。
下限不是品味问题:它也必须满足兄弟 peer 依赖。 像
better-auth这样的包带有同伴(@better-auth/passkey,@better-auth/core),这些同伴反过来又需要它的一个版本。低于同伴要求的下限会批准一个实际上无法安装的配对——>=1.7.0允许 better-auth 1.7.0 与 passkey 1.7.1 一起使用,而 passkey 本身拒绝(^1.7.1)。该范围一般意义上并非错误;它恰好允许了一个无效的交叉组合,正是那种在任何仓库的安装中都不会遇到的情况。从同伴的清单中推导下限,而不是凭直觉:node -e 'console.log(require("./node_modules/@better-auth/passkey/package.json").peerDependencies)'nuxt-extensions 在
test/peer-dependency-ranges.test.ts中断言这一点(下限与同伴的需求对比,以及每个范围与实际测试过的 devDependency 对比)。在任何 peer 范围有同伴的地方都值得复制。一个分裂的成本,实测:nest-server 将 better-auth 1.6.26 固定为硬依赖,而 nuxt-extensions 声明了一个 peer,因此应用移动到 1.7.1 而 api 无法跟随。1.7 为
twoFactor.enable提供了一个带有method的判别结果,而 1.6.26 从未发送——每个全栈项目中的每个 2FA 激活都失败了,带有通用客户端错误且服务器日志中没有任何异常。两个仓库的check在整个时间里都是绿色的:每个内部都是一致的,只有组装后的工作空间才同时拥有两个版本。今天有三个守卫能捕获它,但没有一个能替代此规则——它们捕获错误,此规则防止错误:
-
当清单被合并时(lt CLI
hoist-workspace-pnpm-config.ts,自 1.45.0 起)。合并是后写入者胜出,这对于根对比子项目是正确的,而在兄弟之间是一个陷阱。报告:两个子项目在同一运行中不同地设置一个键;增量情况(add-api然后add-app,其中较早的运行已经提升并清空了其源);以及一个仓库在package.json#pnpm及其自己的pnpm-workspace.yaml中自相矛盾。allowBuilds不仅被报告而且被合并为拒绝胜出——警告不会阻止一个被一个项目明确拒绝的安装脚本,而overrides保持仅报告,因为没有安全的方向用于版本。 -
在组装后的工作空间中(
lt-monorepo/scripts/check-workspace-consistency.mjs,自 3.10.0 起)。当 api 和 app 解析一个关键依赖包不同时,当两个框架承诺不同的 peer 范围时,或者当一个成员安装了该包但未在任何地方固定它时失败——autoInstallPeers默认为 true,因此一个未固定的 peer 今天同意,并在下次安装时自由漂移。 -
在行为中(
nuxt-base-starter/.github/workflows/test.yml,作业e2e-auth,自 2.22.3 起为绿色)。启动 MongoDB 和 nest-server-starter 并针对真实 API 运行认证套件。唯一能看到损坏的契约而不是版本差异的层——另一个层比较声明,而声明在整个分裂期间都是绿色的。两件它需要但容易遗漏的事情,均由它自己的首次运行发现:模板的依赖必须单独安装(它有自己的锁文件,并且其检查链以
cross-env开头——它自己的 devDependency 之一),一个空的数据库会将每个访问者路由到/auth/setup,因此第一个管理员必须在套件运行之前通过POST /system-setup/init创建。
冒烟测试是端到端的门控:它构建一个真正的全栈项目,因此它是线缆分裂显示为失败流程而不是版本差异的地方。
-
-
在
/lt-dev:review针对该仓库的发布差异运行之前,不得发布任何内容(/lt-dev:review --base=<上一个发布标签>,其中包括未提交和未跟踪的文件),在维护之后、版本提升之前。从origin/main获取标签:git describe --tags --abbrev=0 origin/main。发布标签位于每个基础仓库的main上,并且 nest-server 的标签是合并提交,develop从不包含这些提交,因此从HEAD查找会返回过时多个发布的标签(2026-10-27 在 nest-serverdevelop上测量:返回11.33.1而不是12.0.0,审查了 851 个文件而不是 14 个)。链中的每个仓库都基于自己的差异通过它;审查无法修复的发现会停止该仓库的发布以及其下游的所有内容。由 Kai 于 2026-10-21 决定,以便自动化发布链永远不会发布未经任何人审查的代码。 -
无变更 → 无发布。 唯一跳过发布的情况是仓库中确实没有任何变化(工作树干净并且自上次发布版本以来没有提交)——永远不要创建一个不包含任何变更的版本。任何实际的仓库变更——依赖升级、代码、脚本、锁文件、工具链固定——都足以证明新版本的合理性;不要猜测某个变更是否“值得发布”。未更改的仓库报告为“已经是最新的——不发布”并跳过。一个例外是 nest-server-starter:其版本仅随 nest-server 版本移动,因此其自身的变更在没有发布的情况下提交和推送,并在下一个版本下发布(recipes.md,nest-server-starter)。 参考是已发布的制品:对于 npm 包,无法到达 tarball 的提交(在 package.json
files集合之外——例如.claude/agent-memory/**,CI 配置)不会触发它们自己的发布;在不确定时使用npm pack --dry-run进行验证。这样的提交只是随下一个真正的发布一起进行。对于模板,制品是仓库本身,因此每个提交都算数。 -
推送通道: 询问脚本,脚本根据功能决定它:
bash "${CLAUDE_PLUGIN_ROOT}/scripts/check-push-channel.sh" [<repo-root>] # 在仓库内部省略;永远不要传递 "$(pwd)" # -> ssh<TAB>github.com<TAB>authenticated as: Hi kaihaase! … # -> https<TAB>github.com<TAB>no SSH authentication (…)在
https上,通过 HTTPS 推送:git -c credential.helper='!gh auth git-credential' push https://github.com/lenneTech/<repo>.git <branch>。gh release create在两种情况下都不受影响。当推送因看起来像权限问题而失败时,运行此检查。 2026-09-08 在 nest-server-starter 上测量:
git push origin main失败并显示Please make sure you have the correct access rights and the repository exists,而在同样的几分钟内通过 SSH 推送到 nest-server 成功了。账户、协作者状态或远程 URL 都没有问题——只有该路径的到 github.com 的 SSH 连接已死亡,检查显示https<TAB>github.com<TAB>no response from server。运行check-push-channel.sh立即指向了替换,而GIT_SSH_COMMAND尝试不会——git push的默认 SSH 重试在报告访问被拒绝之前已经在三种不同的传输方式上放弃(第一种:SSH over git://;第二种:git over SSH on port 9418;第三种:git over HTTPS)。只有check-push-channel.sh看到传递了什么,并且它所做的第一件事是timeout 3——git 自己的重试是硬编码的*
...(原文被截断)
-
避免
REGISTRY_PUBLISH_TOKEN默认值。 在 LenneTech 生态系统中,REGISTRY_PUBLISH_TOKEN环境变量控制 npm 发布的凭据。此技能的自动化流程不应假设或回退到单个共享令牌;相反,它应始终提示发布者提供有效的令牌,或从安全的凭据存储中读取预期的令牌。当REGISTRY_PUBLISH_TOKEN未设置或设置为空值时,发布步骤必须失败并显示一条明确的消息,指示用户提供令牌。此规则防止了在发布脚本本身内部嵌入凭据或使用容易泄露的默认值的意外行为。在发布操作期间验证令牌:
if [ -z "$REGISTRY_PUBLISH_TOKEN" ] || [ "$REGISTRY_PUBLISH_TOKEN" = "default" ]; then echo "错误:REGISTRY_PUBLISH_TOKEN 未设置或设置为默认值。请提供有效的令牌。" exit 1 fi -
使用
npm publish而不是pnpm publish。 虽然项目使用 pnpm 进行依赖管理,但发布操作必须专门使用npm publish。这样做是为了确保与 npm 注册表的兼容性,并避免与 pnpm 自身的发布逻辑相关的潜在问题,后者可能在某些版本中行为不同。发布命令:
npm publish --access public注意:在发布之前,请确保已通过
npm login或通过设置NODE_AUTH_TOKEN环境变量(通常在 CI 环境中使用)进行了身份验证。 -
使用
npx在需要时运行 CLI 工具。 对于发布过程中需要的任何 CLI 工具(例如,用于更改日志生成的lerna或standard-version),请使用npx来调用它们,假设它们已安装在node_modules中或全局可用。避免硬编码工具的特定路径。示例:
npx standard-version --release-as minor --skip.changelog false --skip.commit false --skip.tag false这确保了即使工具在不同的存储库中不同地安装,命令也能工作。
-
始终同步
package-lock.json或pnpm-lock.yaml。 在对package.json进行任何更改(包括依赖升级或版本提升)之后,必须生成或更新相应的锁文件。这确保了依赖解析在不同环境中是可重复的。对于 pnpm 项目:
pnpm install --frozen-lockfile false # 如果需要更新锁文件以匹配新的 package.json对于 npm 项目(如果存在
package-lock.json):npm install --package-lock-only在提交之前,验证锁文件与
package.json一致,并且没有未跟踪的更改。 -
提交消息遵循约定。 所有由发布过程生成的提交必须遵循 conventional commits 格式。这有助于自动生成变更日志,并维护一个清晰的项目历史。格式为:
<type>(<scope>): <description>其中
<type>通常是chore或release,<scope>是仓库名称(例如nest-server、nuxt-extensions),并且<description>简要描述更改(例如bump version to 1.2.3或upgrade better-auth to ^1.8.0)。发布提交的示例:
chore(nest-server): release v12.0.0遵循此格式确保像
standard-version或semantic-release这样的工具可以正确地自动检测版本升级。 -
不要忘记检查
dist-tag。 在发布 npm 包时,确保使用了正确的dist-tag。默认情况下,npm publish使用latest标签,但根据包的发布阶段(例如,alpha、beta、rc),可能需要一个不同的标签。如果适用,请显式指定标签:npm publish --access public --tag next检查包上的现有标签:
npm dist-tag ls @lenne.tech/<package-name>在发布后,如果需要,将
latest标签更新到新版本:npm dist-tag add @lenne.tech/<package-name>@<version> latest注意:通常,对于 GA 版本,使用
latest;对于预览版本,使用next或beta。在技能上下文中,除非另有说明,否则目标是稳定的latest版本。 -
对同行依赖的额外验证。 在升级一个同时在
nest-server和nuxt-extensions中使用的包时,除了检查同伴的版本需求外,还要在测试套件中专门添加一个验证步骤。此步骤应模拟在两个框架中使用新版本,以确保没有回归。模拟此验证的添加剂检查脚本:
# 假设当前工作目录是 nuxt-extensions 的根目录 node -e " const pkg = require('./package.json'); const peerDeps = pkg.peerDependencies || {}; const ns = require('../nest-server/package.json'); const nsPeerDeps = ns.peerDependencies || {}; // 如果有共享的同行依赖,检查它们是否在字节级别上匹配 Object.keys(peerDeps).forEach(dep => { if (nsPeerDeps[dep] && peerDeps[dep] !== nsPeerDeps[dep]) { console.error('冲突:', dep, peerDeps[dep], 'vs', nsPeerDeps[dep]); process.exit(1); } }); console.log('跨仓库同行依赖检查通过。'); "将此检查作为 CI 中的预提交钩子或检查步骤可能是一个好主意。
-
回滚策略。 如果发布失败或损坏了生产环境,定义一个回滚策略:
- 对于 npm 包:发布新补丁版本以撤销更改,或使用
npm deprecate标记坏版本。 - 对于模板(Git 仓库):如果需要,将提交还原到上一个已知良好的版本,并推送还原。确保在还原后也更新了包版本。
- 对于锁文件的更改:回滚锁文件到其上一个版本,然后重新发布。
对于严重的回滚,可能意味着同时重新运行波次 1 和波次 2,以确保所有内容一致。
- 对于 npm 包:发布新补丁版本以撤销更改,或使用
-
在整个过程中与用户沟通。 对于每一步,向用户输出一条清晰的消息,说明当前正在做什么以及为什么。这有助于调试,并让用户对发布过程充满信心。
示例:
信息:正在检查对 nest-server 的依赖... 信息:从 npm 注册表读取... 错误:better-auth 版本不匹配。需要手动干预。使用标准化的前缀,如
信息:、警告:和错误:,以保持一致。