@spaceflow/review 0.46.0 → 0.47.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.
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "spaceflow",
3
+ "private": true,
4
+ "type": "module",
5
+ "dependencies": {
6
+ "@spaceflow/core": "workspace:*"
7
+ }
8
+ }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.46.0](https://github.com/Lydanne/spaceflow/compare/@spaceflow/review@0.45.0...@spaceflow/review@0.46.0) (2026-02-27)
4
+
5
+ ### 新特性
6
+
7
+ * **cli:** 实现 MCP meta-tool 代理模式 ([86426e9](https://github.com/Lydanne/spaceflow/commit/86426e979ea989a3688721f33e17035d7c96c984))
8
+
9
+ ### 其他修改
10
+
11
+ * **cli:** released version 0.36.0 [no ci] ([e48738a](https://github.com/Lydanne/spaceflow/commit/e48738a3d56a0fc8f5e48f2bbfffd2ca90041376))
12
+ * **core:** released version 0.14.0 [no ci] ([67f47ac](https://github.com/Lydanne/spaceflow/commit/67f47ac3a894529f174f3136925707d24570df98))
13
+ * **publish:** released version 0.38.0 [no ci] ([2a3adf7](https://github.com/Lydanne/spaceflow/commit/2a3adf75af6a44a890b198609bed1090f6d3be6d))
14
+ * **review-summary:** released version 0.15.0 [no ci] ([626b7dd](https://github.com/Lydanne/spaceflow/commit/626b7dd5b73c62d5f5c48f7dc585f60eb775dad0))
15
+ * **scripts:** released version 0.15.0 [no ci] ([bf9e533](https://github.com/Lydanne/spaceflow/commit/bf9e53349884b3bd4ca845f493d28421a5ffc91d))
16
+ * **shared:** released version 0.5.0 [no ci] ([c936cfc](https://github.com/Lydanne/spaceflow/commit/c936cfc432a517e87639e99870a11729b4c91ae4))
17
+ * **shell:** released version 0.15.0 [no ci] ([0dc9b31](https://github.com/Lydanne/spaceflow/commit/0dc9b31f4d73ac359e2efa7f07e1e5778f9e85c2))
18
+
3
19
  ## [0.45.0](https://github.com/Lydanne/spaceflow/compare/@spaceflow/review@0.44.0...@spaceflow/review@0.45.0) (2026-02-27)
4
20
 
5
21
  ### 新特性
package/dist/index.js CHANGED
@@ -4915,17 +4915,13 @@ ${deletedCodeSection}
4915
4915
 
4916
4916
 
4917
4917
 
4918
- /** MCP 工具输入 schema */ const listRulesInputSchema = z.object({
4919
- cwd: z.string().optional().describe(t("review:mcp.dto.cwd"))
4920
- });
4918
+ /** MCP 工具输入 schema */ const listRulesInputSchema = z.object({});
4921
4919
  const getRulesForFileInputSchema = z.object({
4922
4920
  filePath: z.string().describe(t("review:mcp.dto.filePath")),
4923
- cwd: z.string().optional().describe(t("review:mcp.dto.cwd")),
4924
4921
  includeExamples: z.boolean().optional().describe(t("review:mcp.dto.includeExamples"))
4925
4922
  });
4926
4923
  const getRuleDetailInputSchema = z.object({
4927
- ruleId: z.string().describe(t("review:mcp.dto.ruleId")),
4928
- cwd: z.string().optional().describe(t("review:mcp.dto.cwd"))
4924
+ ruleId: z.string().describe(t("review:mcp.dto.ruleId"))
4929
4925
  });
4930
4926
  /**
4931
4927
  * 获取 GitProviderService(可选)
@@ -4978,117 +4974,109 @@ const getRuleDetailInputSchema = z.object({
4978
4974
  }
4979
4975
  return specService.deduplicateSpecs(allSpecs);
4980
4976
  }
4981
- /**
4982
- * Review MCP 服务器定义
4983
- */ const reviewMcpServer = {
4984
- name: "review-mcp",
4985
- version: "1.0.0",
4986
- description: t("review:mcp.serverDescription"),
4987
- tools: [
4988
- {
4989
- name: "list_rules",
4990
- description: t("review:mcp.listRules"),
4991
- inputSchema: listRulesInputSchema,
4992
- handler: async (input, ctx)=>{
4993
- const { cwd } = input;
4994
- const workDir = cwd || process.cwd();
4995
- const specs = await loadAllSpecs(workDir, ctx);
4996
- const rules = specs.flatMap((spec)=>spec.rules.map((rule)=>({
4997
- id: rule.id,
4998
- title: rule.title,
4999
- description: rule.description.slice(0, 200) + (rule.description.length > 200 ? "..." : ""),
5000
- severity: rule.severity || spec.severity,
5001
- extensions: spec.extensions,
5002
- specFile: spec.filename,
5003
- includes: spec.includes,
5004
- hasExamples: rule.examples.length > 0
5005
- })));
5006
- return {
5007
- total: rules.length,
5008
- rules
5009
- };
5010
- }
5011
- },
5012
- {
5013
- name: "get_rules_for_file",
5014
- description: t("review:mcp.getRulesForFile"),
5015
- inputSchema: getRulesForFileInputSchema,
5016
- handler: async (input, ctx)=>{
5017
- const { filePath, cwd, includeExamples } = input;
5018
- const workDir = cwd || process.cwd();
5019
- const allSpecs = await loadAllSpecs(workDir, ctx);
5020
- const specService = new ReviewSpecService();
5021
- const applicableSpecs = specService.filterApplicableSpecs(allSpecs, [
5022
- {
5023
- filename: filePath
5024
- }
5025
- ]);
5026
- const micromatchModule = await __webpack_require__.e(/* import() */ "551").then(__webpack_require__.bind(__webpack_require__, 946));
5027
- const micromatch = micromatchModule.default || micromatchModule;
5028
- const rules = applicableSpecs.flatMap((spec)=>spec.rules.filter((rule)=>{
5029
- const includes = rule.includes || spec.includes;
5030
- if (includes.length === 0) return true;
5031
- return micromatch.isMatch(filePath, includes, {
5032
- matchBase: true
5033
- });
5034
- }).map((rule)=>({
5035
- id: rule.id,
5036
- title: rule.title,
5037
- description: rule.description,
5038
- severity: rule.severity || spec.severity,
5039
- specFile: spec.filename,
5040
- ...includeExamples && rule.examples.length > 0 ? {
5041
- examples: rule.examples.map((ex)=>({
5042
- type: ex.type,
5043
- lang: ex.lang,
5044
- code: ex.code
5045
- }))
5046
- } : {}
5047
- })));
5048
- return {
5049
- file: filePath,
5050
- total: rules.length,
5051
- rules
5052
- };
5053
- }
5054
- },
5055
- {
5056
- name: "get_rule_detail",
5057
- description: t("review:mcp.getRuleDetail"),
5058
- inputSchema: getRuleDetailInputSchema,
5059
- handler: async (input, ctx)=>{
5060
- const { ruleId, cwd } = input;
5061
- const workDir = cwd || process.cwd();
5062
- const specs = await loadAllSpecs(workDir, ctx);
5063
- const specService = new ReviewSpecService();
5064
- const result = specService.findRuleById(ruleId, specs);
5065
- if (!result) {
5066
- return {
5067
- error: t("review:mcp.ruleNotFound", {
5068
- ruleId
5069
- })
5070
- };
4977
+ const tools = [
4978
+ {
4979
+ name: "list_rules",
4980
+ description: t("review:mcp.listRules"),
4981
+ inputSchema: listRulesInputSchema,
4982
+ handler: async (_input, ctx)=>{
4983
+ const workDir = ctx.cwd;
4984
+ const specs = await loadAllSpecs(workDir, ctx);
4985
+ const rules = specs.flatMap((spec)=>spec.rules.map((rule)=>({
4986
+ id: rule.id,
4987
+ title: rule.title,
4988
+ description: rule.description.slice(0, 200) + (rule.description.length > 200 ? "..." : ""),
4989
+ severity: rule.severity || spec.severity,
4990
+ extensions: spec.extensions,
4991
+ specFile: spec.filename,
4992
+ includes: spec.includes,
4993
+ hasExamples: rule.examples.length > 0
4994
+ })));
4995
+ return {
4996
+ total: rules.length,
4997
+ rules
4998
+ };
4999
+ }
5000
+ },
5001
+ {
5002
+ name: "get_rules_for_file",
5003
+ description: t("review:mcp.getRulesForFile"),
5004
+ inputSchema: getRulesForFileInputSchema,
5005
+ handler: async (input, ctx)=>{
5006
+ const { filePath, includeExamples } = input;
5007
+ const workDir = ctx.cwd;
5008
+ const allSpecs = await loadAllSpecs(workDir, ctx);
5009
+ const specService = new ReviewSpecService();
5010
+ const applicableSpecs = specService.filterApplicableSpecs(allSpecs, [
5011
+ {
5012
+ filename: filePath
5071
5013
  }
5072
- const { rule, spec } = result;
5014
+ ]);
5015
+ const micromatchModule = await __webpack_require__.e(/* import() */ "551").then(__webpack_require__.bind(__webpack_require__, 946));
5016
+ const micromatch = micromatchModule.default || micromatchModule;
5017
+ const rules = applicableSpecs.flatMap((spec)=>spec.rules.filter((rule)=>{
5018
+ const includes = rule.includes || spec.includes;
5019
+ if (includes.length === 0) return true;
5020
+ return micromatch.isMatch(filePath, includes, {
5021
+ matchBase: true
5022
+ });
5023
+ }).map((rule)=>({
5024
+ id: rule.id,
5025
+ title: rule.title,
5026
+ description: rule.description,
5027
+ severity: rule.severity || spec.severity,
5028
+ specFile: spec.filename,
5029
+ ...includeExamples && rule.examples.length > 0 ? {
5030
+ examples: rule.examples.map((ex)=>({
5031
+ type: ex.type,
5032
+ lang: ex.lang,
5033
+ code: ex.code
5034
+ }))
5035
+ } : {}
5036
+ })));
5037
+ return {
5038
+ file: filePath,
5039
+ total: rules.length,
5040
+ rules
5041
+ };
5042
+ }
5043
+ },
5044
+ {
5045
+ name: "get_rule_detail",
5046
+ description: t("review:mcp.getRuleDetail"),
5047
+ inputSchema: getRuleDetailInputSchema,
5048
+ handler: async (input, ctx)=>{
5049
+ const { ruleId } = input;
5050
+ const workDir = ctx.cwd;
5051
+ const specs = await loadAllSpecs(workDir, ctx);
5052
+ const specService = new ReviewSpecService();
5053
+ const result = specService.findRuleById(ruleId, specs);
5054
+ if (!result) {
5073
5055
  return {
5074
- id: rule.id,
5075
- title: rule.title,
5076
- description: rule.description,
5077
- severity: rule.severity || spec.severity,
5078
- specFile: spec.filename,
5079
- extensions: spec.extensions,
5080
- includes: spec.includes,
5081
- overrides: rule.overrides,
5082
- examples: rule.examples.map((ex)=>({
5083
- type: ex.type,
5084
- lang: ex.lang,
5085
- code: ex.code
5086
- }))
5056
+ error: t("review:mcp.ruleNotFound", {
5057
+ ruleId
5058
+ })
5087
5059
  };
5088
5060
  }
5061
+ const { rule, spec } = result;
5062
+ return {
5063
+ id: rule.id,
5064
+ title: rule.title,
5065
+ description: rule.description,
5066
+ severity: rule.severity || spec.severity,
5067
+ specFile: spec.filename,
5068
+ extensions: spec.extensions,
5069
+ includes: spec.includes,
5070
+ overrides: rule.overrides,
5071
+ examples: rule.examples.map((ex)=>({
5072
+ type: ex.type,
5073
+ lang: ex.lang,
5074
+ code: ex.code
5075
+ }))
5076
+ };
5089
5077
  }
5090
- ]
5091
- };
5078
+ }
5079
+ ];
5092
5080
 
5093
5081
  ;// CONCATENATED MODULE: ./src/index.ts
5094
5082
 
@@ -5107,7 +5095,7 @@ const extension = defineExtension({
5107
5095
  description: t("review:extensionDescription"),
5108
5096
  configKey: "review",
5109
5097
  configSchema: reviewSchema,
5110
- mcp: reviewMcpServer,
5098
+ tools: tools,
5111
5099
  commands: [
5112
5100
  {
5113
5101
  name: "review",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spaceflow/review",
3
- "version": "0.46.0",
3
+ "version": "0.47.0",
4
4
  "description": "Spaceflow 代码审查插件,使用 LLM 对 PR 代码进行自动审查",
5
5
  "license": "MIT",
6
6
  "author": "Lydanne",
@@ -25,10 +25,10 @@
25
25
  "@vitest/coverage-v8": "^4.0.18",
26
26
  "unplugin-swc": "^1.5.9",
27
27
  "vitest": "^4.0.18",
28
- "@spaceflow/cli": "0.36.0"
28
+ "@spaceflow/cli": "0.37.0"
29
29
  },
30
30
  "peerDependencies": {
31
- "@spaceflow/core": "0.14.0"
31
+ "@spaceflow/core": "0.15.0"
32
32
  },
33
33
  "spaceflow": {
34
34
  "type": "flow",
package/src/index.ts CHANGED
@@ -8,7 +8,7 @@ import { ReviewSpecService } from "./review-spec";
8
8
  import { ReviewReportService, type ReportFormat } from "./review-report";
9
9
  import { IssueVerifyService } from "./issue-verify.service";
10
10
  import { DeletionImpactService } from "./deletion-impact.service";
11
- import { reviewMcpServer } from "./mcp";
11
+ import { tools } from "./mcp";
12
12
 
13
13
  export const extension = defineExtension({
14
14
  name: "review",
@@ -16,7 +16,7 @@ export const extension = defineExtension({
16
16
  description: t("review:extensionDescription"),
17
17
  configKey: "review",
18
18
  configSchema: reviewSchema,
19
- mcp: reviewMcpServer,
19
+ tools,
20
20
  commands: [
21
21
  {
22
22
  name: "review",
package/src/mcp/index.ts CHANGED
@@ -1,29 +1,19 @@
1
- import {
2
- t,
3
- z,
4
- type McpServerDefinition,
5
- type SpaceflowContext,
6
- type GitProviderService,
7
- } from "@spaceflow/core";
1
+ import { t, z, type SpaceflowContext, type GitProviderService } from "@spaceflow/core";
8
2
  import { ReviewSpecService } from "../review-spec";
9
3
  import type { ReviewConfig } from "../review.config";
10
4
  import { join } from "path";
11
5
  import { existsSync } from "fs";
12
6
 
13
7
  /** MCP 工具输入 schema */
14
- export const listRulesInputSchema = z.object({
15
- cwd: z.string().optional().describe(t("review:mcp.dto.cwd")),
16
- });
8
+ export const listRulesInputSchema = z.object({});
17
9
 
18
10
  export const getRulesForFileInputSchema = z.object({
19
11
  filePath: z.string().describe(t("review:mcp.dto.filePath")),
20
- cwd: z.string().optional().describe(t("review:mcp.dto.cwd")),
21
12
  includeExamples: z.boolean().optional().describe(t("review:mcp.dto.includeExamples")),
22
13
  });
23
14
 
24
15
  export const getRuleDetailInputSchema = z.object({
25
16
  ruleId: z.string().describe(t("review:mcp.dto.ruleId")),
26
- cwd: z.string().optional().describe(t("review:mcp.dto.cwd")),
27
17
  });
28
18
 
29
19
  /**
@@ -81,111 +71,98 @@ async function loadAllSpecs(cwd: string, ctx: SpaceflowContext) {
81
71
  return specService.deduplicateSpecs(allSpecs);
82
72
  }
83
73
 
84
- /**
85
- * Review MCP 服务器定义
86
- */
87
- export const reviewMcpServer: McpServerDefinition = {
88
- name: "review-mcp",
89
- version: "1.0.0",
90
- description: t("review:mcp.serverDescription"),
91
- tools: [
92
- {
93
- name: "list_rules",
94
- description: t("review:mcp.listRules"),
95
- inputSchema: listRulesInputSchema,
96
- handler: async (input, ctx) => {
97
- const { cwd } = input as z.infer<typeof listRulesInputSchema>;
98
- const workDir = cwd || process.cwd();
99
- const specs = await loadAllSpecs(workDir, ctx);
100
- const rules = specs.flatMap((spec) =>
101
- spec.rules.map((rule) => ({
102
- id: rule.id,
103
- title: rule.title,
104
- description:
105
- rule.description.slice(0, 200) + (rule.description.length > 200 ? "..." : ""),
106
- severity: rule.severity || spec.severity,
107
- extensions: spec.extensions,
108
- specFile: spec.filename,
109
- includes: spec.includes,
110
- hasExamples: rule.examples.length > 0,
111
- })),
112
- );
113
- return { total: rules.length, rules };
114
- },
115
- },
116
- {
117
- name: "get_rules_for_file",
118
- description: t("review:mcp.getRulesForFile"),
119
- inputSchema: getRulesForFileInputSchema,
120
- handler: async (input, ctx) => {
121
- const { filePath, cwd, includeExamples } = input as z.infer<
122
- typeof getRulesForFileInputSchema
123
- >;
124
- const workDir = cwd || process.cwd();
125
- const allSpecs = await loadAllSpecs(workDir, ctx);
126
- const specService = new ReviewSpecService();
127
- const applicableSpecs = specService.filterApplicableSpecs(allSpecs, [
128
- { filename: filePath },
129
- ]);
130
- const micromatchModule = await import("micromatch");
131
- const micromatch = micromatchModule.default || micromatchModule;
132
- const rules = applicableSpecs.flatMap((spec) =>
133
- spec.rules
134
- .filter((rule) => {
135
- const includes = rule.includes || spec.includes;
136
- if (includes.length === 0) return true;
137
- return micromatch.isMatch(filePath, includes, { matchBase: true });
138
- })
139
- .map((rule) => ({
140
- id: rule.id,
141
- title: rule.title,
142
- description: rule.description,
143
- severity: rule.severity || spec.severity,
144
- specFile: spec.filename,
145
- ...(includeExamples && rule.examples.length > 0
146
- ? {
147
- examples: rule.examples.map((ex) => ({
148
- type: ex.type,
149
- lang: ex.lang,
150
- code: ex.code,
151
- })),
152
- }
153
- : {}),
154
- })),
155
- );
156
- return { file: filePath, total: rules.length, rules };
157
- },
158
- },
159
- {
160
- name: "get_rule_detail",
161
- description: t("review:mcp.getRuleDetail"),
162
- inputSchema: getRuleDetailInputSchema,
163
- handler: async (input, ctx) => {
164
- const { ruleId, cwd } = input as z.infer<typeof getRuleDetailInputSchema>;
165
- const workDir = cwd || process.cwd();
166
- const specs = await loadAllSpecs(workDir, ctx);
167
- const specService = new ReviewSpecService();
168
- const result = specService.findRuleById(ruleId, specs);
169
- if (!result) {
170
- return { error: t("review:mcp.ruleNotFound", { ruleId }) };
171
- }
172
- const { rule, spec } = result;
173
- return {
74
+ export const tools = [
75
+ {
76
+ name: "list_rules",
77
+ description: t("review:mcp.listRules"),
78
+ inputSchema: listRulesInputSchema,
79
+ handler: async (_input, ctx) => {
80
+ const workDir = ctx.cwd;
81
+ const specs = await loadAllSpecs(workDir, ctx);
82
+ const rules = specs.flatMap((spec) =>
83
+ spec.rules.map((rule) => ({
174
84
  id: rule.id,
175
85
  title: rule.title,
176
- description: rule.description,
86
+ description:
87
+ rule.description.slice(0, 200) + (rule.description.length > 200 ? "..." : ""),
177
88
  severity: rule.severity || spec.severity,
178
- specFile: spec.filename,
179
89
  extensions: spec.extensions,
90
+ specFile: spec.filename,
180
91
  includes: spec.includes,
181
- overrides: rule.overrides,
182
- examples: rule.examples.map((ex) => ({
183
- type: ex.type,
184
- lang: ex.lang,
185
- code: ex.code,
92
+ hasExamples: rule.examples.length > 0,
93
+ })),
94
+ );
95
+ return { total: rules.length, rules };
96
+ },
97
+ },
98
+ {
99
+ name: "get_rules_for_file",
100
+ description: t("review:mcp.getRulesForFile"),
101
+ inputSchema: getRulesForFileInputSchema,
102
+ handler: async (input, ctx) => {
103
+ const { filePath, includeExamples } = input as z.infer<typeof getRulesForFileInputSchema>;
104
+ const workDir = ctx.cwd;
105
+ const allSpecs = await loadAllSpecs(workDir, ctx);
106
+ const specService = new ReviewSpecService();
107
+ const applicableSpecs = specService.filterApplicableSpecs(allSpecs, [{ filename: filePath }]);
108
+ const micromatchModule = await import("micromatch");
109
+ const micromatch = micromatchModule.default || micromatchModule;
110
+ const rules = applicableSpecs.flatMap((spec) =>
111
+ spec.rules
112
+ .filter((rule) => {
113
+ const includes = rule.includes || spec.includes;
114
+ if (includes.length === 0) return true;
115
+ return micromatch.isMatch(filePath, includes, { matchBase: true });
116
+ })
117
+ .map((rule) => ({
118
+ id: rule.id,
119
+ title: rule.title,
120
+ description: rule.description,
121
+ severity: rule.severity || spec.severity,
122
+ specFile: spec.filename,
123
+ ...(includeExamples && rule.examples.length > 0
124
+ ? {
125
+ examples: rule.examples.map((ex) => ({
126
+ type: ex.type,
127
+ lang: ex.lang,
128
+ code: ex.code,
129
+ })),
130
+ }
131
+ : {}),
186
132
  })),
187
- };
188
- },
133
+ );
134
+ return { file: filePath, total: rules.length, rules };
135
+ },
136
+ },
137
+ {
138
+ name: "get_rule_detail",
139
+ description: t("review:mcp.getRuleDetail"),
140
+ inputSchema: getRuleDetailInputSchema,
141
+ handler: async (input, ctx) => {
142
+ const { ruleId } = input as z.infer<typeof getRuleDetailInputSchema>;
143
+ const workDir = ctx.cwd;
144
+ const specs = await loadAllSpecs(workDir, ctx);
145
+ const specService = new ReviewSpecService();
146
+ const result = specService.findRuleById(ruleId, specs);
147
+ if (!result) {
148
+ return { error: t("review:mcp.ruleNotFound", { ruleId }) };
149
+ }
150
+ const { rule, spec } = result;
151
+ return {
152
+ id: rule.id,
153
+ title: rule.title,
154
+ description: rule.description,
155
+ severity: rule.severity || spec.severity,
156
+ specFile: spec.filename,
157
+ extensions: spec.extensions,
158
+ includes: spec.includes,
159
+ overrides: rule.overrides,
160
+ examples: rule.examples.map((ex) => ({
161
+ type: ex.type,
162
+ lang: ex.lang,
163
+ code: ex.code,
164
+ })),
165
+ };
189
166
  },
190
- ],
191
- };
167
+ },
168
+ ];