speccore 8.3.87 → 8.3.97

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