mta-mcp 3.16.1 → 3.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -36,17 +36,17 @@
36
36
  5. 只有当 compact 无法解释局部布局/样式时,再补用 measure/style
37
37
  ```
38
38
 
39
- ### 图标资产强制规则(v4.8.0 更新)
39
+ ### 图标资产强制规则(v4.9.0 更新)
40
40
 
41
- - 默认流程改为:**先在 `assets/icons/` 查找用户手工导出的图标 PNG**,文件名优先匹配设计稿图层名对应的 `*_icon@4x.png`,找到即直接复用。
41
+ - 默认流程改为:**先在项目现有素材目录查找用户手工导出的图标 PNG**,文件名优先匹配设计稿图层名主体;`xxx@4x.png` 里的 `@4x` 只是倍率后缀,不是主体语义,找到即直接复用。
42
42
  - 先看测量结果里的图层类型:`icon/svg` 才允许落地为 SVG;如果 Sketch 返回的是 `Image`,禁止伪造 SVG。
43
- - `Image` 类型的 icon/button/arrow/help/customer-service 图层,优先做 **精确文件名查找**,不要先做语义联想或 canonical 合并。
43
+ - `Image` 类型的 icon/button/arrow/help/customer-service 图层,优先按 **文件名主体查找**,不要先做语义联想或 canonical 合并。
44
44
  - `Help Button`、`Customer Service Button`、`Customer Service Icon` 不能只因为位置相近就合并成同一个语义;先按 Sketch 图层名区分资产,再决定是否复用。
45
45
  - 如果 `restorationContract.blockOrder` 中的顶部入口测得是 `Image`,回复里必须明确写出:测得的真实块名、最终采用的 assetPath、以及它来自 `bitmapLookupCandidates` 还是 canonical 副本;未确认前禁止继续猜图标。
46
- - 只有当 `assets/icons/` 中不存在对应的手工导出资源时,才允许提示用户补导;不要再优先产出 page-local PNG 副本。
47
- - 手工 `*_icon@4x.png` 约定下,默认不再强制要求补齐 `2.0x/3.0x`;只有旧位图流程才继续要求多倍率变体。
46
+ - 只有当项目素材目录中不存在对应的手工导出资源时,才允许提示用户补导;不要再优先产出 page-local PNG 副本。
47
+ - 手工高倍率文件(如 `xxx@4x.png`)约定下,默认不再强制要求补齐 `2.0x/3.0x`;只有旧位图流程才继续要求多倍率变体。
48
48
  - 已存在但内容损坏的 SVG 视为无效资产,必须修复或替换;禁止为了绕过损坏文件再平行创建第二个语义相同的新图标。
49
- - 设计源是 `Image` 时,回复里必须明确说明“来源优先是 assets/icons 下的手工导出 PNG,不是设计稿原生 SVG”,避免误报为高清 SVG。
49
+ - 设计源是 `Image` 时,回复里必须明确说明“来源优先是项目素材目录中的手工导出 PNG,不是设计稿原生 SVG”,避免误报为高清 SVG。
50
50
  - 若 `bitmapLookupCandidates` 与代码实际资产名都未命中,必须停止并说明“设计资产未确认”,而不是回退到 help/customer-service 等语义相近资源。
51
51
 
52
52
  **注意:**
@@ -54,7 +54,7 @@
54
54
  - 不要手动拼接 _SKETCH_CMD(由 sketch_measure skill 自动处理)
55
55
  - 测量结果中所有颜色均包含 `flutterColor: "Color(0xAARRGGBB)"` 格式,可直接使用
56
56
  - 设计稿还原时,restorationContract 优先于现有业务组件、路由标题、i18n 文案和常见 CTA 习惯
57
- - 图标资产落地前,必须先做一次 `assets/icons/` 精确搜索;先看设计稿图层名对应的 `*_icon@4x.png`,再考虑其他复用路径
57
+ - 图标资产落地前,必须先做一次项目素材目录搜索;先看设计稿图层名主体对应的现有文件,再考虑其他复用路径
58
58
 
59
59
  ### 测量数据关键字段(v4.6.0 新增带★标记)
60
60
 
@@ -472,7 +472,7 @@ get_standard_by_id({ id: 'sketch-pitfalls' })
472
472
  ```
473
473
  1. 测量插件标记 `isIcon: true` 并输出 iconContentBounds
474
474
  2. 读取 containerSize(Group 尺寸)和 contentSize(实际路径尺寸)
475
- 3. 将 SVG 文件放入 assets/icons/ 目录
475
+ 3. 将 SVG 文件放入项目约定的素材目录
476
476
  4. 在 pubspec.yaml 中声明 assets
477
477
  5. 使用 flutter_svg 包加载,宽高用 contentSize
478
478
  6. 外层用 SizedBox(containerSize) + Center 包裹
