speccore 8.3.86 → 8.3.95

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 (107) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +1 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/analyze.d.ts.map +1 -1
  5. package/dist/commands/analyze.js +176 -17
  6. package/dist/commands/analyze.js.map +1 -1
  7. package/dist/commands/change.js +3 -3
  8. package/dist/commands/change.js.map +1 -1
  9. package/dist/commands/clarify.js +3 -3
  10. package/dist/commands/clarify.js.map +1 -1
  11. package/dist/commands/cleanup.js +6 -6
  12. package/dist/commands/cleanup.js.map +1 -1
  13. package/dist/commands/config.d.ts +2 -1
  14. package/dist/commands/config.d.ts.map +1 -1
  15. package/dist/commands/config.js +21 -2
  16. package/dist/commands/config.js.map +1 -1
  17. package/dist/commands/current.d.ts +2 -1
  18. package/dist/commands/current.d.ts.map +1 -1
  19. package/dist/commands/current.js +25 -5
  20. package/dist/commands/current.js.map +1 -1
  21. package/dist/commands/doc2spec.js +3 -3
  22. package/dist/commands/doc2spec.js.map +1 -1
  23. package/dist/commands/execute.js +92 -17
  24. package/dist/commands/execute.js.map +1 -1
  25. package/dist/commands/graph.js +2 -2
  26. package/dist/commands/graph.js.map +1 -1
  27. package/dist/commands/init.js +3 -3
  28. package/dist/commands/init.js.map +1 -1
  29. package/dist/commands/iteration/split.js +28 -28
  30. package/dist/commands/iteration/split.js.map +1 -1
  31. package/dist/commands/merge-check.d.ts.map +1 -1
  32. package/dist/commands/merge-check.js +41 -6
  33. package/dist/commands/merge-check.js.map +1 -1
  34. package/dist/commands/pr.d.ts.map +1 -1
  35. package/dist/commands/pr.js +63 -41
  36. package/dist/commands/pr.js.map +1 -1
  37. package/dist/commands/rag-index.js +3 -3
  38. package/dist/commands/rag-index.js.map +1 -1
  39. package/dist/commands/update.js +8 -8
  40. package/dist/commands/update.js.map +1 -1
  41. package/dist/commands/verify.d.ts +1 -0
  42. package/dist/commands/verify.d.ts.map +1 -1
  43. package/dist/commands/verify.js +13 -2
  44. package/dist/commands/verify.js.map +1 -1
  45. package/dist/core/analyze-engine.js +2 -2
  46. package/dist/core/analyze-engine.js.map +1 -1
  47. package/dist/core/ask-engine.d.ts.map +1 -1
  48. package/dist/core/ask-engine.js +10 -5
  49. package/dist/core/ask-engine.js.map +1 -1
  50. package/dist/core/change-detection.d.ts +14 -7
  51. package/dist/core/change-detection.d.ts.map +1 -1
  52. package/dist/core/change-detection.js +34 -15
  53. package/dist/core/change-detection.js.map +1 -1
  54. package/dist/core/code-scanner.js +3 -3
  55. package/dist/core/code-scanner.js.map +1 -1
  56. package/dist/core/deploy/engine.js +31 -3
  57. package/dist/core/deploy/engine.js.map +1 -1
  58. package/dist/core/git-integration.d.ts +17 -8
  59. package/dist/core/git-integration.d.ts.map +1 -1
  60. package/dist/core/git-integration.js +38 -24
  61. package/dist/core/git-integration.js.map +1 -1
  62. package/dist/core/global-knowledge.js +3 -3
  63. package/dist/core/global-knowledge.js.map +1 -1
  64. package/dist/core/index-guard.d.ts.map +1 -1
  65. package/dist/core/index-guard.js +2 -1
  66. package/dist/core/index-guard.js.map +1 -1
  67. package/dist/core/knowledge-graph.d.ts.map +1 -1
  68. package/dist/core/knowledge-graph.js +7 -11
  69. package/dist/core/knowledge-graph.js.map +1 -1
  70. package/dist/core/pattern-detector.js +1 -1
  71. package/dist/core/pattern-detector.js.map +1 -1
  72. package/dist/core/perf-verify/engine.js +2 -1
  73. package/dist/core/perf-verify/engine.js.map +1 -1
  74. package/dist/core/prompt-builder.d.ts.map +1 -1
  75. package/dist/core/prompt-builder.js +210 -18
  76. package/dist/core/prompt-builder.js.map +1 -1
  77. package/dist/core/quality-gate.d.ts +5 -0
  78. package/dist/core/quality-gate.d.ts.map +1 -1
  79. package/dist/core/quality-gate.js +2 -0
  80. package/dist/core/quality-gate.js.map +1 -1
  81. package/dist/core/rag-engine.d.ts +8 -0
  82. package/dist/core/rag-engine.d.ts.map +1 -1
  83. package/dist/core/rag-engine.js +41 -3
  84. package/dist/core/rag-engine.js.map +1 -1
  85. package/dist/core/requirement-clarifier.js +4 -4
  86. package/dist/core/requirement-clarifier.js.map +1 -1
  87. package/dist/core/semantic-locator.js +5 -5
  88. package/dist/core/semantic-locator.js.map +1 -1
  89. package/dist/core/ui-verify/route-scanner.js +1 -1
  90. package/dist/core/ui-verify/route-scanner.js.map +1 -1
  91. package/dist/core/ui-verify/smoke-engine.d.ts +2 -0
  92. package/dist/core/ui-verify/smoke-engine.d.ts.map +1 -1
  93. package/dist/core/ui-verify/smoke-engine.js +202 -34
  94. package/dist/core/ui-verify/smoke-engine.js.map +1 -1
  95. package/dist/core/ui-verify/spec-generator.js +2 -0
  96. package/dist/core/ui-verify/spec-generator.js.map +1 -1
  97. package/dist/core/ui-verify/types.d.ts +8 -1
  98. package/dist/core/ui-verify/types.d.ts.map +1 -1
  99. package/dist/core/unified-config.d.ts +17 -0
  100. package/dist/core/unified-config.d.ts.map +1 -1
  101. package/dist/core/unified-config.js +23 -4
  102. package/dist/core/unified-config.js.map +1 -1
  103. package/dist/core/vision-engine.d.ts +27 -0
  104. package/dist/core/vision-engine.d.ts.map +1 -0
  105. package/dist/core/vision-engine.js +358 -0
  106. package/dist/core/vision-engine.js.map +1 -0
  107. package/package.json +1 -1
