speccore 8.3.137 → 8.3.147

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.
Files changed (38) hide show
  1. package/dist/cli.js +24 -11
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/about.d.ts.map +1 -1
  4. package/dist/commands/about.js +4 -0
  5. package/dist/commands/about.js.map +1 -1
  6. package/dist/commands/completion.js +2 -2
  7. package/dist/commands/completion.js.map +1 -1
  8. package/dist/commands/execute.d.ts.map +1 -1
  9. package/dist/commands/execute.js +23 -2
  10. package/dist/commands/execute.js.map +1 -1
  11. package/dist/commands/help.js +13 -13
  12. package/dist/commands/help.js.map +1 -1
  13. package/dist/commands/review.d.ts +26 -0
  14. package/dist/commands/review.d.ts.map +1 -0
  15. package/dist/commands/review.js +369 -0
  16. package/dist/commands/review.js.map +1 -0
  17. package/dist/commands/task/new.d.ts +1 -0
  18. package/dist/commands/task/new.d.ts.map +1 -1
  19. package/dist/commands/task/new.js +34 -24
  20. package/dist/commands/task/new.js.map +1 -1
  21. package/dist/commands/trace.js +1 -1
  22. package/dist/commands/trace.js.map +1 -1
  23. package/dist/commands/update.d.ts.map +1 -1
  24. package/dist/commands/update.js +4 -0
  25. package/dist/commands/update.js.map +1 -1
  26. package/dist/core/ask-engine.d.ts.map +1 -1
  27. package/dist/core/ask-engine.js +50 -4
  28. package/dist/core/ask-engine.js.map +1 -1
  29. package/dist/core/intent-recognition.js +5 -5
  30. package/dist/core/intent-recognition.js.map +1 -1
  31. package/dist/core/prompt-builder.d.ts.map +1 -1
  32. package/dist/core/prompt-builder.js +79 -44
  33. package/dist/core/prompt-builder.js.map +1 -1
  34. package/dist/core/rag-engine.d.ts +14 -0
  35. package/dist/core/rag-engine.d.ts.map +1 -1
  36. package/dist/core/rag-engine.js +58 -5
  37. package/dist/core/rag-engine.js.map +1 -1
  38. package/package.json +1 -1
