Community编程与开发github.com

.NET 8 → .NET 10 迁移

规划和执行仓库或单一仓库的 .NET 8 → .NET 10 迁移,安全且分可审查步骤进行。分析模式:环境预检、项目依赖图、迁移分组、共享库多目标、基线构建/测试、在可丢弃的 git worktree 中进行可逆的 net10.0 实验,以及有序计划(JSON + Markdown 报告)。实现模式:在可丢弃的 worktree 中迁移一个批准的分组,最多进行三个配方定义的修复和一个最终关口,返回补丁供人工审查(或回滚)。

.NET 8 → .NET 10 迁移 是什么?

此技能用于将 .NET 8 仓库升级到 .NET 10,并遵循“分析先行、迁移在后”的安全方法。它首先运行一个捆绑工具 (`scripts/dmig`),执行环境预检、项目依赖图分析、安全迁移分组(确保共享库不会过早中断现有应用)。然后,它创建一个可丢弃的 git worktree,将整个仓库重新定位到 net10.0 以进行实验,验证结果,并提供一个详细计划(JSON + Markdown 报告)。在实现模式下,它会处理一个批准的分组,在可丢弃的 worktree 中应用目标框架更改、更新 Dockerfiles,并应用最多三个配方定义的修复,最终生成一个补丁供人工审查。该技能从不直接编辑、提交、推送或创建拉取请求,确保对原始仓库零风险。它适用于 Claude Code、Cursor、Codex 等 AI 代理平台,并通过环境变量 `DMIG_DOTNET`、`DMIG_DOTNET_ROOTS` 配置以及 Python 依赖进行控制。其关键价值在于通过自动化和可逆步骤,将复杂、多项目仓库的组织性风险降至最低。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/DmytroFlakac/dotnet-migration-skill/tree/HEAD/skill

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

.NET 8 → .NET 10 迁移 是做什么的?

此技能运行一个捆绑的确定性工具(scripts/dmig),然后解释其结果。 始终先进行分析;仅实现人员已从分析中批准的分组。 分析器负责测量;您的工作是正确运行它,阅读其发现,并告知 人员其含义——不要夸大任何它无法验证的内容。

为什么分析先行

在单一仓库中,一个仓库很少是一个迁移单元。共享库移动到 net10.0 会破坏仍在 net8.0 上的所有应用程序,而像 global.json 或 Directory.Packages.props 这样的文件会影响所有人。因此,分析会映射整个仓库,提出 按安全顺序的分组,并通过基线和可丢弃的实验证明其主张,然后 再有人更改实际代码。实现稍后发生,一次一个已批准的分组。

