@spaceflow/review 0.46.0 → 0.48.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/.spaceflow/package.json +8 -0
- package/CHANGELOG.md +47 -0
- package/dist/index.js +101 -113
- package/package.json +3 -3
- package/src/index.ts +2 -2
- package/src/mcp/index.ts +90 -113
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.47.0](https://github.com/Lydanne/spaceflow/compare/@spaceflow/review@0.46.0...@spaceflow/review@0.47.0) (2026-02-27)
|
|
4
|
+
|
|
5
|
+
### 新特性
|
|
6
|
+
|
|
7
|
+
* **shared:** 新增 findProjectRoot 函数用于查找项目根目录 ([7135262](https://github.com/Lydanne/spaceflow/commit/71352621f13e17a192821363e655607cfc097307))
|
|
8
|
+
|
|
9
|
+
### 修复BUG
|
|
10
|
+
|
|
11
|
+
* **cli:** 为扩展动态导入添加错误处理,防止单个扩展加载失败导致整体崩溃 ([09230dd](https://github.com/Lydanne/spaceflow/commit/09230ddf88568286b1f65c389976cb9a1a2fc880))
|
|
12
|
+
* **cli:** 修复 connectProjectMcpClient 函数中工作目录解析问题 ([94a927c](https://github.com/Lydanne/spaceflow/commit/94a927c8850b0b754459e0d8833be70721bc38d1))
|
|
13
|
+
|
|
14
|
+
### 代码重构
|
|
15
|
+
|
|
16
|
+
* **cli:** 分离工作目录和项目根目录概念,修复 .spaceflow 目录定位逻辑 ([5b7daab](https://github.com/Lydanne/spaceflow/commit/5b7daab2a3965fbd298ff85ad210d45028ff9d3d))
|
|
17
|
+
* **cli:** 重构工作目录和 .spaceflow 目录获取逻辑 ([a9708da](https://github.com/Lydanne/spaceflow/commit/a9708da6ea5b4315f802a0709125e24fe42bb10f))
|
|
18
|
+
* **core:** 优化 ExtensionLoader 访问器,简化 MCP 服务上下文获取 ([c1ac91f](https://github.com/Lydanne/spaceflow/commit/c1ac91f1990b565dbbc449e8349561e65fae368f))
|
|
19
|
+
* **core:** 简化 MCP 架构,移除 McpServerDefinition 层级 ([0d963a8](https://github.com/Lydanne/spaceflow/commit/0d963a874d03df50a140c57707b5363407e6b37d))
|
|
20
|
+
* **core:** 统一工作目录管理,优化 MCP 服务上下文 ([efe7244](https://github.com/Lydanne/spaceflow/commit/efe72444ff6207077a503200c629373a97e47a2c))
|
|
21
|
+
* **review:** 简化 MCP 工具的输入参数,统一从上下文获取工作目录 ([104893b](https://github.com/Lydanne/spaceflow/commit/104893b689bf32444850b06a2f4566292a1f8eba))
|
|
22
|
+
|
|
23
|
+
### 其他修改
|
|
24
|
+
|
|
25
|
+
* **.spaceflow:** 初始化 Spaceflow 扩展的包结构和依赖 ([5ac8d70](https://github.com/Lydanne/spaceflow/commit/5ac8d7008edcb96168a15ffc04973d154ca9f955))
|
|
26
|
+
* **cli:** released version 0.37.0 [no ci] ([a260a9a](https://github.com/Lydanne/spaceflow/commit/a260a9a13f1f7d8457ea036e40f5562548b4513e))
|
|
27
|
+
* **core:** released version 0.15.0 [no ci] ([e44cd0a](https://github.com/Lydanne/spaceflow/commit/e44cd0af8caadf1f2b89179d1ea44ecc0d018966))
|
|
28
|
+
* **publish:** released version 0.39.0 [no ci] ([f4db046](https://github.com/Lydanne/spaceflow/commit/f4db04635eab83ad44f8f3b935aa66ecfd02feff))
|
|
29
|
+
* **review-summary:** released version 0.16.0 [no ci] ([912b5f5](https://github.com/Lydanne/spaceflow/commit/912b5f5cf907935e7ef9e39ad32b742c46843b7e))
|
|
30
|
+
* **scripts:** released version 0.16.0 [no ci] ([77be50e](https://github.com/Lydanne/spaceflow/commit/77be50ea413e7b5c969c111429fd8cc425263cd1))
|
|
31
|
+
* **shared:** released version 0.6.0 [no ci] ([fcfdf75](https://github.com/Lydanne/spaceflow/commit/fcfdf75efa2146b5ed91e5c7f273a4f938c032b8))
|
|
32
|
+
* **shell:** released version 0.16.0 [no ci] ([538e157](https://github.com/Lydanne/spaceflow/commit/538e15783ed4482a25faf251a1513eae0dfb33ad))
|
|
33
|
+
|
|
34
|
+
## [0.46.0](https://github.com/Lydanne/spaceflow/compare/@spaceflow/review@0.45.0...@spaceflow/review@0.46.0) (2026-02-27)
|
|
35
|
+
|
|
36
|
+
### 新特性
|
|
37
|
+
|
|
38
|
+
* **cli:** 实现 MCP meta-tool 代理模式 ([86426e9](https://github.com/Lydanne/spaceflow/commit/86426e979ea989a3688721f33e17035d7c96c984))
|
|
39
|
+
|
|
40
|
+
### 其他修改
|
|
41
|
+
|
|
42
|
+
* **cli:** released version 0.36.0 [no ci] ([e48738a](https://github.com/Lydanne/spaceflow/commit/e48738a3d56a0fc8f5e48f2bbfffd2ca90041376))
|
|
43
|
+
* **core:** released version 0.14.0 [no ci] ([67f47ac](https://github.com/Lydanne/spaceflow/commit/67f47ac3a894529f174f3136925707d24570df98))
|
|
44
|
+
* **publish:** released version 0.38.0 [no ci] ([2a3adf7](https://github.com/Lydanne/spaceflow/commit/2a3adf75af6a44a890b198609bed1090f6d3be6d))
|
|
45
|
+
* **review-summary:** released version 0.15.0 [no ci] ([626b7dd](https://github.com/Lydanne/spaceflow/commit/626b7dd5b73c62d5f5c48f7dc585f60eb775dad0))
|
|
46
|
+
* **scripts:** released version 0.15.0 [no ci] ([bf9e533](https://github.com/Lydanne/spaceflow/commit/bf9e53349884b3bd4ca845f493d28421a5ffc91d))
|
|
47
|
+
* **shared:** released version 0.5.0 [no ci] ([c936cfc](https://github.com/Lydanne/spaceflow/commit/c936cfc432a517e87639e99870a11729b4c91ae4))
|
|
48
|
+
* **shell:** released version 0.15.0 [no ci] ([0dc9b31](https://github.com/Lydanne/spaceflow/commit/0dc9b31f4d73ac359e2efa7f07e1e5778f9e85c2))
|
|
49
|
+
|
|
3
50
|
## [0.45.0](https://github.com/Lydanne/spaceflow/compare/@spaceflow/review@0.44.0...@spaceflow/review@0.45.0) (2026-02-27)
|
|
4
51
|
|
|
5
52
|
### 新特性
|
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
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
}
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.48.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.
|
|
28
|
+
"@spaceflow/cli": "0.38.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@spaceflow/core": "0.
|
|
31
|
+
"@spaceflow/core": "0.16.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 {
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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:
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
+
];
|