speccore 6.98.0 → 7.2.0
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/.agents/agents/README.md +79 -0
- package/.agents/agents/spec-analyzer.md +41 -0
- package/.agents/agents/spec-architect.md +57 -0
- package/.agents/agents/spec-change-detector.md +55 -0
- package/.agents/agents/spec-clarifier.md +83 -0
- package/.agents/agents/spec-executor.md +41 -0
- package/.agents/agents/spec-gatekeeper.md +84 -0
- package/.agents/agents/spec-global-analyzer.md +111 -0
- package/.agents/agents/spec-knowledge-curator.md +54 -0
- package/.agents/agents/spec-reviewer.md +46 -0
- package/.agents/agents/spec-security-auditor.md +56 -0
- package/.agents/agents/spec-tester.md +120 -0
- package/README.md +49 -1
- package/dist/cli.js +59 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze.d.ts +6 -0
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +648 -53
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/config.js +15 -4
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/dev.js +30 -8
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/graph.d.ts +25 -0
- package/dist/commands/graph.d.ts.map +1 -0
- package/dist/commands/graph.js +523 -0
- package/dist/commands/graph.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +62 -276
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/iteration-from-global.js +2 -2
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +149 -0
- package/dist/commands/status.js.map +1 -1
- package/dist/core/ask-engine.d.ts.map +1 -1
- package/dist/core/ask-engine.js +12 -5
- package/dist/core/ask-engine.js.map +1 -1
- package/dist/core/change-impact-global.d.ts +20 -0
- package/dist/core/change-impact-global.d.ts.map +1 -0
- package/dist/core/change-impact-global.js +130 -0
- package/dist/core/change-impact-global.js.map +1 -0
- package/dist/core/code-graph/parser.d.ts.map +1 -1
- package/dist/core/code-graph/parser.js +21 -1
- package/dist/core/code-graph/parser.js.map +1 -1
- package/dist/core/dev-llm.d.ts +10 -0
- package/dist/core/dev-llm.d.ts.map +1 -1
- package/dist/core/dev-llm.js.map +1 -1
- package/dist/core/doc-cross-reference.d.ts +10 -0
- package/dist/core/doc-cross-reference.d.ts.map +1 -0
- package/dist/core/doc-cross-reference.js +108 -0
- package/dist/core/doc-cross-reference.js.map +1 -0
- package/dist/core/doc-quality-gate.d.ts +28 -0
- package/dist/core/doc-quality-gate.d.ts.map +1 -0
- package/dist/core/doc-quality-gate.js +209 -0
- package/dist/core/doc-quality-gate.js.map +1 -0
- package/dist/core/graph-semantic.d.ts +51 -0
- package/dist/core/graph-semantic.d.ts.map +1 -0
- package/dist/core/graph-semantic.js +371 -0
- package/dist/core/graph-semantic.js.map +1 -0
- package/dist/core/incremental-analyzer.js +1 -1
- package/dist/core/incremental-analyzer.js.map +1 -1
- package/dist/core/intent-recognition.d.ts.map +1 -1
- package/dist/core/intent-recognition.js +40 -0
- package/dist/core/intent-recognition.js.map +1 -1
- package/dist/core/iteration-cache.d.ts +48 -0
- package/dist/core/iteration-cache.d.ts.map +1 -0
- package/dist/core/iteration-cache.js +148 -0
- package/dist/core/iteration-cache.js.map +1 -0
- package/dist/core/knowledge-graph.d.ts +3 -0
- package/dist/core/knowledge-graph.d.ts.map +1 -1
- package/dist/core/knowledge-graph.js +190 -0
- package/dist/core/knowledge-graph.js.map +1 -1
- package/dist/core/platform-addition.js +3 -3
- package/dist/core/platform-addition.js.map +1 -1
- package/dist/core/rag-engine.d.ts +30 -0
- package/dist/core/rag-engine.d.ts.map +1 -1
- package/dist/core/rag-engine.js +78 -0
- package/dist/core/rag-engine.js.map +1 -1
- package/dist/core/semantic-locator.d.ts +42 -0
- package/dist/core/semantic-locator.d.ts.map +1 -0
- package/dist/core/semantic-locator.js +290 -0
- package/dist/core/semantic-locator.js.map +1 -0
- package/dist/core/structured-extractor.d.ts +89 -0
- package/dist/core/structured-extractor.d.ts.map +1 -0
- package/dist/core/structured-extractor.js +428 -0
- package/dist/core/structured-extractor.js.map +1 -0
- package/dist/utils/mermaid-render.d.ts +39 -0
- package/dist/utils/mermaid-render.d.ts.map +1 -0
- package/dist/utils/mermaid-render.js +284 -0
- package/dist/utils/mermaid-render.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkDocumentQuality = checkDocumentQuality;
|
|
4
|
+
exports.runGlobalQualityGate = runGlobalQualityGate;
|
|
5
|
+
exports.printQualityReport = printQualityReport;
|
|
6
|
+
/**
|
|
7
|
+
* doc-quality-gate — 文档质量门禁
|
|
8
|
+
* v7.2.0+
|
|
9
|
+
*
|
|
10
|
+
* 全局分析文档生成后的自动质量检查:
|
|
11
|
+
* - 检测占位符("待导入"、"待补充"、"示例"、"_待填写_")
|
|
12
|
+
* - 检测空表格(只有表头没有数据行)
|
|
13
|
+
* - 检测字数过浅(少于阈值)
|
|
14
|
+
* - 检测缺少 Mermaid 图表(如 ARCHITECTURE.md 要求有图表)
|
|
15
|
+
* - 检测缺少关键章节
|
|
16
|
+
*
|
|
17
|
+
* 输出: 质量报告,标记问题章节,建议修复命令
|
|
18
|
+
*/
|
|
19
|
+
const fs_extra_1 = require("fs-extra");
|
|
20
|
+
const path_1 = require("path");
|
|
21
|
+
const logger_1 = require("../utils/logger");
|
|
22
|
+
// 占位符关键词
|
|
23
|
+
const PLACEHOLDER_PATTERNS = [
|
|
24
|
+
/待导入|待补充|待填写|待完善|待确认|待审核/,
|
|
25
|
+
/_待\w+_/,
|
|
26
|
+
/示例[::]\s*$/m,
|
|
27
|
+
/TODO|FIXME|XXX|HACK/,
|
|
28
|
+
/\|[\s-]*\|[\s-]*\|[\s-]*\|/, // 空表格行
|
|
29
|
+
];
|
|
30
|
+
// 需要 Mermaid 图表的文档
|
|
31
|
+
const CHART_REQUIRED_DOCS = [
|
|
32
|
+
'ARCHITECTURE.md', 'INTERACTION_MAP.md', 'DATA_FLOW.md',
|
|
33
|
+
'DEPLOYMENT.md', 'UI_FLOW.md', '_ASSOCIATION.md', '_MODULES.md',
|
|
34
|
+
];
|
|
35
|
+
// 关键章节要求
|
|
36
|
+
const REQUIRED_SECTIONS = {
|
|
37
|
+
'ARCHITECTURE.md': ['服务拓扑', '数据流', '部署关系', '容错设计'],
|
|
38
|
+
'API_CONTRACT.yaml': ['接口定义', '鉴权', '限流', '版本策略'],
|
|
39
|
+
'REQUIREMENT.md': ['产品愿景', '用户故事', '验收标准'],
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* 检查单个文档质量
|
|
43
|
+
*/
|
|
44
|
+
async function checkDocumentQuality(filePath) {
|
|
45
|
+
if (!(await (0, fs_extra_1.pathExists)(filePath)))
|
|
46
|
+
return null;
|
|
47
|
+
const content = await (0, fs_extra_1.readFile)(filePath, 'utf-8');
|
|
48
|
+
const lines = content.split('\n');
|
|
49
|
+
const basename_ = filePath.split('/').pop() || '';
|
|
50
|
+
const issues = [];
|
|
51
|
+
// 1. 检测占位符
|
|
52
|
+
for (let i = 0; i < lines.length; i++) {
|
|
53
|
+
const line = lines[i];
|
|
54
|
+
for (const pattern of PLACEHOLDER_PATTERNS) {
|
|
55
|
+
if (pattern.test(line)) {
|
|
56
|
+
issues.push({
|
|
57
|
+
file: filePath,
|
|
58
|
+
line: i + 1,
|
|
59
|
+
type: 'placeholder',
|
|
60
|
+
severity: 'error',
|
|
61
|
+
message: `发现占位符: "${line.trim().slice(0, 40)}"`,
|
|
62
|
+
suggestion: `补充具体内容,删除占位符`,
|
|
63
|
+
});
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// 2. 检测字数
|
|
69
|
+
const wordCount = content.replace(/\s+/g, '').length;
|
|
70
|
+
if (wordCount < 200) {
|
|
71
|
+
issues.push({
|
|
72
|
+
file: filePath,
|
|
73
|
+
type: 'too-short',
|
|
74
|
+
severity: 'error',
|
|
75
|
+
message: `文档过短: ${wordCount} 字(建议 > 500 字)`,
|
|
76
|
+
suggestion: `补充详细内容,增加分析深度`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
else if (wordCount < 500) {
|
|
80
|
+
issues.push({
|
|
81
|
+
file: filePath,
|
|
82
|
+
type: 'too-short',
|
|
83
|
+
severity: 'warning',
|
|
84
|
+
message: `文档偏短: ${wordCount} 字(建议 > 1000 字)`,
|
|
85
|
+
suggestion: `补充更多细节和案例`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// 3. 检测 Mermaid 图表
|
|
89
|
+
const hasMermaid = /```mermaid|:::mermaid/.test(content);
|
|
90
|
+
if (CHART_REQUIRED_DOCS.includes(basename_) && !hasMermaid) {
|
|
91
|
+
issues.push({
|
|
92
|
+
file: filePath,
|
|
93
|
+
type: 'missing-chart',
|
|
94
|
+
severity: 'error',
|
|
95
|
+
message: `缺少 Mermaid 图表(${basename_} 必须包含图表)`,
|
|
96
|
+
suggestion: `添加 Mermaid 图表(graph/sequenceDiagram/stateDiagram 等)`,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
// 4. 检测关键章节
|
|
100
|
+
const required = REQUIRED_SECTIONS[basename_];
|
|
101
|
+
if (required) {
|
|
102
|
+
for (const section of required) {
|
|
103
|
+
if (!content.includes(section)) {
|
|
104
|
+
issues.push({
|
|
105
|
+
file: filePath,
|
|
106
|
+
type: 'missing-section',
|
|
107
|
+
severity: 'warning',
|
|
108
|
+
message: `缺少关键章节: "${section}"`,
|
|
109
|
+
suggestion: `添加 "${section}" 章节`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// 5. 计算质量分
|
|
115
|
+
const errorCount = issues.filter(i => i.severity === 'error').length;
|
|
116
|
+
const warningCount = issues.filter(i => i.severity === 'warning').length;
|
|
117
|
+
const score = Math.max(0, 100 - errorCount * 20 - warningCount * 5);
|
|
118
|
+
return { file: filePath, issues, wordCount, hasMermaid, score };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* 扫描全局分析目录,检查所有文档质量
|
|
122
|
+
*/
|
|
123
|
+
async function runGlobalQualityGate(globalDir) {
|
|
124
|
+
const dir = globalDir || (0, path_1.join)(process.cwd(), '.speccore', 'GLOBAL');
|
|
125
|
+
const reports = [];
|
|
126
|
+
// 扫描 overview/ 和 requirements/ 和 platforms/
|
|
127
|
+
const subdirs = ['overview', 'requirements', 'platforms'];
|
|
128
|
+
for (const sub of subdirs) {
|
|
129
|
+
const subDir = (0, path_1.join)(dir, sub);
|
|
130
|
+
if (!(await (0, fs_extra_1.pathExists)(subDir)))
|
|
131
|
+
continue;
|
|
132
|
+
if (sub === 'platforms') {
|
|
133
|
+
// platforms/ 下有子目录
|
|
134
|
+
const entries = await (0, fs_extra_1.readdir)(subDir, { withFileTypes: true });
|
|
135
|
+
for (const entry of entries) {
|
|
136
|
+
if (entry.isDirectory()) {
|
|
137
|
+
const platformDir = (0, path_1.join)(subDir, entry.name);
|
|
138
|
+
const files = await (0, fs_extra_1.readdir)(platformDir);
|
|
139
|
+
for (const f of files.filter(f => f.endsWith('.md') || f.endsWith('.yaml') || f.endsWith('.yml'))) {
|
|
140
|
+
const report = await checkDocumentQuality((0, path_1.join)(platformDir, f));
|
|
141
|
+
if (report)
|
|
142
|
+
reports.push(report);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
const files = await (0, fs_extra_1.readdir)(subDir);
|
|
149
|
+
for (const f of files.filter(f => f.endsWith('.md') || f.endsWith('.yaml') || f.endsWith('.yml'))) {
|
|
150
|
+
const report = await checkDocumentQuality((0, path_1.join)(subDir, f));
|
|
151
|
+
if (report)
|
|
152
|
+
reports.push(report);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// 扫描 platforms/_shared/
|
|
157
|
+
const sharedDir = (0, path_1.join)(dir, 'platforms', '_shared');
|
|
158
|
+
if (await (0, fs_extra_1.pathExists)(sharedDir)) {
|
|
159
|
+
const files = await (0, fs_extra_1.readdir)(sharedDir);
|
|
160
|
+
for (const f of files.filter(f => f.endsWith('.md'))) {
|
|
161
|
+
const report = await checkDocumentQuality((0, path_1.join)(sharedDir, f));
|
|
162
|
+
if (report)
|
|
163
|
+
reports.push(report);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return reports;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* 打印质量报告
|
|
170
|
+
*/
|
|
171
|
+
function printQualityReport(reports) {
|
|
172
|
+
if (reports.length === 0) {
|
|
173
|
+
logger_1.logger.info('📋 未找到全局分析文档');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const totalErrors = reports.reduce((sum, r) => sum + r.issues.filter(i => i.severity === 'error').length, 0);
|
|
177
|
+
const totalWarnings = reports.reduce((sum, r) => sum + r.issues.filter(i => i.severity === 'warning').length, 0);
|
|
178
|
+
const avgScore = Math.round(reports.reduce((sum, r) => sum + r.score, 0) / reports.length);
|
|
179
|
+
logger_1.logger.info('');
|
|
180
|
+
logger_1.logger.info('🔍 全局分析文档质量报告');
|
|
181
|
+
logger_1.logger.info(` 文档数: ${reports.length} | 平均分: ${avgScore}/100 | ❌ ${totalErrors} | ⚠️ ${totalWarnings}`);
|
|
182
|
+
logger_1.logger.info('');
|
|
183
|
+
for (const report of reports) {
|
|
184
|
+
const fileName = report.file.split('/').slice(-2).join('/');
|
|
185
|
+
const errors = report.issues.filter(i => i.severity === 'error');
|
|
186
|
+
const warnings = report.issues.filter(i => i.severity === 'warning');
|
|
187
|
+
if (errors.length === 0 && warnings.length === 0) {
|
|
188
|
+
logger_1.logger.info(` ✅ ${fileName} (${report.wordCount}字, ${report.score}分)`);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const icon = errors.length > 0 ? '❌' : '⚠️';
|
|
192
|
+
logger_1.logger.info(` ${icon} ${fileName} (${report.wordCount}字, ${report.score}分) — ${errors.length} 错误, ${warnings.length} 警告`);
|
|
193
|
+
for (const issue of report.issues.slice(0, 3)) {
|
|
194
|
+
const lineInfo = issue.line ? `L${issue.line}` : '';
|
|
195
|
+
const icon2 = issue.severity === 'error' ? '❌' : '⚠️';
|
|
196
|
+
logger_1.logger.info(` ${icon2} ${lineInfo} ${issue.message}`);
|
|
197
|
+
}
|
|
198
|
+
if (report.issues.length > 3) {
|
|
199
|
+
logger_1.logger.info(` ... 还有 ${report.issues.length - 3} 个问题`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (totalErrors > 0) {
|
|
203
|
+
logger_1.logger.info('');
|
|
204
|
+
logger_1.logger.info('💡 修复建议:');
|
|
205
|
+
logger_1.logger.info(' speccore analyze --scope global --layer 4 --deep <文档名>');
|
|
206
|
+
logger_1.logger.info(' 或使用 --iterative 逐节补全');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=doc-quality-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doc-quality-gate.js","sourceRoot":"","sources":["../../src/core/doc-quality-gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* graph-semantic — LLM 增强的图谱语义查询
|
|
3
|
+
* v7.0.0+: 让图谱查询从"关键词匹配"升级为"语义理解"
|
|
4
|
+
*
|
|
5
|
+
* 核心能力:
|
|
6
|
+
* 1. 查询词语义扩展 —— "订单相关" → 扩展为 ["order", "booking", "purchase", "交易", "下单", "订单号"]
|
|
7
|
+
* 2. 候选结果语义排序 —— LLM 理解查询意图后,对匹配结果重新排序
|
|
8
|
+
* 3. 零 Token 回退 —— LLM 不可用时,降级到本地规则扩展
|
|
9
|
+
*/
|
|
10
|
+
import type { GraphEntity } from './knowledge-graph';
|
|
11
|
+
export interface SemanticExpansion {
|
|
12
|
+
/** 原始查询词 */
|
|
13
|
+
original: string;
|
|
14
|
+
/** LLM 理解的查询意图 */
|
|
15
|
+
intent: string;
|
|
16
|
+
/** 扩展后的关键词列表(含同义词、相关概念) */
|
|
17
|
+
keywords: string[];
|
|
18
|
+
/** 业务域推断 */
|
|
19
|
+
domain?: string;
|
|
20
|
+
/** 查询类型推断 */
|
|
21
|
+
queryType: 'code' | 'requirement' | 'task' | 'architecture' | 'mixed';
|
|
22
|
+
}
|
|
23
|
+
export interface RankedCandidate {
|
|
24
|
+
entity: GraphEntity;
|
|
25
|
+
/** 本地匹配得分 */
|
|
26
|
+
localScore: number;
|
|
27
|
+
/** LLM 语义得分 (0-100) */
|
|
28
|
+
semanticScore: number;
|
|
29
|
+
/** 综合得分 */
|
|
30
|
+
finalScore: number;
|
|
31
|
+
/** LLM 给出的匹配理由 */
|
|
32
|
+
reason?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 语义扩展查询词
|
|
36
|
+
* 优先使用 LLM,降级到本地规则
|
|
37
|
+
*/
|
|
38
|
+
export declare function expandQuerySemantically(question: string, options?: {
|
|
39
|
+
useLlm?: boolean;
|
|
40
|
+
}): Promise<SemanticExpansion>;
|
|
41
|
+
/**
|
|
42
|
+
* 使用 LLM 对候选结果做语义排序
|
|
43
|
+
*/
|
|
44
|
+
export declare function semanticRank(question: string, candidates: {
|
|
45
|
+
entity: GraphEntity;
|
|
46
|
+
localScore: number;
|
|
47
|
+
}[], options?: {
|
|
48
|
+
topK?: number;
|
|
49
|
+
useLlm?: boolean;
|
|
50
|
+
}): Promise<RankedCandidate[]>;
|
|
51
|
+
//# sourceMappingURL=graph-semantic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-semantic.d.ts","sourceRoot":"","sources":["../../src/core/graph-semantic.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAMrD,MAAM,WAAW,iBAAiB;IAChC,YAAY;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa;IACb,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,GAAG,cAAc,GAAG,OAAO,CAAC;CACvE;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,WAAW,CAAC;IACpB,aAAa;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,uBAAuB;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAMD;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAC7B,OAAO,CAAC,iBAAiB,CAAC,CAe5B;AAoJD;;GAEG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,EAAE,EACzD,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAC5C,OAAO,CAAC,eAAe,EAAE,CAAC,CA4B5B"}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* graph-semantic — LLM 增强的图谱语义查询
|
|
4
|
+
* v7.0.0+: 让图谱查询从"关键词匹配"升级为"语义理解"
|
|
5
|
+
*
|
|
6
|
+
* 核心能力:
|
|
7
|
+
* 1. 查询词语义扩展 —— "订单相关" → 扩展为 ["order", "booking", "purchase", "交易", "下单", "订单号"]
|
|
8
|
+
* 2. 候选结果语义排序 —— LLM 理解查询意图后,对匹配结果重新排序
|
|
9
|
+
* 3. 零 Token 回退 —— LLM 不可用时,降级到本地规则扩展
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.expandQuerySemantically = expandQuerySemantically;
|
|
13
|
+
exports.semanticRank = semanticRank;
|
|
14
|
+
const logger_1 = require("../utils/logger");
|
|
15
|
+
// ═══════════════════════════════════════════════════════════
|
|
16
|
+
// 查询语义扩展
|
|
17
|
+
// ═══════════════════════════════════════════════════════════
|
|
18
|
+
/**
|
|
19
|
+
* 语义扩展查询词
|
|
20
|
+
* 优先使用 LLM,降级到本地规则
|
|
21
|
+
*/
|
|
22
|
+
async function expandQuerySemantically(question, options) {
|
|
23
|
+
const useLlm = options?.useLlm !== false;
|
|
24
|
+
// 尝试 LLM 扩展
|
|
25
|
+
if (useLlm) {
|
|
26
|
+
try {
|
|
27
|
+
const llmResult = await expandWithLLM(question);
|
|
28
|
+
if (llmResult)
|
|
29
|
+
return llmResult;
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
logger_1.logger.debug('LLM 语义扩展失败,降级到本地规则:', e);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// 降级到本地规则扩展
|
|
36
|
+
return expandWithRules(question);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 使用 LLM 进行语义扩展
|
|
40
|
+
*/
|
|
41
|
+
async function expandWithLLM(question) {
|
|
42
|
+
const prompt = buildExpansionPrompt(question);
|
|
43
|
+
const response = await callLLM(prompt);
|
|
44
|
+
if (!response)
|
|
45
|
+
return null;
|
|
46
|
+
try {
|
|
47
|
+
const parsed = parseLlmJson(response);
|
|
48
|
+
if (!parsed || !Array.isArray(parsed.keywords))
|
|
49
|
+
return null;
|
|
50
|
+
return {
|
|
51
|
+
original: question,
|
|
52
|
+
intent: parsed.intent || '通用查询',
|
|
53
|
+
keywords: parsed.keywords,
|
|
54
|
+
domain: parsed.domain,
|
|
55
|
+
queryType: parsed.queryType || 'mixed',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 使用本地规则进行语义扩展(零 Token)
|
|
64
|
+
*/
|
|
65
|
+
function expandWithRules(question) {
|
|
66
|
+
const lower = question.toLowerCase();
|
|
67
|
+
const keywords = [];
|
|
68
|
+
// 1. 提取原始词
|
|
69
|
+
const rawWords = lower
|
|
70
|
+
.replace(/[^\u4e00-\u9fa5a-z0-9\s]/g, ' ')
|
|
71
|
+
.split(/\s+/)
|
|
72
|
+
.filter(w => w.length >= 2);
|
|
73
|
+
keywords.push(...rawWords);
|
|
74
|
+
// 2. 业务域同义词扩展
|
|
75
|
+
const DOMAIN_SYNONYMS = {
|
|
76
|
+
// 订单/交易域
|
|
77
|
+
'订单': ['order', 'booking', 'purchase', 'trade', 'transaction', '下单', '订单号', '订单状态', '订单管理'],
|
|
78
|
+
'order': ['订单', 'booking', 'purchase', 'trade', 'transaction', '下单'],
|
|
79
|
+
'交易': ['transaction', 'trade', 'order', '支付', 'purchase'],
|
|
80
|
+
'支付': ['payment', 'pay', 'checkout', 'transaction', '订单付款'],
|
|
81
|
+
// 用户域
|
|
82
|
+
'用户': ['user', 'account', 'member', 'customer', 'profile', '用户管理', '用户信息'],
|
|
83
|
+
'user': ['用户', 'account', 'member', 'customer', 'profile'],
|
|
84
|
+
'认证': ['auth', 'authentication', 'login', 'oauth', 'jwt', 'token', '登录', '鉴权'],
|
|
85
|
+
'auth': ['认证', 'authentication', 'login', 'oauth', 'jwt', 'token', '鉴权'],
|
|
86
|
+
'登录': ['login', 'signin', 'auth', 'authenticate', '认证', '登入'],
|
|
87
|
+
// 内容域
|
|
88
|
+
'内容': ['content', 'article', 'post', 'document', 'media', '素材'],
|
|
89
|
+
'content': ['内容', 'article', 'post', 'document', 'media'],
|
|
90
|
+
// 消息/通知域
|
|
91
|
+
'消息': ['message', 'notification', 'notice', 'push', 'sms', 'email', '通知'],
|
|
92
|
+
'notification': ['消息', 'notification', 'notice', 'push', '通知'],
|
|
93
|
+
// 数据域
|
|
94
|
+
'数据': ['data', 'database', 'model', 'entity', 'schema', '表', '字段'],
|
|
95
|
+
'data': ['数据', 'database', 'model', 'entity', 'schema'],
|
|
96
|
+
// 搜索域
|
|
97
|
+
'搜索': ['search', 'query', 'filter', 'find', 'lookup', '检索', '查询'],
|
|
98
|
+
'search': ['搜索', 'query', 'filter', 'find', 'lookup', '检索'],
|
|
99
|
+
// 配置域
|
|
100
|
+
'配置': ['config', 'configuration', 'settings', 'options', 'env', '环境变量'],
|
|
101
|
+
'config': ['配置', 'configuration', 'settings', 'options', 'env'],
|
|
102
|
+
// 监控域
|
|
103
|
+
'监控': ['monitor', 'observability', 'metrics', 'alert', 'log', '监控', '告警'],
|
|
104
|
+
'monitor': ['监控', 'observability', 'metrics', 'alert', 'log', '告警'],
|
|
105
|
+
// 安全域
|
|
106
|
+
'安全': ['security', 'safe', 'protect', 'encrypt', 'permission', '安全', '权限'],
|
|
107
|
+
'security': ['安全', 'safe', 'protect', 'encrypt', 'permission', '权限'],
|
|
108
|
+
// 性能域
|
|
109
|
+
'性能': ['performance', 'optimize', 'cache', 'speed', 'latency', '性能', '优化'],
|
|
110
|
+
'performance': ['性能', 'optimize', 'cache', 'speed', 'latency', '优化'],
|
|
111
|
+
// 测试域
|
|
112
|
+
'测试': ['test', 'testing', 'spec', 'e2e', 'unit', 'integration', '测试', '用例'],
|
|
113
|
+
'test': ['测试', 'testing', 'spec', 'e2e', 'unit', 'integration'],
|
|
114
|
+
// 部署域
|
|
115
|
+
'部署': ['deploy', 'deployment', 'release', 'ci/cd', 'pipeline', '部署', '发布'],
|
|
116
|
+
'deploy': ['部署', 'deployment', 'release', 'ci/cd', 'pipeline', '发布'],
|
|
117
|
+
};
|
|
118
|
+
for (const [key, syns] of Object.entries(DOMAIN_SYNONYMS)) {
|
|
119
|
+
if (lower.includes(key)) {
|
|
120
|
+
for (const syn of syns) {
|
|
121
|
+
if (!keywords.includes(syn))
|
|
122
|
+
keywords.push(syn);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// 3. 推断查询类型
|
|
127
|
+
let queryType = 'mixed';
|
|
128
|
+
if (/\b(code|代码|文件|类|函数|方法|源码|实现)\b/.test(lower))
|
|
129
|
+
queryType = 'code';
|
|
130
|
+
else if (/\b(需求|requirement|feature|功能|用户故事|story)\b/.test(lower))
|
|
131
|
+
queryType = 'requirement';
|
|
132
|
+
else if (/\b(任务|task|todo|计划|拆分)\b/.test(lower))
|
|
133
|
+
queryType = 'task';
|
|
134
|
+
else if (/\b(架构|architecture|设计|结构|拓扑|服务|模块关系)\b/.test(lower))
|
|
135
|
+
queryType = 'architecture';
|
|
136
|
+
// 4. 推断业务域
|
|
137
|
+
let domain;
|
|
138
|
+
for (const [key] of Object.entries(DOMAIN_SYNONYMS)) {
|
|
139
|
+
if (lower.includes(key)) {
|
|
140
|
+
domain = key;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
original: question,
|
|
146
|
+
intent: `查询与 "${question}" 相关的实体`,
|
|
147
|
+
keywords: [...new Set(keywords)],
|
|
148
|
+
domain,
|
|
149
|
+
queryType,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 构建语义扩展 Prompt
|
|
154
|
+
*/
|
|
155
|
+
function buildExpansionPrompt(question) {
|
|
156
|
+
return `你是代码知识图谱的语义查询助手。用户的查询可能包含口语化表达、模糊概念或业务术语。
|
|
157
|
+
|
|
158
|
+
任务:分析用户查询,输出结构化的语义扩展结果。
|
|
159
|
+
|
|
160
|
+
用户查询: "${question}"
|
|
161
|
+
|
|
162
|
+
要求:
|
|
163
|
+
1. 理解用户的真实意图(他们在找什么?)
|
|
164
|
+
2. 提取查询中的核心概念
|
|
165
|
+
3. 为每个核心概念生成同义词和相关术语(中英文都要)
|
|
166
|
+
4. 推断查询类型(code/requirement/task/architecture/mixed)
|
|
167
|
+
5. 推断业务域(如订单、用户、支付、内容等)
|
|
168
|
+
|
|
169
|
+
输出 JSON 格式(不要解释,只返回 JSON):
|
|
170
|
+
{
|
|
171
|
+
"intent": "用户的真实意图描述",
|
|
172
|
+
"keywords": ["原始词", "同义词1", "同义词2", "相关术语1", "英文术语1", "英文术语2"],
|
|
173
|
+
"domain": "业务域名称",
|
|
174
|
+
"queryType": "code|requirement|task|architecture|mixed"
|
|
175
|
+
}`;
|
|
176
|
+
}
|
|
177
|
+
// ═══════════════════════════════════════════════════════════
|
|
178
|
+
// 候选结果语义排序
|
|
179
|
+
// ═══════════════════════════════════════════════════════════
|
|
180
|
+
/**
|
|
181
|
+
* 使用 LLM 对候选结果做语义排序
|
|
182
|
+
*/
|
|
183
|
+
async function semanticRank(question, candidates, options) {
|
|
184
|
+
const topK = options?.topK || 20;
|
|
185
|
+
const useLlm = options?.useLlm !== false;
|
|
186
|
+
// 取本地得分最高的候选
|
|
187
|
+
const topCandidates = candidates
|
|
188
|
+
.sort((a, b) => b.localScore - a.localScore)
|
|
189
|
+
.slice(0, topK);
|
|
190
|
+
if (topCandidates.length === 0)
|
|
191
|
+
return [];
|
|
192
|
+
// 尝试 LLM 排序
|
|
193
|
+
if (useLlm && topCandidates.length > 1) {
|
|
194
|
+
try {
|
|
195
|
+
const ranked = await rankWithLLM(question, topCandidates);
|
|
196
|
+
if (ranked)
|
|
197
|
+
return ranked;
|
|
198
|
+
}
|
|
199
|
+
catch (e) {
|
|
200
|
+
logger_1.logger.debug('LLM 语义排序失败,使用本地得分:', e);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// 降级:使用本地得分作为最终得分
|
|
204
|
+
return topCandidates.map(c => ({
|
|
205
|
+
entity: c.entity,
|
|
206
|
+
localScore: c.localScore,
|
|
207
|
+
semanticScore: 0,
|
|
208
|
+
finalScore: c.localScore,
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* LLM 语义排序
|
|
213
|
+
*/
|
|
214
|
+
async function rankWithLLM(question, candidates) {
|
|
215
|
+
const prompt = buildRankingPrompt(question, candidates);
|
|
216
|
+
const response = await callLLM(prompt);
|
|
217
|
+
if (!response)
|
|
218
|
+
return null;
|
|
219
|
+
try {
|
|
220
|
+
const parsed = parseLlmJson(response);
|
|
221
|
+
if (!parsed || !Array.isArray(parsed.ranking))
|
|
222
|
+
return null;
|
|
223
|
+
const rankingMap = new Map();
|
|
224
|
+
for (const item of parsed.ranking) {
|
|
225
|
+
if (item.id && typeof item.score === 'number') {
|
|
226
|
+
rankingMap.set(item.id, { score: item.score, reason: item.reason || '' });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return candidates.map(c => {
|
|
230
|
+
const llmResult = rankingMap.get(c.entity.id);
|
|
231
|
+
const semanticScore = llmResult?.score || 0;
|
|
232
|
+
// 综合得分 = 本地得分 * 0.4 + LLM 语义得分 * 0.6
|
|
233
|
+
const finalScore = c.localScore * 0.4 + semanticScore * 0.6;
|
|
234
|
+
return {
|
|
235
|
+
entity: c.entity,
|
|
236
|
+
localScore: c.localScore,
|
|
237
|
+
semanticScore,
|
|
238
|
+
finalScore,
|
|
239
|
+
reason: llmResult?.reason,
|
|
240
|
+
};
|
|
241
|
+
}).sort((a, b) => b.finalScore - a.finalScore);
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* 构建排序 Prompt
|
|
249
|
+
*/
|
|
250
|
+
function buildRankingPrompt(question, candidates) {
|
|
251
|
+
const candidateList = candidates.map((c, i) => {
|
|
252
|
+
const e = c.entity;
|
|
253
|
+
const info = [
|
|
254
|
+
`ID: ${e.id}`,
|
|
255
|
+
`类型: ${e.type}`,
|
|
256
|
+
`标题: ${e.title}`,
|
|
257
|
+
e.description ? `描述: ${e.description.slice(0, 100)}` : '',
|
|
258
|
+
e.semanticTags?.length ? `语义标签: ${e.semanticTags.join(', ')}` : '',
|
|
259
|
+
e.businessRole ? `业务角色: ${e.businessRole}` : '',
|
|
260
|
+
e.file ? `文件: ${e.file}` : '',
|
|
261
|
+
].filter(Boolean).join('\n ');
|
|
262
|
+
return `${i + 1}. ${info}`;
|
|
263
|
+
}).join('\n\n');
|
|
264
|
+
return `你是代码知识图谱的语义匹配助手。
|
|
265
|
+
|
|
266
|
+
用户查询: "${question}"
|
|
267
|
+
|
|
268
|
+
以下是从知识图谱中初步匹配的候选实体(按本地关键词匹配得分排序):
|
|
269
|
+
|
|
270
|
+
${candidateList}
|
|
271
|
+
|
|
272
|
+
任务:判断每个候选实体与用户查询的语义相关性。
|
|
273
|
+
|
|
274
|
+
评分标准(0-100):
|
|
275
|
+
- 90-100: 完全匹配,直接回答用户问题
|
|
276
|
+
- 70-89: 高度相关,包含用户需要的核心信息
|
|
277
|
+
- 50-69: 部分相关,需要进一步探索
|
|
278
|
+
- 30-49: 弱相关,可能有关联但不直接
|
|
279
|
+
- 0-29: 不相关
|
|
280
|
+
|
|
281
|
+
输出 JSON 格式(不要解释,只返回 JSON):
|
|
282
|
+
{
|
|
283
|
+
"reasoning": "简短的整体分析",
|
|
284
|
+
"ranking": [
|
|
285
|
+
{ "id": "实体ID", "score": 85, "reason": "为什么相关/不相关" },
|
|
286
|
+
...
|
|
287
|
+
]
|
|
288
|
+
}`;
|
|
289
|
+
}
|
|
290
|
+
// ═══════════════════════════════════════════════════════════
|
|
291
|
+
// LLM 调用(复用 ask-llm 逻辑)
|
|
292
|
+
// ═══════════════════════════════════════════════════════════
|
|
293
|
+
async function callLLM(prompt) {
|
|
294
|
+
// 优先使用环境变量配置的 LLM
|
|
295
|
+
const endpoint = process.env.SPECCORE_LLM_ENDPOINT;
|
|
296
|
+
const apiKey = process.env.SPECCORE_LLM_KEY || process.env.OPENAI_API_KEY;
|
|
297
|
+
if (endpoint && apiKey) {
|
|
298
|
+
try {
|
|
299
|
+
const res = await fetch(endpoint, {
|
|
300
|
+
method: 'POST',
|
|
301
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
|
|
302
|
+
body: JSON.stringify({
|
|
303
|
+
model: process.env.SPECCORE_LLM_MODEL || 'gpt-4o-mini',
|
|
304
|
+
messages: [
|
|
305
|
+
{ role: 'system', content: 'You are a helpful assistant. Always respond with valid JSON only.' },
|
|
306
|
+
{ role: 'user', content: prompt },
|
|
307
|
+
],
|
|
308
|
+
temperature: 0.1,
|
|
309
|
+
max_tokens: 2000,
|
|
310
|
+
}),
|
|
311
|
+
signal: AbortSignal.timeout(15000), // 15s 超时
|
|
312
|
+
});
|
|
313
|
+
if (!res.ok) {
|
|
314
|
+
logger_1.logger.debug(`LLM API 错误: ${res.status} ${res.statusText}`);
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
const data = await res.json();
|
|
318
|
+
return data?.choices?.[0]?.message?.content || null;
|
|
319
|
+
}
|
|
320
|
+
catch (e) {
|
|
321
|
+
if (e.name === 'AbortError') {
|
|
322
|
+
logger_1.logger.debug('LLM 请求超时(15s),降级到本地规则');
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
logger_1.logger.debug(`LLM 请求失败: ${e.message}`);
|
|
326
|
+
}
|
|
327
|
+
// fallthrough
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// 尝试本地 Ollama
|
|
331
|
+
try {
|
|
332
|
+
const res = await fetch('http://localhost:11434/api/generate', {
|
|
333
|
+
method: 'POST',
|
|
334
|
+
headers: { 'Content-Type': 'application/json' },
|
|
335
|
+
body: JSON.stringify({
|
|
336
|
+
model: 'qwen2.5:7b',
|
|
337
|
+
prompt,
|
|
338
|
+
stream: false,
|
|
339
|
+
format: 'json',
|
|
340
|
+
options: { temperature: 0.1, num_predict: 2000 },
|
|
341
|
+
}),
|
|
342
|
+
signal: AbortSignal.timeout(8000),
|
|
343
|
+
});
|
|
344
|
+
if (res.ok) {
|
|
345
|
+
const data = await res.json();
|
|
346
|
+
return data?.response || null;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
catch { }
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* 解析 LLM 返回的 JSON
|
|
354
|
+
*/
|
|
355
|
+
function parseLlmJson(text) {
|
|
356
|
+
try {
|
|
357
|
+
return JSON.parse(text);
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
// 尝试提取 JSON 块
|
|
361
|
+
const match = text.match(/\{[\s\S]*\}/);
|
|
362
|
+
if (match) {
|
|
363
|
+
try {
|
|
364
|
+
return JSON.parse(match[0]);
|
|
365
|
+
}
|
|
366
|
+
catch { }
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
//# sourceMappingURL=graph-semantic.js.map
|