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.
- package/README.md +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +176 -17
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/change.js +3 -3
- package/dist/commands/change.js.map +1 -1
- package/dist/commands/clarify.js +3 -3
- package/dist/commands/clarify.js.map +1 -1
- package/dist/commands/cleanup.js +6 -6
- package/dist/commands/cleanup.js.map +1 -1
- package/dist/commands/config.d.ts +2 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +21 -2
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/current.d.ts +2 -1
- package/dist/commands/current.d.ts.map +1 -1
- package/dist/commands/current.js +25 -5
- package/dist/commands/current.js.map +1 -1
- package/dist/commands/doc2spec.js +3 -3
- package/dist/commands/doc2spec.js.map +1 -1
- package/dist/commands/execute.js +92 -17
- package/dist/commands/execute.js.map +1 -1
- package/dist/commands/graph.js +2 -2
- package/dist/commands/graph.js.map +1 -1
- package/dist/commands/init.js +3 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/iteration/split.js +28 -28
- package/dist/commands/iteration/split.js.map +1 -1
- package/dist/commands/merge-check.d.ts.map +1 -1
- package/dist/commands/merge-check.js +41 -6
- package/dist/commands/merge-check.js.map +1 -1
- package/dist/commands/pr.d.ts.map +1 -1
- package/dist/commands/pr.js +63 -41
- package/dist/commands/pr.js.map +1 -1
- package/dist/commands/rag-index.js +3 -3
- package/dist/commands/rag-index.js.map +1 -1
- package/dist/commands/update.js +8 -8
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/verify.d.ts +1 -0
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +13 -2
- package/dist/commands/verify.js.map +1 -1
- package/dist/core/analyze-engine.js +2 -2
- package/dist/core/analyze-engine.js.map +1 -1
- package/dist/core/ask-engine.d.ts.map +1 -1
- package/dist/core/ask-engine.js +10 -5
- package/dist/core/ask-engine.js.map +1 -1
- package/dist/core/change-detection.d.ts +14 -7
- package/dist/core/change-detection.d.ts.map +1 -1
- package/dist/core/change-detection.js +34 -15
- package/dist/core/change-detection.js.map +1 -1
- package/dist/core/code-scanner.js +3 -3
- package/dist/core/code-scanner.js.map +1 -1
- package/dist/core/deploy/engine.js +31 -3
- package/dist/core/deploy/engine.js.map +1 -1
- package/dist/core/git-integration.d.ts +17 -8
- package/dist/core/git-integration.d.ts.map +1 -1
- package/dist/core/git-integration.js +38 -24
- package/dist/core/git-integration.js.map +1 -1
- package/dist/core/global-knowledge.js +3 -3
- package/dist/core/global-knowledge.js.map +1 -1
- package/dist/core/index-guard.d.ts.map +1 -1
- package/dist/core/index-guard.js +2 -1
- package/dist/core/index-guard.js.map +1 -1
- package/dist/core/knowledge-graph.d.ts.map +1 -1
- package/dist/core/knowledge-graph.js +7 -11
- package/dist/core/knowledge-graph.js.map +1 -1
- package/dist/core/pattern-detector.js +1 -1
- package/dist/core/pattern-detector.js.map +1 -1
- package/dist/core/perf-verify/engine.js +2 -1
- package/dist/core/perf-verify/engine.js.map +1 -1
- package/dist/core/prompt-builder.d.ts.map +1 -1
- package/dist/core/prompt-builder.js +210 -18
- package/dist/core/prompt-builder.js.map +1 -1
- package/dist/core/quality-gate.d.ts +5 -0
- package/dist/core/quality-gate.d.ts.map +1 -1
- package/dist/core/quality-gate.js +2 -0
- package/dist/core/quality-gate.js.map +1 -1
- package/dist/core/rag-engine.d.ts +8 -0
- package/dist/core/rag-engine.d.ts.map +1 -1
- package/dist/core/rag-engine.js +41 -3
- package/dist/core/rag-engine.js.map +1 -1
- package/dist/core/requirement-clarifier.js +4 -4
- package/dist/core/requirement-clarifier.js.map +1 -1
- package/dist/core/semantic-locator.js +5 -5
- package/dist/core/semantic-locator.js.map +1 -1
- package/dist/core/ui-verify/route-scanner.js +1 -1
- package/dist/core/ui-verify/route-scanner.js.map +1 -1
- package/dist/core/ui-verify/smoke-engine.d.ts +2 -0
- package/dist/core/ui-verify/smoke-engine.d.ts.map +1 -1
- package/dist/core/ui-verify/smoke-engine.js +202 -34
- package/dist/core/ui-verify/smoke-engine.js.map +1 -1
- package/dist/core/ui-verify/spec-generator.js +2 -0
- package/dist/core/ui-verify/spec-generator.js.map +1 -1
- package/dist/core/ui-verify/types.d.ts +8 -1
- package/dist/core/ui-verify/types.d.ts.map +1 -1
- package/dist/core/unified-config.d.ts +17 -0
- package/dist/core/unified-config.d.ts.map +1 -1
- package/dist/core/unified-config.js +23 -4
- package/dist/core/unified-config.js.map +1 -1
- package/dist/core/vision-engine.d.ts +27 -0
- package/dist/core/vision-engine.d.ts.map +1 -0
- package/dist/core/vision-engine.js +358 -0
- package/dist/core/vision-engine.js.map +1 -0
- 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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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> ... (已截断,原文件 ${
|
|
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
|
|
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
|
|
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> ... (已截断,原文件 ${
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
893
|
-
: e.path.split(
|
|
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?.(` 📋 已注入一致性校验报告: ${
|
|
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
|
+
// 跳过图片链接  — 检查前一个字符
|
|
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 图片链接  */
|
|
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';
|