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.
- 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 +195 -20
- 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 +34 -16
- 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.d.ts +1 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +121 -17
- 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/ai-context-generator.d.ts.map +1 -1
- package/dist/core/ai-context-generator.js +10 -1
- package/dist/core/ai-context-generator.js.map +1 -1
- package/dist/core/analyze-engine.d.ts.map +1 -1
- package/dist/core/analyze-engine.js +20 -4
- 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 +78 -5
- package/dist/core/deploy/engine.js.map +1 -1
- package/dist/core/git-integration.d.ts +10 -5
- package/dist/core/git-integration.d.ts.map +1 -1
- package/dist/core/git-integration.js +25 -17
- 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 +11 -0
- package/dist/core/prompt-builder.d.ts.map +1 -1
- package/dist/core/prompt-builder.js +213 -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 +167 -14
- 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
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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> ... (已截断,原文件 ${
|
|
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
|
|
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
|
|
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> ... (已截断,原文件 ${
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
893
|
-
: e.path.split(
|
|
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?.(` 📋 已注入一致性校验报告: ${
|
|
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
|
+
// 跳过图片链接  — 检查前一个字符
|
|
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 图片链接  */
|
|
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';
|