code-frontmatter 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 coobi7
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ ---
2
+ intent: "README for Code Frontmatter project"
3
+ role: documentation
4
+ when_to_load: "Initial project setup or understanding CFM usage"
5
+ ---
6
+
7
+ # Code Frontmatter (CFM)
8
+
9
+ > **"Identity Cards" for your code files.**
10
+ > **Let AI understand your entire project without reading every single line.**
11
+
12
+ ---
13
+
14
+ Code Frontmatter (CFM) is an open standard and MCP (Model Context Protocol) Server that allows you to embed structured metadata (YAML) at the top of every source code file.
15
+
16
+ By scanning these lightweight headers, AI coding assistants (like Cursor, Windsurf, Claude Code) can build a cognitive map of your entire project with **minimal token cost**, eliminating hallucinations caused by context overload.
17
+
18
+ ## 🚀 Why CFM?
19
+
20
+ | Feature | Without CFM | With CFM |
21
+ |---|---|---|
22
+ | **Context Load** | Full project files (huge tokens) | Just headers (~5% tokens) |
23
+ | **Accuracy** | High hallucination risk | Deterministic file intent |
24
+ | **Cost** | $$$ Expensive API calls | $ Cheap & Efficient |
25
+ | **Scale** | Fails on large repos | Works on 10k+ file repos |
26
+
27
+ ## 📦 Installation
28
+
29
+ To use CFM with your AI IDE (Cursor, Claude Desktop, etc.), install it as an MCP Server:
30
+
31
+ ```bash
32
+ npx -y code-frontmatter
33
+ ```
34
+
35
+ ### Configuration
36
+
37
+ #### For Cursor (`~/.cursor/mcp.json`)
38
+ ```json
39
+ {
40
+ "mcpServers": {
41
+ "code-frontmatter": {
42
+ "command": "npx",
43
+ "args": ["-y", "code-frontmatter"]
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ #### For Claude Desktop
50
+ ```json
51
+ {
52
+ "mcpServers": {
53
+ "code-frontmatter": {
54
+ "command": "npx",
55
+ "args": ["-y", "code-frontmatter"]
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ ## 🛠 Usage
62
+
63
+ ### 1. Add Frontmatter to Your Code
64
+
65
+ Add a CFM header to the top of your files using your language's comment syntax.
66
+
67
+ **JavaScript / TypeScript:**
68
+ ```javascript
69
+ /*---
70
+ intent: "Manages user authentication state and login logic"
71
+ role: service
72
+ exports:
73
+ - "login: Authenticate user with email/pass"
74
+ - "logout: Clear session"
75
+ depends_on: ["api-client.ts", "store.ts"]
76
+ when_to_load: "Modifying auth flow or session handling"
77
+ ---*/
78
+
79
+ export function login(email, password) { ... }
80
+ ```
81
+
82
+ **Python:**
83
+ ```python
84
+ #---
85
+ # intent: "Data model for User entity"
86
+ # role: model
87
+ # exports:
88
+ # - "User: Standard user class"
89
+ # mutates_state: false
90
+ #---
91
+
92
+ class User: ...
93
+ ```
94
+
95
+ ### 2. Available Tools
96
+
97
+ The MCP Server exposes the following tools to your AI assistant:
98
+
99
+ - **`cfm_scan(directory)`**: Scans your project and returns a structured index of all CFM headers.
100
+ - **`cfm_search(query)`**: Search for files by keyword, role, or domain without reading full contents.
101
+ - **`cfm_register_language(name, config)`**: Teach CFM how to parse headers for a new language on the fly.
102
+
103
+ ## 📋 Schema
104
+
105
+ A valid CFM header requires at least:
106
+ - **`intent`**: What is this file for? (String)
107
+ - **`role`**: What is its architectural role? (String, e.g., `component`, `service`, `util`)
108
+ - **`exports`**: What key things does it export? (Array of Strings)
109
+
110
+ Optional fields: `depends_on`, `when_to_load`, `side_effects`, `mutates_state`, `domain`, `ai_notes`.
111
+
112
+ ## 🤝 Contributing
113
+
114
+ We welcome contributions! Please follow the standard GitHub workflow.
115
+
116
+ ## 📄 License
117
+
118
+ MIT © 2026 coobi7
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env node
2
+ /*---
3
+ intent: "MCP Server 入口:注册 cfm_scan、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/scan.ts"
11
+ - "./tools/search.ts"
12
+ - "./tools/register.ts"
13
+ when_to_load: "修改 MCP 工具注册、Server 配置或启动流程时加载"
14
+ ai_notes: "使用 stdio transport,兼容所有 MCP 客户端(Cursor、Claude Desktop 等)"
15
+ ---*/
16
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
18
+ import { z } from "zod";
19
+ import { loadRegistry } from "./registry.js";
20
+ import { scanDirectory } from "./tools/scan.js";
21
+ import { searchFrontmatter } from "./tools/search.js";
22
+ import { registerNewLanguage } from "./tools/register.js";
23
+ /**
24
+ * 创建并配置 MCP Server
25
+ */
26
+ async function main() {
27
+ // 初始化语言注册表
28
+ await loadRegistry();
29
+ // 创建 MCP Server 实例
30
+ const server = new McpServer({
31
+ name: "code-frontmatter",
32
+ version: "0.1.0",
33
+ });
34
+ // ─── 工具 1: cfm_scan ───────────────────────────────────
35
+ server.tool("cfm_scan", "扫描项目目录中所有代码文件的 CFM 表头,返回结构化索引。这是 AI 建立项目全局认知的第一步:用极低 Token 消耗获取整个项目的文件身份信息。", {
36
+ directory: z
37
+ .string()
38
+ .describe("要扫描的项目根目录的绝对路径"),
39
+ cfm_only: z
40
+ .boolean()
41
+ .optional()
42
+ .default(false)
43
+ .describe("是否只返回有 CFM 表头的文件(默认 false,返回全部)"),
44
+ ignore_dirs: z
45
+ .array(z.string())
46
+ .optional()
47
+ .describe("额外忽略的目录名列表(默认已忽略 node_modules, .git, dist 等)"),
48
+ }, async ({ directory, cfm_only, ignore_dirs }) => {
49
+ try {
50
+ const result = await scanDirectory(directory, {
51
+ cfmOnly: cfm_only,
52
+ ignoreDirs: ignore_dirs,
53
+ });
54
+ return {
55
+ content: [
56
+ {
57
+ type: "text",
58
+ text: JSON.stringify(result, null, 2),
59
+ },
60
+ ],
61
+ };
62
+ }
63
+ catch (error) {
64
+ return {
65
+ content: [
66
+ {
67
+ type: "text",
68
+ text: `扫描失败: ${error.message}`,
69
+ },
70
+ ],
71
+ isError: true,
72
+ };
73
+ }
74
+ });
75
+ // ─── 工具 2: cfm_search ─────────────────────────────────
76
+ server.tool("cfm_search", "在项目中搜索匹配条件的 CFM 表头。支持按关键字(在 intent, exports 等字段中全文搜索)、按角色(role)和按业务领域(domain)过滤,用于精准定位目标文件。", {
77
+ directory: z
78
+ .string()
79
+ .describe("要搜索的项目根目录的绝对路径"),
80
+ keyword: z
81
+ .string()
82
+ .optional()
83
+ .describe("关键字搜索,匹配 intent、exports、ai_notes 等字段"),
84
+ role: z
85
+ .string()
86
+ .optional()
87
+ .describe("按文件角色过滤(如 service, component, util, config, page)"),
88
+ domain: z
89
+ .string()
90
+ .optional()
91
+ .describe("按业务领域过滤(如 payment, auth, map-layer)"),
92
+ }, async ({ directory, keyword, role, domain }) => {
93
+ // 至少需要一个搜索条件
94
+ if (!keyword && !role && !domain) {
95
+ return {
96
+ content: [
97
+ {
98
+ type: "text",
99
+ text: "请至少提供一个搜索条件(keyword、role 或 domain)",
100
+ },
101
+ ],
102
+ isError: true,
103
+ };
104
+ }
105
+ try {
106
+ const result = await searchFrontmatter(directory, {
107
+ keyword,
108
+ role,
109
+ domain,
110
+ });
111
+ return {
112
+ content: [
113
+ {
114
+ type: "text",
115
+ text: JSON.stringify(result, null, 2),
116
+ },
117
+ ],
118
+ };
119
+ }
120
+ catch (error) {
121
+ return {
122
+ content: [
123
+ {
124
+ type: "text",
125
+ text: `搜索失败: ${error.message}`,
126
+ },
127
+ ],
128
+ isError: true,
129
+ };
130
+ }
131
+ });
132
+ // ─── 工具 3: cfm_register_language ──────────────────────
133
+ server.tool("cfm_register_language", "注册新的编程语言注释规则。当遇到 CFM 尚不支持的语言时,通过此工具添加该语言的注释语法,使 cfm_scan 能正确提取其表头。注册仅在当前会话有效。", {
134
+ name: z
135
+ .string()
136
+ .describe("语言名称(如 elixir, dart, haskell)"),
137
+ comment_start: z
138
+ .string()
139
+ .describe("CFM 表头的起始标记(如 \"#---\")"),
140
+ comment_end: z
141
+ .string()
142
+ .describe("CFM 表头的结束标记(如 \"#---\")"),
143
+ extensions: z
144
+ .array(z.string())
145
+ .describe("该语言的文件扩展名列表(如 [\".ex\", \".exs\"])"),
146
+ line_prefix: z
147
+ .string()
148
+ .nullable()
149
+ .optional()
150
+ .default(null)
151
+ .describe("行前缀(脚本语言使用,如 \"# \"),C 家族传 null"),
152
+ }, async ({ name, comment_start, comment_end, extensions, line_prefix }) => {
153
+ const result = registerNewLanguage(name, {
154
+ comment_start,
155
+ comment_end,
156
+ extensions,
157
+ line_prefix,
158
+ });
159
+ return {
160
+ content: [
161
+ {
162
+ type: "text",
163
+ text: JSON.stringify(result, null, 2),
164
+ },
165
+ ],
166
+ isError: !result.success,
167
+ };
168
+ });
169
+ // ─── 启动 Server ────────────────────────────────────────
170
+ const transport = new StdioServerTransport();
171
+ await server.connect(transport);
172
+ }
173
+ // 启动
174
+ main().catch((error) => {
175
+ console.error("MCP Server 启动失败:", error);
176
+ process.exit(1);
177
+ });
178
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;KAaK;AAEL,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;GAEG;AACH,KAAK,UAAU,IAAI;IACf,WAAW;IACX,MAAM,YAAY,EAAE,CAAC;IAErB,mBAAmB;IACnB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QACzB,IAAI,EAAE,kBAAkB;QACxB,OAAO,EAAE,OAAO;KACnB,CAAC,CAAC;IAEH,yDAAyD;IACzD,MAAM,CAAC,IAAI,CACP,UAAU,EACV,6EAA6E,EAC7E;QACI,SAAS,EAAE,CAAC;aACP,MAAM,EAAE;aACR,QAAQ,CAAC,gBAAgB,CAAC;QAC/B,QAAQ,EAAE,CAAC;aACN,OAAO,EAAE;aACT,QAAQ,EAAE;aACV,OAAO,CAAC,KAAK,CAAC;aACd,QAAQ,CAAC,iCAAiC,CAAC;QAChD,WAAW,EAAE,CAAC;aACT,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;aACjB,QAAQ,EAAE;aACV,QAAQ,CAAC,8CAA8C,CAAC;KAChE,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,EAAE;QAC3C,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,SAAS,EAAE;gBAC1C,OAAO,EAAE,QAAQ;gBACjB,UAAU,EAAE,WAAW;aAC1B,CAAC,CAAC;YAEH,OAAO;gBACH,OAAO,EAAE;oBACL;wBACI,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;qBACxC;iBACJ;aACJ,CAAC;QACN,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO;gBACH,OAAO,EAAE;oBACL;wBACI,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,SAAU,KAAe,CAAC,OAAO,EAAE;qBAC5C;iBACJ;gBACD,OAAO,EAAE,IAAI;aAChB,CAAC;QACN,CAAC;IACL,CAAC,CACJ,CAAC;IAEF,yDAAyD;IACzD,MAAM,CAAC,IAAI,CACP,YAAY,EACZ,6FAA6F,EAC7F;QACI,SAAS,EAAE,CAAC;aACP,MAAM,EAAE;aACR,QAAQ,CAAC,gBAAgB,CAAC;QAC/B,OAAO,EAAE,CAAC;aACL,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,sCAAsC,CAAC;QACrD,IAAI,EAAE,CAAC;aACF,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,mDAAmD,CAAC;QAClE,MAAM,EAAE,CAAC;aACJ,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,qCAAqC,CAAC;KACvD,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE;QAC3C,aAAa;QACb,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC/B,OAAO;gBACH,OAAO,EAAE;oBACL;wBACI,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,oCAAoC;qBAC7C;iBACJ;gBACD,OAAO,EAAE,IAAI;aAChB,CAAC;QACN,CAAC;QAED,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,SAAS,EAAE;gBAC9C,OAAO;gBACP,IAAI;gBACJ,MAAM;aACT,CAAC,CAAC;YAEH,OAAO;gBACH,OAAO,EAAE;oBACL;wBACI,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;qBACxC;iBACJ;aACJ,CAAC;QACN,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO;gBACH,OAAO,EAAE;oBACL;wBACI,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,SAAU,KAAe,CAAC,OAAO,EAAE;qBAC5C;iBACJ;gBACD,OAAO,EAAE,IAAI;aAChB,CAAC;QACN,CAAC;IACL,CAAC,CACJ,CAAC;IAEF,yDAAyD;IACzD,MAAM,CAAC,IAAI,CACP,uBAAuB,EACvB,+EAA+E,EAC/E;QACI,IAAI,EAAE,CAAC;aACF,MAAM,EAAE;aACR,QAAQ,CAAC,+BAA+B,CAAC;QAC9C,aAAa,EAAE,CAAC;aACX,MAAM,EAAE;aACR,QAAQ,CAAC,yBAAyB,CAAC;QACxC,WAAW,EAAE,CAAC;aACT,MAAM,EAAE;aACR,QAAQ,CAAC,yBAAyB,CAAC;QACxC,UAAU,EAAE,CAAC;aACR,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;aACjB,QAAQ,CAAC,oCAAoC,CAAC;QACnD,WAAW,EAAE,CAAC;aACT,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,EAAE;aACV,OAAO,CAAC,IAAI,CAAC;aACb,QAAQ,CAAC,iCAAiC,CAAC;KACnD,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,EAAE;QACpE,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,EAAE;YACrC,aAAa;YACb,WAAW;YACX,UAAU;YACV,WAAW;SACd,CAAC,CAAC;QAEH,OAAO;YACH,OAAO,EAAE;gBACL;oBACI,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;iBACxC;aACJ;YACD,OAAO,EAAE,CAAC,MAAM,CAAC,OAAO;SAC3B,CAAC;IACN,CAAC,CACJ,CAAC;IAEF,yDAAyD;IACzD,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AACpC,CAAC;AAED,KAAK;AACL,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;IACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC"}
@@ -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":"AAcA,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,162 @@
1
+ /*---
2
+ intent: "从代码文件中提取 CFM 表头:根据语言规则定位注释标记,去除行前缀,解析 YAML 并校验"
3
+ role: service
4
+ exports:
5
+ - "extractFrontmatter: 从单个文件中提取并解析 CFM 表头"
6
+ depends_on: ["./registry.ts", "./schema.ts", "yaml"]
7
+ when_to_load: "修改表头提取逻辑或 YAML 解析方式时加载"
8
+ ai_notes: "使用简单文本匹配而非 AST 解析,保持零重型依赖的设计原则"
9
+ ---*/
10
+ import { extname } from "node:path";
11
+ import { parse as parseYaml } from "yaml";
12
+ import { getLanguageByExtension } from "./registry.js";
13
+ import { CfmSchemaLoose } from "./schema.js";
14
+ /** 读取文件的最大字节数(只读开头部分以查找表头) */
15
+ const MAX_READ_BYTES = 4096;
16
+ /**
17
+ * 从单个文件中提取 CFM 表头
18
+ *
19
+ * 提取流程:
20
+ * 1. 根据文件扩展名查找语言注释规则
21
+ * 2. 在文件开头区域匹配 comment_start / comment_end 标记对
22
+ * 3. 提取标记之间的文本,去除 line_prefix(如 "# ")
23
+ * 4. 用 YAML 解析器解析,再用 Zod Schema 校验
24
+ *
25
+ * @param filePath - 文件的绝对路径
26
+ * @param relativePath - 用于输出的相对路径
27
+ * @returns CFM 条目(含解析数据或 null + 警告信息)
28
+ */
29
+ export async function extractFrontmatter(filePath, relativePath) {
30
+ const ext = extname(filePath);
31
+ const lang = getLanguageByExtension(ext);
32
+ // 未知语言类型,跳过
33
+ if (!lang) {
34
+ return {
35
+ file: relativePath,
36
+ language: undefined,
37
+ frontmatter: null,
38
+ warnings: ["未注册的文件类型,跳过表头提取"],
39
+ };
40
+ }
41
+ const warnings = [];
42
+ try {
43
+ // 只读取文件开头部分,节省 I/O
44
+ const content = await readFileHead(filePath, MAX_READ_BYTES);
45
+ const { comment_start, comment_end, line_prefix } = lang.rule;
46
+ // 查找表头区域
47
+ const headerContent = extractHeaderContent(content, comment_start, comment_end, line_prefix);
48
+ if (!headerContent) {
49
+ return {
50
+ file: relativePath,
51
+ language: lang.name,
52
+ frontmatter: null,
53
+ };
54
+ }
55
+ // 解析 YAML
56
+ let parsed;
57
+ try {
58
+ parsed = parseYaml(headerContent);
59
+ }
60
+ catch (yamlError) {
61
+ return {
62
+ file: relativePath,
63
+ language: lang.name,
64
+ frontmatter: null,
65
+ warnings: [`YAML 解析失败: ${yamlError.message}`],
66
+ };
67
+ }
68
+ // 如果 YAML 解析结果不是对象(可能是纯文本)
69
+ if (!parsed || typeof parsed !== "object") {
70
+ return {
71
+ file: relativePath,
72
+ language: lang.name,
73
+ frontmatter: null,
74
+ warnings: ["表头内容不是有效的 YAML 对象"],
75
+ };
76
+ }
77
+ // 宽松模式校验
78
+ const validation = CfmSchemaLoose.safeParse(parsed);
79
+ if (!validation.success) {
80
+ warnings.push(`Schema 校验警告: ${validation.error.issues.map((i) => i.message).join("; ")}`);
81
+ }
82
+ // 检查必选字段缺失
83
+ if (!parsed.intent)
84
+ warnings.push("缺少必选字段: intent");
85
+ if (!parsed.role)
86
+ warnings.push("缺少推荐字段: role");
87
+ if (!parsed.exports)
88
+ warnings.push("缺少推荐字段: exports");
89
+ return {
90
+ file: relativePath,
91
+ language: lang.name,
92
+ frontmatter: parsed,
93
+ warnings: warnings.length > 0 ? warnings : undefined,
94
+ };
95
+ }
96
+ catch (error) {
97
+ return {
98
+ file: relativePath,
99
+ language: lang.name,
100
+ frontmatter: null,
101
+ warnings: [`文件读取失败: ${error.message}`],
102
+ };
103
+ }
104
+ }
105
+ /**
106
+ * 从文件内容中提取表头区域的 YAML 文本
107
+ * @returns 清理后的 YAML 字符串,或 null(未找到表头)
108
+ */
109
+ function extractHeaderContent(content, commentStart, commentEnd, linePrefix) {
110
+ // 统一换行符为 \n,兼容 Windows (\r\n) 和 Mac (\r)
111
+ const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
112
+ const trimmedContent = normalized.trimStart();
113
+ // 查找起始标记
114
+ if (!trimmedContent.startsWith(commentStart)) {
115
+ return null;
116
+ }
117
+ // 查找结束标记(从起始标记之后开始搜索)
118
+ const afterStart = trimmedContent.slice(commentStart.length);
119
+ const endIndex = afterStart.indexOf(commentEnd);
120
+ if (endIndex === -1) {
121
+ return null;
122
+ }
123
+ // 提取标记之间的原始文本
124
+ let rawContent = afterStart.slice(0, endIndex);
125
+ // 去除每行的 line_prefix(如 Python 的 "# ")
126
+ if (linePrefix) {
127
+ const lines = rawContent.split("\n");
128
+ rawContent = lines
129
+ .map((line) => {
130
+ const trimmedLine = line.trimStart();
131
+ // 尝试完整前缀匹配(如 "# "),然后尝试去尾空格后的匹配(如 "#")
132
+ if (trimmedLine.startsWith(linePrefix)) {
133
+ return trimmedLine.slice(linePrefix.length);
134
+ }
135
+ const trimmedPrefix = linePrefix.trim();
136
+ if (trimmedPrefix && trimmedLine.startsWith(trimmedPrefix)) {
137
+ return trimmedLine.slice(trimmedPrefix.length);
138
+ }
139
+ return line;
140
+ })
141
+ .join("\n");
142
+ }
143
+ const cleaned = rawContent.trim();
144
+ return cleaned.length > 0 ? cleaned : null;
145
+ }
146
+ /**
147
+ * 只读取文件的前 N 个字节
148
+ * 大文件只需读开头就能找到表头,避免全文读取
149
+ */
150
+ async function readFileHead(filePath, maxBytes) {
151
+ const { open } = await import("node:fs/promises");
152
+ const fileHandle = await open(filePath, "r");
153
+ try {
154
+ const buffer = Buffer.alloc(maxBytes);
155
+ const { bytesRead } = await fileHandle.read(buffer, 0, maxBytes, 0);
156
+ return buffer.slice(0, bytesRead).toString("utf-8");
157
+ }
158
+ finally {
159
+ await fileHandle.close();
160
+ }
161
+ }
162
+ //# 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"}
@@ -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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"}