@spaceflow/review 0.29.3 → 0.31.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 +68 -0
- package/dist/index.js +4596 -5042
- package/package.json +3 -10
- package/src/deletion-impact.service.spec.ts +8 -19
- package/src/deletion-impact.service.ts +0 -2
- package/src/index.ts +123 -24
- package/src/issue-verify.service.spec.ts +7 -23
- package/src/issue-verify.service.ts +0 -2
- package/src/locales/en/review.json +1 -0
- package/src/locales/zh-cn/review.json +1 -0
- package/src/mcp/index.ts +191 -0
- package/src/review-report/index.ts +0 -1
- package/src/review-report/review-report.service.ts +0 -2
- package/src/review-spec/index.ts +0 -1
- package/src/review-spec/review-spec.service.spec.ts +3 -8
- package/src/review-spec/review-spec.service.ts +1 -4
- package/src/review.config.ts +61 -21
- package/src/review.service.spec.ts +156 -144
- package/src/review.service.ts +163 -64
- package/tsconfig.json +1 -1
- package/src/dto/mcp.dto.ts +0 -42
- package/src/review-report/review-report.module.ts +0 -8
- package/src/review-spec/review-spec.module.ts +0 -10
- package/src/review.command.ts +0 -244
- package/src/review.mcp.ts +0 -184
- package/src/review.module.ts +0 -52
package/src/review.service.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
-
Injectable,
|
|
3
|
-
ConfigService,
|
|
4
2
|
ConfigReaderService,
|
|
5
3
|
GitProviderService,
|
|
6
4
|
PullRequest,
|
|
7
5
|
PullRequestCommit,
|
|
8
6
|
ChangedFile,
|
|
9
7
|
CreatePullReviewComment,
|
|
8
|
+
REVIEW_STATE,
|
|
10
9
|
CiConfig,
|
|
11
10
|
type LLMMode,
|
|
12
11
|
LlmProxyService,
|
|
@@ -24,6 +23,7 @@ import {
|
|
|
24
23
|
parseHunksFromPatch,
|
|
25
24
|
calculateNewLineNumber,
|
|
26
25
|
} from "@spaceflow/core";
|
|
26
|
+
import type { IConfigReader } from "@spaceflow/core";
|
|
27
27
|
import { type AnalyzeDeletionsMode, type ReviewConfig } from "./review.config";
|
|
28
28
|
import {
|
|
29
29
|
ReviewSpecService,
|
|
@@ -41,7 +41,7 @@ import { execSync } from "child_process";
|
|
|
41
41
|
import { readFile, readdir } from "fs/promises";
|
|
42
42
|
import { join, dirname, extname, relative, isAbsolute } from "path";
|
|
43
43
|
import micromatch from "micromatch";
|
|
44
|
-
import { ReviewOptions } from "./review.
|
|
44
|
+
import { ReviewOptions } from "./review.config";
|
|
45
45
|
import { IssueVerifyService } from "./issue-verify.service";
|
|
46
46
|
import { DeletionImpactService } from "./deletion-impact.service";
|
|
47
47
|
import { parseTitleOptions } from "./parse-title-options";
|
|
@@ -95,6 +95,7 @@ export interface LLMReviewOptions {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
const REVIEW_COMMENT_MARKER = "<!-- spaceflow-review -->";
|
|
98
|
+
const REVIEW_LINE_COMMENTS_MARKER = "<!-- spaceflow-review-lines -->";
|
|
98
99
|
|
|
99
100
|
const REVIEW_SCHEMA: LlmJsonPutSchema = {
|
|
100
101
|
type: "object",
|
|
@@ -138,13 +139,12 @@ const REVIEW_SCHEMA: LlmJsonPutSchema = {
|
|
|
138
139
|
additionalProperties: false,
|
|
139
140
|
};
|
|
140
141
|
|
|
141
|
-
@Injectable()
|
|
142
142
|
export class ReviewService {
|
|
143
143
|
protected readonly llmJsonPut: LlmJsonPut<ReviewResult>;
|
|
144
144
|
|
|
145
145
|
constructor(
|
|
146
146
|
protected readonly gitProvider: GitProviderService,
|
|
147
|
-
protected readonly
|
|
147
|
+
protected readonly config: IConfigReader,
|
|
148
148
|
protected readonly configReader: ConfigReaderService,
|
|
149
149
|
protected readonly reviewSpecService: ReviewSpecService,
|
|
150
150
|
protected readonly llmProxyService: LlmProxyService,
|
|
@@ -172,7 +172,7 @@ export class ReviewService {
|
|
|
172
172
|
|
|
173
173
|
async getContextFromEnv(options: ReviewOptions): Promise<ReviewContext> {
|
|
174
174
|
const reviewConf = this.configReader.getPluginConfig<ReviewConfig>("review");
|
|
175
|
-
const ciConf = this.
|
|
175
|
+
const ciConf = this.config.get<CiConfig>("ci");
|
|
176
176
|
const repository = ciConf?.repository;
|
|
177
177
|
|
|
178
178
|
if (options.ci) {
|
|
@@ -291,6 +291,7 @@ export class ReviewService {
|
|
|
291
291
|
retryDelay: options.retryDelay ?? reviewConf.retryDelay ?? 1000,
|
|
292
292
|
generateDescription: options.generateDescription ?? reviewConf.generateDescription ?? false,
|
|
293
293
|
showAll: options.showAll ?? false,
|
|
294
|
+
flush: options.flush ?? false,
|
|
294
295
|
eventAction: options.eventAction,
|
|
295
296
|
};
|
|
296
297
|
}
|
|
@@ -405,8 +406,8 @@ export class ReviewService {
|
|
|
405
406
|
return this.executeDeletionOnly(context);
|
|
406
407
|
}
|
|
407
408
|
|
|
408
|
-
// 如果是 closed
|
|
409
|
-
if (context.eventAction === "closed") {
|
|
409
|
+
// 如果是 closed 事件或 flush 模式,仅收集 review 状态
|
|
410
|
+
if (context.eventAction === "closed" || context.flush) {
|
|
410
411
|
return this.executeCollectOnly(context);
|
|
411
412
|
}
|
|
412
413
|
|
|
@@ -827,7 +828,7 @@ export class ReviewService {
|
|
|
827
828
|
}
|
|
828
829
|
|
|
829
830
|
/**
|
|
830
|
-
* 仅收集 review 状态模式(用于 PR
|
|
831
|
+
* 仅收集 review 状态模式(用于 PR 关闭或 --flush 指令)
|
|
831
832
|
* 从现有的 AI review 评论中读取问题状态,同步已解决/无效状态,输出统计信息
|
|
832
833
|
*/
|
|
833
834
|
protected async executeCollectOnly(context: ReviewContext): Promise<ReviewResult> {
|
|
@@ -1875,14 +1876,14 @@ ${fileChanges || "无"}`;
|
|
|
1875
1876
|
}
|
|
1876
1877
|
}
|
|
1877
1878
|
|
|
1878
|
-
// 获取已解决的评论,同步 fixed
|
|
1879
|
+
// 获取已解决的评论,同步 fixed 状态(在更新 review 之前)
|
|
1879
1880
|
await this.syncResolvedComments(owner, repo, prNumber, result);
|
|
1880
1881
|
|
|
1881
1882
|
// 获取评论的 reactions,同步 valid 状态(👎 标记为无效)
|
|
1882
1883
|
await this.syncReactionsToIssues(owner, repo, prNumber, result, verbose);
|
|
1883
1884
|
|
|
1884
|
-
//
|
|
1885
|
-
await this.
|
|
1885
|
+
// 查找已有的 AI 评论(Issue Comment)
|
|
1886
|
+
const existingComment = await this.findExistingAiComment(owner, repo, prNumber);
|
|
1886
1887
|
|
|
1887
1888
|
// 调试:检查 issues 是否有 author
|
|
1888
1889
|
if (shouldLog(verbose, 3)) {
|
|
@@ -1903,7 +1904,55 @@ ${fileChanges || "无"}`;
|
|
|
1903
1904
|
const pr = await this.gitProvider.getPullRequest(owner, repo, prNumber);
|
|
1904
1905
|
const commitId = pr.head?.sha;
|
|
1905
1906
|
|
|
1906
|
-
//
|
|
1907
|
+
// 1. 发布或更新主评论(使用 Issue Comment API,支持删除和更新)
|
|
1908
|
+
try {
|
|
1909
|
+
if (existingComment?.id) {
|
|
1910
|
+
await this.gitProvider.updateIssueComment(owner, repo, existingComment.id, reviewBody);
|
|
1911
|
+
console.log(`✅ 已更新 AI Review 评论`);
|
|
1912
|
+
} else {
|
|
1913
|
+
await this.gitProvider.createIssueComment(owner, repo, prNumber, { body: reviewBody });
|
|
1914
|
+
console.log(`✅ 已发布 AI Review 评论`);
|
|
1915
|
+
}
|
|
1916
|
+
} catch (error) {
|
|
1917
|
+
console.warn("⚠️ 发布/更新 AI Review 评论失败:", error);
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
// 2. 删除旧的行级评论(逐条删除 PR Review Comment)
|
|
1921
|
+
try {
|
|
1922
|
+
const reviews = await this.gitProvider.listPullReviews(owner, repo, prNumber);
|
|
1923
|
+
const oldLineReviews = reviews.filter((r) => r.body?.includes(REVIEW_LINE_COMMENTS_MARKER));
|
|
1924
|
+
for (const review of oldLineReviews) {
|
|
1925
|
+
if (review.id) {
|
|
1926
|
+
const reviewComments = await this.gitProvider.listPullReviewComments(
|
|
1927
|
+
owner,
|
|
1928
|
+
repo,
|
|
1929
|
+
prNumber,
|
|
1930
|
+
review.id,
|
|
1931
|
+
);
|
|
1932
|
+
for (const comment of reviewComments) {
|
|
1933
|
+
if (comment.id) {
|
|
1934
|
+
try {
|
|
1935
|
+
await this.gitProvider.deletePullReviewComment(owner, repo, comment.id);
|
|
1936
|
+
} catch {
|
|
1937
|
+
// 删除失败忽略
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
// 评论删除后尝试删除 review 本身
|
|
1942
|
+
try {
|
|
1943
|
+
await this.gitProvider.deletePullReview(owner, repo, prNumber, review.id);
|
|
1944
|
+
} catch {
|
|
1945
|
+
// 已提交的 review 无法删除,忽略
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
if (oldLineReviews.length > 0) {
|
|
1950
|
+
console.log(`🗑️ 已清理 ${oldLineReviews.length} 个旧的行级评论 review`);
|
|
1951
|
+
}
|
|
1952
|
+
} catch (error) {
|
|
1953
|
+
console.warn("⚠️ 清理旧行级评论失败:", error);
|
|
1954
|
+
}
|
|
1955
|
+
// 3. 发布新的行级评论(使用 PR Review API)
|
|
1907
1956
|
let comments: CreatePullReviewComment[] = [];
|
|
1908
1957
|
if (reviewConf.lineComments) {
|
|
1909
1958
|
comments = result.issues
|
|
@@ -1911,24 +1960,61 @@ ${fileChanges || "无"}`;
|
|
|
1911
1960
|
.map((issue) => this.issueToReviewComment(issue))
|
|
1912
1961
|
.filter((comment): comment is CreatePullReviewComment => comment !== null);
|
|
1913
1962
|
}
|
|
1963
|
+
if (comments.length > 0) {
|
|
1964
|
+
try {
|
|
1965
|
+
await this.gitProvider.createPullReview(owner, repo, prNumber, {
|
|
1966
|
+
event: REVIEW_STATE.COMMENT,
|
|
1967
|
+
body: REVIEW_LINE_COMMENTS_MARKER,
|
|
1968
|
+
comments,
|
|
1969
|
+
commit_id: commitId,
|
|
1970
|
+
});
|
|
1971
|
+
console.log(`✅ 已发布 ${comments.length} 条行级评论`);
|
|
1972
|
+
} catch {
|
|
1973
|
+
// 批量失败时逐条发布,跳过无法定位的评论
|
|
1974
|
+
console.warn("⚠️ 批量发布行级评论失败,尝试逐条发布...");
|
|
1975
|
+
let successCount = 0;
|
|
1976
|
+
for (const comment of comments) {
|
|
1977
|
+
try {
|
|
1978
|
+
await this.gitProvider.createPullReview(owner, repo, prNumber, {
|
|
1979
|
+
event: REVIEW_STATE.COMMENT,
|
|
1980
|
+
body: successCount === 0 ? REVIEW_LINE_COMMENTS_MARKER : undefined,
|
|
1981
|
+
comments: [comment],
|
|
1982
|
+
commit_id: commitId,
|
|
1983
|
+
});
|
|
1984
|
+
successCount++;
|
|
1985
|
+
} catch {
|
|
1986
|
+
console.warn(`⚠️ 跳过无法定位的评论: ${comment.path}:${comment.new_position}`);
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
if (successCount > 0) {
|
|
1990
|
+
console.log(`✅ 逐条发布成功 ${successCount}/${comments.length} 条行级评论`);
|
|
1991
|
+
} else {
|
|
1992
|
+
console.warn("⚠️ 所有行级评论均无法定位,已跳过");
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1914
1997
|
|
|
1998
|
+
/**
|
|
1999
|
+
* 查找已有的 AI 评论(Issue Comment)
|
|
2000
|
+
*/
|
|
2001
|
+
protected async findExistingAiComment(
|
|
2002
|
+
owner: string,
|
|
2003
|
+
repo: string,
|
|
2004
|
+
prNumber: number,
|
|
2005
|
+
): Promise<{ id: number } | null> {
|
|
1915
2006
|
try {
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
commit_id: commitId,
|
|
1922
|
-
});
|
|
1923
|
-
const lineMsg = comments.length > 0 ? `,包含 ${comments.length} 条行级评论` : "";
|
|
1924
|
-
console.log(`✅ 已发布 AI Review${lineMsg}`);
|
|
1925
|
-
} catch (error) {
|
|
1926
|
-
console.warn("⚠️ 发布 AI Review 失败:", error);
|
|
2007
|
+
const comments = await this.gitProvider.listIssueComments(owner, repo, prNumber);
|
|
2008
|
+
const aiComment = comments.find((c) => c.body?.includes(REVIEW_COMMENT_MARKER));
|
|
2009
|
+
return aiComment?.id ? { id: aiComment.id } : null;
|
|
2010
|
+
} catch {
|
|
2011
|
+
return null;
|
|
1927
2012
|
}
|
|
1928
2013
|
}
|
|
1929
2014
|
|
|
1930
2015
|
/**
|
|
1931
|
-
*
|
|
2016
|
+
* 从 PR 的所有 resolved review threads 中同步 fixed 状态到 result.issues
|
|
2017
|
+
* 直接通过 GraphQL 查询所有 resolved threads 的 path+line,匹配 issues
|
|
1932
2018
|
*/
|
|
1933
2019
|
protected async syncResolvedComments(
|
|
1934
2020
|
owner: string,
|
|
@@ -1937,31 +2023,16 @@ ${fileChanges || "无"}`;
|
|
|
1937
2023
|
result: ReviewResult,
|
|
1938
2024
|
): Promise<void> {
|
|
1939
2025
|
try {
|
|
1940
|
-
const
|
|
1941
|
-
|
|
1942
|
-
if (!aiReview?.id) {
|
|
2026
|
+
const resolvedThreads = await this.gitProvider.listResolvedThreads(owner, repo, prNumber);
|
|
2027
|
+
if (resolvedThreads.length === 0) {
|
|
1943
2028
|
return;
|
|
1944
2029
|
}
|
|
1945
|
-
// 获取该 review 的所有行级评论
|
|
1946
|
-
const reviewComments = await this.gitProvider.listPullReviewComments(
|
|
1947
|
-
owner,
|
|
1948
|
-
repo,
|
|
1949
|
-
prNumber,
|
|
1950
|
-
aiReview.id,
|
|
1951
|
-
);
|
|
1952
|
-
// 找出已解决的评论(resolver 不为 null)
|
|
1953
|
-
const resolvedComments = reviewComments.filter(
|
|
1954
|
-
(c) => c.resolver !== null && c.resolver !== undefined,
|
|
1955
|
-
);
|
|
1956
|
-
if (resolvedComments.length === 0) {
|
|
1957
|
-
return;
|
|
1958
|
-
}
|
|
1959
|
-
// 根据文件路径和行号匹配 issues,标记为已解决
|
|
1960
2030
|
const now = new Date().toISOString();
|
|
1961
|
-
for (const
|
|
2031
|
+
for (const thread of resolvedThreads) {
|
|
2032
|
+
if (!thread.path) continue;
|
|
1962
2033
|
const matchedIssue = result.issues.find(
|
|
1963
2034
|
(issue) =>
|
|
1964
|
-
issue.file ===
|
|
2035
|
+
issue.file === thread.path && this.lineMatchesPosition(issue.line, thread.line),
|
|
1965
2036
|
);
|
|
1966
2037
|
if (matchedIssue && !matchedIssue.fixed) {
|
|
1967
2038
|
matchedIssue.fixed = now;
|
|
@@ -2000,7 +2071,7 @@ ${fileChanges || "无"}`;
|
|
|
2000
2071
|
): Promise<void> {
|
|
2001
2072
|
try {
|
|
2002
2073
|
const reviews = await this.gitProvider.listPullReviews(owner, repo, prNumber);
|
|
2003
|
-
const aiReview = reviews.find((r) => r.body?.includes(
|
|
2074
|
+
const aiReview = reviews.find((r) => r.body?.includes(REVIEW_LINE_COMMENTS_MARKER));
|
|
2004
2075
|
if (!aiReview?.id) {
|
|
2005
2076
|
if (shouldLog(verbose, 2)) {
|
|
2006
2077
|
console.log(`[syncReactionsToIssues] No AI review found`);
|
|
@@ -2013,7 +2084,7 @@ ${fileChanges || "无"}`;
|
|
|
2013
2084
|
|
|
2014
2085
|
// 1. 从已提交的 review 中获取评审人(排除 AI bot)
|
|
2015
2086
|
for (const review of reviews) {
|
|
2016
|
-
if (review.user?.login && !review.body?.includes(
|
|
2087
|
+
if (review.user?.login && !review.body?.includes(REVIEW_LINE_COMMENTS_MARKER)) {
|
|
2017
2088
|
reviewers.add(review.user.login);
|
|
2018
2089
|
}
|
|
2019
2090
|
}
|
|
@@ -2090,7 +2161,7 @@ ${fileChanges || "无"}`;
|
|
|
2090
2161
|
commentIdToIssue.set(comment.id, matchedIssue);
|
|
2091
2162
|
}
|
|
2092
2163
|
try {
|
|
2093
|
-
const reactions = await this.gitProvider.
|
|
2164
|
+
const reactions = await this.gitProvider.getPullReviewCommentReactions(
|
|
2094
2165
|
owner,
|
|
2095
2166
|
repo,
|
|
2096
2167
|
comment.id,
|
|
@@ -2194,25 +2265,54 @@ ${fileChanges || "无"}`;
|
|
|
2194
2265
|
|
|
2195
2266
|
/**
|
|
2196
2267
|
* 删除已有的 AI review(通过 marker 识别)
|
|
2268
|
+
* - 删除行级评论的 PR Review(带 REVIEW_LINE_COMMENTS_MARKER)
|
|
2269
|
+
* - 删除主评论的 Issue Comment(带 REVIEW_COMMENT_MARKER)
|
|
2197
2270
|
*/
|
|
2198
2271
|
protected async deleteExistingAiReviews(
|
|
2199
2272
|
owner: string,
|
|
2200
2273
|
repo: string,
|
|
2201
2274
|
prNumber: number,
|
|
2202
2275
|
): Promise<void> {
|
|
2276
|
+
let deletedCount = 0;
|
|
2277
|
+
// 删除行级评论的 PR Review
|
|
2203
2278
|
try {
|
|
2204
2279
|
const reviews = await this.gitProvider.listPullReviews(owner, repo, prNumber);
|
|
2205
|
-
const aiReviews = reviews.filter(
|
|
2280
|
+
const aiReviews = reviews.filter(
|
|
2281
|
+
(r) =>
|
|
2282
|
+
r.body?.includes(REVIEW_LINE_COMMENTS_MARKER) || r.body?.includes(REVIEW_COMMENT_MARKER),
|
|
2283
|
+
);
|
|
2206
2284
|
for (const review of aiReviews) {
|
|
2207
2285
|
if (review.id) {
|
|
2208
|
-
|
|
2286
|
+
try {
|
|
2287
|
+
await this.gitProvider.deletePullReview(owner, repo, prNumber, review.id);
|
|
2288
|
+
deletedCount++;
|
|
2289
|
+
} catch {
|
|
2290
|
+
// 已提交的 review 无法删除,忽略
|
|
2291
|
+
}
|
|
2209
2292
|
}
|
|
2210
2293
|
}
|
|
2211
|
-
|
|
2212
|
-
|
|
2294
|
+
} catch (error) {
|
|
2295
|
+
console.warn("⚠️ 列出 PR reviews 失败:", error);
|
|
2296
|
+
}
|
|
2297
|
+
// 删除主评论的 Issue Comment
|
|
2298
|
+
try {
|
|
2299
|
+
const comments = await this.gitProvider.listIssueComments(owner, repo, prNumber);
|
|
2300
|
+
const aiComments = comments.filter((c) => c.body?.includes(REVIEW_COMMENT_MARKER));
|
|
2301
|
+
for (const comment of aiComments) {
|
|
2302
|
+
if (comment.id) {
|
|
2303
|
+
try {
|
|
2304
|
+
await this.gitProvider.deleteIssueComment(owner, repo, comment.id);
|
|
2305
|
+
deletedCount++;
|
|
2306
|
+
} catch (error) {
|
|
2307
|
+
console.warn(`⚠️ 删除评论 ${comment.id} 失败:`, error);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2213
2310
|
}
|
|
2214
2311
|
} catch (error) {
|
|
2215
|
-
console.warn("⚠️
|
|
2312
|
+
console.warn("⚠️ 列出 issue comments 失败:", error);
|
|
2313
|
+
}
|
|
2314
|
+
if (deletedCount > 0) {
|
|
2315
|
+
console.log(`🗑️ 已删除 ${deletedCount} 个旧的 AI review`);
|
|
2216
2316
|
}
|
|
2217
2317
|
}
|
|
2218
2318
|
|
|
@@ -2501,12 +2601,11 @@ ${fileChanges || "无"}`;
|
|
|
2501
2601
|
newIssues: ReviewIssue[],
|
|
2502
2602
|
existingIssues: ReviewIssue[],
|
|
2503
2603
|
): { filteredIssues: ReviewIssue[]; skippedCount: number } {
|
|
2504
|
-
//
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
);
|
|
2604
|
+
// 所有历史问题(无论 valid 状态)都阻止新问题重复添加
|
|
2605
|
+
// valid='false' 的问题已被评审人标记为无效,不应再次报告
|
|
2606
|
+
// valid='true' 的问题已存在,无需重复
|
|
2607
|
+
// fixed 的问题已解决,无需重复
|
|
2608
|
+
const existingKeys = new Set(existingIssues.map((issue) => this.generateIssueKey(issue)));
|
|
2510
2609
|
const filteredIssues = newIssues.filter(
|
|
2511
2610
|
(issue) => !existingKeys.has(this.generateIssueKey(issue)),
|
|
2512
2611
|
);
|
|
@@ -2520,11 +2619,11 @@ ${fileChanges || "无"}`;
|
|
|
2520
2619
|
prNumber: number,
|
|
2521
2620
|
): Promise<ReviewResult | null> {
|
|
2522
2621
|
try {
|
|
2523
|
-
// 从
|
|
2524
|
-
const
|
|
2525
|
-
const
|
|
2526
|
-
if (
|
|
2527
|
-
return this.parseExistingReviewResult(
|
|
2622
|
+
// 从 Issue Comment 获取已有的审查结果
|
|
2623
|
+
const comments = await this.gitProvider.listIssueComments(owner, repo, prNumber);
|
|
2624
|
+
const existingComment = comments.find((c) => c.body?.includes(REVIEW_COMMENT_MARKER));
|
|
2625
|
+
if (existingComment?.body) {
|
|
2626
|
+
return this.parseExistingReviewResult(existingComment.body);
|
|
2528
2627
|
}
|
|
2529
2628
|
} catch (error) {
|
|
2530
2629
|
console.warn("⚠️ 获取已有评论失败:", error);
|
package/tsconfig.json
CHANGED
package/src/dto/mcp.dto.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ApiProperty,
|
|
3
|
-
ApiPropertyOptional,
|
|
4
|
-
IsString,
|
|
5
|
-
IsBoolean,
|
|
6
|
-
IsOptional,
|
|
7
|
-
t,
|
|
8
|
-
} from "@spaceflow/core";
|
|
9
|
-
|
|
10
|
-
export class ListRulesInput {
|
|
11
|
-
@ApiPropertyOptional({ description: t("review:mcp.dto.cwd") })
|
|
12
|
-
@IsString()
|
|
13
|
-
@IsOptional()
|
|
14
|
-
cwd?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class GetRulesForFileInput {
|
|
18
|
-
@ApiProperty({ description: t("review:mcp.dto.filePath") })
|
|
19
|
-
@IsString()
|
|
20
|
-
filePath!: string;
|
|
21
|
-
|
|
22
|
-
@ApiPropertyOptional({ description: t("review:mcp.dto.cwd") })
|
|
23
|
-
@IsString()
|
|
24
|
-
@IsOptional()
|
|
25
|
-
cwd?: string;
|
|
26
|
-
|
|
27
|
-
@ApiPropertyOptional({ description: t("review:mcp.dto.includeExamples") })
|
|
28
|
-
@IsBoolean()
|
|
29
|
-
@IsOptional()
|
|
30
|
-
includeExamples?: boolean;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export class GetRuleDetailInput {
|
|
34
|
-
@ApiProperty({ description: t("review:mcp.dto.ruleId") })
|
|
35
|
-
@IsString()
|
|
36
|
-
ruleId!: string;
|
|
37
|
-
|
|
38
|
-
@ApiPropertyOptional({ description: t("review:mcp.dto.cwd") })
|
|
39
|
-
@IsString()
|
|
40
|
-
@IsOptional()
|
|
41
|
-
cwd?: string;
|
|
42
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { Module } from "@nestjs/common";
|
|
2
|
-
import { GitProviderModule } from "@spaceflow/core";
|
|
3
|
-
import { ReviewSpecService } from "./review-spec.service";
|
|
4
|
-
|
|
5
|
-
@Module({
|
|
6
|
-
imports: [GitProviderModule.forFeature()],
|
|
7
|
-
providers: [ReviewSpecService],
|
|
8
|
-
exports: [ReviewSpecService],
|
|
9
|
-
})
|
|
10
|
-
export class ReviewSpecModule {}
|