@spaceflow/review 5.0.0 → 5.0.1

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 (37) hide show
  1. package/README.md +5 -11
  2. package/dist/index.js +11 -39
  3. package/package.json +3 -3
  4. package/src/README.md +4 -4
  5. package/src/deletion-impact.service.spec.ts +8 -12
  6. package/src/deletion-impact.service.ts +5 -5
  7. package/src/issue-verify.service.spec.ts +0 -4
  8. package/src/locales/en/review.json +2 -2
  9. package/src/locales/zh-cn/review.json +2 -2
  10. package/src/mcp/index.ts +1 -5
  11. package/src/parse-title-options.spec.ts +4 -4
  12. package/src/parse-title-options.ts +3 -3
  13. package/src/review-context.ts +1 -1
  14. package/src/review-llm.spec.ts +1 -18
  15. package/src/review.config.ts +2 -2
  16. package/src/review.service.spec.ts +4 -59
  17. package/src/review.service.ts +0 -27
  18. package/src/utils/review-pr-comment.ts +1 -1
  19. package/src/coverage/base.css +0 -224
  20. package/src/coverage/block-navigation.js +0 -87
  21. package/src/coverage/clover.xml +0 -1942
  22. package/src/coverage/coverage-final.json +0 -7
  23. package/src/coverage/favicon.png +0 -0
  24. package/src/coverage/index.html +0 -131
  25. package/src/coverage/prettify.css +0 -1
  26. package/src/coverage/prettify.js +0 -2
  27. package/src/coverage/sort-arrow-sprite.png +0 -0
  28. package/src/coverage/sorter.js +0 -210
  29. package/src/coverage/src/deletion-impact.service.ts.html +0 -2716
  30. package/src/coverage/src/index.html +0 -161
  31. package/src/coverage/src/issue-verify.service.ts.html +0 -1006
  32. package/src/coverage/src/parse-title-options.ts.html +0 -640
  33. package/src/coverage/src/review-spec/index.html +0 -131
  34. package/src/coverage/src/review-spec/review-spec.service.ts.html +0 -2782
  35. package/src/coverage/src/review-spec/types.ts.html +0 -535
  36. package/src/coverage/src/review.service.ts.html +0 -8911
  37. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
@@ -29,9 +29,6 @@ function mockSummary(overrides: Partial<FileSummary> = {}): FileSummary {
29
29
  }
30
30
 
31
31
  vi.mock("c12");
32
- vi.mock("@anthropic-ai/claude-agent-sdk", () => ({
33
- query: vi.fn(),
34
- }));
35
32
  vi.mock("fs/promises");
36
33
  vi.mock("child_process");