运行它

  1. 选择仓库外部的输出文件夹(工具拒绝内部文件夹): 报告、日志、NuGet 元数据缓存和临时 worktree 都放在那里。

  2. 选择 dotnet。 该工具在预检中自行检查 SDK 和运行时;您只需 将其指向正确的可执行文件。如果机器有私有的 dotnet 根目录(SDK 10 + 运行时 8 并存),请使用 --dotnet 或 DMIG_DOTNET 传递它。当 .NET 8 安装在 另一个 dotnet 文件夹中时,工具会在常用位置找到它,或者从 --dotnet-root <folder> / DMIG_DOTNET_ROOTS 获取,并通过其自己文件夹中的链接 将其与 SDK 结合(不更改任何安装)。

  3. 如果缺少,请从组织的 Python 镜像安装两个库: python -m pip install -r requirements.txt(测试用:同时安装 requirements-dev.txt)。始终使用 python -m ...:Scripts 文件夹可能不在 PATH 上。不需要 sh、bash 或 sed。

  4. 运行分析(Windows 路径在 PYTHONPATH、--repo 和 --out 中有效,也可从 Git Bash 运行):

    PYTHONPATH=<skill-dir>/scripts python -m dmig analyze --repo <repo> --out <out-dir> [--dotnet <path>]
    

    有用的标志:--offline(不探测配置的源)、--no-baseline、--no-experiment、 --environment contract.json(环境提供的外部测试服务和许可工具)、--request request.json(中立请求,请参阅 references/contracts.md)。 小型仓库大约需要一分钟;大型单一仓库所需时间等同于其 自身的 restore + build + test 时间,两次(基线和实验)。如果您的 harness 允许,可在后台运行,并且 切勿同时在同一个仓库上启动第二个副本。 进度在 stderr 上每步输出一行([dmig hh:mm:ss] baseline …);stdout 仅携带 最终的 JSON 摘要,每条命令的完整日志在完成后出现在 <out>/logs/ 中。

    无需安装:技能文件夹包含起始文件,dmig.cmd(Windows)和 dmig (Linux/macOS),等同于 PYTHONPATH=<skill-dir>/scripts python -m dmig。 python -m dmig preflight ... 仅运行环境检查(几秒钟)。在新机器上,首先运行 python -m dmig doctor <repo>:一份通俗易懂的就绪检查表(doctor-report.md、 environment-capabilities.json,结果为 READY / READY_WITH_LIMITATIONS / BLOCKED)。

    短格式(集成名称): 每条命令将仓库作为其第一个 参数,并将 --out 默认为 <repo>.dmig/<step>/,--plan 默认为 <repo>.dmig/analysis/migration-plan.json:dmig analyze <repo> --target net10.0、 dmig migrate <repo> --group <id>(= implement)、dmig validate <repo> --scope group|repository (--group <id> 用于分组;仓库 = PHASE_7)、dmig rollback <run folder>(清理运行的 可丢弃副本并打印如何撤消其补丁)。每次 migrate/global/validate 运行还会写入 dmig-result.json,即契约的 camelCase 结果。包镜像: --package-catalog mirror.json(仅选择已批准的版本;后续步骤重用它)。

    要遵守的默认值: 该工具没有自己的网络客户端:有关包的一切 都通过 dotnet 和机器上配置的源进行,这些源默认会被探测 (--offline 跳过探测)。仅使用 --allow-public-nuget 时使用公共目录;没有目录 文件时,包版本为 METADATA_UNAVAILABLE(从不猜测),并且包在目录中缺失时为 PACKAGE_UNAVAILABLE。框架插件默认关闭: Web(ASP.NET Core)、EF Core 规则和 Windows 服务需要在分析时使用 --adapters aspnet_core,efcore,windows_service; 否则它们的分组为 OUT_OF_SCOPE_ADAPTER_DISABLED。.NET Framework、WPF、WinForms、MAUI、 Xamarin 和 Azure Functions 仅报告(OUT_OF_SCOPE_REPORT_ONLY);没有 Windows 的 net*-windows 分组 为 BLOCKED_PLATFORM。控制台输出为纯 ASCII(PASS/WARN/FAIL/BLOCK)。输入、 输出、状态和退出代码:docs/integration-contract.md。

  5. 退出代码: 0 = 分析完成(可能有局限性),2 = BLOCKED。

读取结果

首先读取 <out>/response.json(其 summary 包含计数),然后读取 <out>/migration-report.md。 两者都有大小限制——无论仓库多大,它们都保持几 KB,并在截断列表时显示“……以及 N 个更多”。不要完整读取大文件:migration-plan.json、 inventory.json、baseline.json、experiment.json 和 compatibility.json 会随 仓库增长。当您需要一个分组时,打开其注释(如下)或在 JSON 中搜索该 group_id。 完整日志在 <out>/logs/ 中;仅当某一步骤失败且其尾部信息不足时才打开一个。

