@spaceflow/core 0.1.3 → 0.3.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.
Files changed (56) hide show
  1. package/dist/index.js +2642 -3247
  2. package/dist/index.js.map +1 -1
  3. package/package.json +2 -13
  4. package/src/config/config-loader.ts +5 -6
  5. package/src/config/config-reader.service.ts +6 -11
  6. package/src/config/config-reader.ts +75 -0
  7. package/src/config/index.ts +4 -1
  8. package/src/config/load-env.ts +15 -0
  9. package/src/config/schema-generator.service.ts +0 -2
  10. package/src/config/spaceflow.config.ts +7 -20
  11. package/src/extension-system/define-extension.ts +25 -0
  12. package/src/extension-system/extension.interface.ts +0 -63
  13. package/src/extension-system/index.ts +5 -0
  14. package/src/extension-system/types.ts +201 -0
  15. package/src/index.ts +15 -18
  16. package/src/shared/claude-setup/claude-setup.service.ts +3 -8
  17. package/src/shared/claude-setup/index.ts +0 -1
  18. package/src/shared/feishu-sdk/feishu-sdk.service.ts +33 -21
  19. package/src/shared/feishu-sdk/fieshu-card.service.ts +9 -11
  20. package/src/shared/feishu-sdk/index.ts +0 -1
  21. package/src/shared/git-provider/adapters/gitea.adapter.ts +59 -22
  22. package/src/shared/git-provider/adapters/github.adapter.spec.ts +2 -1
  23. package/src/shared/git-provider/adapters/github.adapter.ts +200 -29
  24. package/src/shared/git-provider/adapters/gitlab.adapter.ts +78 -26
  25. package/src/shared/git-provider/git-provider.interface.ts +20 -1
  26. package/src/shared/git-provider/git-provider.service.ts +28 -6
  27. package/src/shared/git-provider/index.ts +0 -1
  28. package/src/shared/git-provider/types.ts +27 -0
  29. package/src/shared/git-sdk/git-sdk.service.ts +0 -2
  30. package/src/shared/git-sdk/index.ts +0 -1
  31. package/src/shared/llm-proxy/adapters/claude-code.adapter.spec.ts +15 -32
  32. package/src/shared/llm-proxy/adapters/claude-code.adapter.ts +5 -6
  33. package/src/shared/llm-proxy/adapters/open-code.adapter.ts +1 -3
  34. package/src/shared/llm-proxy/adapters/openai.adapter.spec.ts +7 -21
  35. package/src/shared/llm-proxy/adapters/openai.adapter.ts +1 -3
  36. package/src/shared/llm-proxy/index.ts +0 -1
  37. package/src/shared/llm-proxy/interfaces/config.interface.ts +8 -0
  38. package/src/shared/llm-proxy/llm-proxy.service.spec.ts +91 -68
  39. package/src/shared/llm-proxy/llm-proxy.service.ts +5 -8
  40. package/src/shared/mcp/index.ts +1 -33
  41. package/src/shared/output/index.ts +0 -1
  42. package/src/shared/output/output.service.ts +43 -13
  43. package/src/shared/rspack-config/rspack-config.ts +0 -6
  44. package/src/shared/storage/index.ts +0 -1
  45. package/src/shared/storage/storage.service.ts +16 -8
  46. package/src/shared/storage/types.ts +0 -44
  47. package/src/shared/verbose/index.ts +15 -0
  48. package/src/app.module.ts +0 -18
  49. package/src/config/config-reader.module.ts +0 -16
  50. package/src/shared/claude-setup/claude-setup.module.ts +0 -8
  51. package/src/shared/feishu-sdk/feishu-sdk.module.ts +0 -77
  52. package/src/shared/git-provider/git-provider.module.ts +0 -73
  53. package/src/shared/git-sdk/git-sdk.module.ts +0 -8
  54. package/src/shared/llm-proxy/llm-proxy.module.ts +0 -140
  55. package/src/shared/output/output.module.ts +0 -9
  56. package/src/shared/storage/storage.module.ts +0 -150