37
34
  vi.mock("@opencode-ai/sdk", () => ({
@@ -98,9 +95,6 @@ class TestReviewService extends ReviewService {
98
95
  executeDeletionOnly(context: Partial<ReviewContext>) {
99
96
  return super.executeDeletionOnly(context as ReviewContext);
100
97
  }
101
- ensureClaudeCli(ci?: boolean) {
102
- return super.ensureClaudeCli(ci);
103
- }
104
98
  resolveSourceData(context: Partial<ReviewContext>) {
105
99
  return super.resolveSourceData(context as ReviewContext);
106
100
  }
@@ -208,7 +202,7 @@ describe("ReviewService", () => {
208
202
  chat: vi.fn(),
209
203
  chatStream: vi.fn(),
210
204
  createSession: vi.fn(),
211
- getAvailableAdapters: vi.fn().mockReturnValue(["claude-code", "openai"]),
205
+ getAvailableAdapters: vi.fn().mockReturnValue(["openai", "open-code"]),
212
206
  };
213
207
 
214
208
  service = new TestReviewService(
@@ -292,7 +286,7 @@ describe("ReviewService", () => {
292
286
  specSources: ["/spec/dir"],
293
287
  dryRun: true,
294
288
  ci: false,
295
- llmMode: "claude-code" as const,
289
+ llmMode: "openai" as const,
296
290
  };
297
291
 
298
292
  const mockPR = {
@@ -358,7 +352,7 @@ describe("ReviewService", () => {
358
352
  specSources: ["/spec/dir"],
359
353
  dryRun: false,
360
354
  ci: true,
361
- llmMode: "claude-code" as const,
355
+ llmMode: "openai" as const,
362
356
  } as ReviewContext;
363
357
 
364
358
  const mockPR = {
@@ -408,7 +402,7 @@ describe("ReviewService", () => {
408
402
  specSources: ["/spec/dir"],
409
403
  dryRun: false,
410
404
  ci: false,
411
- llmMode: "claude-code",
405
+ llmMode: "openai",
412
406
  };
413
407
 
414
408
  gitProvider.getPullRequest.mockRejectedValue(new Error("Gitea API Error"));
@@ -922,13 +916,6 @@ describe("ReviewService", () => {
922
916
  });
923
917
  });
924
918
 
925
- describe("ReviewService.ensureClaudeCli", () => {
926
- it("should not throw when claude is installed", async () => {
927
- vi.spyOn(require("child_process"), "execSync").mockImplementation(() => Buffer.from("1.0.0"));
928
- await expect(service.ensureClaudeCli()).resolves.toBeUndefined();
929
- });
930
- });
931
-
932
919
  describe("ReviewService.resolveSourceData - direct file mode", () => {
933
920
  it("should bypass local uncommitted scanning when files are specified", async () => {
934
921
  const context: ReviewContext = {
@@ -1273,46 +1260,4 @@ describe("ReviewService", () => {
1273
1260
  expect(gitProvider.updateIssueComment).toHaveBeenCalled();
1274
1261
  });
1275
1262
  });
1276
-
1277
- describe("ReviewService.ensureClaudeCli", () => {
1278
- it("should do nothing when claude is already installed", async () => {
1279
- const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
1280
- // execSync is already mocked globally
1281
-
1282
- await service.ensureClaudeCli();
1283
- expect(consoleSpy).not.toHaveBeenCalledWith("🔧 Claude CLI 未安装,正在安装...");
1284
- consoleSpy.mockRestore();
1285
- });
1286
-
1287
- it("should install claude when not found", async () => {
1288
- const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
1289
- // Mock execSync to throw then succeed
1290
- const execSyncMock = vi.mocked(await import("child_process"));
1291
- execSyncMock.execSync
1292
- .mockImplementationOnce(() => {
1293
- throw new Error("command not found");
1294
- })
1295
- .mockImplementationOnce(() => Buffer.from(""));
1296
-
1297
- await service.ensureClaudeCli();
1298
- expect(consoleSpy).toHaveBeenCalledWith("🔧 Claude CLI 未安装,正在安装...");
1299
- expect(consoleSpy).toHaveBeenCalledWith("✅ Claude CLI 安装完成");
1300
- consoleSpy.mockRestore();
1301
- });
1302
-
1303
- it("should throw error when installation fails", async () => {
1304
- const execSyncMock = vi.mocked(await import("child_process"));
1305
- execSyncMock.execSync
1306
- .mockImplementationOnce(() => {
1307
- throw new Error("command not found");
1308
- })
1309
- .mockImplementationOnce(() => {
1310
- throw new Error("install failed");
1311
- });
1312
-
1313
- await expect(service.ensureClaudeCli()).rejects.toThrow(
1314
- "Claude CLI 安装失败: install failed",
1315
- );
1316
- });
1317
- });
1318
1263
  });
@@ -20,7 +20,6 @@ import { MarkdownFormatter, ReviewReportService } from "./review-report";
20
20
  import { ReviewOptions } from "./review.config";
21
21
  import { IssueVerifyService } from "./issue-verify.service";
22
22
  import { DeletionImpactService } from "./deletion-impact.service";
23
- import { execSync } from "child_process";
24
23
  import { ReviewContextBuilder, type ReviewContext } from "./review-context";
25
24
  import { ReviewIssueFilter } from "./review-issue-filter";
26
25
  import { filterFilesByIncludes } from "./review-includes-filter";
@@ -730,30 +729,4 @@ export class ReviewService {
730
729
 
731
730
  return result;
732
731
  }
733
-
734
- /**
735
- * 确保 Claude CLI 已安装
736
- */
737
- protected async ensureClaudeCli(ci?: boolean): Promise<void> {
738
- try {
739
- execSync("claude --version", { stdio: "ignore" });
740
- } catch {
741
- if (ci) {
742
- throw new Error(
743
- "Claude CLI 未安装。CI 环境请在 workflow 中预装: npm install -g @anthropic-ai/claude-code",
744
- );
745
- }
746
- console.log("🔧 Claude CLI 未安装,正在安装...");
747
- try {
748
- execSync("npm install -g @anthropic-ai/claude-code", {
749
- stdio: "inherit",
750
- });
751
- console.log("✅ Claude CLI 安装完成");
752
- } catch (installError) {
753
- throw new Error(
754
- `Claude CLI 安装失败: ${installError instanceof Error ? installError.message : String(installError)}`,
755
- );
756
- }
757
- }
758
- }
759
732
  }
@@ -177,7 +177,7 @@ export function calculateIssueStats(issues: ReviewIssue[]): ReviewStats {
177
177
  const validIssue = issues.filter((i) => i.valid !== "false");
178
178
  const validTotal = validIssue.length;
179
179
  const fixed = validIssue.filter((i) => i.fixed).length;
180
- const resolved = validIssue.filter((i) => i.resolved && !i.fixed).length;
180
+ const resolved = validIssue.filter((i) => i.resolved).length;
181
181
  const invalid = total - validTotal;
182
182
  const pending = validTotal - fixed - resolved;
183
183
  const fixRate = validTotal > 0 ? Math.round((fixed / validTotal) * 100 * 10) / 10 : 0;
@@ -1,224 +0,0 @@
1
- body, html {
2
- margin:0; padding: 0;
3
- height: 100%;
4
- }
5
- body {
6
- font-family: Helvetica Neue, Helvetica, Arial;
7
- font-size: 14px;
8
- color:#333;
9
- }
10
- .small { font-size: 12px; }
11
- *, *:after, *:before {
12
- -webkit-box-sizing:border-box;
13
- -moz-box-sizing:border-box;
14
- box-sizing:border-box;
15
- }
16
- h1 { font-size: 20px; margin: 0;}
17
- h2 { font-size: 14px; }
18
- pre {
19
- font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
20
- margin: 0;
21
- padding: 0;
22
- -moz-tab-size: 2;
23
- -o-tab-size: 2;
24
- tab-size: 2;
25
- }
26
- a { color:#0074D9; text-decoration:none; }
27
- a:hover { text-decoration:underline; }
28
- .strong { font-weight: bold; }
29
- .space-top1 { padding: 10px 0 0 0; }
30
- .pad2y { padding: 20px 0; }
31
- .pad1y { padding: 10px 0; }
32
- .pad2x { padding: 0 20px; }
33
- .pad2 { padding: 20px; }
34
- .pad1 { padding: 10px; }
35
- .space-left2 { padding-left:55px; }
36
- .space-right2 { padding-right:20px; }
37
- .center { text-align:center; }
38
- .clearfix { display:block; }
39
- .clearfix:after {
40
- content:'';
41
- display:block;
42
- height:0;
43
- clear:both;
44
- visibility:hidden;
45
- }
46
- .fl { float: left; }
47
- @media only screen and (max-width:640px) {
48
- .col3 { width:100%; max-width:100%; }
49
- .hide-mobile { display:none!important; }
50
- }
51
-
52
- .quiet {
53
- color: #7f7f7f;
54
- color: rgba(0,0,0,0.5);
55
- }
56
- .quiet a { opacity: 0.7; }
57
-
58
- .fraction {
59
- font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
60
- font-size: 10px;
61
- color: #555;
62
- background: #E8E8E8;
63
- padding: 4px 5px;
64
- border-radius: 3px;
65
- vertical-align: middle;
66
- }
67
-
68
- div.path a:link, div.path a:visited { color: #333; }
69
- table.coverage {
70
- border-collapse: collapse;
71
- margin: 10px 0 0 0;
72
- padding: 0;
73
- }
74
-
75
- table.coverage td {
76
- margin: 0;
77
- padding: 0;
78
- vertical-align: top;
79
- }
80
- table.coverage td.line-count {
81
- text-align: right;
82
- padding: 0 5px 0 20px;
83
- }
84
- table.coverage td.line-coverage {
85
- text-align: right;
86
- padding-right: 10px;
87
- min-width:20px;
88
- }
89
-
90
- table.coverage td span.cline-any {
91
- display: inline-block;
92
- padding: 0 5px;
93
- width: 100%;
94
- }
95
- .missing-if-branch {
96
- display: inline-block;
97
- margin-right: 5px;
98
- border-radius: 3px;
99
- position: relative;
100
- padding: 0 4px;
101
- background: #333;
102
- color: yellow;
103
- }
104
-
105
- .skip-if-branch {
106
- display: none;
107
- margin-right: 10px;
108
- position: relative;
109
- padding: 0 4px;
110
- background: #ccc;
111
- color: white;
112
- }
113
- .missing-if-branch .typ, .skip-if-branch .typ {
114
- color: inherit !important;
115
- }
116
- .coverage-summary {
117
- border-collapse: collapse;
118
- width: 100%;
119
- }
120
- .coverage-summary tr { border-bottom: 1px solid #bbb; }
121
- .keyline-all { border: 1px solid #ddd; }
122
- .coverage-summary td, .coverage-summary th { padding: 10px; }
123
- .coverage-summary tbody { border: 1px solid #bbb; }
124
- .coverage-summary td { border-right: 1px solid #bbb; }
125
- .coverage-summary td:last-child { border-right: none; }
126
- .coverage-summary th {
127
- text-align: left;
128
- font-weight: normal;
129
- white-space: nowrap;
130
- }
131
- .coverage-summary th.file { border-right: none !important; }
132
- .coverage-summary th.pct { }
133
- .coverage-summary th.pic,
134
- .coverage-summary th.abs,
135
- .coverage-summary td.pct,
136
- .coverage-summary td.abs { text-align: right; }
137
- .coverage-summary td.file { white-space: nowrap; }
138
- .coverage-summary td.pic { min-width: 120px !important; }
139
- .coverage-summary tfoot td { }
140
-
141
- .coverage-summary .sorter {
142
- height: 10px;
143
- width: 7px;
144
- display: inline-block;
145
- margin-left: 0.5em;
146
- background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
147
- }
148
- .coverage-summary .sorted .sorter {
149
- background-position: 0 -20px;
150
- }
151
- .coverage-summary .sorted-desc .sorter {
152
- background-position: 0 -10px;
153
- }
154
- .status-line { height: 10px; }
155
- /* yellow */
156
- .cbranch-no { background: yellow !important; color: #111; }
157
- /* dark red */
158
- .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
159
- .low .chart { border:1px solid #C21F39 }
160
- .highlighted,
161
- .highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
162
- background: #C21F39 !important;
163
- }
164
- /* medium red */
165
- .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
166
- /* light red */
167
- .low, .cline-no { background:#FCE1E5 }
168
- /* light green */
169
- .high, .cline-yes { background:rgb(230,245,208) }
170
- /* medium green */
171
- .cstat-yes { background:rgb(161,215,106) }
172
- /* dark green */
173
- .status-line.high, .high .cover-fill { background:rgb(77,146,33) }
174
- .high .chart { border:1px solid rgb(77,146,33) }
175
- /* dark yellow (gold) */
176
- .status-line.medium, .medium .cover-fill { background: #f9cd0b; }
177
- .medium .chart { border:1px solid #f9cd0b; }
178
- /* light yellow */
179
- .medium { background: #fff4c2; }
180
-
181
- .cstat-skip { background: #ddd; color: #111; }
182
- .fstat-skip { background: #ddd; color: #111 !important; }
183
- .cbranch-skip { background: #ddd !important; color: #111; }
184
-
185
- span.cline-neutral { background: #eaeaea; }
186
-
187
- .coverage-summary td.empty {
188
- opacity: .5;
189
- padding-top: 4px;
190
- padding-bottom: 4px;
191
- line-height: 1;
192
- color: #888;
193
- }
194
-
195
- .cover-fill, .cover-empty {
196
- display:inline-block;
197
- height: 12px;
198
- }
199
- .chart {
200
- line-height: 0;
201
- }
202
- .cover-empty {
203
- background: white;
204
- }
205
- .cover-full {
206
- border-right: none !important;
207
- }
208
- pre.prettyprint {
209
- border: none !important;
210
- padding: 0 !important;
211
- margin: 0 !important;
212
- }
213
- .com { color: #999 !important; }
214
- .ignore-none { color: #999; font-weight: normal; }
215
-
216
- .wrapper {
217
- min-height: 100%;
218
- height: auto !important;
219
- height: 100%;
220
- margin: 0 auto -48px;
221
- }
222
- .footer, .push {
223
- height: 48px;
224
- }
@@ -1,87 +0,0 @@
1
- /* eslint-disable */
2
- var jumpToCode = (function init() {
3
- // Classes of code we would like to highlight in the file view
4
- var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
5
-
6
- // Elements to highlight in the file listing view
7
- var fileListingElements = ['td.pct.low'];
8
-
9
- // We don't want to select elements that are direct descendants of another match
10
- var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
11
-
12
- // Selector that finds elements on the page to which we can jump
13
- var selector =
14
- fileListingElements.join(', ') +
15
- ', ' +
16
- notSelector +
17
- missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
18
-
19
- // The NodeList of matching elements
20
- var missingCoverageElements = document.querySelectorAll(selector);
21
-
22
- var currentIndex;
23
-
24
- function toggleClass(index) {
25
- missingCoverageElements
26
- .item(currentIndex)
27
- .classList.remove('highlighted');
28
- missingCoverageElements.item(index).classList.add('highlighted');
29
- }
30
-
31
- function makeCurrent(index) {
32
- toggleClass(index);
33
- currentIndex = index;
34
- missingCoverageElements.item(index).scrollIntoView({
35
- behavior: 'smooth',
36
- block: 'center',
37
- inline: 'center'
38
- });
39
- }
40
-
41
- function goToPrevious() {
42
- var nextIndex = 0;
43
- if (typeof currentIndex !== 'number' || currentIndex === 0) {
44
- nextIndex = missingCoverageElements.length - 1;
45
- } else if (missingCoverageElements.length > 1) {
46
- nextIndex = currentIndex - 1;
47
- }
48
-
49
- makeCurrent(nextIndex);
50
- }
51
-
52
- function goToNext() {
53
- var nextIndex = 0;
54
-
55
- if (
56
- typeof currentIndex === 'number' &&
57
- currentIndex < missingCoverageElements.length - 1
58
- ) {
59
- nextIndex = currentIndex + 1;
60
- }
61
-
62
- makeCurrent(nextIndex);
63
- }
64
-
65
- return function jump(event) {
66
- if (
67
- document.getElementById('fileSearch') === document.activeElement &&
68
- document.activeElement != null
69
- ) {
70
- // if we're currently focused on the search input, we don't want to navigate
71
- return;
72
- }
73
-
74
- switch (event.which) {
75
- case 78: // n
76
- case 74: // j
77
- goToNext();
78
- break;
79
- case 66: // b
80
- case 75: // k
81
- case 80: // p
82
- goToPrevious();
83
- break;
84
- }
85
- };
86
- })();
87
- window.addEventListener('keydown', jumpToCode);