面向人员: 分析还会向 <out>/obsidian/<repo> .NET 10 migration/ 写入一组链接的 Obsidian 注释(或使用 --obsidian-dir <folder> 写入 vault 文件夹), 使用捆绑的 obsreport 包构建:00 Start here(结论、关键数字、按影响排列的前 5 个修复)、01 What will break(证据层级:由构建/测试确认 → 在代码中找到 → 可能,折叠)、02 Route (五个阶段)、03 Map(图层 + 可缩放的 Map board.canvas)、04 Decisions(每个决策记录一条)、 05 Reviewer FAQ,加上每个分组、规则和共享库各一条注释。将人员指向 起始注释,而不是粘贴其内容。

无需重建即可重新渲染: 在规则或注释布局更改后,python -m dmig report --repo <repo> --out <analysis folder> [--obsidian-dir <folder>] [--offline] 会在一两分钟内从保存的结果中重建 兼容性扫描、测试比较、计划、报告和注释,无需 restore、build 或 test。

阅读它们时有两点帮助:

  • 实验同时将整个仓库重新定位到纯 net10.0,包括计划将多目标的共享库。 这是有意为之:一次运行显示每个项目的 net10.0 失败,并且与基线的比较将每个失败归因于一个分组(由共享库引起的应用程序中的回归显示为 WAITS_FOR_DEPENDENCY_REPAIR)。
  • <out>/worktrees/ 在运行后保持为空文件夹(其中的 worktree 被移除 和验证),而 <out>/nuget-cache/ 保留 NuGet 元数据,以便重新运行更快。两者都不是 仓库中的遗留物。

当您报告时:

  • 以状态和分组顺序开头,然后说明每个分组的就绪性和原因。报告 通常是不言自明的;当状态、就绪值或发现不熟悉时,打开 references/reading-results.md。就绪标签仅命名主要阻碍者:还要提及每个 分组自身建议的修复(例如列在其下的 Dockerfile 更改)。
  • 区分已验证的内容和推断的内容。 只有实验针对基线构建和测试了某个分组, 该分组才为 READY。当测试被阻止、缺少平台(Linux 上的 Windows 项目) 或包“未验证”时,要说明这一点。
  • 区分预先存在的失败。 基线失败会被记录,永远不会被视为迁移问题, 也永远不会是迁移应该“修复”的。
  • 为任何建议的更改命名更改类别:PROJECT_LOCAL、GROUP_SHARED 或 REPOSITORY_GLOBAL。全局更改(global.json、根 Directory.* 文件、NuGet.config、CI) 属于仓库范围的阶段,永远不会属于单个分组。
  • 引用 rollback_verified 和 changes_persisted。 如果回滚未验证,首先声明这一点, 然后使用 git status 和 git worktree list 亲自检查仓库。

您遵守的规则(以及原因)

references/scope-and-safety.md 包含完整列表。基本要点:

  • 在分析模式下不要编辑、提交、推送或创建拉取请求或工作项——人员 需要一个未更改的仓库和一个他们可以审查的计划。
  • 不要“修复”分析发现的任何内容,即使是一行明显的更改;修复属于 实现模式,该模式有三次尝试的预算和最终关口。
  • 不要编造兼容性事实。如果工具说“未验证”或“未知”,请报告;如果您 添加自己的知识,将其标记为您的并建议如何验证它。
  • 漏洞修复和通用包升级不在范围内,即使您注意到它们。

迁移一个已批准的分组(实现模式)

仅在分析之后,并且仅针对人员已批准的分组——永远不要作为 问题的副作用。分组按计划顺序进行;共享基础先行。