package/dist/index.js CHANGED
@@ -6295,7 +6295,7 @@ var OUTPUT_GUIDE = {
6295
6295
  "- **\u5173\u952E\u89C4\u5219**: \u5F53 colorStrategy=preserve\uFF0C\u6216 SVG \u672C\u8EAB\u5E26 fill/fill-opacity/\u6E10\u53D8\u65F6\uFF0C**\u7981\u6B62**\u518D\u5957 ColorFilter\uFF0C\u5426\u5219\u4F1A\u51FA\u73B0\u989C\u8272\u53D1\u7070\u3001\u900F\u660E\u5EA6\u88AB\u4E8C\u6B21\u5904\u7406\u7684\u95EE\u9898",
6296
6296
  "- **flutterSvgCode**: \u53EF\u76F4\u63A5\u7C98\u8D34\u7684 SvgPicture.string(...) \u4EE3\u7801\uFF1B\u53EA\u6709\u5355\u8272\u56FE\u6807\u624D\u4F1A\u81EA\u52A8\u9644\u5E26 ColorFilter",
6297
6297
  "- **iconContentBounds**: \u56FE\u6807\u5185\u5BB9\u5B9E\u9645\u8FB9\u754C\uFF08vs \u5BB9\u5668\u5C3A\u5BF8\uFF09\uFF0C\u7528\u4E8E SizedBox \u7EA6\u675F",
6298
- "- **iconExportPath/iconSvgFileName**: \u82E5\u9700\u8981\u843D\u76D8\u8D44\u6E90\uFF0C\u4F7F\u7528\u63D2\u4EF6\u5EFA\u8BAE\u6587\u4EF6\u540D\u4FDD\u5B58\u5230 assets/icons/",
6298
+ "- **iconExportPath/iconSvgFileName**: \u82E5\u9700\u8981\u843D\u76D8\u8D44\u6E90\uFF0C\u4F7F\u7528\u63D2\u4EF6\u5EFA\u8BAE\u6587\u4EF6\u540D\u4FDD\u5B58\u5230\u9879\u76EE\u73B0\u6709\u7D20\u6750\u76EE\u5F55\uFF0C\u76EE\u5F55\u540D\u9075\u5FAA\u9879\u76EE\u7EA6\u5B9A\u800C\u4E0D\u662F\u56FA\u5B9A assets/icons/",
6299
6299
  "- **\u9875\u9762\u5C40\u90E8\u4FEE\u590D\u89C4\u5219**: \u82E5\u95EE\u9898\u53EA\u51FA\u5728\u67D0\u4E2A chevron/flag/badge/icon\uFF0C\u4F18\u5148\u91CD\u65B0\u9009\u4E2D\u8BE5\u56FE\u5C42\u6267\u884C cmd=svg \u6216\u5C40\u90E8\u5BFC\u51FA\uFF1B\u4E0D\u8981\u76F4\u63A5\u590D\u7528\u5176\u5B83\u9875\u9762\u7684\u540C\u540D\u5168\u5C40\u8D44\u4EA7",
6300
6300
  "- **\u8D44\u6E90\u590D\u7528\u6821\u9A8C**: \u65E7\u8D44\u4EA7\u5728\u590D\u7528\u524D\uFF0C\u5FC5\u987B\u6838\u5BF9 viewBox\u3001containerSize/contentSize\u3001fill-opacity \u548C\u5F53\u524D\u9875\u9762\u56FE\u5C42\u6765\u6E90\u662F\u5426\u4E00\u81F4",
6301
6301
  "- \u8986\u76D6\u4FEE\u6B63: \u56DB\u8FB9\u8DDD\u22642pt \u6216\u5BBD\u9AD8\u2265\u7236\u5BB9\u566888% \u7684 ShapePath \u662F\u80CC\u666F\u5C42\uFF0C\u5DF2\u81EA\u52A8\u6392\u9664",
@@ -6423,7 +6423,7 @@ var OUTPUT_GUIDE = {
6423
6423
  "- \u56FE\u6807\u662F\u5426\u8BEF\u7528\u65E7\u9875\u9762\u8D44\u4EA7\uFF1A\u540C\u540D\u7BAD\u5934/\u56FE\u6807\u9ED8\u8BA4\u4E0D\u53EF\u4FE1\uFF0C\u5148\u6838\u5BF9\u6765\u6E90\u56FE\u5C42",
6424
6424
  "- SVG \u662F\u5426\u8BEF\u52A0 ColorFilter\uFF1A\u4FDD\u7559\u539F\u8272\u7684 SVG \u4E0D\u5F97\u4E8C\u6B21\u67D3\u8272",
6425
6425
  "- \u56FE\u6807\u662F\u5426\u7528\u9519\u5C3A\u5BF8\uFF1A16x16 \u69FD\u4F4D\u4E0D\u7B49\u4E8E 16x16 \u56FE\u5F62\uFF0C\u6309 xs/cs \u5206\u79BB\u5360\u4F4D\u4E0E\u6E32\u67D3",
6426
- "- Bitmap \u662F\u5426\u8BEF\u5F53 SVG\uFF1ASketch Image \u56FE\u5C42\u5FC5\u987B\u4F18\u5148\u5728 assets/icons/ \u4E2D\u67E5\u627E\u8BBE\u8BA1\u539F\u540D\u6216 bitmapLookupCandidates \u6307\u5411\u7684\u4F4D\u56FE\uFF0C\u547D\u4E2D\u540E\u76F4\u63A5\u590D\u7528\uFF0C\u4E0D\u518D\u4F2A\u9020 SVG",
6426
+ "- Bitmap \u662F\u5426\u8BEF\u5F53 SVG\uFF1ASketch Image \u56FE\u5C42\u5FC5\u987B\u4F18\u5148\u5728\u9879\u76EE\u7D20\u6750\u76EE\u5F55\u4E2D\u6309\u8BBE\u8BA1\u539F\u540D\u6216 bitmapLookupCandidates \u7684\u6587\u4EF6\u540D\u4E3B\u4F53\u67E5\u627E\u4F4D\u56FE\uFF0C\u547D\u4E2D\u540E\u76F4\u63A5\u590D\u7528\uFF0C\u4E0D\u518D\u4F2A\u9020 SVG",
6427
6427
  "- \u9876\u90E8 Help/Customer Service/Support Entry \u82E5\u5728 blockOrder \u4E2D\u662F Image\uFF0C\u5FC5\u987B\u5148\u6309\u771F\u5B9E\u5757\u540D\u548C bitmapLookupCandidates \u590D\u7528\uFF0C\u4E0D\u5F97\u7528\u8BED\u4E49\u76F8\u8FD1\u6309\u94AE\u66FF\u4EE3",
6428
6428
  "- \u5168\u5C4F app \u9875\u9762\u5728\u5BBD\u5C4F\u5E94\u8DDF\u968F\u5BBF\u4E3B Scaffold/SafeArea/AppBar \u548C\u9875\u9762 padding\uFF0C\u7981\u6B62\u628A\u6574\u9875\u9501\u6210 390 \u753B\u677F\u58F3\u5B50",
6429
6429
  "- Sketch \u9876\u90E8\u65F6\u95F4/\u4FE1\u53F7/WiFi/\u7535\u6C60\u5C5E\u4E8E\u8BBE\u5907\u7CFB\u7EDF chrome\uFF0C\u4E0D\u5C5E\u4E8E\u9875\u9762\u4E1A\u52A1 UI\uFF0C\u7981\u6B62\u76F4\u63A5\u8FD8\u539F\u5230 Flutter \u9875\u9762",
@@ -6528,14 +6528,14 @@ async function sketchMeasure(args) {
6528
6528
  scriptPath: tempScriptPath,
6529
6529
  scriptLength: fullScript.length,
6530
6530
  pluginInstall: installStatus,
6531
- instruction: "\u5C06 loaderScript \u4F20\u5165 mcp_sketch_run_code({ code: loaderScript }) \u6267\u884C\u3002\u8BBE\u8BA1\u7A3F\u8FD8\u539F\u9996\u8F6E\u4F18\u5148\u4F7F\u7528 cmd=compact\uFF1B\u53EA\u6709\u5F53 compact contract \u4E0D\u8DB3\u4EE5\u89E3\u91CA\u5C40\u90E8\u5E03\u5C40\u6216\u6837\u5F0F\u65F6\uFF0C\u518D\u8865\u7528 measure/style\u3002\u82E5\u504F\u5DEE\u96C6\u4E2D\u5728\u5355\u4E2A icon/svg/bitmap\uFF0C\u8BF7\u5148\u5728 assets/icons/ \u4E2D\u6309\u8BBE\u8BA1\u7A3F\u56FE\u5C42\u540D\u3001bitmapLookupCandidates \u548C bitmapReuseMode \u67E5\u627E\u771F\u5B9E\u4F4D\u56FE\u6216\u89C4\u8303\u5316\u526F\u672C\uFF0C\u907F\u514D\u7EE7\u7EED\u63A8\u65AD\u65E7\u9875\u9762\u8D44\u4EA7\u6216\u4F2A\u9020 SVG\u3002\u9876\u90E8 Help/Customer Service/Support Entry \u82E5 blockOrder \u6807\u8BB0\u4E3A Image\uFF0C\u5FC5\u987B\u5148\u590D\u7528\u5BF9\u5E94\u4F4D\u56FE\uFF0C\u672A\u547D\u4E2D\u65F6\u5E94\u505C\u6B62\u731C\u6D4B\u3002\u5168\u5C4F app \u9875\u9762\u5728\u5BBD\u5C4F\u5E94\u8DDF\u968F\u5BBF\u4E3B Scaffold/SafeArea/AppBar \u548C\u9875\u9762 padding \u9002\u914D\uFF1BSketch \u9876\u90E8\u65F6\u95F4/\u4FE1\u53F7/WiFi/\u7535\u6C60\u5C5E\u4E8E\u8BBE\u5907\u7CFB\u7EDF chrome\uFF0C\u7981\u6B62\u76F4\u63A5\u8FD8\u539F\u5230 Flutter \u9875\u9762\u3002compact \u82E5\u68C0\u6D4B\u5230\u4F60\u9009\u4E2D\u7684\u662F\u5185\u5C42 glyph\uFF0C\u4F1A\u81EA\u52A8\u63D0\u5347\u5230\u6240\u5C5E\u753B\u677F\u5E76\u901A\u8FC7 selectionContext \u56DE\u62A5\u3002\u8BF7\u5148\u5728 Sketch \u4E2D\u9009\u4E2D\u76EE\u6807\u753B\u677F/\u56FE\u5C42\u3002",
6531
+ instruction: "\u5C06 loaderScript \u4F20\u5165 mcp_sketch_run_code({ code: loaderScript }) \u6267\u884C\u3002\u8BBE\u8BA1\u7A3F\u8FD8\u539F\u9996\u8F6E\u4F18\u5148\u4F7F\u7528 cmd=compact\uFF1B\u53EA\u6709\u5F53 compact contract \u4E0D\u8DB3\u4EE5\u89E3\u91CA\u5C40\u90E8\u5E03\u5C40\u6216\u6837\u5F0F\u65F6\uFF0C\u518D\u8865\u7528 measure/style\u3002\u82E5\u504F\u5DEE\u96C6\u4E2D\u5728\u5355\u4E2A icon/svg/bitmap\uFF0C\u8BF7\u5148\u5728\u9879\u76EE\u73B0\u6709\u7D20\u6750\u76EE\u5F55\u4E2D\u6309\u8BBE\u8BA1\u7A3F\u56FE\u5C42\u540D\u4E3B\u4F53\u3001bitmapLookupCandidates \u548C bitmapReuseMode \u67E5\u627E\u771F\u5B9E\u4F4D\u56FE\u6216\u89C4\u8303\u5316\u526F\u672C\uFF0C\u907F\u514D\u7EE7\u7EED\u63A8\u65AD\u65E7\u9875\u9762\u8D44\u4EA7\u6216\u4F2A\u9020 SVG\u3002\u6587\u4EF6\u540D\u5339\u914D\u91CD\u70B9\u770B xxx \u4E3B\u4F53\uFF0C@4x \u53EA\u662F\u500D\u7387\u540E\u7F00\u3002\u9876\u90E8 Help/Customer Service/Support Entry \u82E5 blockOrder \u6807\u8BB0\u4E3A Image\uFF0C\u5FC5\u987B\u5148\u590D\u7528\u5BF9\u5E94\u4F4D\u56FE\uFF0C\u672A\u547D\u4E2D\u65F6\u5E94\u505C\u6B62\u731C\u6D4B\u3002\u5168\u5C4F app \u9875\u9762\u5728\u5BBD\u5C4F\u5E94\u8DDF\u968F\u5BBF\u4E3B Scaffold/SafeArea/AppBar \u548C\u9875\u9762 padding \u9002\u914D\uFF1BSketch \u9876\u90E8\u65F6\u95F4/\u4FE1\u53F7/WiFi/\u7535\u6C60\u5C5E\u4E8E\u8BBE\u5907\u7CFB\u7EDF chrome\uFF0C\u7981\u6B62\u76F4\u63A5\u8FD8\u539F\u5230 Flutter \u9875\u9762\u3002compact \u82E5\u68C0\u6D4B\u5230\u4F60\u9009\u4E2D\u7684\u662F\u5185\u5C42 glyph\uFF0C\u4F1A\u81EA\u52A8\u63D0\u5347\u5230\u6240\u5C5E\u753B\u677F\u5E76\u901A\u8FC7 selectionContext \u56DE\u62A5\u3002\u8BF7\u5148\u5728 Sketch \u4E2D\u9009\u4E2D\u76EE\u6807\u753B\u677F/\u56FE\u5C42\u3002",
6532
6532
  measureOutputPath,
6533
6533
  restoreChecklist: [
6534
6534
  "\u5148\u6309 restorationContract \u9501\u5B9A\u9875\u9762\u9AA8\u67B6\uFF0C\u518D\u5904\u7406\u5C40\u90E8\u6837\u5F0F",
6535
- "\u5355\u4E2A\u56FE\u6807\u504F\u5DEE\u5148\u67E5 assets/icons/ \u4E2D\u8BBE\u8BA1\u7A3F\u540C\u540D\u3001bitmapLookupCandidates \u5217\u51FA\u7684\u771F\u5B9E\u4F4D\u56FE\uFF0C\u518D\u51B3\u5B9A\u662F\u5426\u8865\u5BFC",
6535
+ "\u5355\u4E2A\u56FE\u6807\u504F\u5DEE\u5148\u67E5\u9879\u76EE\u7D20\u6750\u76EE\u5F55\u4E2D\u8BBE\u8BA1\u7A3F\u540C\u540D\u3001bitmapLookupCandidates \u5217\u51FA\u7684\u540C\u4E3B\u4F53\u4F4D\u56FE\uFF0C\u518D\u51B3\u5B9A\u662F\u5426\u8865\u5BFC",
6536
6536
  "preserve \u6A21\u5F0F SVG \u7981\u6B62\u8FFD\u52A0 ColorFilter",
6537
6537
  "16x16 \u69FD\u4F4D\u4E0E 6x8/12x12 \u56FE\u5F62\u8981\u5206\u5F00\u5904\u7406",
6538
- "Sketch Image \u56FE\u5C42\u4F18\u5148\u590D\u7528 assets/icons \u4E2D\u6309\u56FE\u5C42\u539F\u540D\u5BFC\u51FA\u7684\u4F4D\u56FE\u6216 bitmapLookupCandidates \u547D\u4E2D\u7684\u8D44\u6E90\uFF0C\u4E0D\u8981\u4F2A\u88C5\u6210 SVG\uFF1B\u53EA\u6709\u65E7\u4F4D\u56FE\u6D41\u7A0B\u624D\u8865 2.0x/3.0x",
6538
+ "Sketch Image \u56FE\u5C42\u4F18\u5148\u590D\u7528\u9879\u76EE\u7D20\u6750\u76EE\u5F55\u4E2D\u6309\u56FE\u5C42\u539F\u540D\u6216\u540C\u4E3B\u4F53\u547D\u4E2D\u7684\u4F4D\u56FE\uFF0C\u4E0D\u8981\u4F2A\u88C5\u6210 SVG\uFF1B\u53EA\u6709\u65E7\u4F4D\u56FE\u6D41\u7A0B\u624D\u8865 2.0x/3.0x",
6539
6539
  "\u9876\u90E8 Help/Customer Service/Support Entry \u82E5\u6D4B\u5F97\u4E3A Image\uFF0C\u7981\u6B62\u7528\u8BED\u4E49\u76F8\u8FD1\u7684\u5176\u4ED6\u6309\u94AE\u4F4D\u56FE\u6216 help glyph \u66FF\u4EE3",
6540
6540
  "\u5168\u5C4F app \u9875\u9762\u5728\u5BBD\u5C4F\u5E94\u8DDF\u968F\u5BBF\u4E3B Scaffold/SafeArea/AppBar \u548C\u9875\u9762 padding\uFF0C\u53EA\u6709\u660E\u786E\u6D4B\u5F97\u7684\u5C40\u90E8\u6D6E\u5C42/\u5361\u7247\u5BB9\u5668\u624D\u505A clamp",
6541
6541
  "Sketch \u9876\u90E8\u65F6\u95F4/\u4FE1\u53F7/WiFi/\u7535\u6C60\u5C5E\u4E8E\u8BBE\u5907\u7CFB\u7EDF chrome\uFF0C\u4E0D\u5C5E\u4E8E\u9875\u9762\u4E1A\u52A1 UI\uFF0C\u7981\u6B62\u76F4\u63A5\u8FD8\u539F\u5230 Flutter \u9875\u9762",
@@ -6635,6 +6635,15 @@ function loadMeasurePayload(args) {
6635
6635
  function normalizeSemanticKey(value) {
6636
6636
  return value.replace(/[\s_-]+/g, "").toLowerCase();
6637
6637
  }
6638
+ function toPosixAssetPath(value) {
6639
+ return value.replace(/\\/g, "/").replace(/[?#].*$/, "");
6640
+ }
6641
+ function getAssetBaseName(value) {
6642
+ return path20.posix.basename(toPosixAssetPath(value));
6643
+ }
6644
+ function normalizeAssetMatchKey(value) {
6645
+ return getAssetBaseName(value).replace(/\.[^.]+$/, "").replace(/@\d+(?:\.\d+)?x$/i, "").replace(/[\s_-]+/g, "").toLowerCase();
6646
+ }
6638
6647
  function hasAnyPattern(source, patterns) {
6639
6648
  return patterns.some((pattern) => pattern.test(source));
6640
6649
  }
@@ -6645,7 +6654,7 @@ function collectAssetContracts(node, bucket = []) {
6645
6654
  if (typeof node.svgFile === "string") {
6646
6655
  bucket.push({
6647
6656
  sketchName: node.iconSketchName || node.n || node.svgFile,
6648
- fileName: path20.posix.basename(node.svgFile),
6657
+ fileName: getAssetBaseName(node.svgFile),
6649
6658
  mode: node.svgMode,
6650
6659
  sourceType: "vector",
6651
6660
  containerSize: node.cs,
@@ -6655,7 +6664,7 @@ function collectAssetContracts(node, bucket = []) {
6655
6664
  if (node.t === "Image") {
6656
6665
  bucket.push({
6657
6666
  sketchName: node.n || "Image",
6658
- fileName: typeof node.bitmapFile === "string" ? path20.posix.basename(node.bitmapFile) : typeof node.bitmapPath === "string" ? path20.posix.basename(node.bitmapPath) : void 0,
6667
+ fileName: typeof node.bitmapFile === "string" ? getAssetBaseName(node.bitmapFile) : typeof node.bitmapPath === "string" ? getAssetBaseName(node.bitmapPath) : void 0,
6659
6668
  sourceType: "bitmap",
6660
6669
  scaleVariants: node.bitmapScales,
6661
6670
  lookupCandidates: node.bitmapLookupCandidates,
@@ -6669,11 +6678,11 @@ function collectAssetContracts(node, bucket = []) {
6669
6678
  return bucket;
6670
6679
  }
6671
6680
  function extractCodeAssets(codeContent) {
6672
- const matches = codeContent.match(/assets\/[^'")]+\.(svg|png|jpg|jpeg|webp)/g);
6673
- return matches ? [...new Set(matches)] : [];
6681
+ const matches = [...codeContent.matchAll(/['"`]([^'"`\n]+\.(?:svg|png|jpg|jpeg|webp))(?:\?[^'"`\n]*)?['"`]/gi)].map(([, assetPath]) => toPosixAssetPath(assetPath.trim())).filter((assetPath) => !/^(https?:\/\/|data:|<svg)/i.test(assetPath));
6682
+ return [...new Set(matches)];
6674
6683
  }
6675
- function isManualExport4xBitmapAsset(assetPath) {
6676
- return /_icon@4x\.(png|jpg|jpeg|webp)$/i.test(path20.posix.basename(assetPath));
6684
+ function hasExplicitBitmapScaleSuffix(assetPath) {
6685
+ return /@\d+(?:\.\d+)?x\.(png|jpg|jpeg|webp)$/i.test(getAssetBaseName(assetPath));
6677
6686
  }
6678
6687
  function isCriticalBitmapEntryName(name) {
6679
6688
  return /help[_ -]?button|customer[_ -]?service(?:[_ -]?(button|icon))?|support(?:[_ -]?entry)?|帮助|客服/i.test(name);
@@ -6681,13 +6690,23 @@ function isCriticalBitmapEntryName(name) {
6681
6690
  function buildBitmapCandidateSet(contract) {
6682
6691
  const candidates = /* @__PURE__ */ new Set();
6683
6692
  if (contract.fileName) {
6684
- candidates.add(path20.posix.basename(contract.fileName).toLowerCase());
6693
+ candidates.add(getAssetBaseName(contract.fileName).toLowerCase());
6685
6694
  }
6686
6695
  for (const candidate of contract.lookupCandidates || []) {
6687
- candidates.add(path20.posix.basename(candidate).toLowerCase());
6696
+ candidates.add(getAssetBaseName(candidate).toLowerCase());
6688
6697
  }
6689
6698
  return candidates;
6690
6699
  }
6700
+ function buildBitmapMatchKeySet(contract) {
6701
+ const matchKeys = /* @__PURE__ */ new Set();
6702
+ if (contract.fileName) {
6703
+ matchKeys.add(normalizeAssetMatchKey(contract.fileName));
6704
+ }
6705
+ for (const candidate of contract.lookupCandidates || []) {
6706
+ matchKeys.add(normalizeAssetMatchKey(candidate));
6707
+ }
6708
+ return matchKeys;
6709
+ }
6691
6710
  function collectCriticalBitmapContracts(payload, assetContracts) {
6692
6711
  var _a;
6693
6712
  const criticalBitmapKeys = new Set(
@@ -6777,7 +6796,8 @@ function extractRouteChunk(routeContent, routeIndex) {
6777
6796
  return routeContent.slice(start, nextBlockStart);
6778
6797
  }
6779
6798
  function resolveProjectAssetPath(projectPath, assetPath) {
6780
- return path20.join(projectPath, assetPath);
6799
+ const normalizedPath = toPosixAssetPath(assetPath).replace(/^\.\//, "").replace(/^@\//, "src/");
6800
+ return path20.join(projectPath, normalizedPath.replace(/^\/+/, ""));
6781
6801
  }
6782
6802
  function getBitmapScaleVariantPaths(projectPath, assetPath) {
6783
6803
  const relativeDir = path20.posix.dirname(assetPath);
@@ -6910,12 +6930,15 @@ function buildAssetValidator(payload, codeContent, args) {
6910
6930
  );
6911
6931
  const expectedBitmapCount = assetContracts.filter((item) => item.sourceType === "bitmap").length;
6912
6932
  const codeAssets = extractCodeAssets(codeContent);
6913
- const codeVectorAssets = codeAssets.filter((asset) => asset.endsWith(".svg"));
6914
- const codeBitmapAssets = codeAssets.filter((asset) => /\.(png|jpg|jpeg|webp)$/.test(asset));
6933
+ const codeVectorAssets = codeAssets.filter((asset) => asset.toLowerCase().endsWith(".svg"));
6934
+ const codeBitmapAssets = codeAssets.filter((asset) => /\.(png|jpg|jpeg|webp)$/i.test(asset));
6915
6935
  const codeBitmapFileNames = new Set(
6916
- codeBitmapAssets.map((asset) => path20.posix.basename(asset).toLowerCase())
6936
+ codeBitmapAssets.map((asset) => getAssetBaseName(asset).toLowerCase())
6937
+ );
6938
+ const codeBitmapMatchKeys = new Set(
6939
+ codeBitmapAssets.map((asset) => normalizeAssetMatchKey(asset))
6917
6940
  );
6918
- const unexpectedVectorAssets = codeVectorAssets.filter((asset) => !expectedVectorFiles.has(path20.posix.basename(asset).toLowerCase()));
6941
+ const unexpectedVectorAssets = codeVectorAssets.filter((asset) => !expectedVectorFiles.has(getAssetBaseName(asset).toLowerCase()));
6919
6942
  if (unexpectedVectorAssets.length > 0) {
6920
6943
  findings.push({
6921
6944
  id: "unexpected_vector_assets",
@@ -6936,8 +6959,9 @@ function buildAssetValidator(payload, codeContent, args) {
6936
6959
  if (criticalBitmapContracts.length > 0 && codeBitmapAssets.length > 0) {
6937
6960
  const mismatchedCriticalBitmapEntries = criticalBitmapContracts.map((contract) => {
6938
6961
  const candidateSet = buildBitmapCandidateSet(contract);
6939
- const matched = [...codeBitmapFileNames].some((fileName) => candidateSet.has(fileName));
6940
- return matched ? null : `${contract.sketchName} -> expected one of ${[...candidateSet].join(", ")}`;
6962
+ const candidateMatchKeys = buildBitmapMatchKeySet(contract);
6963
+ const matched = [...codeBitmapMatchKeys].some((matchKey) => candidateMatchKeys.has(matchKey));
6964
+ return matched ? null : `${contract.sketchName} -> expected asset stem matching one of ${[...candidateSet].join(", ")}`;
6941
6965
  }).filter((item) => item !== null);
6942
6966
  if (mismatchedCriticalBitmapEntries.length > 0) {
6943
6967
  findings.push({
@@ -6955,24 +6979,22 @@ function buildAssetValidator(payload, codeContent, args) {
6955
6979
  severity: "info",
6956
6980
  message: "\u4EE3\u7801\u4E2D\u5B58\u5728\u4F4D\u56FE\u8D44\u6E90\uFF0C\u9700\u4EBA\u5DE5\u786E\u8BA4\u5B83\u4EEC\u6765\u81EA\u5F53\u524D\u753B\u677F\uFF0C\u6216\u5DF2\u6B63\u786E\u590D\u7528\u5F53\u524D\u8BBE\u8BA1\u7EA6\u5B9A\u7684 canonical \u5171\u4EAB\u8D44\u4EA7\u3002",
6957
6981
  evidence: codeBitmapAssets,
6958
- suggestion: "\u4F18\u5148\u5148\u5728 assets/icons/ \u4E2D\u67E5\u627E\u7528\u6237\u624B\u5DE5\u5BFC\u51FA\u7684\u8BBE\u8BA1\u540C\u540D *_icon@4x.png\uFF1B\u53EA\u6709\u7F3A\u5931\u65F6\uFF0C\u624D\u6309 contract \u6307\u5B9A\u65B9\u5F0F\u8865\u5BFC\u6216\u590D\u7528\u5171\u4EAB\u8D44\u4EA7\u3002"
6982
+ suggestion: "\u4F18\u5148\u6309\u8BBE\u8BA1\u7A3F\u56FE\u5C42\u540D\u4E3B\u4F53\u4E0E bitmapLookupCandidates \u5728\u9879\u76EE\u73B0\u6709\u7D20\u6750\u76EE\u5F55\u4E2D\u67E5\u627E\u8D44\u6E90\uFF1B\u6587\u4EF6\u540D\u5339\u914D\u91CD\u70B9\u770B\u4E3B\u4F53\uFF0C\u4F8B\u5982 xxx / xxx@4x\uFF0C\u53EA\u6709\u7F3A\u5931\u65F6\u624D\u6309 contract \u6307\u5B9A\u65B9\u5F0F\u8865\u5BFC\u6216\u590D\u7528\u5171\u4EAB\u8D44\u4EA7\u3002"
6959
6983
  });
6960
6984
  }
6961
- const sharedBitmapFiles = new Set(
6962
- assetContracts.filter((item) => item.sourceType === "bitmap" && item.reuseMode === "shared-canonical" && item.fileName).map((item) => item.fileName.toLowerCase())
6963
- );
6964
- if (sharedBitmapFiles.size > 0) {
6965
- const usedBitmapFiles = new Set(
6966
- codeBitmapAssets.map((asset) => path20.posix.basename(asset).toLowerCase())
6967
- );
6968
- const missingSharedBitmapFiles = [...sharedBitmapFiles].filter((fileName) => !usedBitmapFiles.has(fileName));
6985
+ const sharedBitmapFiles = assetContracts.filter((item) => item.sourceType === "bitmap" && item.reuseMode === "shared-canonical" && item.fileName).map((item) => ({
6986
+ fileName: item.fileName,
6987
+ matchKey: normalizeAssetMatchKey(item.fileName)
6988
+ }));
6989
+ if (sharedBitmapFiles.length > 0) {
6990
+ const missingSharedBitmapFiles = sharedBitmapFiles.filter((item) => !codeBitmapMatchKeys.has(item.matchKey)).map((item) => item.fileName);
6969
6991
  if (missingSharedBitmapFiles.length > 0) {
6970
6992
  findings.push({
6971
6993
  id: "shared_bitmap_asset_not_reused",
6972
6994
  severity: "warning",
6973
- message: "\u8BBE\u8BA1\u7A3F\u5DF2\u6807\u8BB0\u8BE5\u4F4D\u56FE\u4E3A\u5171\u4EAB canonical \u8D44\u4EA7\uFF0C\u4F46\u4EE3\u7801\u6CA1\u6709\u590D\u7528\u5BF9\u5E94\u6587\u4EF6\u540D\uFF0C\u5B58\u5728\u91CD\u590D\u7F13\u5B58\u6216\u628A Help/Customer Service \u8BED\u4E49\u8BEF\u5408\u5E76\u7684\u98CE\u9669\u3002",
6995
+ message: "\u8BBE\u8BA1\u7A3F\u5DF2\u6807\u8BB0\u8BE5\u4F4D\u56FE\u4E3A\u5171\u4EAB canonical \u8D44\u4EA7\uFF0C\u4F46\u4EE3\u7801\u6CA1\u6709\u590D\u7528\u5BF9\u5E94\u6587\u4EF6\u540D\u4E3B\u4F53\uFF0C\u5B58\u5728\u91CD\u590D\u7F13\u5B58\u6216\u628A Help/Customer Service \u8BED\u4E49\u8BEF\u5408\u5E76\u7684\u98CE\u9669\u3002",
6974
6996
  evidence: missingSharedBitmapFiles,
6975
- suggestion: "\u4F18\u5148\u590D\u7528 contract \u8FD4\u56DE\u7684\u5171\u4EAB bitmap \u6587\u4EF6\u540D\uFF1BHelp Button \u4E0E Customer Service Button \u5FC5\u987B\u6309\u5404\u81EA canonical \u6587\u4EF6\u72EC\u7ACB\u590D\u7528\uFF0C\u4E0D\u80FD\u518D\u5408\u5E76\u6210\u540C\u4E00\u4E2A\u540D\u5B57\u3002"
6997
+ suggestion: "\u4F18\u5148\u590D\u7528 contract \u8FD4\u56DE\u7684\u5171\u4EAB bitmap \u6587\u4EF6\u540D\u4E3B\u4F53\uFF1B\u76EE\u5F55\u548C @4x \u53EA\u662F\u9879\u76EE\u5B9E\u73B0\u7EC6\u8282\uFF0CHelp Button \u4E0E Customer Service Button \u5FC5\u987B\u6309\u5404\u81EA\u4E3B\u4F53\u72EC\u7ACB\u590D\u7528\uFF0C\u4E0D\u80FD\u518D\u5408\u5E76\u6210\u540C\u4E00\u4E2A\u540D\u5B57\u3002"
6976
6998
  });
6977
6999
  }
6978
7000
  }
@@ -6980,13 +7002,13 @@ function buildAssetValidator(payload, codeContent, args) {
6980
7002
  (item) => item.sourceType === "bitmap" && item.reuseMode !== "shared-canonical"
6981
7003
  );
6982
7004
  if (expectedBitmapCount > 0 && codeBitmapAssets.length > 0 && requiresPageLocalBitmap) {
6983
- const hasManualExport4xBitmap = codeBitmapAssets.some((asset) => isManualExport4xBitmapAsset(asset));
6984
- if (!hasManualExport4xBitmap) {
7005
+ const hasScaledBitmapExport = codeBitmapAssets.some((asset) => hasExplicitBitmapScaleSuffix(asset));
7006
+ if (!hasScaledBitmapExport) {
6985
7007
  const pageClassName = extractPageClassName(codeContent) || "";
6986
7008
  const normalizedStem = pageClassName.replace(/Page$/, "").replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
6987
7009
  if (normalizedStem) {
6988
7010
  const hasPageLocalBitmap = codeBitmapAssets.some(
6989
- (asset) => path20.posix.basename(asset).toLowerCase().includes(normalizedStem)
7011
+ (asset) => getAssetBaseName(asset).toLowerCase().includes(normalizedStem)
6990
7012
  );
6991
7013
  if (!hasPageLocalBitmap) {
6992
7014
  findings.push({
@@ -6994,7 +7016,7 @@ function buildAssetValidator(payload, codeContent, args) {
6994
7016
  severity: "warning",
6995
7017
  message: "\u5F53\u524D\u9875\u9762\u4F4D\u56FE\u8D44\u6E90\u770B\u8D77\u6765\u4E0D\u662F page-local \u547D\u540D\uFF0C\u5B58\u5728\u7EE7\u7EED\u590D\u7528\u65E7\u9875\u9762 PNG \u7684\u98CE\u9669\u3002",
6996
7018
  evidence: codeBitmapAssets,
6997
- suggestion: "\u4F4D\u56FE\u8D44\u6E90\u4F18\u5148\u6309\u5F53\u524D\u9875\u9762\u547D\u540D\u5BFC\u51FA\uFF0C\u4F8B\u5982\u5E26\u4E0A\u9875\u9762 stem\uFF1B\u82E5\u9879\u76EE\u5DF2\u6539\u4E3A\u624B\u5DE5 *_icon@4x.png \u7EA6\u5B9A\uFF0C\u5219\u65E0\u9700\u518D\u8FFD\u52A0 page-local stem\u3002"
7019
+ suggestion: "\u4F4D\u56FE\u8D44\u6E90\u4F18\u5148\u6309\u5F53\u524D\u9875\u9762\u547D\u540D\u5BFC\u51FA\uFF0C\u4F8B\u5982\u5E26\u4E0A\u9875\u9762 stem\uFF1B\u82E5\u9879\u76EE\u5DF2\u91C7\u7528\u663E\u5F0F\u500D\u7387\u6587\u4EF6\u540D\uFF08\u5982 xxx@4x.png\uFF09\uFF0C\u5219\u65E0\u9700\u518D\u8FFD\u52A0 page-local stem\u3002"
6998
7020
  });
6999
7021
  }
7000
7022
  }
@@ -7008,7 +7030,7 @@ function buildAssetValidator(payload, codeContent, args) {
7008
7030
  if (requiresRetinaVariants) {
7009
7031
  const missingRetinaEvidence = [];
7010
7032
  for (const asset of codeBitmapAssets) {
7011
- if (isManualExport4xBitmapAsset(asset)) {
7033
+ if (hasExplicitBitmapScaleSuffix(asset)) {
7012
7034
  continue;
7013
7035
  }
7014
7036
  const assetOnDisk = resolveProjectAssetPath(args.projectPath, asset);
@@ -7026,9 +7048,9 @@ function buildAssetValidator(payload, codeContent, args) {
7026
7048
  findings.push({
7027
7049
  id: "bitmap_retina_variants_missing",
7028
7050
  severity: "error",
7029
- message: "\u4F4D\u56FE\u8D44\u6E90\u65E2\u6CA1\u6709\u4F7F\u7528\u624B\u5DE5\u5BFC\u51FA\u7684 *_icon@4x.png \u7EA6\u5B9A\uFF0C\u4E5F\u7F3A\u5C11 2.0x/3.0x \u53D8\u4F53\uFF0C\u9AD8\u5206\u5C4F\u4E0B\u5BB9\u6613\u53D1\u7CCA\u3002",
7051
+ message: "\u4F4D\u56FE\u8D44\u6E90\u65E2\u6CA1\u6709\u4F7F\u7528\u663E\u5F0F\u500D\u7387\u547D\u540D\uFF08\u4F8B\u5982 xxx@4x.png\uFF09\uFF0C\u4E5F\u7F3A\u5C11 2.0x/3.0x \u53D8\u4F53\uFF0C\u9AD8\u5206\u5C4F\u4E0B\u5BB9\u6613\u53D1\u7CCA\u3002",
7030
7052
  evidence: missingRetinaEvidence,
7031
- suggestion: "\u4F18\u5148\u6539\u4E3A assets/icons/*_icon@4x.png \u7684\u624B\u5DE5\u5BFC\u51FA\u7EA6\u5B9A\uFF1B\u5982\u679C\u4ECD\u8D70\u65E7\u4F4D\u56FE\u6D41\u7A0B\uFF0C\u518D\u8865\u9F50 assets/.../2.0x \u4E0E 3.0x \u53D8\u4F53\u3002"
7053
+ suggestion: "\u4F18\u5148\u4F7F\u7528\u9879\u76EE\u73B0\u6709\u7684\u9AD8\u500D\u7387\u624B\u5DE5\u5BFC\u51FA\u547D\u540D\uFF08\u5982 xxx@4x.png\uFF09\uFF1B\u5982\u679C\u4ECD\u8D70\u591A\u500D\u7387\u76EE\u5F55\u6D41\u7A0B\uFF0C\u518D\u8865\u9F50\u540C\u76EE\u5F55\u7684 2.0x \u4E0E 3.0x \u53D8\u4F53\u3002"
7032
7054
  });
7033
7055
  }
7034
7056
  }