@@ -321,7 +321,7 @@ async function loadExtraSpecs(cwd, taskDir, platform, iteration, options) {
321
321
  // v8.3.93+: Markdown 链接自动展开 + 图片提取
322
322
  if (fullPath.endsWith('.md')) {
323
323
  content = await processMarkdownContent(content, fullPath, seenPaths, visionConfig, {
324
- maxLinkDepth: 2, maxLinkChars: 1200, maxSvgChars: 1500,
324
+ maxLinkDepth: 3, maxLinkChars: 2000, maxSvgChars: 1500,
325
325
  });
326
326
  }
327
327
  // 跳过空文件或纯占位符文件
@@ -361,7 +361,7 @@ async function loadExtraSpecs(cwd, taskDir, platform, iteration, options) {
361
361
  // v8.3.93+: Markdown 链接自动展开 + 图片提取
362
362
  if (fullPath.endsWith('.md')) {
363
363
  content = await processMarkdownContent(content, fullPath, seenPaths, visionConfig, {
364
- maxLinkDepth: 2, maxLinkChars: 1200, maxSvgChars: 1500,
364
+ maxLinkDepth: 3, maxLinkChars: 2000, maxSvgChars: 1500,
365
365
  });
366
366
  }
367
367
  if (content.trim().length <= 50 || content.trim().match(/^#+\s*待填充|^<!--\s*AI-FILL\s*-->$/m)) {
@@ -536,7 +536,7 @@ async function loadAllTaskContext(cwd, taskDir, platform, iteration, graph) {
536
536
  // v8.3.93+: Markdown 链接自动展开 + 图片提取
537
537
  if (fullPath.endsWith('.md')) {
538
538
  content = await processMarkdownContent(content, fullPath, seen, visionConfig, {
539
- maxLinkDepth: 2, maxLinkChars: 1500, maxSvgChars: 2000,
539
+ maxLinkDepth: 3, maxLinkChars: 2000, maxSvgChars: 2000,
540
540
  });
541
541
  }
542
542
  if (content.trim().length <= 50 || content.trim().match(/^#+\s*待填充|^<!--\s*AI-FILL\s*-->$/m))
@@ -741,13 +741,17 @@ function extractSummary(content) {
741
741
  }
742
742
  /**
743
743
  * 从路径和内容推断涉及的端
744
+ * v8.3.144+: 使用项目实际端名(工程标识),不再硬编码 backend/frontend 等
744
745
  */
745
- function extractPlatforms(path, content) {
746
+ function extractPlatforms(path, content, projectPlatforms) {
746
747
  const platforms = new Set();
747
- const knownPlatforms = ['backend', 'admin', 'h5', 'miniapp', 'app', 'web', 'ios', 'android'];
748
+ // 优先使用项目 CONSTITUTION.md 中定义的工程标识,fallback 到常见端名
749
+ const knownPlatforms = projectPlatforms?.length
750
+ ? projectPlatforms
751
+ : ['backend', 'admin', 'h5', 'miniapp', 'app', 'web', 'ios', 'android'];
748
752
  // 从路径推断
749
753
  for (const p of knownPlatforms) {
750
- if (path.toLowerCase().includes(p)) {
754
+ if (path.toLowerCase().includes(p.toLowerCase())) {
751
755
  platforms.add(p);
752
756
  }
753
757
  }
@@ -759,7 +763,7 @@ function extractPlatforms(path, content) {
759
763
  // 从内容中扫描(前 2000 字)
760
764
  const head = content.slice(0, 2000).toLowerCase();
761
765
  for (const p of knownPlatforms) {
762
- if (head.includes(p)) {
766
+ if (head.includes(p.toLowerCase())) {
763
767
  platforms.add(p);
764
768
  }
765
769
  }
@@ -796,15 +800,16 @@ function extractTags(headings) {
796
800
  }
797
801
  /**
798
802
  * 构建单个 TOC 条目(含摘要/端/行数/标签)
803
+ * v8.3.144+: 异步化,支持传入项目实际端名(工程标识)
799
804
  */
800
- function buildTOCEntry(path, description, content, maxSections) {
805
+ async function buildTOCEntry(path, description, content, maxSections, projectPlatforms) {
801
806
  const sections = extractHeadings(content);
802
807
  return {
803
808
  path,
804
809
  description,
805
810
  sections: maxSections ? sections.slice(0, maxSections) : sections,
806
811
  summary: extractSummary(content) || undefined,
807
- platforms: extractPlatforms(path, content) || undefined,
812
+ platforms: extractPlatforms(path, content, projectPlatforms) || undefined,
808
813
  lineCount: content.split(/\r?\n/).length,
809
814
  tags: extractTags(sections) || undefined,
810
815
  };
@@ -816,13 +821,15 @@ function buildTOCEntry(path, description, content, maxSections) {
816
821
  async function buildGlobalTOC(globalDir) {
817
822
  const toc = [];
818
823
  const speccoreDir = (0, path_1.join)(globalDir, '..'); // .speccore/
824
+ // v8.3.144+: 读取项目实际端名(工程标识),不再硬编码 backend/frontend
825
+ const projectPlatforms = await (0, spec_paths_1.parsePlatformList)();
819
826
  // 1. synthesis/ 下的综合文档
820
827
  const synthesisDir = (0, path_1.join)(globalDir, 'synthesis');
821
828
  if (await (0, fs_extra_1.pathExists)(synthesisDir)) {
822
829
  const files = await (0, fs_extra_1.readdir)(synthesisDir);
823
830
  for (const f of files.filter(f => f.endsWith('.md') && !(0, task_utils_1.isTimestampBackup)(f))) {
824
831
  const content = await (0, fs_extra_1.readFile)((0, path_1.join)(synthesisDir, f), 'utf-8');
825
- toc.push(buildTOCEntry(`synthesis/${f}`, FILE_DESC[f] || f.replace('.md', ''), content));
832
+ toc.push(await buildTOCEntry(`synthesis/${f}`, FILE_DESC[f] || f.replace('.md', ''), content, undefined, projectPlatforms));
826
833
  }
827
834
  }
828
835
  // 2. platforms/ 下各端文档
@@ -836,7 +843,7 @@ async function buildGlobalTOC(globalDir) {
836
843
  const subFiles = await (0, fs_extra_1.readdir)((0, path_1.join)(platformsDir, platformName));
837
844
  for (const f of subFiles.filter(f => f.endsWith('.md') && !(0, task_utils_1.isTimestampBackup)(f))) {
838
845
  const content = await (0, fs_extra_1.readFile)((0, path_1.join)(platformsDir, platformName, f), 'utf-8');
839
- toc.push(buildTOCEntry(`platforms/${platformName}/${f}`, `${platformName} 端 — ${f.replace('.md', '')}`, content));
846
+ toc.push(await buildTOCEntry(`platforms/${platformName}/${f}`, `${platformName} 端 — ${f.replace('.md', '')}`, content, undefined, projectPlatforms));
840
847
  }
841
848
  }
842
849
  }
@@ -851,7 +858,7 @@ async function buildGlobalTOC(globalDir) {
851
858
  const subFiles = await (0, fs_extra_1.readdir)((0, path_1.join)(projectsDir, projectName));
852
859
  for (const f of subFiles.filter(f => f.endsWith('.md') && !(0, task_utils_1.isTimestampBackup)(f))) {
853
860
  const content = await (0, fs_extra_1.readFile)((0, path_1.join)(projectsDir, projectName, f), 'utf-8');
854
- toc.push(buildTOCEntry(`PROJECTS/${projectName}/${f}`, `${projectName} — ${f.replace('.md', '')}`, content));
861
+ toc.push(await buildTOCEntry(`PROJECTS/${projectName}/${f}`, `${projectName} — ${f.replace('.md', '')}`, content, undefined, projectPlatforms));
855
862
  }
856
863
  }
857
864
  }
@@ -865,7 +872,7 @@ async function buildGlobalTOC(globalDir) {
865
872
  return true;
866
873
  })) {
867
874
  const content = await (0, fs_extra_1.readFile)((0, path_1.join)(globalDir, f), 'utf-8');
868
- toc.push(buildTOCEntry(`GLOBAL:${f}`, FILE_DESC[f] || f.replace('.md', ''), content));
875
+ toc.push(await buildTOCEntry(`GLOBAL:${f}`, FILE_DESC[f] || f.replace('.md', ''), content, undefined, projectPlatforms));
869
876
  }
870
877
  // 5. PATTERNS/ 可复用模式(含 TEMPLATES/ 写作模板)
871
878
  const patternsDir = (0, path_1.join)(speccoreDir, 'PATTERNS');
@@ -882,7 +889,7 @@ async function buildGlobalTOC(globalDir) {
882
889
  const label = isTemplate
883
890
  ? entry.name.replace('-template.md', '').toUpperCase() + ' 写作模板'
884
891
  : (prefix ? prefix.slice(0, -1).replace(/\//g, ' › ') + ' › ' : '') + entry.name.replace('.md', '');
885
- toc.push(buildTOCEntry(`PATTERNS:${prefix}${entry.name}`, label, content, isTemplate ? 6 : 4));
892
+ toc.push(await buildTOCEntry(`PATTERNS:${prefix}${entry.name}`, label, content, isTemplate ? 6 : 4, projectPlatforms));
886
893
  }
887
894
  }
888
895
  };
@@ -894,7 +901,7 @@ async function buildGlobalTOC(globalDir) {
894
901
  const files = await (0, fs_extra_1.readdir)(rulesDir);
895
902
  for (const f of files.filter(f => f.endsWith('.md') && !(0, task_utils_1.isTimestampBackup)(f))) {
896
903
  const content = await (0, fs_extra_1.readFile)((0, path_1.join)(rulesDir, f), 'utf-8');
897
- toc.push(buildTOCEntry(`RULES:${f}`, RULES_DESC[f] || f.replace('.md', ''), content));
904
+ toc.push(await buildTOCEntry(`RULES:${f}`, RULES_DESC[f] || f.replace('.md', ''), content, undefined, projectPlatforms));
898
905
  }
899
906
  }
900
907
  // 7. SKILLS/ 技术能力库(v8.3.134+ 四层架构:TOC 可见性)
@@ -903,7 +910,7 @@ async function buildGlobalTOC(globalDir) {
903
910
  const files = await (0, fs_extra_1.readdir)(skillsDir);
904
911
  for (const f of files.filter(f => f.endsWith('.md') && !(0, task_utils_1.isTimestampBackup)(f))) {
905
912
  const content = await (0, fs_extra_1.readFile)((0, path_1.join)(skillsDir, f), 'utf-8');
906
- toc.push(buildTOCEntry(`SKILLS:${f}`, SKILLS_DESC[f] || f.replace('.md', ''), content));
913
+ toc.push(await buildTOCEntry(`SKILLS:${f}`, SKILLS_DESC[f] || f.replace('.md', ''), content, undefined, projectPlatforms));
907
914
  }
908
915
  }
909
916
  return toc;
@@ -2403,7 +2410,9 @@ function outputNeedsInfo(req) {
2403
2410
  // ═══════════════════════════════════════════════════════════
2404
2411
  // Markdown 链接自动展开 + 图片提取(v8.3.93+)
2405
2412
  // ═══════════════════════════════════════════════════════════
2406
- /** 提取 Markdown 文本链接 [text](path),排除图片链接 */
2413
+ /** 提取 Markdown 文本链接 [text](path),排除图片链接
2414
+ * v8.3.125+: 不再跳过外部链接 — 由调用方决定如何处理(收集到外链清单)
2415
+ */
2407
2416
  function extractMarkdownLinks(content) {
2408
2417
  const links = [];
2409
2418
  const regex = /\[([^\]]+)\]\(([^)]+)\)/g;
@@ -2414,8 +2423,10 @@ function extractMarkdownLinks(content) {
2414
2423
  // 跳过图片链接 ![alt](path) — 检查前一个字符
2415
2424
  if (match.index > 0 && content.charAt(match.index - 1) === '!')
2416
2425
  continue;
2417
- // 跳过外部链接、锚点、协议链接
2418
- if (/^(https?:|mailto:|ftp:|#|javascript:)/i.test(path))
2426
+ // 跳过锚点和危险协议(保留 http/https/ftp 外链,由调用方处理)
2427
+ if (/^(mailto:|javascript:)/i.test(path))
2428
+ continue;
2429
+ if (path.startsWith('#'))
2419
2430
  continue;
2420
2431
  links.push({ text, path });
2421
2432
  }
@@ -2441,19 +2452,22 @@ function resolveLinkPath(baseDir, linkPath) {
2441
2452
  return linkPath;
2442
2453
  return (0, path_1.join)(baseDir, linkPath);
2443
2454
  }
2444
- /** 递归展开 Markdown 链接指向的文件内容(防循环,限深度) */
2445
- async function expandMarkdownLinks(content, baseDir, seenPaths, maxDepth = 2, currentDepth = 0, maxChars = 1500) {
2455
+ /** 递归展开 Markdown 链接指向的文件内容(防循环,限深度)
2456
+ * v8.3.138+: HTML 文件独立阈值(8KB),避免原型文件被过度截断
2457
+ * maxDepth 默认 3:支持文档 A → B → C 三级展开
2458
+ */
2459
+ async function expandMarkdownLinks(content, baseDir, seenPaths, maxDepth = 3, currentDepth = 0, maxChars = 1500) {
2446
2460
  if (currentDepth >= maxDepth)
2447
2461
  return content;
2448
2462
  const links = extractMarkdownLinks(content);
2449
2463
  if (links.length === 0)
2450
2464
  return content;
2451
2465
  const expansions = [];
2452
- // v8.3.125+: 收集外链资源,供宿主 AI 按需读取
2453
- const externalLinks = [];
2466
+ // v8.3.125+: 收集外链资源,供宿主 AI 按需读取(用 Map 按 URL 去重)
2467
+ const externalLinks = new Map(); // path -> text
2454
2468
  for (const link of links) {
2455
2469
  if (isExternalLink(link.path)) {
2456
- externalLinks.push(link);
2470
+ externalLinks.set(link.path, link.text);
2457
2471
  continue;
2458
2472
  }
2459
2473
  const resolved = resolveLinkPath(baseDir, link.path);
@@ -2467,15 +2481,32 @@ async function expandMarkdownLinks(content, baseDir, seenPaths, maxDepth = 2, cu
2467
2481
  if (!st.isFile())
2468
2482
  continue;
2469
2483
  let linkContent = await (0, fs_extra_1.readFile)(resolved, 'utf-8');
2484
+ if (linkContent.trim().length <= 30)
2485
+ continue;
2486
+ // v8.3.138+: HTML/原型文件使用独立阈值(8KB),避免原型文件被过度截断。
2487
+ // 超过阈值不截断 inline(避免结构残缺误导 AI),而是提示宿主 AI 直接打开查看。
2488
+ const isHtml = resolved.endsWith('.html') || resolved.endsWith('.htm');
2489
+ const htmlMaxChars = 8192; // 8KB,足够覆盖大多数 HTML 原型
2490
+ const isLarge = isHtml ? linkContent.length > htmlMaxChars : linkContent.length > maxChars;
2491
+ if (isHtml && isLarge) {
2492
+ expansions.push(`\n\n---\n📄 **HTML 原型文件**(内容过大未展开,宿主 AI 请务必直接打开查看):\n` +
2493
+ `- 文件: ${resolved}\n` +
2494
+ `- 大小: ${(linkContent.length / 1024).toFixed(1)} KB\n` +
2495
+ `- 描述: ${link.text || '无描述'}\n` +
2496
+ `> ⚠️ **请打开此 HTML 文件查看完整内容**。此为页面原型/设计稿,与需求理解高度相关,CLI 因文件过大未完整展开,需要你直接打开文件查看。`);
2497
+ continue;
2498
+ }
2470
2499
  // v8.3.122+: HTML 原型文件保留完整内容,不再提取纯文本
2471
- if (resolved.endsWith('.html') || resolved.endsWith('.htm')) {
2500
+ if (isHtml) {
2472
2501
  // 保留原始 HTML(包含 CSS/JS/结构),让 AI 完整解析原型
2473
2502
  }
2474
- if (linkContent.trim().length <= 30)
2475
- continue;
2476
- const isTruncated = linkContent.length > maxChars;
2503
+ const isTruncated = isHtml ? linkContent.length > htmlMaxChars : linkContent.length > maxChars;
2477
2504
  if (isTruncated) {
2478
- linkContent = linkContent.slice(0, maxChars) + `\n\n> ... (已截断,完整内容请 Read: ${resolved})`;
2505
+ linkContent = linkContent.slice(0, maxChars) +
2506
+ `\n\n---\n📄 **文件内容已截断**(宿主 AI 请务必打开查看完整内容):\n` +
2507
+ `- 文件: ${resolved}\n` +
2508
+ `- 大小: ${(await (0, fs_extra_1.stat)(resolved)).size} 字节\n` +
2509
+ `> ⚠️ **请打开此文件查看完整内容**。此文件与需求理解高度相关,CLI 因长度限制已截断,需要你直接打开文件查看。`;
2479
2510
  }
2480
2511
  expansions.push(`\n\n<!-- 展开链接: ${link.path} -->\n**[链接展开] ${link.text}** (${link.path}):\n\n${linkContent}`);
2481
2512
  }
@@ -2483,11 +2514,11 @@ async function expandMarkdownLinks(content, baseDir, seenPaths, maxDepth = 2, cu
2483
2514
  }
2484
2515
  let expanded = content + expansions.join('');
2485
2516
  // v8.3.125+: 在内容末尾附加外链资源清单,提示宿主 AI 按需读取
2486
- if (externalLinks.length > 0) {
2517
+ if (externalLinks.size > 0) {
2487
2518
  const externalSection = [
2488
2519
  '\n\n---',
2489
- '📎 **外链资源清单**(CLI 无法直接获取内容,宿主 AI 可按需访问):',
2490
- ...externalLinks.map(l => `- [${l.text || '链接'}](${l.path})`),
2520
+ '📎 **外链资源清单**(CLI 无法直接获取内容,宿主 AI 请务必按需访问):',
2521
+ ...Array.from(externalLinks.entries()).map(([path, text]) => `- [${text || '链接'}](${path})`),
2491
2522
  ].join('\n');
2492
2523
  expanded += externalSection;
2493
2524
  }
@@ -2505,11 +2536,11 @@ async function inlineMarkdownImages(content, baseDir, seenPaths, visionConfig, m
2505
2536
  const inlines = [];
2506
2537
  let visionCallCount = 0;
2507
2538
  const maxVisionCalls = visionConfig?.maxImagesPerPrompt ?? 10;
2508
- // v8.3.125+: 收集外链图片,供宿主 AI 按需查看
2509
- const externalImages = [];
2539
+ // v8.3.125+: 收集外链图片,供宿主 AI 按需查看(用 Map 按 URL 去重)
2540
+ const externalImages = new Map(); // path -> alt
2510
2541
  for (const img of images) {
2511
2542
  if (isExternalLink(img.path)) {
2512
- externalImages.push(img);
2543
+ externalImages.set(img.path, img.alt);
2513
2544
  inlines.push(`\n<!-- 外链图片: ${img.alt || '无描述'} | URL: ${img.path} -->`);
2514
2545
  continue;
2515
2546
  }
@@ -2526,7 +2557,7 @@ async function inlineMarkdownImages(content, baseDir, seenPaths, visionConfig, m
2526
2557
  inlines.push(`\n<!-- SVG 图片: ${img.path} -->\n${svgContent}`);
2527
2558
  }
2528
2559
  else {
2529
- inlines.push(`\n<!-- SVG 图片: ${img.path} (内容过长已省略,大小: ${svgContent.length} 字符) -->`);
2560
+ inlines.push(`\n\n---\n🖼️ **SVG 图片文件**(内容过长已省略,宿主 AI 请务必直接打开查看):\n- ${img.path}\n- 大小: ${svgContent.length} 字符(超出 ${maxSvgChars} 字符限制)\n> ⚠️ **请打开此 SVG 文件查看完整内容**。此 SVG 与需求理解高度相关,因内容过长被省略,需要你直接打开文件查看。`);
2530
2561
  }
2531
2562
  }
2532
2563
  }
@@ -2541,31 +2572,35 @@ async function inlineMarkdownImages(content, baseDir, seenPaths, visionConfig, m
2541
2572
  inlines.push(`\n<!-- 图片描述 (视觉模型: ${visionConfig.provider}): ${img.path} -->\n**[图片内容]** ${img.alt || '无描述'}\n\n${result.description}`);
2542
2573
  }
2543
2574
  else {
2544
- inlines.push(`\n<!-- 图片: ${img.alt || '无描述'} | 路径: ${img.path} | 描述失败: ${result.error} -->`);
2575
+ // v8.3.138+: Vision 描述失败时,明确提示宿主 AI 打开文件查看
2576
+ inlines.push(`\n\n---\n🖼️ **图片文件**(视觉模型描述失败,宿主 AI 请务必直接打开查看):\n- ${img.alt || '无描述'}: ${resolved}\n> ⚠️ **请打开此图片文件查看内容**。此图片与需求理解高度相关,视觉模型未能成功描述,需要你直接打开文件查看。`);
2545
2577
  }
2546
2578
  }
2547
2579
  catch (e) {
2548
- inlines.push(`\n<!-- 图片: ${img.alt || '无描述'} | 路径: ${img.path} | 描述失败: ${e.message || e} -->`);
2580
+ // v8.3.138+: 描述异常时,同样提示宿主 AI 打开查看
2581
+ inlines.push(`\n\n---\n🖼️ **图片文件**(视觉模型异常,宿主 AI 请务必直接打开查看):\n- ${img.alt || '无描述'}: ${resolved}\n> ⚠️ **请打开此图片文件查看内容**。此图片与需求理解高度相关,视觉模型异常未能描述,需要你直接打开文件查看。`);
2549
2582
  }
2550
2583
  }
2551
2584
  else {
2552
- // 其他图片(PNG/JPG/GIF/WebP 等):提取 alt + 路径 + 文件大小信息
2553
- inlines.push(`\n<!-- 图片: ${img.alt || '无描述'} | 路径: ${img.path} -->`);
2585
+ // v8.3.138+: 无视觉模型时,明确提示宿主 AI 打开图片查看(不要用 HTML 注释,AI 会忽略)
2586
+ inlines.push(`\n\n---\n🖼️ **图片文件**(CLI 无法解析图片内容,宿主 AI 请务必直接打开查看):`);
2587
+ inlines.push(`- ${img.alt || '无描述'}: ${resolved}`);
2554
2588
  try {
2555
2589
  if (await (0, fs_extra_1.pathExists)(resolved)) {
2556
2590
  const st = await (0, fs_extra_1.stat)(resolved);
2557
- inlines.push(`<!-- 图片大小: ${(st.size / 1024).toFixed(1)} KB | 完整路径: ${resolved} -->`);
2591
+ inlines.push(`- 文件大小: ${(st.size / 1024).toFixed(1)} KB`);
2558
2592
  }
2559
2593
  }
2560
2594
  catch { /* ignore */ }
2595
+ inlines.push(`> ⚠️ **请打开此图片文件查看内容**。此图片与需求理解高度相关,CLI 无法自动解析图片内容,需要你直接打开文件查看。`);
2561
2596
  }
2562
2597
  }
2563
2598
  // v8.3.125+: 在内容末尾附加外链图片清单,提示宿主 AI 按需查看
2564
- if (externalImages.length > 0) {
2599
+ if (externalImages.size > 0) {
2565
2600
  inlines.push([
2566
2601
  '\n\n---',
2567
- '🖼️ **外链图片清单**(CLI 无法直接获取内容,宿主 AI 可按需查看):',
2568
- ...externalImages.map(img => `- ${img.alt || '无描述'}: ${img.path}`),
2602
+ '🖼️ **外链图片清单**(CLI 无法直接获取内容,宿主 AI 请务必按需查看):',
2603
+ ...Array.from(externalImages.entries()).map(([path, alt]) => `- ${alt || '无描述'}: ${path}`),
2569
2604
  ].join('\n'));
2570
2605
  }
2571
2606
  return content + inlines.join('');