@@ -1,9 +1,7 @@
1
- import { Inject, Injectable, type OnModuleDestroy, type OnModuleInit } from "@nestjs/common";
2
- import { EventEmitter2 } from "@nestjs/event-emitter";
1
+ import { EventEmitter } from "events";
3
2
  import * as lark from "@larksuiteoapi/node-sdk";
4
3
  import {
5
4
  FeishuModuleOptions,
6
- FEISHU_MODULE_OPTIONS,
7
5
  FeishuUser,
8
6
  GetUserParams,
9
7
  FEISHU_CARD_ACTION_TRIGGER,
@@ -15,12 +13,39 @@ import {
15
13
  /**
16
14
  * 飞书 API 服务
17
15
  */
18
- @Injectable()
19
- export class FeishuSdkService implements OnModuleInit, OnModuleDestroy {
16
+ export class FeishuSdkService {
20
17
  protected readonly client: lark.Client;
18
+ protected readonly eventEmitter: EventEmitter;
19
+ private eventDispatcher: lark.EventDispatcher | null = null;
21
20
 
22
- onModuleInit(): void {
23
- const eventDispatcher = new lark.EventDispatcher({
21
+ constructor(protected readonly options: FeishuModuleOptions) {
22
+ this.eventEmitter = new EventEmitter();
23
+ this.client = new lark.Client({
24
+ appId: options.appId,
25
+ appSecret: options.appSecret,
26
+ appType: options.appType === "store" ? lark.AppType.ISV : lark.AppType.SelfBuild,
27
+ domain: options.domain === "lark" ? lark.Domain.Lark : lark.Domain.Feishu,
28
+ });
29
+
30
+ this.initEventDispatcher();
31
+ }
32
+
33
+ /**
34
+ * 注册事件监听器
35
+ */
36
+ on(eventName: string, listener: (...args: any[]) => void): void {
37
+ this.eventEmitter.on(eventName, listener);
38
+ }
39
+
40
+ /**
41
+ * 移除事件监听器
42
+ */
43
+ off(eventName: string, listener: (...args: any[]) => void): void {
44
+ this.eventEmitter.off(eventName, listener);
45
+ }
46
+
47
+ private initEventDispatcher(): void {
48
+ this.eventDispatcher = new lark.EventDispatcher({
24
49
  encryptKey: "encrypt key",
25
50
  }).register<CardEvents>({
26
51
  [FEISHU_CARD_ACTION_TRIGGER]: async (data) => {
@@ -41,23 +66,10 @@ export class FeishuSdkService implements OnModuleInit, OnModuleDestroy {
41
66
  });
42
67
  }
43
68
 
44
- onModuleDestroy(): void {
69
+ destroy(): void {
45
70
  this.eventEmitter.removeAllListeners();
46
71
  }
47
72
 
48
- constructor(
49
- @Inject(FEISHU_MODULE_OPTIONS)
50
- protected readonly options: FeishuModuleOptions,
51
- protected readonly eventEmitter: EventEmitter2,
52
- ) {
53
- this.client = new lark.Client({
54
- appId: options.appId,
55
- appSecret: options.appSecret,
56
- appType: options.appType === "store" ? lark.AppType.ISV : lark.AppType.SelfBuild,
57
- domain: options.domain === "lark" ? lark.Domain.Lark : lark.Domain.Feishu,
58
- });
59
- }
60
-
61
73
  /**
62
74
  * 验证飞书配置
63
75
  */
@@ -1,4 +1,3 @@
1
- import { Injectable } from "@nestjs/common";
2
1
  import { FeishuSdkService } from "./feishu-sdk.service";
3
2
  import {
4
3
  SendCardParams,
@@ -8,25 +7,24 @@ import {
8
7
  CardContent,
9
8
  type CardActionTriggerCallback,
10
9
  } from "./types";
11
- import { OnEvent, type EventEmitter2 } from "@nestjs/event-emitter";
12
10
  import { FEISHU_CARD_ACTION_TRIGGER } from "./types";
13
11
 
14
12
  /**
15
13
  * 飞书卡片消息服务
16
14
  * 提供卡片消息的发送、回复、更新功能
17
15
  */
18
- @Injectable()
19
16
  export class FeishuCardService {
20
- constructor(
21
- protected readonly feishuSdkService: FeishuSdkService,
22
- protected readonly eventEmitter: EventEmitter2,
23
- ) {}
17
+ constructor(protected readonly feishuSdkService: FeishuSdkService) {
18
+ // 监听卡片动作触发事件
19
+ feishuSdkService.on(FEISHU_CARD_ACTION_TRIGGER, (event) => {
20
+ this.handleCardActionTrigger(event);
21
+ });
22
+ }
24
23
 
25
- @OnEvent(FEISHU_CARD_ACTION_TRIGGER)
26
- async handleCardActionTrigger(event: CardActionTriggerCallback) {
27
- const event_hook = event.header.event_type ?? event.event.action.tag;
24
+ async handleCardActionTrigger(event: CardActionTriggerCallback): Promise<void> {
25
+ const _eventHook = event.header.event_type ?? event.event.action.tag;
28
26
 
29
- // console.log(event_hook);
27
+ // console.log(_eventHook);
30
28
  }
31
29
 
32
30
  /**
@@ -1,4 +1,3 @@
1
- export * from "./feishu-sdk.module";
2
1
  export * from "./feishu-sdk.service";
3
2
  export * from "./fieshu-card.service";
4
3
  export * from "./types/index";
@@ -5,28 +5,30 @@ import type {
5
5
  LockBranchOptions,
6
6
  ListPullRequestsOptions,
7
7
  } from "../git-provider.interface";
8
- import type {
9
- GitProviderModuleOptions,
10
- BranchProtection,
11
- CreateBranchProtectionOption,
12
- EditBranchProtectionOption,
13
- Branch,
14
- Repository,
15
- PullRequest,
16
- PullRequestCommit,
17
- ChangedFile,
18
- CommitInfo,
19
- IssueComment,
20
- CreateIssueCommentOption,
21
- CreateIssueOption,
22
- Issue,
23
- CreatePullReviewOption,
24
- PullReview,
25
- PullReviewComment,
26
- Reaction,
27
- EditPullRequestOption,
28
- User,
29
- RepositoryContent,
8
+ import {
9
+ REVIEW_STATE,
10
+ type GitProviderModuleOptions,
11
+ type BranchProtection,
12
+ type CreateBranchProtectionOption,
13
+ type EditBranchProtectionOption,
14
+ type Branch,
15
+ type Repository,
16
+ type PullRequest,
17
+ type PullRequestCommit,
18
+ type ChangedFile,
19
+ type CommitInfo,
20
+ type IssueComment,
21
+ type CreateIssueCommentOption,
22
+ type CreateIssueOption,
23
+ type Issue,
24
+ type CreatePullReviewOption,
25
+ type PullReview,
26
+ type PullReviewComment,
27
+ type Reaction,
28
+ type EditPullRequestOption,
29
+ type User,
30
+ type RepositoryContent,
31
+ type ResolvedThread,
30
32
  } from "../types";
31
33
 
32
34
  /**
@@ -439,6 +441,21 @@ export class GiteaAdapter implements GitProvider {
439
441
  return this.request<PullReview[]>("GET", `/repos/${owner}/${repo}/pulls/${index}/reviews`);
440
442
  }
441
443
 
444
+ async updatePullReview(
445
+ owner: string,
446
+ repo: string,
447
+ index: number,
448
+ reviewId: number,
449
+ body: string,
450
+ ): Promise<PullReview> {
451
+ // Gitea 不支持更新 review,使用删除+创建的方式模拟
452
+ await this.deletePullReview(owner, repo, index, reviewId);
453
+ return this.createPullReview(owner, repo, index, {
454
+ event: REVIEW_STATE.COMMENT,
455
+ body,
456
+ });
457
+ }
458
+
442
459
  async deletePullReview(
443
460
  owner: string,
444
461
  repo: string,
@@ -463,6 +480,14 @@ export class GiteaAdapter implements GitProvider {
463
480
  );
464
481
  }
465
482
 
483
+ async deletePullReviewComment(owner: string, repo: string, commentId: number): Promise<void> {
484
+ await this.request<void>("DELETE", `/repos/${owner}/${repo}/pulls/comments/${commentId}`);
485
+ }
486
+
487
+ async listResolvedThreads(): Promise<ResolvedThread[]> {
488
+ return [];
489
+ }
490
+
466
491
  // ============ Reaction 操作 ============
467
492
 
468
493
  async getIssueCommentReactions(
@@ -476,6 +501,18 @@ export class GiteaAdapter implements GitProvider {
476
501
  );
477
502
  }
478
503
 
504
+ async getPullReviewCommentReactions(
505
+ owner: string,
506
+ repo: string,
507
+ commentId: number,
508
+ ): Promise<Reaction[]> {
509
+ // Gitea: PR review comment reactions 使用与 issue comment 相同的路径
510
+ return this.request<Reaction[]>(
511
+ "GET",
512
+ `/repos/${owner}/${repo}/issues/comments/${commentId}/reactions`,
513
+ );
514
+ }
515
+
479
516
  async getIssueReactions(owner: string, repo: string, index: number): Promise<Reaction[]> {
480
517
  return this.request<Reaction[]>("GET", `/repos/${owner}/${repo}/issues/${index}/reactions`);
481
518
  }
@@ -300,7 +300,8 @@ describe("GithubAdapter", () => {
300
300
  });
301
301
  const body = JSON.parse(fetchSpy.mock.calls[0][1].body);
302
302
  expect(body.event).toBe("COMMENT");
303
- expect(body.comments[0].position).toBe(10);
303
+ expect(body.comments[0].line).toBe(10);
304
+ expect(body.comments[0].side).toBe("RIGHT");
304
305
  expect(body.comments[0].path).toBe("a.ts");
305
306
  });
306
307
  });
@@ -4,30 +4,42 @@ import type {
4
4
  LockBranchOptions,
5
5
  ListPullRequestsOptions,
6
6
  } from "../git-provider.interface";
7
- import type {
8
- GitProviderModuleOptions,
9
- BranchProtection,
10
- CreateBranchProtectionOption,
11
- EditBranchProtectionOption,
12
- Branch,
13
- Repository,
14
- PullRequest,
15
- PullRequestCommit,
16
- ChangedFile,
17
- CommitInfo,
18
- IssueComment,
19
- CreateIssueCommentOption,
20
- CreateIssueOption,
21
- Issue,
22
- CreatePullReviewOption,
23
- PullReview,
24
- PullReviewComment,
25
- Reaction,
26
- EditPullRequestOption,
27
- User,
28
- RepositoryContent,
7
+ import {
8
+ REVIEW_STATE,
9
+ DIFF_SIDE,
10
+ type GitProviderModuleOptions,
11
+ type BranchProtection,
12
+ type CreateBranchProtectionOption,
13
+ type EditBranchProtectionOption,
14
+ type Branch,
15
+ type Repository,
16
+ type PullRequest,
17
+ type PullRequestCommit,
18
+ type ChangedFile,
19
+ type CommitInfo,
20
+ type IssueComment,
21
+ type CreateIssueCommentOption,
22
+ type CreateIssueOption,
23
+ type Issue,
24
+ type CreatePullReviewOption,
25
+ type PullReview,
26
+ type PullReviewComment,
27
+ type Reaction,
28
+ type EditPullRequestOption,
29
+ type User,
30
+ type RepositoryContent,
31
+ type ResolvedThread,
29
32
  } from "../types";
30
33
 
34
+ /** GraphQL review thread 节点类型 */
35
+ interface GraphQLReviewThread {
36
+ isResolved: boolean;
37
+ resolvedBy?: { login: string; databaseId: number } | null;
38
+ path?: string | null;
39
+ line?: number | null;
40
+ comments: { nodes: Array<{ databaseId: number }> };
41
+ }
42
+
31
43
  /**
32
44
  * GitHub 平台适配器
33
45
  */
@@ -72,6 +84,31 @@ export class GithubAdapter implements GitProvider {
72
84
  return response.json() as Promise<T>;
73
85
  }
74
86
 
87
+ protected async requestGraphQL<T>(
88
+ query: string,
89
+ variables?: Record<string, unknown>,
90
+ ): Promise<T> {
91
+ const graphqlUrl =
92
+ this.baseUrl.replace(/\/v3\/?$/, "").replace(/\/api\/v3\/?$/, "") + "/graphql";
93
+ const response = await fetch(graphqlUrl, {
94
+ method: "POST",
95
+ headers: {
96
+ Authorization: `Bearer ${this.token}`,
97
+ "Content-Type": "application/json",
98
+ },
99
+ body: JSON.stringify({ query, variables }),
100
+ });
101
+ if (!response.ok) {
102
+ const errorText = await response.text();
103
+ throw new Error(`GitHub GraphQL error: ${response.status} - ${errorText}`);
104
+ }
105
+ const json = (await response.json()) as { data: T; errors?: unknown[] };
106
+ if (json.errors) {
107
+ throw new Error(`GitHub GraphQL errors: ${JSON.stringify(json.errors)}`);
108
+ }
109
+ return json.data;
110
+ }
111
+
75
112
  protected async fetchText(url: string): Promise<string> {
76
113
  const response = await fetch(url, {
77
114
  headers: {
@@ -468,7 +505,8 @@ export class GithubAdapter implements GitProvider {
468
505
  body.comments = options.comments.map((c) => ({
469
506
  path: c.path,
470
507
  body: c.body,
471
- position: c.new_position,
508
+ line: c.new_position,
509
+ side: DIFF_SIDE.RIGHT,
472
510
  }));
473
511
  }
474
512
  const result = await this.request<Record<string, unknown>>(
@@ -487,6 +525,26 @@ export class GithubAdapter implements GitProvider {
487
525
  return results.map((r) => this.mapPullReview(r));
488
526
  }
489
527
 
528
+ async updatePullReview(
529
+ owner: string,
530
+ repo: string,
531
+ index: number,
532
+ reviewId: number,
533
+ body: string,
534
+ ): Promise<PullReview> {
535
+ // GitHub 的 updatePullReview 只能更新 PENDING 状态的 review
536
+ // 已提交的 review 无法更新,所以使用删除+创建的方式
537
+ try {
538
+ await this.deletePullReview(owner, repo, index, reviewId);
539
+ } catch {
540
+ // 已提交的 review 无法删除,忽略错误
541
+ }
542
+ return this.createPullReview(owner, repo, index, {
543
+ event: REVIEW_STATE.COMMENT,
544
+ body,
545
+ });
546
+ }
547
+
490
548
  async deletePullReview(
491
549
  owner: string,
492
550
  repo: string,
@@ -509,7 +567,23 @@ export class GithubAdapter implements GitProvider {
509
567
  "GET",
510
568
  `/repos/${owner}/${repo}/pulls/${index}/reviews/${reviewId}/comments`,
511
569
  );
512
- return results.map((c) => this.mapPullReviewComment(c));
570
+ const comments = results.map((c) => this.mapPullReviewComment(c));
571
+ // 通过 GraphQL 补充 resolved 状态
572
+ try {
573
+ const resolvedMap = await this.fetchResolvedThreads(owner, repo, index);
574
+ for (const comment of comments) {
575
+ if (comment.id && resolvedMap.has(comment.id)) {
576
+ comment.resolver = resolvedMap.get(comment.id) ?? null;
577
+ }
578
+ }
579
+ } catch {
580
+ // GraphQL 查询失败不影响主流程
581
+ }
582
+ return comments;
583
+ }
584
+
585
+ async deletePullReviewComment(owner: string, repo: string, commentId: number): Promise<void> {
586
+ await this.request<void>("DELETE", `/repos/${owner}/${repo}/pulls/comments/${commentId}`);
513
587
  }
514
588
 
515
589
  // ============ Reaction 操作 ============
@@ -526,6 +600,18 @@ export class GithubAdapter implements GitProvider {
526
600
  return results.map((r) => this.mapReaction(r));
527
601
  }
528
602
 
603
+ async getPullReviewCommentReactions(
604
+ owner: string,
605
+ repo: string,
606
+ commentId: number,
607
+ ): Promise<Reaction[]> {
608
+ const results = await this.request<Array<Record<string, unknown>>>(
609
+ "GET",
610
+ `/repos/${owner}/${repo}/pulls/comments/${commentId}/reactions`,
611
+ );
612
+ return results.map((r) => this.mapReaction(r));
613
+ }
614
+
529
615
  async getIssueReactions(owner: string, repo: string, index: number): Promise<Reaction[]> {
530
616
  const results = await this.request<Array<Record<string, unknown>>>(
531
617
  "GET",
@@ -722,6 +808,91 @@ export class GithubAdapter implements GitProvider {
722
808
  };
723
809
  }
724
810
 
811
+ /**
812
+ * 通过 GraphQL 查询 PR 的 review threads resolved 状态
813
+ * 返回 Map<commentId, resolver>(用于 listPullReviewComments 内部补充 resolver)
814
+ */
815
+ protected async fetchResolvedThreads(
816
+ owner: string,
817
+ repo: string,
818
+ prNumber: number,
819
+ ): Promise<Map<number, { id?: number; login?: string } | null>> {
820
+ const threads = await this.queryReviewThreads(owner, repo, prNumber);
821
+ const resolvedMap = new Map<number, { id?: number; login?: string } | null>();
822
+ for (const thread of threads) {
823
+ if (!thread.isResolved) continue;
824
+ const firstComment = thread.comments.nodes[0];
825
+ if (!firstComment?.databaseId) continue;
826
+ resolvedMap.set(
827
+ firstComment.databaseId,
828
+ thread.resolvedBy
829
+ ? { id: thread.resolvedBy.databaseId, login: thread.resolvedBy.login }
830
+ : null,
831
+ );
832
+ }
833
+ return resolvedMap;
834
+ }
835
+
836
+ async listResolvedThreads(owner: string, repo: string, index: number): Promise<ResolvedThread[]> {
837
+ const threads = await this.queryReviewThreads(owner, repo, index);
838
+ const result: ResolvedThread[] = [];
839
+ for (const thread of threads) {
840
+ if (!thread.isResolved) continue;
841
+ result.push({
842
+ path: thread.path ?? undefined,
843
+ line: thread.line ?? undefined,
844
+ resolvedBy: thread.resolvedBy
845
+ ? { id: thread.resolvedBy.databaseId, login: thread.resolvedBy.login }
846
+ : null,
847
+ });
848
+ }
849
+ return result;
850
+ }
851
+
852
+ /**
853
+ * GraphQL 查询 PR 的所有 review threads
854
+ */
855
+ protected async queryReviewThreads(
856
+ owner: string,
857
+ repo: string,
858
+ prNumber: number,
859
+ ): Promise<GraphQLReviewThread[]> {
860
+ const QUERY = `
861
+ query($owner: String!, $repo: String!, $prNumber: Int!) {
862
+ repository(owner: $owner, name: $repo) {
863
+ pullRequest(number: $prNumber) {
864
+ reviewThreads(first: 100) {
865
+ nodes {
866
+ isResolved
867
+ resolvedBy { login databaseId }
868
+ path
869
+ line
870
+ comments(first: 1) {
871
+ nodes { databaseId }
872
+ }
873
+ }
874
+ }
875
+ }
876
+ }
877
+ }
878
+ `;
879
+ interface GraphQLResult {
880
+ repository: {
881
+ pullRequest: {
882
+ reviewThreads: {
883
+ nodes: GraphQLReviewThread[];
884
+ };
885
+ };
886
+ };
887
+ }
888
+ const data = await this.requestGraphQL<GraphQLResult>(QUERY, {
889
+ owner,
890
+ repo,
891
+ prNumber,
892
+ });
893
+ return data.repository.pullRequest.reviewThreads.nodes;
894
+ }
895
+
725
896
  protected mapPullReviewComment(data: Record<string, unknown>): PullReviewComment {
726
897
  const user = data.user as Record<string, unknown> | undefined;
727
898
  return {
@@ -809,12 +980,12 @@ export class GithubAdapter implements GitProvider {
809
980
 
810
981
  protected mapReviewEvent(event?: string): string {
811
982
  const eventMap: Record<string, string> = {
812
- APPROVE: "APPROVE",
813
- REQUEST_CHANGES: "REQUEST_CHANGES",
814
- COMMENT: "COMMENT",
815
- PENDING: "PENDING",
983
+ [REVIEW_STATE.APPROVE]: REVIEW_STATE.APPROVE,
984
+ [REVIEW_STATE.REQUEST_CHANGES]: REVIEW_STATE.REQUEST_CHANGES,
985
+ [REVIEW_STATE.COMMENT]: REVIEW_STATE.COMMENT,
986
+ [REVIEW_STATE.PENDING]: REVIEW_STATE.PENDING,
816
987
  };
817
- return event ? eventMap[event] || event : "COMMENT";
988
+ return event ? eventMap[event] || event : REVIEW_STATE.COMMENT;
818
989
  }
819
990
 
820
991
  protected mapSortParam(sort: string): string {
@@ -4,28 +4,30 @@ import type {
4
4
  LockBranchOptions,
5
5
  ListPullRequestsOptions,
6
6
  } from "../git-provider.interface";
7
- import type {
8
- GitProviderModuleOptions,
9
- BranchProtection,
10
- CreateBranchProtectionOption,
11
- EditBranchProtectionOption,
12
- Branch,
13
- Repository,
14
- PullRequest,
15
- PullRequestCommit,
16
- ChangedFile,
17
- CommitInfo,
18
- IssueComment,
19
- CreateIssueCommentOption,
20
- CreateIssueOption,
21
- Issue,
22
- CreatePullReviewOption,
23
- PullReview,
24
- PullReviewComment,
25
- Reaction,
26
- EditPullRequestOption,
27
- User,
28
- RepositoryContent,
7
+ import {
8
+ REVIEW_STATE,
9
+ type GitProviderModuleOptions,
10
+ type BranchProtection,
11
+ type CreateBranchProtectionOption,
12
+ type EditBranchProtectionOption,
13
+ type Branch,
14
+ type Repository,
15
+ type PullRequest,
16
+ type PullRequestCommit,
17
+ type ChangedFile,
18
+ type CommitInfo,
19
+ type IssueComment,
20
+ type CreateIssueCommentOption,
21
+ type CreateIssueOption,
22
+ type Issue,
23
+ type CreatePullReviewOption,
24
+ type PullReview,
25
+ type PullReviewComment,
26
+ type Reaction,
27
+ type EditPullRequestOption,
28
+ type User,
29
+ type RepositoryContent,
30
+ type ResolvedThread,
29
31
  } from "../types";
30
32
 
31
33
  /**
@@ -553,7 +555,7 @@ export class GitlabAdapter implements GitProvider {
553
555
  return {
554
556
  id: note.id,
555
557
  body: note.body,
556
- state: options.event || "COMMENT",
558
+ state: options.event || REVIEW_STATE.COMMENT,
557
559
  user: note.user,
558
560
  created_at: note.created_at,
559
561
  updated_at: note.updated_at,
@@ -570,13 +572,13 @@ export class GitlabAdapter implements GitProvider {
570
572
  }
571
573
  }
572
574
  // 如果是 APPROVE 事件,调用 approve API
573
- if (options.event === "APPROVE") {
575
+ if (options.event === REVIEW_STATE.APPROVE) {
574
576
  await this.request<void>("POST", `/projects/${project}/merge_requests/${index}/approve`);
575
577
  }
576
578
  return {
577
579
  id: 0,
578
580
  body: options.body || "",
579
- state: options.event || "COMMENT",
581
+ state: options.event || REVIEW_STATE.COMMENT,
580
582
  };
581
583
  }
582
584
 
@@ -594,7 +596,7 @@ export class GitlabAdapter implements GitProvider {
594
596
  return {
595
597
  id: note.id,
596
598
  body: note.body,
597
- state: "COMMENT",
599
+ state: REVIEW_STATE.COMMENT,
598
600
  user: note.user,
599
601
  created_at: note.created_at,
600
602
  updated_at: note.updated_at,
@@ -602,6 +604,32 @@ export class GitlabAdapter implements GitProvider {
602
604
  });
603
605
  }
604
606
 
607
+ async updatePullReview(
608
+ owner: string,
609
+ repo: string,
610
+ index: number,
611
+ reviewId: number,
612
+ body: string,
613
+ ): Promise<PullReview> {
614
+ const project = this.encodeProject(owner, repo);
615
+ const result = await this.request<Record<string, unknown>>(
616
+ "PUT",
617
+ `/projects/${project}/merge_requests/${index}/notes/${reviewId}`,
618
+ { body },
619
+ );
620
+ return {
621
+ id: result.id as number,
622
+ body: result.body as string,
623
+ state: REVIEW_STATE.COMMENT,
624
+ user: result.author
625
+ ? {
626
+ id: (result.author as Record<string, unknown>).id as number,
627
+ login: (result.author as Record<string, unknown>).username as string,
628
+ }
629
+ : undefined,
630
+ };
631
+ }
632
+
605
633
  async deletePullReview(
606
634
  owner: string,
607
635
  repo: string,
@@ -645,6 +673,21 @@ export class GitlabAdapter implements GitProvider {
645
673
  });
646
674
  }
647
675
 
676
+ async deletePullReviewComment(owner: string, repo: string, commentId: number): Promise<void> {
677
+ // GitLab: 删除 MR note
678
+ const project = this.encodeProject(owner, repo);
679
+ // GitLab 删除 note 需要 merge_request_iid,但此处只有 commentId(note_id)
680
+ // 使用全局 note 删除不可行,需要通过其他方式获取 MR iid
681
+ // 暂时忽略,GitLab 场景下行级评论删除不常用
682
+ console.warn(
683
+ `⚠️ GitLab 暂不支持删除单条 review comment (id: ${commentId}, project: ${project})`,
684
+ );
685
+ }
686
+
687
+ async listResolvedThreads(): Promise<ResolvedThread[]> {
688
+ return [];
689
+ }
690
+
648
691
  // ============ Reaction 操作 ============
649
692
 
650
693
  async getIssueCommentReactions(
@@ -656,6 +699,15 @@ export class GitlabAdapter implements GitProvider {
656
699
  return [];
657
700
  }
658
701
 
702
+ async getPullReviewCommentReactions(
703
+ _owner: string,
704
+ _repo: string,
705
+ _commentId: number,
706
+ ): Promise<Reaction[]> {
707
+ // GitLab: award emoji on notes(需要 noteable_iid,此处简化返回空)
708
+ return [];
709
+ }
710
+
659
711
  async getIssueReactions(owner: string, repo: string, index: number): Promise<Reaction[]> {
660
712
  const project = this.encodeProject(owner, repo);
661
713
  try {