Add XCode skills for entity caching, ORM, and sharding ETL
大石头 authored at 2026-04-02 18:30:07
22.27 KiB
NewLife.Skills
# XCode 协作指令 本指令面向新生命团队(NewLife)开源数据中间件 XCode,帮助 Copilot 在 .NET 项目中正确使用 XCode 进行数据建模和实体操作。 --- ## 1. XCode 定位与边界 ``` NewLife.Core(基础库)→ copilot-instructions.md ↓ NewLife.XCode(数据中间件)← 本指令 ↓ NewLife.Cube(Web 快速开发框架)→ cube.instructions.md ``` **本指令覆盖**:数据模型设计(Model.xml)、实体类生成与使用、数据库 CRUD 操作、运行时配置、项目初始化与 XCode 接入。 **不包含**(由 `cube.instructions.md` 负责):Web 控制器、视图与前端、权限管理、魔方区域深度定制。 ### 1.1 支持的数据库 | 常用 | 国产/信创 | 时序/嵌入 | |------|----------|----------| | SqlServer、MySql、SQLite、PostgreSQL、Oracle | 达梦(DaMeng)、人大金仓(KingBase)、瀚高(HighGo)、VastBase | TDengine、InfluxDB、NovaDb | > 另支持 DB2、Hana、IRIS、Access 等,完整列表见 `DatabaseType` 枚举。 --- ## 2. 使用场景与快速入口 ### 2.1 场景一:已有项目接入 XCode 1. 引入 NuGet 包:`dotnet add package NewLife.XCode` 2. 创建或编辑 `Model.xml` 进行数据建模 3. 执行 `xcode` 命令生成实体类 4. 在业务代码中使用实体类进行数据操作 ### 2.2 场景二:从零创建新项目 1. 安装模板:`dotnet new install NewLife.Templates` 2. 创建数据类库:`dotnet new xcode -n {系统名}.Data` 3. 创建应用项目: - Web 应用:`dotnet new cube -n {系统名}Web` - 控制台应用:`dotnet new nconsole -n {系统名}App` 4. 编辑 `Model.xml` 进行数据建模 5. 执行 `xcode` 生成实体类 ### 2.3 核心工作流 ``` 理解业务需求 → 数据建模(Model.xml) → 生成实体类(xcode) → 编写业务代码 ``` **关键点**:无论哪种场景,核心都是 **充分理解业务需求,在 Model.xml 中设计合理的数据表结构**。 --- ## 3. 环境准备 ### 3.1 前置要求 ```powershell # 检查 .NET SDK(要求 8.0+) dotnet --version # 安装全局工具 xcodetool dotnet tool install xcodetool -g ``` ### 3.2 模板版本检查 NewLife.Templates 模板会持续更新,使用前需检查版本: ```powershell # 查看模板详情 dotnet new details NewLife.Templates ``` **判定规则**:输出包含类似 `包版本: 1.1.2025.820-beta1836` - 其中 `1.1.2025.820` 表示 v1.1,发布日期为 2025-08-20 - **要求**:发布日期需 > 2025-08-01(即严格晚于 2025-08-01) ```powershell # 若未安装或版本过旧,执行安装/更新 dotnet new install NewLife.Templates ``` > **强约束**:仅当未安装或发布日期 ≤ 2025-08-01 时才执行安装;否则保持现状,避免无谓变更。 ### 3.3 模板说明 | 模板 | 命令 | 用途 | |------|------|------| | `xcode` | `dotnet new xcode -n Name.Data` | 数据类库项目 | | `cube` | `dotnet new cube -n NameWeb` | Web 管理后台 | | `nconsole` | `dotnet new nconsole -n NameApp` | 控制台应用 | --- ## 4. Model.xml 完整参考 ### 4.1 文件结构 ```xml <?xml version="1.0" encoding="utf-8"?> <EntityModel xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:schemaLocation="https://newlifex.com https://newlifex.com/Model202509.xsd" xmlns="https://newlifex.com/Model202509.xsd"> <Option> <!-- 全局配置 --> </Option> <Tables> <Table> <Columns> <Column /> </Columns> <Indexes> <Index /> </Indexes> </Table> </Tables> </EntityModel> ``` ### 4.2 Option 配置项 | 配置项 | 说明 | 示例 | |--------|------|------| | `Namespace` | 命名空间 | `Zero.Data` | | `ConnName` | 数据库连接名 | `Zero` | | `Output` | 实体类输出目录 | `.\` | | `BaseClass` | 实体基类 | `Entity` | | `ChineseFileName` | 使用中文文件名 | `True` | | `Nullable` | 生成可空引用类型 | `True` | | `HasIModel` | 实现 IModel 接口 | `True` | | `ModelClass` | 模型类模板 | `{name}Model` | | `ModelsOutput` | 模型类输出目录 | `.\Models\` | | `ModelInterface` | 模型接口模板 | `I{name}` | | `InterfacesOutput` | 接口输出目录 | `.\Interfaces\` | | `ClassNameTemplate` | 类名模板 | `{name}Model`/`I{name}Dto` | | `DisplayNameTemplate` | 显示名模板 | `{displayName}` | | `ModelNameForCopy` | Copy 函数参数类型 | `{name}`/`I{name}` | | `ModelNameForToModel` | ToModel 转换目标类型 | `{name}`/`{name}DTO` | | `ExtendNameSpace` | 额外引用命名空间(逗号分隔) | `System.Xml.Serialization` | | `NameFormat` | 命名格式 | `Default`/`Upper`/`Lower`/`Underline` | | `DisplayName` | 魔方区域显示名 | `订单管理` | | `CubeOutput` | 魔方控制器输出目录 | `../../Web/Areas/Order` | ### 4.3 Table 属性 | 属性 | 说明 | 示例 | |------|------|------| | `Name` | 实体类名 | `User` | | `TableName` | 数据库表名(可选,默认同 Name) | `sys_user` | | `Description` | 表说明 | `用户。用户账号信息` | | `ConnName` | 独立连接名(覆盖全局) | `Log` | | `BaseType` | 基类(支持实体继承) | `EntityBase` | | `InsertOnly` | 仅插入模式(日志表优化) | `True` | | `IsView` | 视图标识 | `True` | ### 4.4 Column 属性完整参考 #### 基础属性 | 属性 | 说明 | 示例 | |------|------|------| | `Name` | 属性名 | `UserName` | | `ColumnName` | 数据库列名(可选) | `user_name` | | `DataType` | 数据类型 | `Int32`/`Int64`/`String`/`DateTime`/`Boolean`/`Double`/`Decimal` | | `Description` | 字段说明 | `用户名。登录账号` | | `Length` | 字符串长度 | `50`/`200`/`-1`(大文本) | | `Precision` | 数值精度 | `18` | | `Scale` | 小数位数 | `2` | #### 主键与标识 | 属性 | 说明 | 示例 | |------|------|------| | `PrimaryKey` | 主键 | `True` | | `Identity` | 自增标识 | `True` | | `Master` | 主字段(业务主要字段) | `True` | **主键设计约定**: - 普通表:`Int32` 自增 → `PrimaryKey="True" Identity="True"` - 大数据表:`Int64` 雪花 ID → `PrimaryKey="True" DataScale="time"`(不设 Identity) #### 约束与默认值 | 属性 | 说明 | 示例 | |------|------|------| | `Nullable` | 允许空 | `False` | | `DefaultValue` | 默认值 | `0`/`''`/`getdate()` | #### 映射关系(Map) 格式:`表名@主键@显示字段@属性名` | 格式 | 说明 | 示例 | |------|------|------| | `Table@Id@Name` | 基本映射(三段) | `Role@Id@Name` | | `Table@Id@Name@RoleName` | 指定属性名(四段) | `Role@Id@Name@RoleName` | | `NS.Table@Id@Path@AreaPath` | 完整命名空间 | `XCode.Membership.Area@Id@Path@AreaPath` | #### 元素类型(ItemType) 用于魔方前端渲染,常用值:`image`、`file`、`mail`、`mobile`、`url`、`TimeSpan`、`GMK`、`html`、`code`、`json`。详细用法见 `cube.instructions.md`。 #### 显示选项(ShowIn) 控制字段在魔方各区域(List/Detail/AddForm/EditForm/Search)的显示。推荐具名列表语法: ``` ShowIn="List,Search" # 仅 List 和 Search 显示 ShowIn="-EditForm,-Detail" # 编辑表单和详情隐藏 ShowIn="All,-Detail" # 全部显示,详情隐藏 ``` 区域别名:`List(L)`、`Detail(D)`、`AddForm(Add/A)`、`EditForm(Edit/E)`、`Search(S)`、`Form(F)`(同时控制 Add 和 Edit) **语法二:管道分隔** ``` ShowIn="Y|Y|N||A" # List=显示|Detail=显示|Add=隐藏|Edit=自动|Search=自动 ``` **语法三:5字符掩码** ``` ShowIn="11110" # 1=显示, 0=隐藏, A/?/-=自动 ``` #### 分表字段(DataScale) | 值 | 说明 | |----|------| | `time` | 大数据单表的时间字段(用于雪花 ID) | | `timeShard:yyMMdd` | 分表字段,按日期格式分表 | #### 其他属性 | 属性 | 说明 | 示例 | |------|------|------| | `Type` | 枚举类型 | `XCode.Membership.SexKinds` | | `Category` | 表单分组 | `登录信息`/`扩展` | | `Attribute` | 额外特性 | `XmlIgnore, IgnoreDataMember` | | `Model` | 是否包含在模型类中 | `False` | | `RawType` | 原始数据库类型 | `varchar(50)` | ### 4.5 Index 属性 | 属性 | 说明 | 示例 | |------|------|------| | `Columns` | 索引列(逗号分隔) | `Name`/`Category,CreateTime` | | `Unique` | 唯一索引 | `True` | ### 4.6 完整示例 ```xml <?xml version="1.0" encoding="utf-8"?> <EntityModel xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:schemaLocation="https://newlifex.com https://newlifex.com/Model202509.xsd" xmlns="https://newlifex.com/Model202509.xsd"> <Option> <Namespace>Order.Data</Namespace> <ConnName>Order</ConnName> <Output>.\</Output> <ChineseFileName>True</ChineseFileName> <Nullable>True</Nullable> <HasIModel>True</HasIModel> <DisplayName>订单管理</DisplayName> <CubeOutput>../../OrderWeb/Areas/Order</CubeOutput> </Option> <Tables> <Table Name="Order" Description="订单。电商订单主表"> <Columns> <Column Name="Id" DataType="Int64" PrimaryKey="True" DataScale="time" Description="编号" /> <Column Name="OrderNo" DataType="String" Master="True" Length="50" Nullable="False" Description="订单号" /> <Column Name="UserId" DataType="Int32" Map="User@Id@Name" Description="用户" /> <Column Name="Status" DataType="Int32" Type="Order.Data.OrderStatus" Description="状态" /> <Column Name="TotalAmount" DataType="Decimal" Precision="18" Scale="2" Description="总金额" /> <Column Name="Remark" DataType="String" Length="500" Description="备注" Category="扩展" /> <Column Name="CreateUser" DataType="String" Description="创建者" Model="False" Category="扩展" /> <Column Name="CreateTime" DataType="DateTime" Nullable="False" Description="创建时间" Category="扩展" /> <Column Name="UpdateTime" DataType="DateTime" Description="更新时间" Model="False" Category="扩展" /> </Columns> <Indexes> <Index Columns="OrderNo" Unique="True" /> <Index Columns="UserId" /> <Index Columns="Status,CreateTime" /> </Indexes> </Table> </Tables> </EntityModel> ``` --- ## 5. 实体类操作指南 ### 5.1 基础 CRUD ```csharp // 新增 var entity = new User { Name = "test", Password = "123456" }; entity.Insert(); // 查询单个(按主键) var user = User.FindByKey(1); // 查询单个(按条件) var user2 = User.Find(User._.Name == "test"); // 查询列表 var list = User.FindAll(); var list2 = User.FindAll(User._.Status == 1, User._.Id.Desc(), null, 0, 10); // 更新 user.Name = "newName"; user.Update(); // 删除 user.Delete(); // 保存(自动判断 Insert/Update) entity.Save(); ``` ### 5.2 高级查询 ```csharp // 分页查询 var page = new PageParameter { PageIndex = 1, PageSize = 20 }; var list = User.FindAll(User._.Status == 1, page); // 条件组合 var where = new WhereExpression(); where &= User._.Status == 1; where &= User._.CreateTime >= DateTime.Today; if (!key.IsNullOrEmpty()) where &= User._.Name.Contains(key); var list2 = User.FindAll(where, page); // 统计 var count = User.FindCount(User._.Status == 1); // 查询最大/最小值 var maxId = User.FindMax(User._.Id, null); ``` ### 5.3 批量操作 ```csharp // 批量插入 var list = new List<User>(); for (var i = 0; i < 100; i++) { list.Add(new User { Name = $"user{i}" }); } list.Insert(); // 批量更新(将 Status==1 的记录改为 Status=2) User.Update(User._.Status == 2, User._.Status == 1); // 批量删除 User.Delete(User._.Status == 0); ``` ### 5.4 异步操作 ```csharp var user = await User.FindAsync(User._.Id == 1); var list = await User.FindAllAsync(User._.Status == 1, page); await entity.InsertAsync(); await entity.SaveAsync(); // 异步保存,用于日志等高频写入 ``` ### 5.5 缓存查询 ```csharp // 实体缓存(适用于小表) var list = User.FindAllWithCache(); // 单对象缓存(按主键) var user = User.FindByKeyWithCache(1); ``` ### 5.6 Biz 文件数据层逻辑规范 **核心理念**:所有需要人工编写的数据层逻辑代码,一律放在实体类的 Biz 文件(`*.Biz.cs`)中,包括**高级查询**(`#region 高级查询`)与**添删改查重载**等。外部调用方只传语义化参数,不感知 `WhereExpression` 拼接细节。 #### 高级查询封装选择 | 场景 | 方法形式 | 说明 | 示例 | |------|---------|------|------| | 返回单个对象,参数 ≤2 个 | `FindByXxx` | 未查到时返回 `null` | `FindByUserId(userId)` | | 返回列表,参数 ≤2 个,无模糊查询、无分页 | `FindAllByXxx` / `FindAllByXxxAndYyy` | 未查到时返回空列表,**不返回 null** | `FindAllByUserId(userId)` | | 参数较多,或含模糊查询,或含分页 | `Search(...)` | 未查到时返回空列表,**不返回 null** | `Search(userId, key, page)` | | 实体缓存内过滤(`Meta.Cache.FindAll(...)`) | `FindAllCachedXxx` / `FindCachedXxx` | — | `FindAllCachedEnabled()` / `FindCachedByQuestion(q)` | **命名约定说明**: - `FindByXxx`:返回**单个对象**(`TEntity?`),语义为"按条件查找一条记录",未找到返回 `null` - `FindAllByXxx`:返回**对象列表**(`IList<TEntity>`),语义为"按条件查找所有匹配记录",结果为空时返回空列表而非 `null` - `Search`:同样返回**对象列表**,结果为空时返回空列表而非 `null` #### Search 方法签名约定 参数顺序(由左到右,按重要程度): ``` Search(业务过滤字段..., DateTime start, DateTime end, String? key, PageParameter page) ``` - 时间区间 `(DateTime start, DateTime end)` 放在 key / page 左边 - 模糊查询关键词 `String? key` 放在 page 左边(倒数第二) - 分页参数 `PageParameter page` 始终最后 #### 表达式简写 在 Biz 文件的静态方法内部,可**省略类名前缀**: ```csharp // ✅ 推荐(Biz 文件内部) var exp = _.UserId == userId; if (!keyword.IsNullOrEmpty()) exp &= _.Title.Contains(keyword.Trim()); return FindAll(exp, page); // ❌ 避免(外部业务代码中拼接表达式) var exp = Conversation._.UserId == userId; if (!keyword.IsNullOrEmpty()) exp &= Conversation._.Title.Contains(keyword.Trim()); var list = Conversation.FindAll(exp, p); ``` #### 示例 ```csharp // Biz 文件内 #region 高级查询 /// <summary>根据用户编号查找最新一条会话</summary> /// <param name="userId">用户编号</param> /// <returns>会话对象,不存在时返回 null</returns> public static Conversation? FindByUserId(Int32 userId) => Find(_.UserId == userId); /// <summary>根据用户编号查找所有会话</summary> /// <param name="userId">用户编号</param> /// <returns>会话列表,不存在时返回空列表</returns> public static IList<Conversation> FindAllByUserId(Int32 userId) => FindAll(_.UserId == userId); /// <summary>分页搜索用户会话列表</summary> /// <param name="userId">用户编号</param> /// <param name="keyword">标题关键字,为空时不过滤</param> /// <param name="page">分页参数</param> /// <returns>会话列表,不存在时返回空列表</returns> public static IList<Conversation> Search(Int32 userId, String? keyword, PageParameter page) { var exp = new WhereExpression(); exp &= _.UserId == userId; if (!keyword.IsNullOrEmpty()) exp &= _.Title.Contains(keyword.Trim()); return FindAll(exp, page); } ``` --- ## 6. 运行时机制 ### 6.1 实体拦截器(自动填充字段) XCode 内置全局拦截器,实体包含特定命名字段时自动触发,**无需业务代码手动赋值**: | 拦截器 | 匹配字段名 | 字段类型 | 行为 | |--------|-----------|---------|------| | `TimeInterceptor` | `CreateTime` | `DateTime` | Insert 时自动填充当前时间 | | | `UpdateTime` | `DateTime` | Insert/Update 时自动填充当前时间 | | `UserInterceptor` | `CreateUserID` | `Int32`/`Int64` | Insert 时填充当前用户 ID | | | `CreateUser` | `String` | Insert 时填充当前用户名 | | | `UpdateUserID` | `Int32`/`Int64` | Insert/Update 时填充当前用户 ID | | | `UpdateUser` | `String` | Insert/Update 时填充当前用户名 | | `IPInterceptor` | `CreateIP` | `String` | Insert 时填充客户端 IP | | | `UpdateIP` | `String` | Insert/Update 时填充客户端 IP | | `TraceInterceptor` | `TraceId` | `String` | Insert/Update 时填充链路追踪 ID | **建模约定**:这些自动填充字段通常设置 `Category="扩展"` + `Model="False"`,不暴露到模型类中。 示例(Model.xml 中的扩展字段标准写法): ```xml <Column Name="CreateUser" DataType="String" Description="创建者" Model="False" Category="扩展" /> <Column Name="CreateUserID" DataType="Int32" Description="创建者" Model="False" Category="扩展" /> <Column Name="CreateTime" DataType="DateTime" Description="创建时间" Model="False" Category="扩展" /> <Column Name="CreateIP" DataType="String" Description="创建地址" Model="False" Category="扩展" /> <Column Name="UpdateUser" DataType="String" Description="更新者" Model="False" Category="扩展" /> <Column Name="UpdateUserID" DataType="Int32" Description="更新者" Model="False" Category="扩展" /> <Column Name="UpdateTime" DataType="DateTime" Description="更新时间" Model="False" Category="扩展" /> <Column Name="UpdateIP" DataType="String" Description="更新地址" Model="False" Category="扩展" /> <Column Name="TraceId" DataType="String" Description="链路追踪" Model="False" Category="扩展" /> ``` ### 6.2 反向工程(自动建表) XCode 启动时自动对比实体模型与数据库表结构,根据 `Migration` 配置决定行为: | 模式 | 说明 | 适用场景 | |------|------|---------| | `Off` | 关闭,不检查不执行 | 生产环境(表结构由 DBA 管理) | | `ReadOnly` | 只读,检查差异但不执行 DDL | 生产环境排查 | | `On` | 打开,仅新建表/列(默认值) | 开发/测试环境 | | `Full` | 完全,可修改列类型、删除列/索引 | 开发初期快速迭代 | ### 6.3 XCodeSetting 配置 通过配置文件 `XCode.json` 或 `appsettings.json` 的 `XCode` 节控制运行时行为: | 配置项 | 默认值 | 说明 | |--------|--------|------| | `Debug` | `true` | 是否启用调试日志 | | `ShowSQL` | `true` | 是否输出 SQL 语句 | | `SQLPath` | `""` | SQL 日志独立目录,生产环境建议设置 | | `TraceSQLTime` | `1000` | SQL 慢查询阈值(毫秒) | | `UseParameter` | `false` | 参数化添删改查 | | `CommandTimeout` | `0` | 命令超时(秒),0 不限制 | | `RetryOnFailure` | `0` | 失败重试次数 | | `Migration` | `On` | 反向工程模式(见 6.2) | | `BatchSize` | `5000` | 批量操作数据量 | | `EntityCacheExpire` | `10` | 实体缓存过期时间(秒) | | `SingleCacheExpire` | `10` | 单对象缓存过期时间(秒) | **生产环境建议**: ```json { "XCode": { "Migration": "Off", "ShowSQL": false, "SQLPath": "../SqlLog", "TraceSQLTime": 500 } } ``` --- ## 7. 多模块项目结构 对于复杂业务系统,建议按模块组织: ``` Zero.Data/ ├── Order/ # 订单模块 │ ├── Order.xml # 订单模型 │ ├── 订单.cs │ └── 订单明细.cs ├── Product/ # 商品模块 │ ├── Product.xml # 商品模型 │ ├── 商品.cs │ └── 分类.cs └── Member/ # 会员模块 ├── Member.xml # 会员模型 └── 会员.cs ``` 每个模块目录内有独立的 `*.xml` 模型文件,在各自目录执行 `xcode` 命令生成实体类。 --- ## 8. xcode 命令参考 ```powershell # 在模型文件所在目录执行(自动查找所有 *.xml) xcode # 指定模型文件 xcode Model.xml xcode Order.xml ``` **执行效果**: 1. 读取 XML 模型文件 2. 生成实体类(`*.cs`) 3. 生成模型类(如配置了 `ModelClass`) 4. 生成接口(如配置了 `ModelInterface`) 5. 生成数据字典(`*.htm`) 6. 生成魔方控制器(如配置了 `CubeOutput`) --- ## 9. 常见问题 ### 9.1 模型文件命名 - 默认:`Model.xml` - 推荐:`{系统英文名}.xml` 或 `{模块名}.xml` - 复杂项目:每个模块目录一个模型文件 ### 9.2 实体类生成位置 - 实体类生成在 `xcode` 命令执行目录 - 可通过 `Output` 配置项指定输出目录 - 魔方控制器通过 `CubeOutput` 指定 ### 9.3 数据库连接 在应用配置文件中配置连接字符串: ```json { "ConnectionStrings": { "Order": "Server=.;Database=Order;Uid=sa;Pwd=xxx" } } ``` 连接名对应 Model.xml 中的 `ConnName`。未配置连接字符串时,默认创建同名 SQLite 数据库。 ### 9.4 生成的代码能否手动修改 实体类分为两个文件: - `实体名.cs`(如 `订单.cs`):自动生成的数据映射代码,**每次 `xcode` 会覆盖,禁止手动修改** - `实体名.Biz.cs`(如 `订单.Biz.cs`):业务扩展代码,仅首次生成,**可自由修改** 需要调整字段、类型、索引时,应修改 `Model.xml` 后重新执行 `xcode`。 ### 9.5 如何调试 SQL 配置 `XCodeSetting.ShowSQL = true`(默认开启),所有 SQL 输出到日志。设置 `SQLPath` 可将 SQL 日志独立存放。`TraceSQLTime` 控制慢查询阈值。 ### 9.6 与 Cube 的协作 需要生成 Web 管理界面时:在 Model.xml 中配置 `CubeOutput` 指向 Web 项目的 Areas 目录,配置 `DisplayName` 作为区域名称,执行 `xcode` 自动生成控制器。深度定制见 `cube.instructions.md`。 --- ## 10. Copilot 行为指引 ### 10.1 数据建模时 1. **充分理解业务**:在设计表结构前,确保理解业务场景和数据关系 2. **合理设计主键**: - 普通表:`Int32` + `PrimaryKey="True" Identity="True"` - 大数据表:`Int64` + `PrimaryKey="True" DataScale="time"`(雪花 ID) 3. **必要的索引**:为查询条件字段添加索引 4. **字段长度**:String 类型必须指定合理的 `Length` 5. **扩展字段**:`CreateTime`/`UpdateTime`/`CreateUser`/`UpdateUser`/`CreateIP`/`UpdateIP` 等由拦截器自动填充(见 6.1),设置 `Model="False" Category="扩展"` ### 10.2 生成代码时 1. 确保在正确目录执行 `xcode` 2. 生成后检查编译是否通过 3. 如需修改生成的代码,应修改 Model.xml 后重新生成 4. 业务逻辑写在 `.Biz.cs` 文件中 ### 10.3 边界意识 - 数据模型和实体操作 → 本指令 - Web 控制器和界面 → `cube.instructions.md` - 基础编码规范 → `copilot-instructions.md`