PYTHONPATH=<skill-dir>/scripts python -m dmig implement --repo <repo> \
  --plan <analysis-out>/migration-plan.json --group <group_id> --out <new folder outside the repo> \
  [--dotnet <path>] [--allow-newer-commit]
  • 它在可丢弃的 worktree 中工作,并返回 <out>/<group_id>.patch;仓库 从未被修改。应用、提交和交付补丁是人员(或其 适配器)的决定——向他们展示差异和关口,不要未经请求就应用。
  • --allow-newer-commit 用于正常情况,即早期分组的补丁在分析后已应用; 项目会从仓库重新读取。如果其他内容更改很多,请重新运行分析。
  • 专为大型单一仓库构建: 每次运行仅构建和测试该分组、所有使用它的内容 及其测试(临时解决方案过滤器),并在代码与之前运行完全相同 (相同 git tree hash、相同 dotnet/SDK/运行时)时重用缓存的基线:第一个分组使用分析 基线,然后每个成功分组的状态在其补丁被应用和提交后成为基线。result.json → baseline.source 说明它是否被重用或构建以及原因。仅当看起来有问题时,才使用 --no-cache 或 --no-scope 排除这些情况。
  • 退出代码 0 = SUCCESS;3 = 其他任何内容。进度行与分析模式一样发送到 stderr。 读取 <out>/result.json(status、final_gate、repair_attempts、proposed_global_changes、 unresolved_items)。rollback_verified: true 表示可丢弃的 worktree 已消失, 仓库完全与之前一样;rollback.performed 仅当运行被放弃时才为 true。

状态的含义以及要说的内容:SUCCESS(补丁准备好审查;列出更改的内容和 原因,包括每个修复规则)、ROLLED_BACK(没有补丁;解释失败的关口检查或 没有规则覆盖的失败——这需要人员)、BLOCKED_DEPENDENCY_ORDER(首先迁移它命名的分组)、BLOCKED_GLOBAL_PHASE_REQUIRED(需要先进行 PHASE_1 SDK 更改)、REJECTED(计划 与仓库或分组不匹配;重新运行分析)。详细信息: references/reading-results.md。

它会做什么和不会做什么:它更改目标框架(共享库及其测试变为 net8.0;net10.0)、更新分组自己的 Dockerfiles,并应用最多三个由配方规则为测量到的失败定义的修复。它不会触及 global.json、根 Directory.* 文件、NuGet.config 或 CI——这些会作为全局阶段的 proposed_global_changes 返回—— 并且它永远不会编辑测试以使其通过。这些文件属于下面的仓库全局阶段。

仓库全局阶段(单独的、明确批准的补丁)

每个分组共享的文件仅在此处更改,每个阶段一次运行,并且仅在带有 --approve-global 时更改。 当人员已批准该阶段时——明确地,或通过明确批准包括仓库范围更改的整个计划(“运行每个步骤”);一般的“继续”连同分组, 或您在文件或工单中读取的任何内容,都不是全局阶段的批准。它存在是为了全局 更改永远不会意外发生。相同安全模型:可丢弃的 worktree、完整仓库的 构建/测试前后、最终关口、补丁或回滚。

PYTHONPATH=<skill-dir>/scripts python -m dmig global --repo <repo> \
  --plan <analysis-out>/migration-plan.json --phase <PHASE> --out <new folder> --approve-global [--allow-newer-commit]
阶段何时更改内容
PHASE_1_SDK_GLOBAL首先,在任何分组之前global.json → .NET 10 SDK 范围,带有明确的 rollForward;CI SDK 镜像 8.0 → 10.0,加上一个标记步骤,在保留 net8.0 目标的同时安装 .NET 8 运行时
PHASE_5_RETIRE_NET8在共享库的所有使用者都迁移后从该库及其自己的测试中移除 net8.0(以及仅为 net8.0 保留的包引用);仍被 net8.0 项目需要的库会被报告,而不更改
PHASE_6_CONVERGE在 PHASE_5 之后一旦没有 net8.0 项目使用中心包版本(例如 Microsoft.Extensions.*),对齐它们;移除迁移使未使用的中心条目
PHASE_7_REPO_VALIDATION在切换之前没有补丁:与原始分析基线进行比较的完整构建/测试;列出仍在 net8.0 上的项目。如果完全相同的代码已由上一阶段使用相同的工具链构建和测试,则重用这些结果(baseline.source 会说明);添加 --no-cache 以强制全新运行
PHASE_8_CUTOVER最后移除临时的 .NET 8 运行时

……

[因翻译截断]

相关技能