@@ -21,7 +21,10 @@ const logger_1 = require("../utils/logger");
21
21
  const knowledge_graph_1 = require("./knowledge-graph");
22
22
  const context_builder_1 = require("./context-builder");
23
23
  const spec_paths_1 = require("./spec-paths");
24
+ const rag_engine_1 = require("./rag-engine");
24
25
  const unified_retrieval_1 = require("./unified-retrieval");
26
+ // v8.3.94+: 视觉模型引擎( specs 图片理解)
27
+ const vision_engine_1 = require("./vision-engine");
25
28
  // v6.93.0+: Prompt 插件系统
26
29
  const prompt_plugins_1 = require("./prompt-plugins");
27
30
  const techStackCache = new Map();
@@ -118,7 +121,7 @@ async function loadApiSpecs(cwd, taskDir, reqContent) {
118
121
  if (tableMatch) {
119
122
  const startIdx = content.indexOf(tableMatch[0]);
120
123
  const afterTable = content.substring(startIdx);
121
- const lines = afterTable.split('\n');
124
+ const lines = afterTable.split(/\r?\n/);
122
125
  for (let i = 2; i < lines.length; i++) { // 跳过表头和分隔线
123
126
  const line = lines[i].trim();
124
127
  if (!line.startsWith('|'))
@@ -151,7 +154,7 @@ async function loadDataModels(cwd, taskDir, reqContent) {
151
154
  const fieldTableRegex = /\|.*字段.*\|.*类型.*\|.*说明.*\|/gi;
152
155
  let match;
153
156
  let currentModel = null;
154
- const lines = section.split('\n');
157
+ const lines = section.split(/\r?\n/);
155
158
  for (let i = 0; i < lines.length; i++) {
156
159
  // 检测模型名称(### 标题 或 **粗体**)
157
160
  const headingMatch = lines[i].match(/^#{2,4}\s*(.+)/);
@@ -192,7 +195,7 @@ async function loadBusinessRules(cwd, taskDir, reqContent) {
192
195
  const content = await cachedRead(constitutionCache, constitutionPath, async () => await (0, fs_extra_1.readFile)(constitutionPath, 'utf-8'));
193
196
  const namingSection = content.match(/##\s*命名规范[\s\S]*?(?=## |$)/i);
194
197
  if (namingSection) {
195
- const lines = namingSection[0].split('\n');
198
+ const lines = namingSection[0].split(/\r?\n/);
196
199
  for (const line of lines) {
197
200
  if (line.match(/^[-*]\s+(.+)/)) {
198
201
  rules.push({ rule: RegExp.$1.trim() });
@@ -205,7 +208,7 @@ async function loadBusinessRules(cwd, taskDir, reqContent) {
205
208
  if (content) {
206
209
  const ruleSection = content.match(/(?:业务规则|约束条件|Constraint)[\s\S]*?(?=## |\n##|$)/i);
207
210
  if (ruleSection) {
208
- const lines = ruleSection[0].split('\n');
211
+ const lines = ruleSection[0].split(/\r?\n/);
209
212
  for (const line of lines) {
210
213
  if (line.match(/^[-*]\s+(.+)/)) {
211
214
  rules.push({ rule: RegExp.$1.trim() });
@@ -227,6 +230,8 @@ async function loadExtraSpecs(cwd, taskDir, platform, iteration, options) {
227
230
  const MAX_TOTAL = options?.maxTotalChars ?? 8000;
228
231
  let totalChars = 0;
229
232
  const seenPaths = new Set();
233
+ // v8.3.94+: 读取视觉模型配置(用于 specs 图片理解)
234
+ const visionConfig = await (0, vision_engine_1.loadVisionConfig)(cwd);
230
235
  const files = [
231
236
  { name: '开发指南', path: '00-specs/DEV_GUIDE.md' },
232
237
  { name: '任务上下文', path: '_shared/CONTEXT.md' },
@@ -258,7 +263,9 @@ async function loadExtraSpecs(cwd, taskDir, platform, iteration, options) {
258
263
  if (await (0, fs_extra_1.pathExists)(featurePlatDir)) {
259
264
  const entries = await (0, fs_extra_1.readdir)(featurePlatDir, { withFileTypes: true });
260
265
  for (const entry of entries) {
261
- if (entry.name.endsWith('.md') && !(0, task_utils_1.isTimestampBackup)(entry.name)) {
266
+ const isMd = entry.name.endsWith('.md') && !(0, task_utils_1.isTimestampBackup)(entry.name);
267
+ const isHtml = (entry.name.endsWith('.html') || entry.name.endsWith('.htm')) && !(0, task_utils_1.isTimestampBackup)(entry.name);
268
+ if (isMd || isHtml) {
262
269
  files.push({
263
270
  name: `${feature}-${platform}端规格`,
264
271
  path: (0, path_1.join)(featurePlatDir, entry.name),
@@ -314,14 +321,23 @@ async function loadExtraSpecs(cwd, taskDir, platform, iteration, options) {
314
321
  const fullPath = (0, path_1.join)(cwd, taskDir, f.path);
315
322
  seenPaths.add(fullPath);
316
323
  if (await (0, fs_extra_1.pathExists)(fullPath)) {
317
- let content = await (0, fs_extra_1.readFile)(fullPath, 'utf-8');
324
+ let rawContent = await (0, fs_extra_1.readFile)(fullPath, 'utf-8');
325
+ // v8.3.92+: HTML 原型文件提取文本内容
326
+ const isHtml = fullPath.endsWith('.html') || fullPath.endsWith('.htm');
327
+ let content = isHtml ? (0, rag_engine_1.extractHtmlText)(rawContent) : rawContent;
328
+ // v8.3.93+: Markdown 链接自动展开 + 图片提取
329
+ if (fullPath.endsWith('.md')) {
330
+ content = await processMarkdownContent(content, fullPath, seenPaths, visionConfig, {
331
+ maxLinkDepth: 2, maxLinkChars: 1200, maxSvgChars: 1500,
332
+ });
333
+ }
318
334
  // 跳过空文件或纯占位符文件
319
335
  if (content.trim().length <= 50 || content.trim().match(/^#+\s*待填充|^<!--\s*AI-FILL\s*-->$/m)) {
320
336
  continue;
321
337
  }
322
338
  // 单文件大小限制
323
339
  if (content.length > MAX_PER_FILE) {
324
- content = content.slice(0, MAX_PER_FILE) + `\n\n> ... (已截断,原文件 ${content.length} 字)`;
340
+ content = content.slice(0, MAX_PER_FILE) + `\n\n> ... (已截断,原文件 ${rawContent.length} 字)`;
325
341
  }
326
342
  // 总大小限制
327
343
  if (totalChars + content.length > MAX_TOTAL) {
@@ -345,7 +361,16 @@ async function loadExtraSpecs(cwd, taskDir, platform, iteration, options) {
345
361
  continue;
346
362
  seenPaths.add(fullPath);
347
363
  try {
348
- let content = await (0, fs_extra_1.readFile)(fullPath, 'utf-8');
364
+ let rawContent = await (0, fs_extra_1.readFile)(fullPath, 'utf-8');
365
+ // v8.3.92+: HTML 原型文件提取文本内容
366
+ const isHtml = fullPath.endsWith('.html') || fullPath.endsWith('.htm');
367
+ let content = isHtml ? (0, rag_engine_1.extractHtmlText)(rawContent) : rawContent;
368
+ // v8.3.93+: Markdown 链接自动展开 + 图片提取
369
+ if (fullPath.endsWith('.md')) {
370
+ content = await processMarkdownContent(content, fullPath, seenPaths, visionConfig, {
371
+ maxLinkDepth: 2, maxLinkChars: 1200, maxSvgChars: 1500,
372
+ });
373
+ }
349
374
  if (content.trim().length <= 50 || content.trim().match(/^#+\s*待填充|^<!--\s*AI-FILL\s*-->$/m)) {
350
375
  continue;
351
376
  }
@@ -374,7 +399,7 @@ async function loadExtraSpecs(cwd, taskDir, platform, iteration, options) {
374
399
  */
375
400
  async function scanUserCustomFiles(cwd, taskDir, platform, seenPaths) {
376
401
  const results = [];
377
- const validExts = ['.md', '.yaml', '.yml', '.json'];
402
+ const validExts = ['.md', '.yaml', '.yml', '.json', '.html', '.htm'];
378
403
  const skipDirs = new Set(['.meta', '.git', 'node_modules', 'tests', 'src', 'dist', 'build']);
379
404
  // 1. 扫描 00-specs/ 下所有文件(排除已在白名单中的)
380
405
  const specsDir = (0, path_1.join)(cwd, taskDir, '00-specs');
@@ -448,17 +473,28 @@ async function loadAllTaskContext(cwd, taskDir, platform, iteration, graph) {
448
473
  const MAX_TOTAL = 20000;
449
474
  let totalChars = 0;
450
475
  const seen = new Set();
476
+ // v8.3.94+: 读取视觉模型配置(用于 specs 图片理解)
477
+ const visionConfig = await (0, vision_engine_1.loadVisionConfig)(cwd);
451
478
  const addFile = async (fullPath, name, relPath) => {
452
479
  if (seen.has(fullPath))
453
480
  return;
454
481
  seen.add(fullPath);
455
482
  if (!(await (0, fs_extra_1.pathExists)(fullPath)))
456
483
  return;
457
- let content = await (0, fs_extra_1.readFile)(fullPath, 'utf-8');
484
+ let rawContent = await (0, fs_extra_1.readFile)(fullPath, 'utf-8');
485
+ // v8.3.92+: HTML 原型文件提取文本内容
486
+ const isHtml = fullPath.endsWith('.html') || fullPath.endsWith('.htm');
487
+ let content = isHtml ? (0, rag_engine_1.extractHtmlText)(rawContent) : rawContent;
488
+ // v8.3.93+: Markdown 链接自动展开 + 图片提取
489
+ if (fullPath.endsWith('.md')) {
490
+ content = await processMarkdownContent(content, fullPath, seen, visionConfig, {
491
+ maxLinkDepth: 2, maxLinkChars: 1500, maxSvgChars: 2000,
492
+ });
493
+ }
458
494
  if (content.trim().length <= 50 || content.trim().match(/^#+\s*待填充|^<!--\s*AI-FILL\s*-->$/m))
459
495
  return;
460
496
  if (content.length > MAX_PER_FILE) {
461
- content = content.slice(0, MAX_PER_FILE) + `\n\n> ... (已截断,原文件 ${content.length} 字)`;
497
+ content = content.slice(0, MAX_PER_FILE) + `\n\n> ... (已截断,原文件 ${rawContent.length} 字)`;
462
498
  }
463
499
  if (totalChars + content.length > MAX_TOTAL)
464
500
  return;
@@ -484,7 +520,7 @@ async function loadAllTaskContext(cwd, taskDir, platform, iteration, graph) {
484
520
  continue;
485
521
  await scanTaskDir(fullPath, `${prefix}${item.name}/`);
486
522
  }
487
- else if (/\.(md|yaml|yml)$/i.test(item.name)) {
523
+ else if (/\.(md|yaml|yml|html|htm)$/i.test(item.name)) {
488
524
  // 排除自检阶段文件(TEST.md / SCHEMA.md / REVIEW.md 等)
489
525
  if (CODEGEN_EXCLUDE_FILES.has(item.name.toLowerCase()))
490
526
  continue;
@@ -593,7 +629,7 @@ const RULES_DESC = {
593
629
  */
594
630
  function extractHeadings(content) {
595
631
  const headings = [];
596
- for (const line of content.split('\n')) {
632
+ for (const line of content.split(/\r?\n/)) {
597
633
  if (line.startsWith('## ')) {
598
634
  headings.push(line.replace(/^##\s+/, '').trim());
599
635
  }
@@ -604,7 +640,7 @@ function extractHeadings(content) {
604
640
  * 提取首段摘要:# 标题后的第一段非空内容,≤ 200 字
605
641
  */
606
642
  function extractSummary(content) {
607
- const lines = content.split('\n');
643
+ const lines = content.split(/\r?\n/);
608
644
  let foundTitle = false;
609
645
  let summaryLines = [];
610
646
  let totalLen = 0;
@@ -698,7 +734,7 @@ function buildTOCEntry(path, description, content, maxSections) {
698
734
  sections: maxSections ? sections.slice(0, maxSections) : sections,
699
735
  summary: extractSummary(content) || undefined,
700
736
  platforms: extractPlatforms(path, content) || undefined,
701
- lineCount: content.split('\n').length,
737
+ lineCount: content.split(/\r?\n/).length,
702
738
  tags: extractTags(sections) || undefined,
703
739
  };
704
740
  }
@@ -889,8 +925,8 @@ function formatGlobalContext(ctx, platform) {
889
925
  for (const e of entries) {
890
926
  // PATTERNS:architecture/x.md → architecture; platforms/admin/x.md → admin
891
927
  const sub = group.prefix === 'PATTERNS:'
892
- ? e.path.replace('PATTERNS:', '').split('/')[0]
893
- : e.path.split('/')[1];
928
+ ? e.path.replace('PATTERNS:', '').split(/[\\/]/)[0]
929
+ : e.path.split(/[\\/]/)[1];
894
930
  // v8.2.0+: PATTERNS 按端过滤 — 只保留通用分类 + 当前端相关模式
895
931
  if (group.prefix === 'PATTERNS:' && platform) {
896
932
  const isCommon = PATTERN_COMMON_CATEGORIES.has(sub);
@@ -1291,7 +1327,7 @@ async function buildPrompt(command, options) {
1291
1327
  path: ccPath,
1292
1328
  content: `## 前后端一致性校验报告\n\n${ccContent}`,
1293
1329
  });
1294
- logger_1.logger?.info?.(` 📋 已注入一致性校验报告: ${ccPath.replace(cwd + '/', '')}`);
1330
+ logger_1.logger?.info?.(` 📋 已注入一致性校验报告: ${(0, path_1.relative)(cwd, ccPath)}`);
1295
1331
  break;
1296
1332
  }
1297
1333
  }
@@ -1721,6 +1757,162 @@ function outputNeedsInfo(req) {
1721
1757
  process.stdout.write(lines.join('\n'));
1722
1758
  process.exitCode = 11;
1723
1759
  }
1760
+ // ═══════════════════════════════════════════════════════════
1761
+ // Markdown 链接自动展开 + 图片提取(v8.3.93+)
1762
+ // ═══════════════════════════════════════════════════════════
1763
+ /** 提取 Markdown 文本链接 [text](path),排除图片链接 */
1764
+ function extractMarkdownLinks(content) {
1765
+ const links = [];
1766
+ const regex = /\[([^\]]+)\]\(([^)]+)\)/g;
1767
+ let match;
1768
+ while ((match = regex.exec(content)) !== null) {
1769
+ const text = match[1];
1770
+ const path = match[2].trim();
1771
+ // 跳过图片链接 ![alt](path) — 检查前一个字符
1772
+ if (match.index > 0 && content.charAt(match.index - 1) === '!')
1773
+ continue;
1774
+ // 跳过外部链接、锚点、协议链接
1775
+ if (/^(https?:|mailto:|ftp:|#|javascript:)/i.test(path))
1776
+ continue;
1777
+ links.push({ text, path });
1778
+ }
1779
+ return links;
1780
+ }
1781
+ /** 提取 Markdown 图片链接 ![alt](path) */
1782
+ function extractMarkdownImages(content) {
1783
+ const images = [];
1784
+ const regex = /!\[([^\]]*)\]\(([^)]+)\)/g;
1785
+ let match;
1786
+ while ((match = regex.exec(content)) !== null) {
1787
+ images.push({ alt: match[1], path: match[2].trim() });
1788
+ }
1789
+ return images;
1790
+ }
1791
+ /** 判断是否为外部链接 */
1792
+ function isExternalLink(path) {
1793
+ return /^(https?:|mailto:|ftp:|javascript:)/i.test(path);
1794
+ }
1795
+ /** 解析链接相对路径 */
1796
+ function resolveLinkPath(baseDir, linkPath) {
1797
+ if (linkPath.startsWith('/'))
1798
+ return linkPath;
1799
+ return (0, path_1.join)(baseDir, linkPath);
1800
+ }
1801
+ /** 递归展开 Markdown 链接指向的文件内容(防循环,限深度) */
1802
+ async function expandMarkdownLinks(content, baseDir, seenPaths, maxDepth = 2, currentDepth = 0, maxChars = 1500) {
1803
+ if (currentDepth >= maxDepth)
1804
+ return content;
1805
+ const links = extractMarkdownLinks(content);
1806
+ if (links.length === 0)
1807
+ return content;
1808
+ const expansions = [];
1809
+ for (const link of links) {
1810
+ if (isExternalLink(link.path))
1811
+ continue;
1812
+ const resolved = resolveLinkPath(baseDir, link.path);
1813
+ if (seenPaths.has(resolved))
1814
+ continue;
1815
+ seenPaths.add(resolved);
1816
+ try {
1817
+ if (!await (0, fs_extra_1.pathExists)(resolved))
1818
+ continue;
1819
+ const st = await (0, fs_extra_1.stat)(resolved);
1820
+ if (!st.isFile())
1821
+ continue;
1822
+ let linkContent = await (0, fs_extra_1.readFile)(resolved, 'utf-8');
1823
+ // HTML 文件提取文本
1824
+ if (resolved.endsWith('.html') || resolved.endsWith('.htm')) {
1825
+ linkContent = (0, rag_engine_1.extractHtmlText)(linkContent);
1826
+ }
1827
+ if (linkContent.trim().length <= 30)
1828
+ continue;
1829
+ if (linkContent.length > maxChars) {
1830
+ linkContent = linkContent.slice(0, maxChars) + '\n\n> ... (已截断)';
1831
+ }
1832
+ expansions.push(`\n\n<!-- 展开链接: ${link.path} -->\n**[链接展开] ${link.text}** (${link.path}):\n\n${linkContent}`);
1833
+ }
1834
+ catch { /* 忽略读取失败的链接文件 */ }
1835
+ }
1836
+ const expanded = content + expansions.join('');
1837
+ // 递归展开新内容中的链接(深度 + 1)
1838
+ if (currentDepth + 1 < maxDepth) {
1839
+ return expandMarkdownLinks(expanded, baseDir, seenPaths, maxDepth, currentDepth + 1, maxChars);
1840
+ }
1841
+ return expanded;
1842
+ }
1843
+ /** inline Markdown 图片信息(SVG 直接读取,启用视觉模型时描述位图,其他记录元信息) */
1844
+ async function inlineMarkdownImages(content, baseDir, seenPaths, visionConfig, maxSvgChars = 2000) {
1845
+ const images = extractMarkdownImages(content);
1846
+ if (images.length === 0)
1847
+ return content;
1848
+ const inlines = [];
1849
+ let visionCallCount = 0;
1850
+ const maxVisionCalls = visionConfig?.maxImagesPerPrompt ?? 10;
1851
+ for (const img of images) {
1852
+ if (isExternalLink(img.path)) {
1853
+ inlines.push(`\n<!-- 图片: ${img.alt || '无描述'} | URL: ${img.path} -->`);
1854
+ continue;
1855
+ }
1856
+ const resolved = resolveLinkPath(baseDir, img.path);
1857
+ if (seenPaths.has(resolved))
1858
+ continue;
1859
+ seenPaths.add(resolved);
1860
+ // SVG 直接 inline 其文本内容
1861
+ if (resolved.endsWith('.svg')) {
1862
+ try {
1863
+ if (await (0, fs_extra_1.pathExists)(resolved)) {
1864
+ const svgContent = await (0, fs_extra_1.readFile)(resolved, 'utf-8');
1865
+ if (svgContent.length <= maxSvgChars) {
1866
+ inlines.push(`\n<!-- SVG 图片: ${img.path} -->\n${svgContent}`);
1867
+ }
1868
+ else {
1869
+ inlines.push(`\n<!-- SVG 图片: ${img.path} (内容过长已省略,大小: ${svgContent.length} 字符) -->`);
1870
+ }
1871
+ }
1872
+ }
1873
+ catch { /* ignore */ }
1874
+ }
1875
+ else if ((0, vision_engine_1.isVisionEnabled)(visionConfig) && visionCallCount < maxVisionCalls) {
1876
+ // v8.3.94+: 调用视觉模型描述图片
1877
+ visionCallCount++;
1878
+ try {
1879
+ const result = await (0, vision_engine_1.describeImage)(resolved, visionConfig);
1880
+ if (result.success) {
1881
+ inlines.push(`\n<!-- 图片描述 (视觉模型: ${visionConfig.provider}): ${img.path} -->\n**[图片内容]** ${img.alt || '无描述'}\n\n${result.description}`);
1882
+ }
1883
+ else {
1884
+ inlines.push(`\n<!-- 图片: ${img.alt || '无描述'} | 路径: ${img.path} | 描述失败: ${result.error} -->`);
1885
+ }
1886
+ }
1887
+ catch (e) {
1888
+ inlines.push(`\n<!-- 图片: ${img.alt || '无描述'} | 路径: ${img.path} | 描述失败: ${e.message || e} -->`);
1889
+ }
1890
+ }
1891
+ else {
1892
+ // 其他图片(PNG/JPG/GIF/WebP 等):提取 alt + 路径 + 文件大小信息
1893
+ inlines.push(`\n<!-- 图片: ${img.alt || '无描述'} | 路径: ${img.path} -->`);
1894
+ try {
1895
+ if (await (0, fs_extra_1.pathExists)(resolved)) {
1896
+ const st = await (0, fs_extra_1.stat)(resolved);
1897
+ inlines.push(`<!-- 图片大小: ${(st.size / 1024).toFixed(1)} KB | 完整路径: ${resolved} -->`);
1898
+ }
1899
+ }
1900
+ catch { /* ignore */ }
1901
+ }
1902
+ }
1903
+ return content + inlines.join('');
1904
+ }
1905
+ /**
1906
+ * 统一处理 Markdown 内容:展开链接 + 提取图片
1907
+ * 在 loadExtraSpecs / loadAllTaskContext 的文件加载后调用
1908
+ */
1909
+ async function processMarkdownContent(content, filePath, seenPaths, visionConfig, options) {
1910
+ const baseDir = (0, path_1.dirname)(filePath);
1911
+ let processed = content;
1912
+ processed = await expandMarkdownLinks(processed, baseDir, seenPaths, options?.maxLinkDepth ?? 2, 0, options?.maxLinkChars ?? 1500);
1913
+ processed = await inlineMarkdownImages(processed, baseDir, seenPaths, visionConfig, options?.maxSvgChars ?? 2000);
1914
+ return processed;
1915
+ }
1724
1916
  function generateExamples(command, req) {
1725
1917
  const iter = req.provided.iteration || 'Q1';
1726
1918
  const task = req.availableOptions?.tasks?.[0] || 'Task-001';