@spaceflow/review 0.76.0 → 0.78.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/CHANGELOG.md +47 -0
- package/dist/index.js +3830 -2469
- package/package.json +2 -2
- package/src/deletion-impact.service.ts +17 -130
- package/src/index.ts +34 -2
- package/src/issue-verify.service.ts +18 -82
- package/src/locales/en/review.json +2 -1
- package/src/locales/zh-cn/review.json +2 -1
- package/src/mcp/index.ts +4 -1
- package/src/prompt/code-review.ts +95 -0
- package/src/prompt/deletion-impact.ts +105 -0
- package/src/prompt/index.ts +37 -0
- package/src/prompt/issue-verify.ts +86 -0
- package/src/prompt/pr-description.ts +149 -0
- package/src/prompt/schemas.ts +106 -0
- package/src/prompt/types.ts +53 -0
- package/src/pull-request-model.ts +236 -0
- package/src/review-context.ts +433 -0
- package/src/review-includes-filter.spec.ts +284 -0
- package/src/review-includes-filter.ts +196 -0
- package/src/review-issue-filter.ts +523 -0
- package/src/review-llm.ts +543 -0
- package/src/review-result-model.spec.ts +657 -0
- package/src/review-result-model.ts +1046 -0
- package/src/review-spec/review-spec.service.ts +26 -5
- package/src/review-spec/types.ts +2 -0
- package/src/review.config.ts +40 -5
- package/src/review.service.spec.ts +102 -1625
- package/src/review.service.ts +608 -2742
- package/src/system-rules/index.ts +48 -0
- package/src/system-rules/max-lines-per-file.ts +57 -0
- package/src/types/review-llm.ts +21 -0
- package/src/utils/review-llm.spec.ts +277 -0
- package/src/utils/review-llm.ts +177 -0
- package/src/utils/review-pr-comment.spec.ts +340 -0
- package/src/utils/review-pr-comment.ts +186 -0
- package/tsconfig.json +1 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { PromptFn } from "./types";
|
|
2
|
+
import { validateArray, validateRequired } from "./types";
|
|
3
|
+
import type { DeletedCodeBlock } from "../deletion-impact.service";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 删除影响分析 - 标准 LLM 模式
|
|
7
|
+
*/
|
|
8
|
+
export interface DeletionImpactContext {
|
|
9
|
+
deletedBlocks: DeletedCodeBlock[];
|
|
10
|
+
references: Map<string, string[]>;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DELETION_IMPACT_SYSTEM = `你是一个代码审查专家,专门分析删除代码可能带来的影响。
|
|
15
|
+
|
|
16
|
+
## 任务
|
|
17
|
+
分析以下被删除的代码块,判断删除这些代码是否会影响到其他功能。
|
|
18
|
+
|
|
19
|
+
## 分析要点
|
|
20
|
+
1. **功能依赖**: 被删除的代码是否被其他模块调用或依赖
|
|
21
|
+
2. **接口变更**: 删除是否会导致 API 或接口不兼容
|
|
22
|
+
3. **副作用**: 删除是否会影响系统的其他行为
|
|
23
|
+
4. **数据流**: 删除是否会中断数据处理流程
|
|
24
|
+
|
|
25
|
+
## 风险等级判断标准
|
|
26
|
+
- **high**: 删除的代码被其他文件直接调用,删除后会导致编译错误或运行时异常
|
|
27
|
+
- **medium**: 删除的代码可能影响某些功能的行为,但不会导致直接错误
|
|
28
|
+
- **low**: 删除的代码影响较小,可能只是清理无用代码
|
|
29
|
+
- **none**: 删除的代码确实是无用代码,不会产生任何影响
|
|
30
|
+
|
|
31
|
+
## 输出要求
|
|
32
|
+
- 对每个有风险的删除块给出详细分析
|
|
33
|
+
- 如果删除是安全的,也要说明原因
|
|
34
|
+
- 提供具体的建议`;
|
|
35
|
+
|
|
36
|
+
function buildDeletedCodeSection(ctx: DeletionImpactContext): string {
|
|
37
|
+
return ctx.deletedBlocks
|
|
38
|
+
.map((block, index) => {
|
|
39
|
+
const refs = ctx.references.get(`${block.file}:${block.startLine}-${block.endLine}`) || [];
|
|
40
|
+
return `### 删除块 ${index + 1}: ${block.file}:${block.startLine}-${block.endLine}\n\n\`\`\`\n${block.content}\n\`\`\`\n\n可能引用此代码的文件: ${refs.length > 0 ? refs.join(", ") : "未发现直接引用"}\n`;
|
|
41
|
+
})
|
|
42
|
+
.join("\n");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const buildDeletionImpactPrompt: PromptFn<DeletionImpactContext> = (ctx) => {
|
|
46
|
+
validateArray(ctx.deletedBlocks, "deletedBlocks");
|
|
47
|
+
validateRequired(ctx.references, "references");
|
|
48
|
+
return {
|
|
49
|
+
systemPrompt: DELETION_IMPACT_SYSTEM,
|
|
50
|
+
userPrompt: `## 被删除的代码块\n\n${buildDeletedCodeSection(ctx)}\n请分析这些删除操作可能带来的影响。`,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** @deprecated 使用 buildDeletionImpactPrompt */
|
|
55
|
+
export const buildDeletionImpactSystemPrompt: PromptFn<DeletionImpactContext> = (ctx) =>
|
|
56
|
+
buildDeletionImpactPrompt(ctx);
|
|
57
|
+
|
|
58
|
+
/** @deprecated 使用 buildDeletionImpactPrompt */
|
|
59
|
+
export const buildDeletionImpactUserPrompt: PromptFn<DeletionImpactContext> = (ctx) =>
|
|
60
|
+
buildDeletionImpactPrompt(ctx);
|
|
61
|
+
|
|
62
|
+
const DELETION_IMPACT_AGENT_SYSTEM = `你是一个资深代码架构师,擅长分析代码变更的影响范围和潜在风险。
|
|
63
|
+
|
|
64
|
+
## 任务
|
|
65
|
+
深入分析以下被删除的代码块,评估删除操作对代码库的影响。
|
|
66
|
+
|
|
67
|
+
## 你的能力
|
|
68
|
+
你可以使用以下工具来深入分析代码:
|
|
69
|
+
- **Read**: 读取文件内容,查看被删除代码的完整上下文
|
|
70
|
+
- **Grep**: 搜索代码库,查找对被删除代码的引用
|
|
71
|
+
- **Glob**: 查找匹配模式的文件
|
|
72
|
+
|
|
73
|
+
## 分析流程
|
|
74
|
+
1. 首先阅读被删除代码的上下文,理解其功能
|
|
75
|
+
2. 使用 Grep 搜索代码库中对这些代码的引用
|
|
76
|
+
3. 分析引用处的代码,判断删除后的影响
|
|
77
|
+
4. 给出风险评估和建议
|
|
78
|
+
|
|
79
|
+
## 风险等级判断标准
|
|
80
|
+
- **high**: 删除的代码被其他文件直接调用,删除后会导致编译错误或运行时异常
|
|
81
|
+
- **medium**: 删除的代码可能影响某些功能的行为,但不会导致直接错误
|
|
82
|
+
- **low**: 删除的代码影响较小,可能只是清理无用代码
|
|
83
|
+
- **none**: 删除的代码确实是无用代码,不会产生任何影响
|
|
84
|
+
|
|
85
|
+
## 输出要求
|
|
86
|
+
- 对每个有风险的删除块给出详细分析
|
|
87
|
+
- 如果删除是安全的,也要说明原因
|
|
88
|
+
- 提供具体的建议`;
|
|
89
|
+
|
|
90
|
+
export const buildDeletionImpactAgentPrompt: PromptFn<DeletionImpactContext> = (ctx) => {
|
|
91
|
+
validateArray(ctx.deletedBlocks, "deletedBlocks");
|
|
92
|
+
validateRequired(ctx.references, "references");
|
|
93
|
+
return {
|
|
94
|
+
systemPrompt: DELETION_IMPACT_AGENT_SYSTEM,
|
|
95
|
+
userPrompt: `## 被删除的代码块\n\n${buildDeletedCodeSection(ctx)}\n## 补充说明\n\n请使用你的工具能力深入分析这些删除操作可能带来的影响。\n- 如果需要查看更多上下文,请读取相关文件\n- 如果需要确认引用关系,请搜索代码库\n- 分析完成后,给出结构化的影响评估`,
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/** @deprecated 使用 buildDeletionImpactAgentPrompt */
|
|
100
|
+
export const buildDeletionImpactAgentSystemPrompt: PromptFn<DeletionImpactContext> = (ctx) =>
|
|
101
|
+
buildDeletionImpactAgentPrompt(ctx);
|
|
102
|
+
|
|
103
|
+
/** @deprecated 使用 buildDeletionImpactAgentPrompt */
|
|
104
|
+
export const buildDeletionImpactAgentUserPrompt: PromptFn<DeletionImpactContext> = (ctx) =>
|
|
105
|
+
buildDeletionImpactAgentPrompt(ctx);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// 统一导出所有提示词
|
|
2
|
+
|
|
3
|
+
// 类型定义
|
|
4
|
+
export type { PromptFn, PromptContext, PromptResult } from "./types";
|
|
5
|
+
|
|
6
|
+
// JSON Schemas
|
|
7
|
+
export { REVIEW_SCHEMA, DELETION_IMPACT_SCHEMA, VERIFY_SCHEMA } from "./schemas";
|
|
8
|
+
|
|
9
|
+
// 代码审查提示词
|
|
10
|
+
export {
|
|
11
|
+
buildCodeReviewSystemPrompt,
|
|
12
|
+
buildFileReviewPrompt,
|
|
13
|
+
type CodeReviewSystemContext,
|
|
14
|
+
type FileReviewContext,
|
|
15
|
+
} from "./code-review";
|
|
16
|
+
|
|
17
|
+
// PR 描述生成提示词
|
|
18
|
+
export {
|
|
19
|
+
buildPrDescriptionPrompt,
|
|
20
|
+
buildPrTitlePrompt,
|
|
21
|
+
type PrDescriptionContext,
|
|
22
|
+
type PrTitleContext,
|
|
23
|
+
} from "./pr-description";
|
|
24
|
+
|
|
25
|
+
// 删除影响分析提示词
|
|
26
|
+
export {
|
|
27
|
+
buildDeletionImpactPrompt,
|
|
28
|
+
buildDeletionImpactAgentPrompt,
|
|
29
|
+
buildDeletionImpactSystemPrompt,
|
|
30
|
+
buildDeletionImpactUserPrompt,
|
|
31
|
+
buildDeletionImpactAgentSystemPrompt,
|
|
32
|
+
buildDeletionImpactAgentUserPrompt,
|
|
33
|
+
type DeletionImpactContext,
|
|
34
|
+
} from "./deletion-impact";
|
|
35
|
+
|
|
36
|
+
// 问题验证提示词
|
|
37
|
+
export { buildIssueVerifyPrompt, type IssueVerifyContext } from "./issue-verify";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { PromptFn } from "./types";
|
|
2
|
+
import { validateRequired, validateArray } from "./types";
|
|
3
|
+
import type { ReviewIssue, ReviewRule, ReviewSpec, FileContentLine } from "../review-spec";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 问题验证提示词上下文
|
|
7
|
+
*/
|
|
8
|
+
export interface IssueVerifyContext {
|
|
9
|
+
issue: ReviewIssue;
|
|
10
|
+
fileContent: FileContentLine[];
|
|
11
|
+
ruleInfo: { rule: ReviewRule; spec: ReviewSpec } | null;
|
|
12
|
+
specsSection?: string;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 构建问题验证提示词
|
|
18
|
+
*/
|
|
19
|
+
export const buildIssueVerifyPrompt: PromptFn<IssueVerifyContext> = (ctx) => {
|
|
20
|
+
// 验证必需的输入参数
|
|
21
|
+
validateRequired(ctx.issue, "issue");
|
|
22
|
+
validateArray(ctx.fileContent, "fileContent");
|
|
23
|
+
|
|
24
|
+
const padWidth = String(ctx.fileContent.length).length;
|
|
25
|
+
const linesWithNumbers = ctx.fileContent
|
|
26
|
+
.map(([, line], index) => `${String(index + 1).padStart(padWidth)}| ${line}`)
|
|
27
|
+
.join("\n");
|
|
28
|
+
|
|
29
|
+
const systemPrompt = `你是一个代码审查专家。你的任务是判断之前发现的一个代码问题:
|
|
30
|
+
1. 是否有效(是否真的违反了规则)
|
|
31
|
+
2. 是否已经被修复
|
|
32
|
+
|
|
33
|
+
请仔细分析当前的代码内容。
|
|
34
|
+
|
|
35
|
+
## 输出要求
|
|
36
|
+
- valid: 布尔值,true 表示问题有效(代码确实违反了规则),false 表示问题无效(误报)
|
|
37
|
+
- fixed: 布尔值,true 表示问题已经被修复,false 表示问题仍然存在
|
|
38
|
+
- reason: 判断依据
|
|
39
|
+
|
|
40
|
+
## 判断标准
|
|
41
|
+
|
|
42
|
+
### valid 判断
|
|
43
|
+
- 根据规则 ID 和问题描述,判断代码是否真的违反了该规则
|
|
44
|
+
- 如果问题描述与实际代码不符,valid 为 false
|
|
45
|
+
- 如果规则不适用于该代码场景,valid 为 false
|
|
46
|
+
|
|
47
|
+
### fixed 判断
|
|
48
|
+
- 只有当问题所在的代码已被修改,且修改后的代码不再违反规则时,fixed 才为 true
|
|
49
|
+
- 如果问题所在的代码仍然存在且仍违反规则,fixed 必须为 false
|
|
50
|
+
- 如果代码行号发生变化但问题本质仍存在,fixed 必须为 false
|
|
51
|
+
|
|
52
|
+
## 重要提醒
|
|
53
|
+
- valid=false 时,fixed 的值无意义(无效问题无需修复)
|
|
54
|
+
- 请确保 valid 和 fixed 的值与 reason 的描述一致!`;
|
|
55
|
+
|
|
56
|
+
// 构建规则定义部分
|
|
57
|
+
let ruleSection = "";
|
|
58
|
+
if (ctx.specsSection) {
|
|
59
|
+
ruleSection = ctx.specsSection;
|
|
60
|
+
} else if (ctx.ruleInfo) {
|
|
61
|
+
const { spec, rule } = ctx.ruleInfo;
|
|
62
|
+
ruleSection = `### ${spec.filename} (${spec.type})\n\n${spec.content.slice(0, 200)}...\n\n#### 规则\n- ${rule.id}: ${rule.title}\n ${rule.description}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const userPrompt = `## 规则定义
|
|
66
|
+
|
|
67
|
+
${ruleSection}
|
|
68
|
+
|
|
69
|
+
## 之前发现的问题
|
|
70
|
+
|
|
71
|
+
- **文件**: ${ctx.issue.file}
|
|
72
|
+
- **行号**: ${ctx.issue.line}
|
|
73
|
+
- **规则**: ${ctx.issue.ruleId} (来自 ${ctx.issue.specFile})
|
|
74
|
+
- **问题描述**: ${ctx.issue.reason}
|
|
75
|
+
${ctx.issue.suggestion ? `- **原建议**: ${ctx.issue.suggestion}` : ""}
|
|
76
|
+
|
|
77
|
+
## 当前文件内容
|
|
78
|
+
|
|
79
|
+
\`\`\`
|
|
80
|
+
${linesWithNumbers}
|
|
81
|
+
\`\`\`
|
|
82
|
+
|
|
83
|
+
请判断这个问题是否有效,以及是否已经被修复。`;
|
|
84
|
+
|
|
85
|
+
return { systemPrompt, userPrompt };
|
|
86
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { PromptFn } from "./types";
|
|
2
|
+
import { validateArray } from "./types";
|
|
3
|
+
import type { PullRequestCommit, ChangedFile } from "@spaceflow/core";
|
|
4
|
+
import type { FileContentsMap } from "../review-spec";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 内存使用限制常量
|
|
8
|
+
*/
|
|
9
|
+
const MEMORY_LIMITS = {
|
|
10
|
+
MAX_TOTAL_LENGTH: 8000, // 代码变更内容最大总长度
|
|
11
|
+
MAX_FILES: 30, // 最大文件数量
|
|
12
|
+
MAX_SNIPPET_LENGTH: 50, // 每个文件最大代码行数
|
|
13
|
+
MAX_COMMITS: 10, // 最大 commit 数量(用于标题生成)
|
|
14
|
+
MAX_FILES_FOR_TITLE: 20, // 标题生成时最大文件数量
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* PR 描述生成提示词
|
|
19
|
+
*/
|
|
20
|
+
export interface PrDescriptionContext {
|
|
21
|
+
commits: PullRequestCommit[];
|
|
22
|
+
changedFiles: ChangedFile[];
|
|
23
|
+
fileContents?: FileContentsMap;
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const buildPrDescriptionPrompt: PromptFn<PrDescriptionContext> = (ctx) => {
|
|
28
|
+
// 验证必需的输入参数
|
|
29
|
+
validateArray(ctx.commits, "commits");
|
|
30
|
+
validateArray(ctx.changedFiles, "changedFiles");
|
|
31
|
+
|
|
32
|
+
const commitMessages = ctx.commits
|
|
33
|
+
.map((c) => `- ${c.sha?.slice(0, 7)}: ${c.commit?.message?.split("\n")[0]}`)
|
|
34
|
+
.join("\n");
|
|
35
|
+
const fileChanges = ctx.changedFiles
|
|
36
|
+
.slice(0, MEMORY_LIMITS.MAX_FILES)
|
|
37
|
+
.map((f) => `- ${f.filename} (${f.status})`)
|
|
38
|
+
.join("\n");
|
|
39
|
+
|
|
40
|
+
// 构建代码变更内容(只包含变更行,优化内存使用)
|
|
41
|
+
let codeChangesSection = "";
|
|
42
|
+
if (ctx.fileContents && ctx.fileContents.size > 0) {
|
|
43
|
+
const codeSnippets: string[] = [];
|
|
44
|
+
let totalLength = 0;
|
|
45
|
+
|
|
46
|
+
// 使用 Map.entries() 进行更高效的迭代
|
|
47
|
+
for (const [filename, lines] of ctx.fileContents) {
|
|
48
|
+
if (totalLength >= MEMORY_LIMITS.MAX_TOTAL_LENGTH) break;
|
|
49
|
+
|
|
50
|
+
// 只提取有变更的行(commitHash 不是 "-------")
|
|
51
|
+
const changedLines = lines
|
|
52
|
+
.map(([hash, code], idx) => (hash !== "-------" ? `${idx + 1}: ${code}` : null))
|
|
53
|
+
.filter(Boolean);
|
|
54
|
+
|
|
55
|
+
if (changedLines.length > 0) {
|
|
56
|
+
// 限制每个文件的代码行数,避免单个文件占用过多内存
|
|
57
|
+
const limitedLines = changedLines.slice(0, MEMORY_LIMITS.MAX_SNIPPET_LENGTH);
|
|
58
|
+
const snippet = `### ${filename}\n\`\`\`\n${limitedLines.join("\n")}\n\`\`\``;
|
|
59
|
+
|
|
60
|
+
// 检查添加此片段是否会超过内存限制
|
|
61
|
+
if (totalLength + snippet.length <= MEMORY_LIMITS.MAX_TOTAL_LENGTH) {
|
|
62
|
+
codeSnippets.push(snippet);
|
|
63
|
+
totalLength += snippet.length;
|
|
64
|
+
} else {
|
|
65
|
+
// 如果添加当前片段会超过限制,尝试截断它
|
|
66
|
+
const remainingLength = MEMORY_LIMITS.MAX_TOTAL_LENGTH - totalLength;
|
|
67
|
+
if (remainingLength > 100) {
|
|
68
|
+
// 至少保留 100 字符的片段
|
|
69
|
+
// snippet 格式为 "### filename\n```\ncode\n```"
|
|
70
|
+
// 截断时去掉结尾的 ``` 再追加,避免双重代码块
|
|
71
|
+
const closingTag = "\n```";
|
|
72
|
+
const contentEnd = snippet.lastIndexOf(closingTag);
|
|
73
|
+
const truncateAt = Math.max(
|
|
74
|
+
0,
|
|
75
|
+
contentEnd > 0 ? Math.min(remainingLength - 20, contentEnd) : remainingLength - 20,
|
|
76
|
+
);
|
|
77
|
+
const truncatedSnippet = snippet.substring(0, truncateAt) + "\n..." + closingTag;
|
|
78
|
+
codeSnippets.push(truncatedSnippet);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (codeSnippets.length > 0) {
|
|
87
|
+
codeChangesSection = `\n\n## 代码变更内容\n${codeSnippets.join("\n\n")}`;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
systemPrompt: "",
|
|
93
|
+
userPrompt: `请根据以下 PR 的 commit 记录、文件变更和代码内容,用简洁的中文总结这个 PR 实现了什么功能。
|
|
94
|
+
要求:
|
|
95
|
+
1. 第一行输出 PR 标题,格式必须是: Feat xxx 或 Fix xxx 或 Refactor xxx(根据变更类型选择,整体不超过 50 个字符)
|
|
96
|
+
2. 空一行后输出详细描述
|
|
97
|
+
3. 描述应该简明扼要,突出核心功能点
|
|
98
|
+
4. 使用 Markdown 格式
|
|
99
|
+
5. 不要逐条列出 commit,而是归纳总结
|
|
100
|
+
6. 重点分析代码变更的实际功能
|
|
101
|
+
|
|
102
|
+
## Commit 记录 (${ctx.commits.length} 个)
|
|
103
|
+
${commitMessages || "无"}
|
|
104
|
+
|
|
105
|
+
## 文件变更 (${ctx.changedFiles.length} 个文件)
|
|
106
|
+
${fileChanges || "无"}${ctx.changedFiles.length > MEMORY_LIMITS.MAX_FILES ? `\n... 等 ${ctx.changedFiles.length - MEMORY_LIMITS.MAX_FILES} 个文件` : ""}${codeChangesSection}`,
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* PR 标题生成提示词
|
|
112
|
+
*/
|
|
113
|
+
export interface PrTitleContext {
|
|
114
|
+
commits: PullRequestCommit[];
|
|
115
|
+
changedFiles: ChangedFile[];
|
|
116
|
+
[key: string]: unknown;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const buildPrTitlePrompt: PromptFn<PrTitleContext> = (ctx) => {
|
|
120
|
+
// 验证必需的输入参数
|
|
121
|
+
validateArray(ctx.commits, "commits");
|
|
122
|
+
validateArray(ctx.changedFiles, "changedFiles");
|
|
123
|
+
|
|
124
|
+
const commitMessages = ctx.commits
|
|
125
|
+
.slice(0, MEMORY_LIMITS.MAX_COMMITS)
|
|
126
|
+
.map((c) => c.commit?.message?.split("\n")[0])
|
|
127
|
+
.filter(Boolean)
|
|
128
|
+
.join("\n");
|
|
129
|
+
const fileChanges = ctx.changedFiles
|
|
130
|
+
.slice(0, MEMORY_LIMITS.MAX_FILES_FOR_TITLE)
|
|
131
|
+
.map((f) => `${f.filename} (${f.status})`)
|
|
132
|
+
.join("\n");
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
systemPrompt: "",
|
|
136
|
+
userPrompt: `请根据以下 commit 记录和文件变更,生成一个简短的 PR 标题。
|
|
137
|
+
要求:
|
|
138
|
+
1. 格式必须是: Feat: xxx 或 Fix: xxx 或 Refactor: xxx
|
|
139
|
+
2. 根据变更内容选择合适的前缀(新功能用 Feat,修复用 Fix,重构用 Refactor)
|
|
140
|
+
3. xxx 部分用简短的中文描述(整体不超过 50 个字符)
|
|
141
|
+
4. 只输出标题,不要加任何解释
|
|
142
|
+
|
|
143
|
+
Commit 记录:
|
|
144
|
+
${commitMessages || "无"}
|
|
145
|
+
|
|
146
|
+
文件变更:
|
|
147
|
+
${fileChanges || "无"}`,
|
|
148
|
+
};
|
|
149
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { LlmJsonPutSchema } from "@spaceflow/core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 代码审查结果 JSON Schema
|
|
5
|
+
*/
|
|
6
|
+
export const REVIEW_SCHEMA: LlmJsonPutSchema = {
|
|
7
|
+
type: "object",
|
|
8
|
+
properties: {
|
|
9
|
+
issues: {
|
|
10
|
+
type: "array",
|
|
11
|
+
items: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
file: { type: "string", description: "发生问题的文件路径" },
|
|
15
|
+
line: {
|
|
16
|
+
type: "string",
|
|
17
|
+
description:
|
|
18
|
+
"问题所在的行号,只支持单行或多行 (如 123 或 123-125),不允许使用 `,` 分隔多个行号",
|
|
19
|
+
},
|
|
20
|
+
ruleId: { type: "string", description: "违反的规则 ID(如 JsTs.FileName.UpperCamel)" },
|
|
21
|
+
specFile: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "规则来源的规范文件名(如 js&ts.file-name.md)",
|
|
24
|
+
},
|
|
25
|
+
reason: { type: "string", description: "问题的简要概括" },
|
|
26
|
+
suggestion: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description:
|
|
29
|
+
"修改后的完整代码片段。要求以代码为主体,并在代码中使用详细的中文注释解释逻辑改进点。不要包含 Markdown 反引号。",
|
|
30
|
+
},
|
|
31
|
+
commit: { type: "string", description: "相关的 7 位 commit SHA" },
|
|
32
|
+
severity: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "问题严重程度,根据规则文档中的 severity 标记确定",
|
|
35
|
+
enum: ["error", "warn"],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
required: ["file", "line", "ruleId", "specFile", "reason"],
|
|
39
|
+
additionalProperties: false,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
summary: { type: "string", description: "本次代码审查的整体总结" },
|
|
43
|
+
},
|
|
44
|
+
required: ["issues", "summary"],
|
|
45
|
+
additionalProperties: false,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 删除影响分析结果 JSON Schema
|
|
50
|
+
*/
|
|
51
|
+
export const DELETION_IMPACT_SCHEMA: LlmJsonPutSchema = {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
impacts: {
|
|
55
|
+
type: "array",
|
|
56
|
+
items: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
file: { type: "string", description: "被删除代码所在的文件路径" },
|
|
60
|
+
deletedCode: { type: "string", description: "被删除的代码片段摘要(前50字符)" },
|
|
61
|
+
riskLevel: {
|
|
62
|
+
type: "string",
|
|
63
|
+
enum: ["high", "medium", "low", "none"],
|
|
64
|
+
description:
|
|
65
|
+
"风险等级:high=可能导致功能异常,medium=可能影响部分功能,low=影响较小,none=无影响",
|
|
66
|
+
},
|
|
67
|
+
affectedFiles: {
|
|
68
|
+
type: "array",
|
|
69
|
+
items: { type: "string" },
|
|
70
|
+
description: "可能受影响的文件列表",
|
|
71
|
+
},
|
|
72
|
+
reason: { type: "string", description: "影响分析的详细说明" },
|
|
73
|
+
suggestion: { type: "string", description: "建议的处理方式" },
|
|
74
|
+
},
|
|
75
|
+
required: ["file", "deletedCode", "riskLevel", "affectedFiles", "reason"],
|
|
76
|
+
additionalProperties: false,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
summary: { type: "string", description: "删除代码影响的整体总结" },
|
|
80
|
+
},
|
|
81
|
+
required: ["impacts", "summary"],
|
|
82
|
+
additionalProperties: false,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 问题验证结果 JSON Schema
|
|
87
|
+
*/
|
|
88
|
+
export const VERIFY_SCHEMA: LlmJsonPutSchema = {
|
|
89
|
+
type: "object",
|
|
90
|
+
properties: {
|
|
91
|
+
fixed: {
|
|
92
|
+
type: "boolean",
|
|
93
|
+
description: "问题是否已被修复",
|
|
94
|
+
},
|
|
95
|
+
valid: {
|
|
96
|
+
type: "boolean",
|
|
97
|
+
description: "问题是否有效,有效的条件就是你需要看看代码是否符合规范",
|
|
98
|
+
},
|
|
99
|
+
reason: {
|
|
100
|
+
type: "string",
|
|
101
|
+
description: "判断依据,说明为什么认为问题已修复或仍存在",
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
required: ["fixed", "valid", "reason"],
|
|
105
|
+
additionalProperties: false,
|
|
106
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 提示词回调函数类型定义
|
|
3
|
+
*/
|
|
4
|
+
export interface PromptContext {
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface PromptResult {
|
|
9
|
+
systemPrompt: string;
|
|
10
|
+
userPrompt: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 输入验证错误类
|
|
15
|
+
*/
|
|
16
|
+
export class PromptValidationError extends Error {
|
|
17
|
+
constructor(message: string) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "PromptValidationError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 输入验证工具函数
|
|
25
|
+
*/
|
|
26
|
+
export function validateRequired<T>(value: T | undefined | null, fieldName: string): T {
|
|
27
|
+
if (value === undefined || value === null) {
|
|
28
|
+
throw new PromptValidationError(`${fieldName} is required but was ${value}`);
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function validateNonEmptyString(
|
|
34
|
+
value: string | undefined | null,
|
|
35
|
+
fieldName: string,
|
|
36
|
+
): string {
|
|
37
|
+
if (value === undefined || value === null || value.trim() === "") {
|
|
38
|
+
throw new PromptValidationError(`${fieldName} is required and cannot be empty`);
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function validateArray<T>(value: T[] | undefined | null, fieldName: string): T[] {
|
|
44
|
+
if (!Array.isArray(value)) {
|
|
45
|
+
throw new PromptValidationError(`${fieldName} must be an array but was ${typeof value}`);
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 提示词函数类型 - 接收上下文,返回 systemPrompt 和 userPrompt
|
|
52
|
+
*/
|
|
53
|
+
export type PromptFn<T extends PromptContext = PromptContext> = (ctx: T) => PromptResult;
|