code-frontmatter 0.2.5 → 0.2.7
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/README.md +91 -51
- package/README.zh-CN.md +156 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +253 -18
- package/dist/index.js.map +1 -0
- package/dist/parser.d.ts +16 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +164 -0
- package/dist/parser.js.map +1 -0
- package/dist/registry.d.ts +28 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +96 -0
- package/dist/registry.js.map +1 -0
- package/dist/schema.d.ts +141 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +82 -0
- package/dist/schema.js.map +1 -0
- package/dist/tools/read.d.ts +15 -0
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +97 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/register.d.ts +25 -0
- package/dist/tools/register.d.ts.map +1 -0
- package/dist/tools/register.js +71 -0
- package/dist/tools/register.js.map +1 -0
- package/dist/tools/search.d.ts +22 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +104 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/write.d.ts +32 -0
- package/dist/tools/write.d.ts.map +1 -0
- package/dist/tools/write.js +130 -0
- package/dist/tools/write.js.map +1 -0
- package/package.json +7 -4
package/dist/index.js
CHANGED
|
@@ -1,19 +1,254 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
2
|
+
/*---
|
|
3
|
+
intent: "MCP Server 入口:注册 cfm_read、cfm_write、cfm_search、cfm_register_language 四个工具,通过 stdio 与 AI IDE 通信"
|
|
4
|
+
role: entry
|
|
5
|
+
exports:
|
|
6
|
+
- "main: MCP Server 启动函数"
|
|
7
|
+
depends_on:
|
|
8
|
+
- "@modelcontextprotocol/sdk"
|
|
9
|
+
- "./registry.ts"
|
|
10
|
+
- "./tools/read.ts"
|
|
11
|
+
- "./tools/search.ts"
|
|
12
|
+
- "./tools/register.ts"
|
|
13
|
+
- "./tools/write.ts"
|
|
14
|
+
when_to_load: "修改 MCP 工具注册、Server 配置或启动流程时加载"
|
|
15
|
+
ai_notes: "使用 stdio transport,兼容所有 MCP 客户端(Cursor、Claude Desktop 等)"
|
|
16
|
+
---*/
|
|
17
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
18
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
import { loadRegistry } from "./registry.js";
|
|
21
|
+
import { scanDirectory } from "./tools/read.js";
|
|
22
|
+
import { searchFrontmatter } from "./tools/search.js";
|
|
23
|
+
import { registerNewLanguage } from "./tools/register.js";
|
|
24
|
+
import { writeFrontmatter } from "./tools/write.js";
|
|
25
|
+
/**
|
|
26
|
+
* 创建并配置 MCP Server
|
|
27
|
+
*/
|
|
28
|
+
async function main() {
|
|
29
|
+
// 初始化语言注册表
|
|
30
|
+
await loadRegistry();
|
|
31
|
+
// 创建 MCP Server 实例
|
|
32
|
+
const server = new McpServer({
|
|
33
|
+
name: "code-frontmatter",
|
|
34
|
+
version: "0.2.0",
|
|
35
|
+
});
|
|
36
|
+
// ─── 工具 1: cfm_read ───────────────────────────────────
|
|
37
|
+
server.tool("cfm_read", "扫描项目目录中所有代码文件的 CFM 表头,返回结构化索引。这是 AI 建立项目全局认知的第一步:用极低 Token 消耗获取整个项目的文件身份信息。", {
|
|
38
|
+
directory: z
|
|
39
|
+
.string()
|
|
40
|
+
.describe("要扫描的项目根目录的绝对路径"),
|
|
41
|
+
cfm_only: z
|
|
42
|
+
.boolean()
|
|
43
|
+
.optional()
|
|
44
|
+
.default(false)
|
|
45
|
+
.describe("是否只返回有 CFM 表头的文件(默认 false,返回全部)"),
|
|
46
|
+
ignore_dirs: z
|
|
47
|
+
.array(z.string())
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("额外忽略的目录名列表(默认已忽略 node_modules, .git, dist 等)"),
|
|
50
|
+
}, async ({ directory, cfm_only, ignore_dirs }) => {
|
|
51
|
+
try {
|
|
52
|
+
const result = await scanDirectory(directory, {
|
|
53
|
+
cfmOnly: cfm_only,
|
|
54
|
+
ignoreDirs: ignore_dirs,
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
type: "text",
|
|
60
|
+
text: JSON.stringify(result, null, 2),
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
return {
|
|
67
|
+
content: [
|
|
68
|
+
{
|
|
69
|
+
type: "text",
|
|
70
|
+
text: `扫描失败: ${error.message}`,
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
isError: true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
// ─── 工具 2: cfm_write ──────────────────────────────────
|
|
78
|
+
server.tool("cfm_write", "将标准 CFM 表头写入指定代码文件。CRITICAL: 更新表头时必须执行'主动维护':1. 读取旧表头。2. 保留'永久性技术约束/经验教训'。3. 丢弃'变更日志/历史/流程信息'。4. 将剩余内容极致精简。禁止盲目覆盖!", {
|
|
79
|
+
file: z
|
|
80
|
+
.string()
|
|
81
|
+
.describe("目标文件的绝对路径"),
|
|
82
|
+
intent: z
|
|
83
|
+
.string()
|
|
84
|
+
.max(300)
|
|
85
|
+
.describe("文件的核心用途。必须精简高效(<50字),拒绝废话。"),
|
|
86
|
+
role: z
|
|
87
|
+
.string()
|
|
88
|
+
.describe("文件角色(如 service, component, util, config, page, model, entry, example)"),
|
|
89
|
+
exports: z
|
|
90
|
+
.array(z.string())
|
|
91
|
+
.describe("导出的关键 API/组件列表。格式:'Name: Brief desc'。只列出最重要的,拒绝冗长签名。"),
|
|
92
|
+
depends_on: z
|
|
93
|
+
.array(z.string())
|
|
94
|
+
.optional()
|
|
95
|
+
.describe("关键依赖的文件路径列表"),
|
|
96
|
+
when_to_load: z
|
|
97
|
+
.string()
|
|
98
|
+
.optional()
|
|
99
|
+
.describe("什么场景下才需要读取此文件全文。只描述关键触发条件,保持简短。"),
|
|
100
|
+
mutates_state: z
|
|
101
|
+
.boolean()
|
|
102
|
+
.optional()
|
|
103
|
+
.describe("是否修改外部状态(数据库、全局变量等)"),
|
|
104
|
+
side_effects: z
|
|
105
|
+
.array(z.string())
|
|
106
|
+
.optional()
|
|
107
|
+
.describe("副作用描述列表"),
|
|
108
|
+
domain: z
|
|
109
|
+
.string()
|
|
110
|
+
.optional()
|
|
111
|
+
.describe("业务领域标签(如 payment, auth, map)"),
|
|
112
|
+
ai_notes: z
|
|
113
|
+
.string()
|
|
114
|
+
.optional()
|
|
115
|
+
.describe("给 AI 的关键技术约束或警示。必须是永久性知识(如'使用捕获阶段监听')。严禁记录变更日志/历史/作者/日期!只保留对理解代码至关重要的信息,越短越好。"),
|
|
116
|
+
}, async ({ file, intent, role, exports, depends_on, when_to_load, mutates_state, side_effects, domain, ai_notes }) => {
|
|
117
|
+
try {
|
|
118
|
+
const result = await writeFrontmatter(file, {
|
|
119
|
+
intent,
|
|
120
|
+
role,
|
|
121
|
+
exports,
|
|
122
|
+
depends_on,
|
|
123
|
+
when_to_load,
|
|
124
|
+
mutates_state,
|
|
125
|
+
side_effects,
|
|
126
|
+
domain,
|
|
127
|
+
ai_notes,
|
|
128
|
+
});
|
|
129
|
+
return {
|
|
130
|
+
content: [
|
|
131
|
+
{
|
|
132
|
+
type: "text",
|
|
133
|
+
text: JSON.stringify(result, null, 2),
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
isError: !result.success,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
return {
|
|
141
|
+
content: [
|
|
142
|
+
{
|
|
143
|
+
type: "text",
|
|
144
|
+
text: `写入失败: ${error.message}`,
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
isError: true,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
// ─── 工具 3: cfm_search ─────────────────────────────────
|
|
152
|
+
server.tool("cfm_search", "在项目中搜索匹配条件的 CFM 表头。支持按关键字(在 intent, exports 等字段中全文搜索)、按角色(role)和按业务领域(domain)过滤,用于精准定位目标文件。", {
|
|
153
|
+
directory: z
|
|
154
|
+
.string()
|
|
155
|
+
.describe("要搜索的项目根目录的绝对路径"),
|
|
156
|
+
keyword: z
|
|
157
|
+
.string()
|
|
158
|
+
.optional()
|
|
159
|
+
.describe("关键字搜索,匹配 intent、exports、ai_notes 等字段"),
|
|
160
|
+
role: z
|
|
161
|
+
.string()
|
|
162
|
+
.optional()
|
|
163
|
+
.describe("按文件角色过滤(如 service, component, util, config, page)"),
|
|
164
|
+
domain: z
|
|
165
|
+
.string()
|
|
166
|
+
.optional()
|
|
167
|
+
.describe("按业务领域过滤(如 payment, auth, map-layer)"),
|
|
168
|
+
}, async ({ directory, keyword, role, domain }) => {
|
|
169
|
+
// 至少需要一个搜索条件
|
|
170
|
+
if (!keyword && !role && !domain) {
|
|
171
|
+
return {
|
|
172
|
+
content: [
|
|
173
|
+
{
|
|
174
|
+
type: "text",
|
|
175
|
+
text: "请至少提供一个搜索条件(keyword、role 或 domain)",
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
isError: true,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
const result = await searchFrontmatter(directory, {
|
|
183
|
+
keyword,
|
|
184
|
+
role,
|
|
185
|
+
domain,
|
|
186
|
+
});
|
|
187
|
+
return {
|
|
188
|
+
content: [
|
|
189
|
+
{
|
|
190
|
+
type: "text",
|
|
191
|
+
text: JSON.stringify(result, null, 2),
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
return {
|
|
198
|
+
content: [
|
|
199
|
+
{
|
|
200
|
+
type: "text",
|
|
201
|
+
text: `搜索失败: ${error.message}`,
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
isError: true,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
// ─── 工具 4: cfm_register_language ──────────────────────
|
|
209
|
+
server.tool("cfm_register_language", "注册新的编程语言注释规则。当遇到 CFM 尚不支持的语言时,通过此工具添加该语言的注释语法,使 cfm_read 能正确提取其表头。注册仅在当前会话有效。", {
|
|
210
|
+
name: z
|
|
211
|
+
.string()
|
|
212
|
+
.describe("语言名称(如 elixir, dart, haskell)"),
|
|
213
|
+
comment_start: z
|
|
214
|
+
.string()
|
|
215
|
+
.describe("CFM 表头的起始标记(如 \"#---\")"),
|
|
216
|
+
comment_end: z
|
|
217
|
+
.string()
|
|
218
|
+
.describe("CFM 表头的结束标记(如 \"#---\")"),
|
|
219
|
+
extensions: z
|
|
220
|
+
.array(z.string())
|
|
221
|
+
.describe("该语言的文件扩展名列表(如 [\".ex\", \".exs\"])"),
|
|
222
|
+
line_prefix: z
|
|
223
|
+
.string()
|
|
224
|
+
.nullable()
|
|
225
|
+
.optional()
|
|
226
|
+
.default(null)
|
|
227
|
+
.describe("行前缀(脚本语言使用,如 \"# \"),C 家族传 null"),
|
|
228
|
+
}, async ({ name, comment_start, comment_end, extensions, line_prefix }) => {
|
|
229
|
+
const result = registerNewLanguage(name, {
|
|
230
|
+
comment_start,
|
|
231
|
+
comment_end,
|
|
232
|
+
extensions,
|
|
233
|
+
line_prefix,
|
|
234
|
+
});
|
|
235
|
+
return {
|
|
236
|
+
content: [
|
|
237
|
+
{
|
|
238
|
+
type: "text",
|
|
239
|
+
text: JSON.stringify(result, null, 2),
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
isError: !result.success,
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
// ─── 启动 Server ────────────────────────────────────────
|
|
246
|
+
const transport = new StdioServerTransport();
|
|
247
|
+
await server.connect(transport);
|
|
248
|
+
}
|
|
249
|
+
// 启动
|
|
250
|
+
main().catch((error) => {
|
|
251
|
+
console.error("MCP Server 启动失败:", error);
|
|
252
|
+
process.exit(1);
|
|
253
|
+
});
|
|
254
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type CfmEntry } from "./schema.js";
|
|
2
|
+
/**
|
|
3
|
+
* 从单个文件中提取 CFM 表头
|
|
4
|
+
*
|
|
5
|
+
* 提取流程:
|
|
6
|
+
* 1. 根据文件扩展名查找语言注释规则
|
|
7
|
+
* 2. 在文件开头区域匹配 comment_start / comment_end 标记对
|
|
8
|
+
* 3. 提取标记之间的文本,去除 line_prefix(如 "# ")
|
|
9
|
+
* 4. 用 YAML 解析器解析,再用 Zod Schema 校验
|
|
10
|
+
*
|
|
11
|
+
* @param filePath - 文件的绝对路径
|
|
12
|
+
* @param relativePath - 用于输出的相对路径
|
|
13
|
+
* @returns CFM 条目(含解析数据或 null + 警告信息)
|
|
14
|
+
*/
|
|
15
|
+
export declare function extractFrontmatter(filePath: string, relativePath: string): Promise<CfmEntry>;
|
|
16
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAK5D;;;;;;;;;;;;GAYG;AACH,wBAAsB,kBAAkB,CACpC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GACrB,OAAO,CAAC,QAAQ,CAAC,CAuFnB"}
|
package/dist/parser.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { extname } from "node:path";
|
|
2
|
+
import { parse as parseYaml } from "yaml";
|
|
3
|
+
import { getLanguageByExtension } from "./registry.js";
|
|
4
|
+
import { CfmSchemaLoose } from "./schema.js";
|
|
5
|
+
/** 读取文件的最大字节数(只读开头部分以查找表头) */
|
|
6
|
+
const MAX_READ_BYTES = 4096;
|
|
7
|
+
/**
|
|
8
|
+
* 从单个文件中提取 CFM 表头
|
|
9
|
+
*
|
|
10
|
+
* 提取流程:
|
|
11
|
+
* 1. 根据文件扩展名查找语言注释规则
|
|
12
|
+
* 2. 在文件开头区域匹配 comment_start / comment_end 标记对
|
|
13
|
+
* 3. 提取标记之间的文本,去除 line_prefix(如 "# ")
|
|
14
|
+
* 4. 用 YAML 解析器解析,再用 Zod Schema 校验
|
|
15
|
+
*
|
|
16
|
+
* @param filePath - 文件的绝对路径
|
|
17
|
+
* @param relativePath - 用于输出的相对路径
|
|
18
|
+
* @returns CFM 条目(含解析数据或 null + 警告信息)
|
|
19
|
+
*/
|
|
20
|
+
export async function extractFrontmatter(filePath, relativePath) {
|
|
21
|
+
const ext = extname(filePath);
|
|
22
|
+
const lang = getLanguageByExtension(ext);
|
|
23
|
+
// 未知语言类型,跳过
|
|
24
|
+
if (!lang) {
|
|
25
|
+
return {
|
|
26
|
+
file: relativePath,
|
|
27
|
+
language: undefined,
|
|
28
|
+
frontmatter: null,
|
|
29
|
+
warnings: ["未注册的文件类型,跳过表头提取"],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const warnings = [];
|
|
33
|
+
try {
|
|
34
|
+
// 只读取文件开头部分,节省 I/O
|
|
35
|
+
const content = await readFileHead(filePath, MAX_READ_BYTES);
|
|
36
|
+
const { comment_start, comment_end, line_prefix } = lang.rule;
|
|
37
|
+
// 查找表头区域
|
|
38
|
+
const headerContent = extractHeaderContent(content, comment_start, comment_end, line_prefix);
|
|
39
|
+
if (!headerContent) {
|
|
40
|
+
return {
|
|
41
|
+
file: relativePath,
|
|
42
|
+
language: lang.name,
|
|
43
|
+
frontmatter: null,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// 解析 YAML
|
|
47
|
+
let parsed;
|
|
48
|
+
try {
|
|
49
|
+
parsed = parseYaml(headerContent);
|
|
50
|
+
}
|
|
51
|
+
catch (yamlError) {
|
|
52
|
+
return {
|
|
53
|
+
file: relativePath,
|
|
54
|
+
language: lang.name,
|
|
55
|
+
frontmatter: null,
|
|
56
|
+
warnings: [`YAML 解析失败: ${yamlError.message}`],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// 如果 YAML 解析结果不是对象(可能是纯文本)
|
|
60
|
+
if (!parsed || typeof parsed !== "object") {
|
|
61
|
+
return {
|
|
62
|
+
file: relativePath,
|
|
63
|
+
language: lang.name,
|
|
64
|
+
frontmatter: null,
|
|
65
|
+
warnings: ["表头内容不是有效的 YAML 对象"],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// 宽松模式校验
|
|
69
|
+
const validation = CfmSchemaLoose.safeParse(parsed);
|
|
70
|
+
if (!validation.success) {
|
|
71
|
+
warnings.push(`Schema 校验警告: ${validation.error.issues.map((i) => i.message).join("; ")}`);
|
|
72
|
+
}
|
|
73
|
+
// 检查必选字段缺失
|
|
74
|
+
if (!parsed.intent)
|
|
75
|
+
warnings.push("缺少必选字段: intent");
|
|
76
|
+
if (!parsed.role)
|
|
77
|
+
warnings.push("缺少推荐字段: role");
|
|
78
|
+
if (!parsed.exports)
|
|
79
|
+
warnings.push("缺少推荐字段: exports");
|
|
80
|
+
return {
|
|
81
|
+
file: relativePath,
|
|
82
|
+
language: lang.name,
|
|
83
|
+
frontmatter: parsed,
|
|
84
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
return {
|
|
89
|
+
file: relativePath,
|
|
90
|
+
language: lang.name,
|
|
91
|
+
frontmatter: null,
|
|
92
|
+
warnings: [`文件读取失败: ${error.message}`],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 从文件内容中提取表头区域的 YAML 文本
|
|
98
|
+
* @returns 清理后的 YAML 字符串,或 null(未找到表头)
|
|
99
|
+
*/
|
|
100
|
+
function extractHeaderContent(content, commentStart, commentEnd, linePrefix) {
|
|
101
|
+
// 统一换行符为 \n,兼容 Windows (\r\n) 和 Mac (\r)
|
|
102
|
+
let normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
103
|
+
// 去除 Shebang 行(如 #!/usr/bin/env node)
|
|
104
|
+
if (normalized.startsWith("#!")) {
|
|
105
|
+
const firstNewLine = normalized.indexOf("\n");
|
|
106
|
+
if (firstNewLine !== -1) {
|
|
107
|
+
normalized = normalized.slice(firstNewLine + 1);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// 文件只有 Shebang
|
|
111
|
+
normalized = "";
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const trimmedContent = normalized.trimStart();
|
|
115
|
+
// 查找起始标记
|
|
116
|
+
if (!trimmedContent.startsWith(commentStart)) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
// 查找结束标记(从起始标记之后开始搜索)
|
|
120
|
+
const afterStart = trimmedContent.slice(commentStart.length);
|
|
121
|
+
const endIndex = afterStart.indexOf(commentEnd);
|
|
122
|
+
if (endIndex === -1) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
// 提取标记之间的原始文本
|
|
126
|
+
let rawContent = afterStart.slice(0, endIndex);
|
|
127
|
+
// 去除每行的 line_prefix(如 Python 的 "# ")
|
|
128
|
+
if (linePrefix) {
|
|
129
|
+
const lines = rawContent.split("\n");
|
|
130
|
+
rawContent = lines
|
|
131
|
+
.map((line) => {
|
|
132
|
+
const trimmedLine = line.trimStart();
|
|
133
|
+
// 尝试完整前缀匹配(如 "# "),然后尝试去尾空格后的匹配(如 "#")
|
|
134
|
+
if (trimmedLine.startsWith(linePrefix)) {
|
|
135
|
+
return trimmedLine.slice(linePrefix.length);
|
|
136
|
+
}
|
|
137
|
+
const trimmedPrefix = linePrefix.trim();
|
|
138
|
+
if (trimmedPrefix && trimmedLine.startsWith(trimmedPrefix)) {
|
|
139
|
+
return trimmedLine.slice(trimmedPrefix.length);
|
|
140
|
+
}
|
|
141
|
+
return line;
|
|
142
|
+
})
|
|
143
|
+
.join("\n");
|
|
144
|
+
}
|
|
145
|
+
const cleaned = rawContent.trim();
|
|
146
|
+
return cleaned.length > 0 ? cleaned : null;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* 只读取文件的前 N 个字节
|
|
150
|
+
* 大文件只需读开头就能找到表头,避免全文读取
|
|
151
|
+
*/
|
|
152
|
+
async function readFileHead(filePath, maxBytes) {
|
|
153
|
+
const { open } = await import("node:fs/promises");
|
|
154
|
+
const fileHandle = await open(filePath, "r");
|
|
155
|
+
try {
|
|
156
|
+
const buffer = Buffer.alloc(maxBytes);
|
|
157
|
+
const { bytesRead } = await fileHandle.read(buffer, 0, maxBytes, 0);
|
|
158
|
+
return buffer.slice(0, bytesRead).toString("utf-8");
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
await fileHandle.close();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type LanguageRule } from "./schema.js";
|
|
2
|
+
/**
|
|
3
|
+
* 从 languages/registry.json 加载内置语言注册表
|
|
4
|
+
* 应在 MCP Server 启动时调用一次
|
|
5
|
+
*/
|
|
6
|
+
export declare function loadRegistry(): Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* 根据文件扩展名查找对应的语言注释规则
|
|
9
|
+
* @param ext - 文件扩展名,如 ".js", ".py"
|
|
10
|
+
* @returns 匹配的语言规则,未找到则返回 null
|
|
11
|
+
*/
|
|
12
|
+
export declare function getLanguageByExtension(ext: string): {
|
|
13
|
+
name: string;
|
|
14
|
+
rule: LanguageRule;
|
|
15
|
+
} | null;
|
|
16
|
+
/**
|
|
17
|
+
* 运行时动态注册新语言
|
|
18
|
+
* @param name - 语言名称
|
|
19
|
+
* @param rule - 语言注释规则
|
|
20
|
+
* @throws 如果该语言已存在则抛出错误
|
|
21
|
+
*/
|
|
22
|
+
export declare function registerLanguage(name: string, rule: LanguageRule): void;
|
|
23
|
+
/**
|
|
24
|
+
* 获取所有已注册的语言及其规则
|
|
25
|
+
* @returns 语言名称与规则的映射
|
|
26
|
+
*/
|
|
27
|
+
export declare function getAllLanguages(): Record<string, LanguageRule>;
|
|
28
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAeA,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAcpE;;;GAGG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAmBlD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAClC,GAAG,EAAE,MAAM,GACZ;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,YAAY,CAAA;CAAE,GAAG,IAAI,CAU7C;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAuBvE;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAM9D"}
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/*---
|
|
2
|
+
intent: "管理语言注册表:加载内置语言规则、按文件扩展名查找语言、支持运行时动态注册新语言"
|
|
3
|
+
role: service
|
|
4
|
+
exports:
|
|
5
|
+
- "loadRegistry: 从 registry.json 加载内置语言注册表"
|
|
6
|
+
- "getLanguageByExtension: 根据文件扩展名查找对应的语言规则"
|
|
7
|
+
- "registerLanguage: 运行时注册新的语言注释规则"
|
|
8
|
+
- "getAllLanguages: 获取所有已注册的语言列表"
|
|
9
|
+
depends_on: ["./schema.ts", "../languages/registry.json"]
|
|
10
|
+
when_to_load: "修改语言查找逻辑或注册机制时加载"
|
|
11
|
+
---*/
|
|
12
|
+
import { readFile } from "node:fs/promises";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { dirname, join } from "node:path";
|
|
15
|
+
import { LanguageRuleSchema } from "./schema.js";
|
|
16
|
+
/**
|
|
17
|
+
* 语言注册表:语言名称 → 注释规则映射
|
|
18
|
+
* 键为语言名(如 "javascript"),值为注释标记配置
|
|
19
|
+
*/
|
|
20
|
+
const registry = new Map();
|
|
21
|
+
/**
|
|
22
|
+
* 扩展名反向索引:扩展名 → 语言名映射
|
|
23
|
+
* 用于快速按扩展名查找语言
|
|
24
|
+
*/
|
|
25
|
+
const extensionIndex = new Map();
|
|
26
|
+
/**
|
|
27
|
+
* 从 languages/registry.json 加载内置语言注册表
|
|
28
|
+
* 应在 MCP Server 启动时调用一次
|
|
29
|
+
*/
|
|
30
|
+
export async function loadRegistry() {
|
|
31
|
+
// 定位 registry.json 文件路径(相对于当前模块)
|
|
32
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
33
|
+
const __dirname = dirname(__filename);
|
|
34
|
+
const registryPath = join(__dirname, "..", "languages", "registry.json");
|
|
35
|
+
const raw = await readFile(registryPath, "utf-8");
|
|
36
|
+
const data = JSON.parse(raw);
|
|
37
|
+
for (const [name, rule] of Object.entries(data)) {
|
|
38
|
+
// 用 Zod 校验每条语言规则的格式
|
|
39
|
+
const parsed = LanguageRuleSchema.parse(rule);
|
|
40
|
+
registry.set(name, parsed);
|
|
41
|
+
// 建立扩展名 → 语言名的反向索引
|
|
42
|
+
for (const ext of parsed.extensions) {
|
|
43
|
+
extensionIndex.set(ext.toLowerCase(), name);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 根据文件扩展名查找对应的语言注释规则
|
|
49
|
+
* @param ext - 文件扩展名,如 ".js", ".py"
|
|
50
|
+
* @returns 匹配的语言规则,未找到则返回 null
|
|
51
|
+
*/
|
|
52
|
+
export function getLanguageByExtension(ext) {
|
|
53
|
+
const normalizedExt = ext.toLowerCase();
|
|
54
|
+
const langName = extensionIndex.get(normalizedExt);
|
|
55
|
+
if (!langName)
|
|
56
|
+
return null;
|
|
57
|
+
const rule = registry.get(langName);
|
|
58
|
+
if (!rule)
|
|
59
|
+
return null;
|
|
60
|
+
return { name: langName, rule };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 运行时动态注册新语言
|
|
64
|
+
* @param name - 语言名称
|
|
65
|
+
* @param rule - 语言注释规则
|
|
66
|
+
* @throws 如果该语言已存在则抛出错误
|
|
67
|
+
*/
|
|
68
|
+
export function registerLanguage(name, rule) {
|
|
69
|
+
const normalizedName = name.toLowerCase();
|
|
70
|
+
if (registry.has(normalizedName)) {
|
|
71
|
+
throw new Error(`语言 "${name}" 已存在于注册表中。如需更新,请先移除旧注册。`);
|
|
72
|
+
}
|
|
73
|
+
// 检查扩展名是否与已有语言冲突
|
|
74
|
+
for (const ext of rule.extensions) {
|
|
75
|
+
const existing = extensionIndex.get(ext.toLowerCase());
|
|
76
|
+
if (existing) {
|
|
77
|
+
throw new Error(`扩展名 "${ext}" 已被语言 "${existing}" 注册。`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
registry.set(normalizedName, rule);
|
|
81
|
+
for (const ext of rule.extensions) {
|
|
82
|
+
extensionIndex.set(ext.toLowerCase(), normalizedName);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 获取所有已注册的语言及其规则
|
|
87
|
+
* @returns 语言名称与规则的映射
|
|
88
|
+
*/
|
|
89
|
+
export function getAllLanguages() {
|
|
90
|
+
const result = {};
|
|
91
|
+
for (const [name, rule] of registry.entries()) {
|
|
92
|
+
result[name] = rule;
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
|