@spaceflow/review 0.45.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.
- package/.spaceflow/package.json +8 -0
- package/CHANGELOG.md +32 -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,37 @@
|
|
|
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
|
+
|
|
19
|
+
## [0.45.0](https://github.com/Lydanne/spaceflow/compare/@spaceflow/review@0.44.0...@spaceflow/review@0.45.0) (2026-02-27)
|
|
20
|
+
|
|
21
|
+
### 新特性
|
|
22
|
+
|
|
23
|
+
* **shared:** 在非 workspace 模式下为 pnpm 创建空 pnpm-workspace.yaml ([0b72b8c](https://github.com/Lydanne/spaceflow/commit/0b72b8c50068f8d1ce131f70e60438fb0ad3c0f9))
|
|
24
|
+
|
|
25
|
+
### 其他修改
|
|
26
|
+
|
|
27
|
+
* **cli:** released version 0.35.0 [no ci] ([527b4bc](https://github.com/Lydanne/spaceflow/commit/527b4bcec3a2dbe10f6f5848e80418df733a57db))
|
|
28
|
+
* **core:** released version 0.13.0 [no ci] ([9244d7c](https://github.com/Lydanne/spaceflow/commit/9244d7cf8d217ea3af22d1ef1fa7a2ccec852615))
|
|
29
|
+
* **publish:** released version 0.37.0 [no ci] ([c1e39bd](https://github.com/Lydanne/spaceflow/commit/c1e39bd28f52a40ca4423ae1088b3b29cffe4946))
|
|
30
|
+
* **review-summary:** released version 0.14.0 [no ci] ([4e39c73](https://github.com/Lydanne/spaceflow/commit/4e39c7337f74ac66f10c15cfae2b6c32eccae561))
|
|
31
|
+
* **scripts:** released version 0.14.0 [no ci] ([6b3bb66](https://github.com/Lydanne/spaceflow/commit/6b3bb6659666f58cfd8aa109f12df13694c9895f))
|
|
32
|
+
* **shared:** released version 0.4.0 [no ci] ([ea8bcde](https://github.com/Lydanne/spaceflow/commit/ea8bcdebc41ccbfa7ed9fd66f867c327976aa334))
|
|
33
|
+
* **shell:** released version 0.14.0 [no ci] ([04f61bf](https://github.com/Lydanne/spaceflow/commit/04f61bfd5a45ab37319aadd6fd4a064259e62e1d))
|
|
34
|
+
|
|
3
35
|
## [0.44.0](https://github.com/Lydanne/spaceflow/compare/@spaceflow/review@0.43.0...@spaceflow/review@0.44.0) (2026-02-27)
|
|
4
36
|
|
|
5
37
|
### 新特性
|
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.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.
|
|
28
|
+
"@spaceflow/cli": "0.37.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@spaceflow/core": "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 {
|
|
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
|
+
];
|