speccore 6.71.3 → 6.73.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/dist/cli.js +11 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +106 -0
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/change.d.ts +10 -0
- package/dist/commands/change.d.ts.map +1 -1
- package/dist/commands/change.js +550 -175
- package/dist/commands/change.js.map +1 -1
- package/dist/commands/execute.js +10 -0
- package/dist/commands/execute.js.map +1 -1
- package/dist/commands/iteration/split.d.ts +1 -0
- package/dist/commands/iteration/split.d.ts.map +1 -1
- package/dist/commands/iteration/split.js +33 -4
- package/dist/commands/iteration/split.js.map +1 -1
- package/dist/core/ai-impact-analyzer.d.ts +127 -0
- package/dist/core/ai-impact-analyzer.d.ts.map +1 -0
- package/dist/core/ai-impact-analyzer.js +531 -0
- package/dist/core/ai-impact-analyzer.js.map +1 -0
- package/dist/core/change-inbox.d.ts +80 -0
- package/dist/core/change-inbox.d.ts.map +1 -0
- package/dist/core/change-inbox.js +352 -0
- package/dist/core/change-inbox.js.map +1 -0
- package/dist/core/change-parser.d.ts +27 -0
- package/dist/core/change-parser.d.ts.map +1 -0
- package/dist/core/change-parser.js +248 -0
- package/dist/core/change-parser.js.map +1 -0
- package/dist/core/prompt-builder.d.ts.map +1 -1
- package/dist/core/prompt-builder.js +29 -0
- package/dist/core/prompt-builder.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ai-impact-analyzer — AI 影响分析器(v6.73.0)
|
|
3
|
+
*
|
|
4
|
+
* 分层 AI + 知识图谱联动的变更影响分析引擎:
|
|
5
|
+
* 1. 检索层(零 LLM 成本): unifiedSearch 语义检索 + 知识图谱查询
|
|
6
|
+
* 2. 推理层(1 次 LLM 调用): 将检索结果送入 LLM,输出结构化影响分析
|
|
7
|
+
* 3. 生成层(合并到同 1 次 LLM 调用): 生成 CHANGE_TODO / 代码变更清单
|
|
8
|
+
*
|
|
9
|
+
* 降级策略:LLM 不可用时,回退到基于规则的分级(语义相关度阈值)
|
|
10
|
+
*/
|
|
11
|
+
import { UnifiedResult } from './unified-retrieval';
|
|
12
|
+
import { GraphEntity, GraphRelation } from './knowledge-graph';
|
|
13
|
+
import { ChangeCategory } from './change-parser';
|
|
14
|
+
/** 语义检索后的任务匹配结果 */
|
|
15
|
+
export interface TaskSemanticMatch {
|
|
16
|
+
taskId: string;
|
|
17
|
+
taskName: string;
|
|
18
|
+
status: string;
|
|
19
|
+
score: number;
|
|
20
|
+
matchedContext: string;
|
|
21
|
+
files: string[];
|
|
22
|
+
}
|
|
23
|
+
/** 代码级影响 */
|
|
24
|
+
export interface CodeImpact {
|
|
25
|
+
file: string;
|
|
26
|
+
currentImplementation: string;
|
|
27
|
+
suggestedChange: string;
|
|
28
|
+
reason: string;
|
|
29
|
+
platform?: string;
|
|
30
|
+
}
|
|
31
|
+
/** 全局层影响 */
|
|
32
|
+
export interface GlobalImpact {
|
|
33
|
+
artifact: string;
|
|
34
|
+
reason: string;
|
|
35
|
+
suggestedAction: string;
|
|
36
|
+
}
|
|
37
|
+
/** 跨迭代影响 */
|
|
38
|
+
export interface CrossIterationImpact {
|
|
39
|
+
iteration: string;
|
|
40
|
+
taskId: string;
|
|
41
|
+
reason: string;
|
|
42
|
+
severity: 'warning' | 'critical';
|
|
43
|
+
}
|
|
44
|
+
/** AI 影响分析结果 */
|
|
45
|
+
export interface AiImpactAnalysis {
|
|
46
|
+
thinking: string;
|
|
47
|
+
taskImpacts: {
|
|
48
|
+
direct: TaskSemanticMatch[];
|
|
49
|
+
indirect: TaskSemanticMatch[];
|
|
50
|
+
unaffected: TaskSemanticMatch[];
|
|
51
|
+
};
|
|
52
|
+
codeImpacts: CodeImpact[];
|
|
53
|
+
globalImpacts: GlobalImpact[];
|
|
54
|
+
crossIterationImpacts: CrossIterationImpact[];
|
|
55
|
+
executionPlan: {
|
|
56
|
+
steps: string[];
|
|
57
|
+
regressionTests: string[];
|
|
58
|
+
globalRefreshSuggestions: string[];
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** 检索上下文 */
|
|
62
|
+
export interface RetrievalContext {
|
|
63
|
+
documentChunks: {
|
|
64
|
+
source: string;
|
|
65
|
+
content: string;
|
|
66
|
+
score: number;
|
|
67
|
+
}[];
|
|
68
|
+
codeSlices: {
|
|
69
|
+
file: string;
|
|
70
|
+
content: string;
|
|
71
|
+
score: number;
|
|
72
|
+
}[];
|
|
73
|
+
kgEntities: GraphEntity[];
|
|
74
|
+
kgRelations: GraphRelation[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 语义检索:替代关键词匹配,使用 unifiedSearch 做真正的语义检索
|
|
78
|
+
*/
|
|
79
|
+
export declare function semanticImpactAnalysis(desc: string, iteration: string, options?: {
|
|
80
|
+
withCode?: boolean;
|
|
81
|
+
}): Promise<{
|
|
82
|
+
docResult: UnifiedResult;
|
|
83
|
+
codeResult: UnifiedResult | null;
|
|
84
|
+
}>;
|
|
85
|
+
/**
|
|
86
|
+
* 知识图谱查询:获取变更相关的实体和关联关系
|
|
87
|
+
*/
|
|
88
|
+
export declare function analyzeWithKnowledgeGraph(desc: string, iteration: string, matchedTaskIds: string[]): Promise<{
|
|
89
|
+
entities: GraphEntity[];
|
|
90
|
+
relations: GraphRelation[];
|
|
91
|
+
}>;
|
|
92
|
+
/**
|
|
93
|
+
* 按任务分组,计算语义相关度
|
|
94
|
+
*/
|
|
95
|
+
export declare function groupByTask(docResult: UnifiedResult, allTasks: {
|
|
96
|
+
id: string;
|
|
97
|
+
name: string;
|
|
98
|
+
status: string;
|
|
99
|
+
}[]): TaskSemanticMatch[];
|
|
100
|
+
/**
|
|
101
|
+
* 基于相关度阈值分类任务影响级别
|
|
102
|
+
* 降级策略:LLM 不可用时使用
|
|
103
|
+
*/
|
|
104
|
+
export declare function classifyByThreshold(matches: TaskSemanticMatch[], graphContent: string): {
|
|
105
|
+
direct: TaskSemanticMatch[];
|
|
106
|
+
indirect: TaskSemanticMatch[];
|
|
107
|
+
unaffected: TaskSemanticMatch[];
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* AI 影响分析主入口
|
|
111
|
+
* 合并检索 → LLM 推理 → 生成实施计划 为一次调用
|
|
112
|
+
*
|
|
113
|
+
* 降级:LLM 不可用时返回基于阈值的分类结果
|
|
114
|
+
*/
|
|
115
|
+
export declare function aiImpactAnalysis(desc: string, category: ChangeCategory, iteration: string, allTasks: {
|
|
116
|
+
id: string;
|
|
117
|
+
name: string;
|
|
118
|
+
status: string;
|
|
119
|
+
}[], options?: {
|
|
120
|
+
withCode?: boolean;
|
|
121
|
+
useLlm?: boolean;
|
|
122
|
+
}): Promise<AiImpactAnalysis>;
|
|
123
|
+
/**
|
|
124
|
+
* 生成 CHANGE_TODO.md 内容
|
|
125
|
+
*/
|
|
126
|
+
export declare function generateChangeTodo(changeDesc: string, category: ChangeCategory, analysis: AiImpactAnalysis, changeId?: string): string;
|
|
127
|
+
//# sourceMappingURL=ai-impact-analyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-impact-analyzer.d.ts","sourceRoot":"","sources":["../../src/core/ai-impact-analyzer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ai-impact-analyzer — AI 影响分析器(v6.73.0)
|
|
4
|
+
*
|
|
5
|
+
* 分层 AI + 知识图谱联动的变更影响分析引擎:
|
|
6
|
+
* 1. 检索层(零 LLM 成本): unifiedSearch 语义检索 + 知识图谱查询
|
|
7
|
+
* 2. 推理层(1 次 LLM 调用): 将检索结果送入 LLM,输出结构化影响分析
|
|
8
|
+
* 3. 生成层(合并到同 1 次 LLM 调用): 生成 CHANGE_TODO / 代码变更清单
|
|
9
|
+
*
|
|
10
|
+
* 降级策略:LLM 不可用时,回退到基于规则的分级(语义相关度阈值)
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.semanticImpactAnalysis = semanticImpactAnalysis;
|
|
14
|
+
exports.analyzeWithKnowledgeGraph = analyzeWithKnowledgeGraph;
|
|
15
|
+
exports.groupByTask = groupByTask;
|
|
16
|
+
exports.classifyByThreshold = classifyByThreshold;
|
|
17
|
+
exports.aiImpactAnalysis = aiImpactAnalysis;
|
|
18
|
+
exports.generateChangeTodo = generateChangeTodo;
|
|
19
|
+
const logger_1 = require("../utils/logger");
|
|
20
|
+
const unified_retrieval_1 = require("./unified-retrieval");
|
|
21
|
+
const knowledge_graph_1 = require("./knowledge-graph");
|
|
22
|
+
// ── 1. 检索层(零 LLM 成本)──
|
|
23
|
+
/**
|
|
24
|
+
* 语义检索:替代关键词匹配,使用 unifiedSearch 做真正的语义检索
|
|
25
|
+
*/
|
|
26
|
+
async function semanticImpactAnalysis(desc, iteration, options = {}) {
|
|
27
|
+
const cwd = process.cwd();
|
|
28
|
+
// 1. 文档语义检索(迭代级)
|
|
29
|
+
const docResult = await (0, unified_retrieval_1.unifiedSearch)(cwd, {
|
|
30
|
+
query: desc,
|
|
31
|
+
iteration,
|
|
32
|
+
});
|
|
33
|
+
// 2. 代码语义检索(如果 withCode)
|
|
34
|
+
let codeResult = null;
|
|
35
|
+
if (options.withCode) {
|
|
36
|
+
codeResult = await (0, unified_retrieval_1.unifiedSearch)(cwd, {
|
|
37
|
+
query: desc,
|
|
38
|
+
iteration,
|
|
39
|
+
sourceScope: 'code',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
logger_1.logger.debug(`语义检索: 文档 ${docResult.stats.docChunksFound} chunks, 代码 ${codeResult?.stats.codeSlicesFound || 0} slices`);
|
|
43
|
+
return { docResult, codeResult };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 知识图谱查询:获取变更相关的实体和关联关系
|
|
47
|
+
*/
|
|
48
|
+
async function analyzeWithKnowledgeGraph(desc, iteration, matchedTaskIds) {
|
|
49
|
+
const graph = await (0, knowledge_graph_1.loadKnowledgeGraph)(process.cwd());
|
|
50
|
+
if (!graph) {
|
|
51
|
+
logger_1.logger.debug('知识图谱未构建,跳过图谱分析');
|
|
52
|
+
return { entities: [], relations: [] };
|
|
53
|
+
}
|
|
54
|
+
const entities = [];
|
|
55
|
+
const relations = [];
|
|
56
|
+
const seen = new Set();
|
|
57
|
+
// 1. 找到与匹配任务相关的实体
|
|
58
|
+
for (const taskId of matchedTaskIds) {
|
|
59
|
+
const entity = graph.entities[taskId];
|
|
60
|
+
if (!entity || seen.has(taskId))
|
|
61
|
+
continue;
|
|
62
|
+
entities.push(entity);
|
|
63
|
+
seen.add(taskId);
|
|
64
|
+
// 2. 获取 1-hop 邻居关系
|
|
65
|
+
for (const rel of graph.relations) {
|
|
66
|
+
if (rel.from === taskId || rel.to === taskId) {
|
|
67
|
+
relations.push(rel);
|
|
68
|
+
// 3. 获取关联实体
|
|
69
|
+
const otherId = rel.from === taskId ? rel.to : rel.from;
|
|
70
|
+
if (!seen.has(otherId) && graph.entities[otherId]) {
|
|
71
|
+
entities.push(graph.entities[otherId]);
|
|
72
|
+
seen.add(otherId);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
logger_1.logger.debug(`知识图谱: ${entities.length} 实体, ${relations.length} 关系`);
|
|
78
|
+
return { entities, relations };
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 按任务分组,计算语义相关度
|
|
82
|
+
*/
|
|
83
|
+
function groupByTask(docResult, allTasks) {
|
|
84
|
+
const taskMap = new Map();
|
|
85
|
+
for (const chunk of docResult.documentChunks) {
|
|
86
|
+
// 从 chunk.filePath 提取任务 ID
|
|
87
|
+
const taskIdMatch = chunk.filePath.match(/(Task-\d+)/);
|
|
88
|
+
if (!taskIdMatch)
|
|
89
|
+
continue;
|
|
90
|
+
const taskId = taskIdMatch[1];
|
|
91
|
+
const taskInfo = allTasks.find(t => t.id === taskId);
|
|
92
|
+
if (!taskMap.has(taskId)) {
|
|
93
|
+
taskMap.set(taskId, {
|
|
94
|
+
taskId,
|
|
95
|
+
taskName: taskInfo?.name || taskId,
|
|
96
|
+
status: taskInfo?.status || 'unknown',
|
|
97
|
+
score: chunk.relevanceScore || 0,
|
|
98
|
+
matchedContext: chunk.content.slice(0, 200),
|
|
99
|
+
files: [chunk.filePath],
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const existing = taskMap.get(taskId);
|
|
104
|
+
existing.score = Math.max(existing.score, chunk.relevanceScore || 0);
|
|
105
|
+
if (!existing.files.includes(chunk.filePath)) {
|
|
106
|
+
existing.files.push(chunk.filePath);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// 按相关度排序
|
|
111
|
+
return Array.from(taskMap.values()).sort((a, b) => b.score - a.score);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 基于相关度阈值分类任务影响级别
|
|
115
|
+
* 降级策略:LLM 不可用时使用
|
|
116
|
+
*/
|
|
117
|
+
function classifyByThreshold(matches, graphContent) {
|
|
118
|
+
const direct = [];
|
|
119
|
+
const indirect = [];
|
|
120
|
+
for (const match of matches) {
|
|
121
|
+
if (match.score >= 0.75) {
|
|
122
|
+
direct.push(match);
|
|
123
|
+
}
|
|
124
|
+
else if (match.score >= 0.40) {
|
|
125
|
+
indirect.push(match);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const affectedIds = new Set([...direct.map(d => d.taskId), ...indirect.map(i => i.taskId)]);
|
|
129
|
+
const unaffected = matches.filter(m => !affectedIds.has(m.taskId));
|
|
130
|
+
return { direct, indirect, unaffected };
|
|
131
|
+
}
|
|
132
|
+
// ── 2. 推理层 + 生成层(LLM 1 次调用)──
|
|
133
|
+
/**
|
|
134
|
+
* AI 影响分析主入口
|
|
135
|
+
* 合并检索 → LLM 推理 → 生成实施计划 为一次调用
|
|
136
|
+
*
|
|
137
|
+
* 降级:LLM 不可用时返回基于阈值的分类结果
|
|
138
|
+
*/
|
|
139
|
+
async function aiImpactAnalysis(desc, category, iteration, allTasks, options = {}) {
|
|
140
|
+
const cwd = process.cwd();
|
|
141
|
+
// Step 1: 语义检索
|
|
142
|
+
const { docResult, codeResult } = await semanticImpactAnalysis(desc, iteration, options);
|
|
143
|
+
// Step 2: 按任务分组
|
|
144
|
+
const taskMatches = groupByTask(docResult, allTasks);
|
|
145
|
+
// Step 3: 知识图谱查询
|
|
146
|
+
const topTaskIds = taskMatches.slice(0, 8).map(m => m.taskId);
|
|
147
|
+
const kgResult = await analyzeWithKnowledgeGraph(desc, iteration, topTaskIds);
|
|
148
|
+
// Step 4: LLM 分析(如果启用且可用)
|
|
149
|
+
if (options.useLlm !== false) {
|
|
150
|
+
try {
|
|
151
|
+
const llmResult = await callLlmForImpactAnalysis(desc, category, taskMatches, codeResult, kgResult);
|
|
152
|
+
if (llmResult) {
|
|
153
|
+
return llmResult;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
logger_1.logger.warn(`LLM 分析失败,降级到规则引擎: ${e.message}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// 降级:基于阈值的规则分类
|
|
161
|
+
logger_1.logger.info('🔄 使用规则引擎进行影响分析(语义检索 + 阈值分级)');
|
|
162
|
+
const classified = classifyByThreshold(taskMatches, '');
|
|
163
|
+
return {
|
|
164
|
+
thinking: '基于语义检索相关度阈值分类(LLM 不可用时的降级策略)',
|
|
165
|
+
taskImpacts: classified,
|
|
166
|
+
codeImpacts: [],
|
|
167
|
+
globalImpacts: inferGlobalImpacts(category),
|
|
168
|
+
crossIterationImpacts: [],
|
|
169
|
+
executionPlan: {
|
|
170
|
+
steps: [`基于规则分析: ${classified.direct.length} 个直接受影响任务需更新`, `${classified.indirect.length} 个间接影响任务需回归验证`],
|
|
171
|
+
regressionTests: classified.indirect.map(i => `验证 ${i.taskId} 是否受影响`),
|
|
172
|
+
globalRefreshSuggestions: inferGlobalImpacts(category).map(g => g.suggestedAction),
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* 调用 LLM 进行影响分析和实施计划生成
|
|
178
|
+
*/
|
|
179
|
+
async function callLlmForImpactAnalysis(desc, category, taskMatches, codeResult, kgResult) {
|
|
180
|
+
const prompt = buildImpactAnalysisPrompt(desc, category, taskMatches, codeResult, kgResult);
|
|
181
|
+
const response = await callLlm(prompt);
|
|
182
|
+
if (!response)
|
|
183
|
+
return null;
|
|
184
|
+
return parseImpactAnalysisResponse(response);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* 构建影响分析 Prompt
|
|
188
|
+
*/
|
|
189
|
+
function buildImpactAnalysisPrompt(desc, category, taskMatches, codeResult, kgResult) {
|
|
190
|
+
const sections = [];
|
|
191
|
+
sections.push('你是一位资深软件架构师,正在分析一个需求变更的影响范围。');
|
|
192
|
+
sections.push('');
|
|
193
|
+
// 变更描述
|
|
194
|
+
sections.push('## 变更描述');
|
|
195
|
+
sections.push(desc);
|
|
196
|
+
sections.push('');
|
|
197
|
+
// 意图分类
|
|
198
|
+
sections.push('## 意图分类');
|
|
199
|
+
sections.push(`- 类别: ${category}`);
|
|
200
|
+
sections.push('');
|
|
201
|
+
// 语义检索到的相关任务
|
|
202
|
+
sections.push('## 检索到的相关任务(按相关度排序)');
|
|
203
|
+
for (const m of taskMatches.slice(0, 10)) {
|
|
204
|
+
sections.push(`- ${m.taskId} [相关度 ${(m.score * 100).toFixed(0)}%] — ${m.taskName} [状态: ${m.status}]`);
|
|
205
|
+
sections.push(` 匹配上下文: ${m.matchedContext.slice(0, 100)}`);
|
|
206
|
+
}
|
|
207
|
+
sections.push('');
|
|
208
|
+
// 代码检索结果
|
|
209
|
+
if (codeResult && codeResult.codeSlices.length > 0) {
|
|
210
|
+
sections.push('## 检索到的相关代码');
|
|
211
|
+
for (const slice of codeResult.codeSlices.slice(0, 8)) {
|
|
212
|
+
sections.push(`- ${slice.filePath}`);
|
|
213
|
+
sections.push(` \`\`\`${slice.body.slice(0, 200)}\`\`\``);
|
|
214
|
+
}
|
|
215
|
+
sections.push('');
|
|
216
|
+
}
|
|
217
|
+
// 知识图谱关联
|
|
218
|
+
if (kgResult.entities.length > 0) {
|
|
219
|
+
sections.push('## 知识图谱关联');
|
|
220
|
+
for (const rel of kgResult.relations.slice(0, 10)) {
|
|
221
|
+
const from = kgResult.entities.find(e => e.id === rel.from);
|
|
222
|
+
const to = kgResult.entities.find(e => e.id === rel.to);
|
|
223
|
+
sections.push(`- ${from?.title || rel.from} --[${rel.type}]--> ${to?.title || rel.to}`);
|
|
224
|
+
}
|
|
225
|
+
sections.push('');
|
|
226
|
+
}
|
|
227
|
+
// 输出要求
|
|
228
|
+
sections.push('## 你的任务');
|
|
229
|
+
sections.push('请分析这个变更的影响范围,输出 JSON 格式。注意:');
|
|
230
|
+
sections.push('1. 只输出 JSON,不要其他内容');
|
|
231
|
+
sections.push('2. 基于证据判断,confidence 必须合理');
|
|
232
|
+
sections.push('3. 如果信息不足,标注 "insufficient_data"');
|
|
233
|
+
sections.push('4. 代码级影响需给出具体文件名和变更建议');
|
|
234
|
+
sections.push('');
|
|
235
|
+
sections.push('```json');
|
|
236
|
+
sections.push('{');
|
|
237
|
+
sections.push(' "thinking": "你的推理过程(中文)",');
|
|
238
|
+
sections.push(' "taskImpacts": {');
|
|
239
|
+
sections.push(' "direct": [{ "taskId": "Task-001", "taskName": "名称", "score": 0.92, "reason": "为什么直接影响", "files": ["REQ.md"] }],');
|
|
240
|
+
sections.push(' "indirect": [{ "taskId": "Task-005", "taskName": "名称", "score": 0.45, "reason": "为什么间接影响" }],');
|
|
241
|
+
sections.push(' "unaffected": []');
|
|
242
|
+
sections.push(' },');
|
|
243
|
+
sections.push(' "codeImpacts": [');
|
|
244
|
+
sections.push(' { "file": "User.java", "currentImplementation": "phone: String(11)", "suggestedChange": "phone: String(20); countryCode: String(5)", "reason": "字段长度需要扩展" }');
|
|
245
|
+
sections.push(' ],');
|
|
246
|
+
sections.push(' "globalImpacts": [');
|
|
247
|
+
sections.push(' { "artifact": "API_CONTRACT.yaml", "reason": "phone 字段格式定义需更新", "suggestedAction": "运行 speccore analyze --global --withCode" }');
|
|
248
|
+
sections.push(' ],');
|
|
249
|
+
sections.push(' "crossIterationImpacts": [],');
|
|
250
|
+
sections.push(' "executionPlan": {');
|
|
251
|
+
sections.push(' "steps": ["更新 Task-001 的 REQ.md", "修改 User.java 字段定义"],');
|
|
252
|
+
sections.push(' "regressionTests": ["验证 Task-005 注册流程"],');
|
|
253
|
+
sections.push(' "globalRefreshSuggestions": ["刷新 API_CONTRACT.yaml"]');
|
|
254
|
+
sections.push(' }');
|
|
255
|
+
sections.push('}');
|
|
256
|
+
sections.push('```');
|
|
257
|
+
return sections.join('\n');
|
|
258
|
+
}
|
|
259
|
+
// ── LLM 调用 ──
|
|
260
|
+
/**
|
|
261
|
+
* 调用 LLM
|
|
262
|
+
* 复制自 ask-llm.ts 的 callLlm,适配本模块的 prompt 格式
|
|
263
|
+
*/
|
|
264
|
+
async function callLlm(userPrompt) {
|
|
265
|
+
const systemPrompt = '你是 SpecCore CLI 的资深架构师助手,擅长分析软件变更的影响范围。请基于提供的检索结果和知识图谱,给出精确的影响分析。只输出 JSON,不要解释。';
|
|
266
|
+
// 方案1: OpenAI 兼容接口
|
|
267
|
+
const endpoint = process.env.SPECCORE_LLM_ENDPOINT || 'https://api.openai.com/v1/chat/completions';
|
|
268
|
+
const apiKey = process.env.SPECCORE_LLM_KEY || process.env.OPENAI_API_KEY;
|
|
269
|
+
if (!apiKey) {
|
|
270
|
+
logger_1.logger.debug('未配置 LLM API Key,跳过 LLM 分析');
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
const res = await fetch(endpoint, {
|
|
275
|
+
method: 'POST',
|
|
276
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
|
|
277
|
+
body: JSON.stringify({
|
|
278
|
+
model: process.env.SPECCORE_LLM_MODEL || 'gpt-4o-mini',
|
|
279
|
+
messages: [
|
|
280
|
+
{ role: 'system', content: systemPrompt },
|
|
281
|
+
{ role: 'user', content: userPrompt },
|
|
282
|
+
],
|
|
283
|
+
temperature: 0.2,
|
|
284
|
+
max_tokens: 3000,
|
|
285
|
+
}),
|
|
286
|
+
});
|
|
287
|
+
if (!res.ok) {
|
|
288
|
+
logger_1.logger.debug(`LLM API 返回错误: ${res.status}`);
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
const data = await res.json();
|
|
292
|
+
return data?.choices?.[0]?.message?.content || null;
|
|
293
|
+
}
|
|
294
|
+
catch (e) {
|
|
295
|
+
logger_1.logger.debug(`LLM 调用失败: ${e.message}`);
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* 解析 LLM 返回的影响分析 JSON
|
|
301
|
+
*/
|
|
302
|
+
function parseImpactAnalysisResponse(response) {
|
|
303
|
+
try {
|
|
304
|
+
// 尝试直接解析
|
|
305
|
+
const parsed = JSON.parse(response);
|
|
306
|
+
return normalizeAnalysisResult(parsed);
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
// 尝试提取 JSON 块
|
|
310
|
+
const match = response.match(/\{[\s\S]*\}/);
|
|
311
|
+
if (match) {
|
|
312
|
+
try {
|
|
313
|
+
const parsed = JSON.parse(match[0]);
|
|
314
|
+
return normalizeAnalysisResult(parsed);
|
|
315
|
+
}
|
|
316
|
+
catch { }
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* 规范化分析结果(处理 LLM 可能的不一致输出)
|
|
323
|
+
*/
|
|
324
|
+
function normalizeAnalysisResult(parsed) {
|
|
325
|
+
return {
|
|
326
|
+
thinking: String(parsed.thinking || 'AI 推理过程未提供'),
|
|
327
|
+
taskImpacts: {
|
|
328
|
+
direct: (parsed.taskImpacts?.direct || []).map((t) => ({
|
|
329
|
+
taskId: String(t.taskId || t.id || ''),
|
|
330
|
+
taskName: String(t.taskName || t.name || ''),
|
|
331
|
+
status: String(t.status || 'unknown'),
|
|
332
|
+
score: Number(t.score || 0.8),
|
|
333
|
+
matchedContext: String(t.reason || ''),
|
|
334
|
+
files: Array.isArray(t.files) ? t.files : [],
|
|
335
|
+
})),
|
|
336
|
+
indirect: (parsed.taskImpacts?.indirect || []).map((t) => ({
|
|
337
|
+
taskId: String(t.taskId || t.id || ''),
|
|
338
|
+
taskName: String(t.taskName || t.name || ''),
|
|
339
|
+
status: String(t.status || 'unknown'),
|
|
340
|
+
score: Number(t.score || 0.5),
|
|
341
|
+
matchedContext: String(t.reason || ''),
|
|
342
|
+
files: Array.isArray(t.files) ? t.files : [],
|
|
343
|
+
})),
|
|
344
|
+
unaffected: (parsed.taskImpacts?.unaffected || []).map((t) => ({
|
|
345
|
+
taskId: String(t.taskId || t.id || ''),
|
|
346
|
+
taskName: String(t.taskName || t.name || ''),
|
|
347
|
+
status: String(t.status || 'unknown'),
|
|
348
|
+
score: Number(t.score || 0),
|
|
349
|
+
matchedContext: '',
|
|
350
|
+
files: [],
|
|
351
|
+
})),
|
|
352
|
+
},
|
|
353
|
+
codeImpacts: (parsed.codeImpacts || []).map((c) => ({
|
|
354
|
+
file: String(c.file || ''),
|
|
355
|
+
currentImplementation: String(c.currentImplementation || c.current || ''),
|
|
356
|
+
suggestedChange: String(c.suggestedChange || c.suggested || ''),
|
|
357
|
+
reason: String(c.reason || ''),
|
|
358
|
+
platform: c.platform ? String(c.platform) : undefined,
|
|
359
|
+
})),
|
|
360
|
+
globalImpacts: (parsed.globalImpacts || []).map((g) => ({
|
|
361
|
+
artifact: String(g.artifact || ''),
|
|
362
|
+
reason: String(g.reason || ''),
|
|
363
|
+
suggestedAction: String(g.suggestedAction || g.action || ''),
|
|
364
|
+
})),
|
|
365
|
+
crossIterationImpacts: (parsed.crossIterationImpacts || []).map((c) => ({
|
|
366
|
+
iteration: String(c.iteration || ''),
|
|
367
|
+
taskId: String(c.taskId || ''),
|
|
368
|
+
reason: String(c.reason || ''),
|
|
369
|
+
severity: (c.severity === 'critical' ? 'critical' : 'warning'),
|
|
370
|
+
})),
|
|
371
|
+
executionPlan: {
|
|
372
|
+
steps: Array.isArray(parsed.executionPlan?.steps) ? parsed.executionPlan.steps.map(String) : [],
|
|
373
|
+
regressionTests: Array.isArray(parsed.executionPlan?.regressionTests) ? parsed.executionPlan.regressionTests.map(String) : [],
|
|
374
|
+
globalRefreshSuggestions: Array.isArray(parsed.executionPlan?.globalRefreshSuggestions) ? parsed.executionPlan.globalRefreshSuggestions.map(String) : [],
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
// ── 辅助函数 ──
|
|
379
|
+
/**
|
|
380
|
+
* 根据变更类别推断全局层影响(规则层,零成本)
|
|
381
|
+
*/
|
|
382
|
+
function inferGlobalImpacts(category) {
|
|
383
|
+
const impacts = [];
|
|
384
|
+
switch (category) {
|
|
385
|
+
case 'field-change':
|
|
386
|
+
impacts.push({
|
|
387
|
+
artifact: 'API_CONTRACT.yaml',
|
|
388
|
+
reason: '字段类型/长度/格式变更需同步到接口契约',
|
|
389
|
+
suggestedAction: '运行 speccore analyze --global --withCode',
|
|
390
|
+
});
|
|
391
|
+
impacts.push({
|
|
392
|
+
artifact: 'CONSISTENCY_CHECK.md',
|
|
393
|
+
reason: '前后端字段定义需保持一致',
|
|
394
|
+
suggestedAction: '运行 speccore analyze --global --focus consistency',
|
|
395
|
+
});
|
|
396
|
+
break;
|
|
397
|
+
case 'api-change':
|
|
398
|
+
impacts.push({
|
|
399
|
+
artifact: 'API_CONTRACT.yaml',
|
|
400
|
+
reason: '接口 URL/参数/返回值变更',
|
|
401
|
+
suggestedAction: '运行 speccore analyze --global --withCode',
|
|
402
|
+
});
|
|
403
|
+
impacts.push({
|
|
404
|
+
artifact: 'FUNCTION_MAP.md',
|
|
405
|
+
reason: '接口列表变更',
|
|
406
|
+
suggestedAction: '运行 speccore analyze --global',
|
|
407
|
+
});
|
|
408
|
+
break;
|
|
409
|
+
case 'flow-change':
|
|
410
|
+
impacts.push({
|
|
411
|
+
artifact: '各端 TECH.md',
|
|
412
|
+
reason: '流程顺序变更可能影响多端',
|
|
413
|
+
suggestedAction: '检查各端 TECH.md 中的流程描述',
|
|
414
|
+
});
|
|
415
|
+
break;
|
|
416
|
+
case 'ui-change':
|
|
417
|
+
impacts.push({
|
|
418
|
+
artifact: 'COMPONENT_TREE.md',
|
|
419
|
+
reason: 'UI 组件变更',
|
|
420
|
+
suggestedAction: '更新前端组件树文档',
|
|
421
|
+
});
|
|
422
|
+
break;
|
|
423
|
+
case 'logic-change':
|
|
424
|
+
impacts.push({
|
|
425
|
+
artifact: '各端 TEST.md',
|
|
426
|
+
reason: '业务逻辑变更需同步测试用例',
|
|
427
|
+
suggestedAction: '检查各端测试文档',
|
|
428
|
+
});
|
|
429
|
+
break;
|
|
430
|
+
case 'config-change':
|
|
431
|
+
impacts.push({
|
|
432
|
+
artifact: 'ARCHITECTURE.md',
|
|
433
|
+
reason: '配置项变更需记录到架构文档',
|
|
434
|
+
suggestedAction: '更新架构配置说明',
|
|
435
|
+
});
|
|
436
|
+
break;
|
|
437
|
+
case 'feature':
|
|
438
|
+
impacts.push({
|
|
439
|
+
artifact: 'FUNCTION_MAP.md',
|
|
440
|
+
reason: '新增功能单元',
|
|
441
|
+
suggestedAction: '追加新功能单元到 FUNCTION_MAP',
|
|
442
|
+
});
|
|
443
|
+
impacts.push({
|
|
444
|
+
artifact: 'REQUIREMENT.md',
|
|
445
|
+
reason: '新增需求章节',
|
|
446
|
+
suggestedAction: '追加需求描述到 REQUIREMENT.md',
|
|
447
|
+
});
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
return impacts;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* 生成 CHANGE_TODO.md 内容
|
|
454
|
+
*/
|
|
455
|
+
function generateChangeTodo(changeDesc, category, analysis, changeId) {
|
|
456
|
+
const lines = [];
|
|
457
|
+
const now = new Date().toISOString().split('T')[0];
|
|
458
|
+
lines.push(`# 变更实施清单: ${changeId || 'Change-XXX'}`);
|
|
459
|
+
lines.push('');
|
|
460
|
+
lines.push('## 变更描述');
|
|
461
|
+
lines.push(changeDesc);
|
|
462
|
+
lines.push('');
|
|
463
|
+
lines.push('## 意图分类');
|
|
464
|
+
lines.push(`- 类型: ${analysis.taskImpacts.direct.length > 0 ? 'change' : 'new'}`);
|
|
465
|
+
lines.push(`- 类别: ${category}`);
|
|
466
|
+
lines.push('');
|
|
467
|
+
// 影响分析
|
|
468
|
+
if (analysis.taskImpacts.direct.length > 0) {
|
|
469
|
+
lines.push(`## 直接影响任务(${analysis.taskImpacts.direct.length} 个)`);
|
|
470
|
+
lines.push('');
|
|
471
|
+
lines.push('| 任务 | 相关度 | 需要更新 | 代码需改 | 优先级 |');
|
|
472
|
+
lines.push('| :--- | :--- | :--- | :--- | :--- |');
|
|
473
|
+
for (const t of analysis.taskImpacts.direct) {
|
|
474
|
+
const hasCode = analysis.codeImpacts.some(c => c.file.includes(t.taskId));
|
|
475
|
+
lines.push(`| ${t.taskId} ${t.taskName} | ${(t.score * 100).toFixed(0)}% | 是 | ${hasCode ? '是' : '否'} | P0 |`);
|
|
476
|
+
}
|
|
477
|
+
lines.push('');
|
|
478
|
+
}
|
|
479
|
+
if (analysis.taskImpacts.indirect.length > 0) {
|
|
480
|
+
lines.push(`## 间接影响任务(${analysis.taskImpacts.indirect.length} 个)`);
|
|
481
|
+
lines.push('');
|
|
482
|
+
lines.push('| 任务 | 相关度 | 需要更新 | 代码需改 | 优先级 |');
|
|
483
|
+
lines.push('| :--- | :--- | :--- | :--- | :--- |');
|
|
484
|
+
for (const t of analysis.taskImpacts.indirect) {
|
|
485
|
+
lines.push(`| ${t.taskId} ${t.taskName} | ${(t.score * 100).toFixed(0)}% | 检查 | 否 | P1 |`);
|
|
486
|
+
}
|
|
487
|
+
lines.push('');
|
|
488
|
+
}
|
|
489
|
+
// 全局层刷新项
|
|
490
|
+
if (analysis.globalImpacts.length > 0) {
|
|
491
|
+
lines.push('## 全局层刷新项');
|
|
492
|
+
lines.push('');
|
|
493
|
+
for (const g of analysis.globalImpacts) {
|
|
494
|
+
lines.push(`- [ ] \`${g.artifact}\` — ${g.reason}`);
|
|
495
|
+
lines.push(` - 建议: ${g.suggestedAction}`);
|
|
496
|
+
}
|
|
497
|
+
lines.push('');
|
|
498
|
+
}
|
|
499
|
+
// 代码级变更项
|
|
500
|
+
if (analysis.codeImpacts.length > 0) {
|
|
501
|
+
lines.push('## 代码级变更项');
|
|
502
|
+
lines.push('');
|
|
503
|
+
lines.push('| 文件 | 当前实现 | AI 建议变更 | 优先级 |');
|
|
504
|
+
lines.push('| :--- | :--- | :--- | :--- |');
|
|
505
|
+
for (const c of analysis.codeImpacts) {
|
|
506
|
+
lines.push(`| \`${c.file}\` | ${c.currentImplementation.slice(0, 40)} | ${c.suggestedChange.slice(0, 60)} | P0 |`);
|
|
507
|
+
}
|
|
508
|
+
lines.push('');
|
|
509
|
+
}
|
|
510
|
+
// 回归验证项
|
|
511
|
+
if (analysis.executionPlan.regressionTests.length > 0) {
|
|
512
|
+
lines.push('## 回归验证项');
|
|
513
|
+
lines.push('');
|
|
514
|
+
for (const test of analysis.executionPlan.regressionTests) {
|
|
515
|
+
lines.push(`- [ ] ${test}`);
|
|
516
|
+
}
|
|
517
|
+
lines.push('');
|
|
518
|
+
}
|
|
519
|
+
// 实施步骤
|
|
520
|
+
if (analysis.executionPlan.steps.length > 0) {
|
|
521
|
+
lines.push('## 实施步骤');
|
|
522
|
+
lines.push('');
|
|
523
|
+
for (let i = 0; i < analysis.executionPlan.steps.length; i++) {
|
|
524
|
+
lines.push(`${i + 1}. ${analysis.executionPlan.steps[i]}`);
|
|
525
|
+
}
|
|
526
|
+
lines.push('');
|
|
527
|
+
}
|
|
528
|
+
lines.push(`---\n*生成时间: ${now} by SpecCore AI Impact Analyzer v6.73.0*`);
|
|
529
|
+
return lines.join('\n');
|
|
530
|
+
}
|
|
531
|
+
//# sourceMappingURL=ai-impact-analyzer.js.map
|