pit-docs-mcp 1.0.2

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.
Files changed (203) hide show
  1. package/README.md +180 -0
  2. package/dist/codegen.d.ts +5 -0
  3. package/dist/codegen.d.ts.map +1 -0
  4. package/dist/codegen.js +112 -0
  5. package/dist/constants.d.ts +6 -0
  6. package/dist/constants.d.ts.map +1 -0
  7. package/dist/constants.js +10 -0
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +115 -0
  11. package/dist/resources.d.ts +3 -0
  12. package/dist/resources.d.ts.map +1 -0
  13. package/dist/resources.js +80 -0
  14. package/dist/tools.d.ts +3 -0
  15. package/dist/tools.d.ts.map +1 -0
  16. package/dist/tools.js +258 -0
  17. package/dist/utils.d.ts +26 -0
  18. package/dist/utils.d.ts.map +1 -0
  19. package/dist/utils.js +198 -0
  20. package/docs/codegen/pit-simplify-web.md +686 -0
  21. package/docs/pitBusinessUi/README.md +102 -0
  22. package/docs/pitBusinessUi/add-date-picker.md +57 -0
  23. package/docs/pitBusinessUi/add-operation.md +64 -0
  24. package/docs/pitBusinessUi/batch-download.md +44 -0
  25. package/docs/pitBusinessUi/bill-tree-dialog.md +82 -0
  26. package/docs/pitBusinessUi/collapse.md +88 -0
  27. package/docs/pitBusinessUi/date-time-picker.md +74 -0
  28. package/docs/pitBusinessUi/design-report-explain.md +47 -0
  29. package/docs/pitBusinessUi/dialog.md +89 -0
  30. package/docs/pitBusinessUi/dic-radio.md +67 -0
  31. package/docs/pitBusinessUi/dic.md +78 -0
  32. package/docs/pitBusinessUi/document-preview.md +54 -0
  33. package/docs/pitBusinessUi/drawer.md +67 -0
  34. package/docs/pitBusinessUi/editor.md +63 -0
  35. package/docs/pitBusinessUi/expand-search-form.md +65 -0
  36. package/docs/pitBusinessUi/file-preview.md +56 -0
  37. package/docs/pitBusinessUi/flow-table-status.md +47 -0
  38. package/docs/pitBusinessUi/icon-box.md +74 -0
  39. package/docs/pitBusinessUi/image-preview.md +55 -0
  40. package/docs/pitBusinessUi/image.md +53 -0
  41. package/docs/pitBusinessUi/input-formatter.md +100 -0
  42. package/docs/pitBusinessUi/input-number.md +47 -0
  43. package/docs/pitBusinessUi/input-select.md +68 -0
  44. package/docs/pitBusinessUi/input.md +56 -0
  45. package/docs/pitBusinessUi/jm-preview.md +47 -0
  46. package/docs/pitBusinessUi/json-editor.md +71 -0
  47. package/docs/pitBusinessUi/loading-btn.md +64 -0
  48. package/docs/pitBusinessUi/monaco.md +82 -0
  49. package/docs/pitBusinessUi/money-input.md +59 -0
  50. package/docs/pitBusinessUi/pagination.md +70 -0
  51. package/docs/pitBusinessUi/password-strength.md +59 -0
  52. package/docs/pitBusinessUi/positive-number.md +63 -0
  53. package/docs/pitBusinessUi/preview-image.md +62 -0
  54. package/docs/pitBusinessUi/preview-office.md +50 -0
  55. package/docs/pitBusinessUi/preview.md +57 -0
  56. package/docs/pitBusinessUi/quill.md +52 -0
  57. package/docs/pitBusinessUi/runflow-btn.md +52 -0
  58. package/docs/pitBusinessUi/search-date-picker.md +60 -0
  59. package/docs/pitBusinessUi/select-bid-list.md +66 -0
  60. package/docs/pitBusinessUi/select-color.md +38 -0
  61. package/docs/pitBusinessUi/select-contract-tree.md +41 -0
  62. package/docs/pitBusinessUi/select-dept.md +38 -0
  63. package/docs/pitBusinessUi/select-project-unit.md +48 -0
  64. package/docs/pitBusinessUi/select-section-tree.md +45 -0
  65. package/docs/pitBusinessUi/select-section.md +66 -0
  66. package/docs/pitBusinessUi/select-string.md +60 -0
  67. package/docs/pitBusinessUi/select-system-unit.md +41 -0
  68. package/docs/pitBusinessUi/select-user-by-role.md +51 -0
  69. package/docs/pitBusinessUi/switch.md +43 -0
  70. package/docs/pitBusinessUi/table-contract.md +66 -0
  71. package/docs/pitBusinessUi/table-operation.md +81 -0
  72. package/docs/pitBusinessUi/table.md +75 -0
  73. package/docs/pitBusinessUi/tag.md +86 -0
  74. package/docs/pitBusinessUi/textarea.md +65 -0
  75. package/docs/pitBusinessUi/transfer-direct.md +57 -0
  76. package/docs/pitBusinessUi/transfer-user-project.md +78 -0
  77. package/docs/pitBusinessUi/transfer.md +68 -0
  78. package/docs/pitBusinessUi/tree-lazy.md +72 -0
  79. package/docs/pitBusinessUi/tree-select-name.md +59 -0
  80. package/docs/pitBusinessUi/tree-select-plus.md +106 -0
  81. package/docs/pitBusinessUi/tree-select.md +86 -0
  82. package/docs/pitBusinessUi/upload-avatar.md +60 -0
  83. package/docs/pitBusinessUi/upload-file-dialog.md +86 -0
  84. package/docs/pitBusinessUi/upload-file.md +77 -0
  85. package/docs/pitBusinessUi/upload-list-card.md +62 -0
  86. package/docs/pitBusinessUi/upload-table.md +77 -0
  87. package/docs/pitBusinessUi/user-transform.md +72 -0
  88. package/docs/pitBusinessUi/utils.md +272 -0
  89. package/docs/pitBusinessUtils/README.md +144 -0
  90. package/docs/pitBusinessUtils/auth.md +170 -0
  91. package/docs/pitBusinessUtils/clipboard.md +72 -0
  92. package/docs/pitBusinessUtils/filePreview.md +60 -0
  93. package/docs/pitBusinessUtils/formValidate.md +75 -0
  94. package/docs/pitBusinessUtils/generateTitle.md +49 -0
  95. package/docs/pitBusinessUtils/get-page-title.md +65 -0
  96. package/docs/pitBusinessUtils/i18n.md +130 -0
  97. package/docs/pitBusinessUtils/jwks.md +82 -0
  98. package/docs/pitBusinessUtils/oss.md +391 -0
  99. package/docs/pitBusinessUtils/passwordValidate.md +120 -0
  100. package/docs/pitBusinessUtils/pit.md +496 -0
  101. package/docs/pitBusinessUtils/print.md +126 -0
  102. package/docs/pitBusinessUtils/request.md +137 -0
  103. package/docs/pitBusinessUtils/scroll-to.md +68 -0
  104. package/docs/pitBusinessUtils/utils.md +762 -0
  105. package/docs/pitBusinessUtils/validate.md +224 -0
  106. package/docs/pitElementUi/alert.md +238 -0
  107. package/docs/pitElementUi/avatar.md +147 -0
  108. package/docs/pitElementUi/backtop.md +60 -0
  109. package/docs/pitElementUi/badge.md +120 -0
  110. package/docs/pitElementUi/base-tabs.md +73 -0
  111. package/docs/pitElementUi/border.md +135 -0
  112. package/docs/pitElementUi/breadcrumb.md +44 -0
  113. package/docs/pitElementUi/button.md +301 -0
  114. package/docs/pitElementUi/calendar.md +66 -0
  115. package/docs/pitElementUi/card.md +170 -0
  116. package/docs/pitElementUi/carousel.md +212 -0
  117. package/docs/pitElementUi/cascader.md +1966 -0
  118. package/docs/pitElementUi/checkbox.md +283 -0
  119. package/docs/pitElementUi/collapse.md +131 -0
  120. package/docs/pitElementUi/color-picker.md +123 -0
  121. package/docs/pitElementUi/color.md +244 -0
  122. package/docs/pitElementUi/container.md +240 -0
  123. package/docs/pitElementUi/custom-theme.md +131 -0
  124. package/docs/pitElementUi/date-picker.md +448 -0
  125. package/docs/pitElementUi/datetime-picker.md +254 -0
  126. package/docs/pitElementUi/descriptions.md +191 -0
  127. package/docs/pitElementUi/dialog-header.md +53 -0
  128. package/docs/pitElementUi/dialog.md +239 -0
  129. package/docs/pitElementUi/divider.md +61 -0
  130. package/docs/pitElementUi/drawer.md +307 -0
  131. package/docs/pitElementUi/dropdown.md +308 -0
  132. package/docs/pitElementUi/empty.md +61 -0
  133. package/docs/pitElementUi/font-family.md +90 -0
  134. package/docs/pitElementUi/form-base.md +1239 -0
  135. package/docs/pitElementUi/form-item-checkbox-group.md +48 -0
  136. package/docs/pitElementUi/form-item-date.md +60 -0
  137. package/docs/pitElementUi/form-item-dic.md +18 -0
  138. package/docs/pitElementUi/form-item-editor.md +16 -0
  139. package/docs/pitElementUi/form-item-input-money.md +19 -0
  140. package/docs/pitElementUi/form-item-input-number.md +20 -0
  141. package/docs/pitElementUi/form-item-input.md +18 -0
  142. package/docs/pitElementUi/form-item-radio-group.md +21 -0
  143. package/docs/pitElementUi/form-item-select.md +21 -0
  144. package/docs/pitElementUi/form-item-switch.md +15 -0
  145. package/docs/pitElementUi/form-item-textarea.md +20 -0
  146. package/docs/pitElementUi/form-item-tree-select.md +27 -0
  147. package/docs/pitElementUi/form-item-upload-card.md +18 -0
  148. package/docs/pitElementUi/form-item-upload.md +1 -0
  149. package/docs/pitElementUi/form-two.md +102 -0
  150. package/docs/pitElementUi/form.md +952 -0
  151. package/docs/pitElementUi/i18n.md +228 -0
  152. package/docs/pitElementUi/icon-custom.md +99 -0
  153. package/docs/pitElementUi/icon-line-custom.md +12 -0
  154. package/docs/pitElementUi/icon.md +28 -0
  155. package/docs/pitElementUi/image.md +178 -0
  156. package/docs/pitElementUi/infiniteScroll.md +87 -0
  157. package/docs/pitElementUi/input-number.md +197 -0
  158. package/docs/pitElementUi/input-select.md +1 -0
  159. package/docs/pitElementUi/input.md +800 -0
  160. package/docs/pitElementUi/installation.md +9 -0
  161. package/docs/pitElementUi/layout-column.md +376 -0
  162. package/docs/pitElementUi/layout-tree.md +715 -0
  163. package/docs/pitElementUi/layout.md +354 -0
  164. package/docs/pitElementUi/link.md +66 -0
  165. package/docs/pitElementUi/loading.md +208 -0
  166. package/docs/pitElementUi/menu.md +403 -0
  167. package/docs/pitElementUi/message-box.md +326 -0
  168. package/docs/pitElementUi/message.md +219 -0
  169. package/docs/pitElementUi/notification.md +311 -0
  170. package/docs/pitElementUi/page-header.md +40 -0
  171. package/docs/pitElementUi/pagination.md +200 -0
  172. package/docs/pitElementUi/popconfirm.md +60 -0
  173. package/docs/pitElementUi/popover.md +167 -0
  174. package/docs/pitElementUi/progress.md +178 -0
  175. package/docs/pitElementUi/quickstart.md +290 -0
  176. package/docs/pitElementUi/radio.md +211 -0
  177. package/docs/pitElementUi/rate.md +135 -0
  178. package/docs/pitElementUi/result.md +76 -0
  179. package/docs/pitElementUi/select-tree.md +661 -0
  180. package/docs/pitElementUi/select.md +586 -0
  181. package/docs/pitElementUi/skeleton.md +316 -0
  182. package/docs/pitElementUi/slider.md +237 -0
  183. package/docs/pitElementUi/steps.md +154 -0
  184. package/docs/pitElementUi/switch.md +142 -0
  185. package/docs/pitElementUi/table.md +4023 -0
  186. package/docs/pitElementUi/tabs.md +303 -0
  187. package/docs/pitElementUi/tag.md +203 -0
  188. package/docs/pitElementUi/time-picker.md +199 -0
  189. package/docs/pitElementUi/timeline.md +154 -0
  190. package/docs/pitElementUi/tooltip.md +177 -0
  191. package/docs/pitElementUi/transfer.md +249 -0
  192. package/docs/pitElementUi/transition.md +155 -0
  193. package/docs/pitElementUi/tree.md +1157 -0
  194. package/docs/pitElementUi/typography.md +151 -0
  195. package/docs/pitElementUi/upload-table.md +39 -0
  196. package/docs/pitElementUi/upload.md +392 -0
  197. package/docs/pitElementUi/virtual-list.md +154 -0
  198. package/docs/pitElementUi/virtual-select-tree.md +243 -0
  199. package/docs/pitElementUi/virtual-select.md +451 -0
  200. package/docs/pitElementUi/virtual-table-column.md +1 -0
  201. package/docs/pitElementUi/virtual-table.md +490 -0
  202. package/docs/pitElementUi/virtual-tree.md +119 -0
  203. package/package.json +33 -0
package/dist/tools.js ADDED
@@ -0,0 +1,258 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import { z } from "zod";
4
+ import { DOCS_PATH } from "./constants.js";
5
+ import { grepSearch, extractPropsSection, getCachedFileContent, getDocCatalog, pathToUri, uriToFilePath, safePath, } from "./utils.js";
6
+ import { loadCodegenSpec, extractSections, getComponentDocs, } from "./codegen.js";
7
+ export function registerTools(server) {
8
+ registerSearchDocsTool(server);
9
+ registerReadDocTool(server);
10
+ registerListDocsTool(server);
11
+ registerGetCodegenSpecTool(server);
12
+ }
13
+ // --- search_docs ---
14
+ function registerSearchDocsTool(server) {
15
+ const schema = z.object({
16
+ keyword: z
17
+ .string()
18
+ .describe("要搜索的关键字,支持多个词用空格分隔(OR 语义)。如:pit-document-preview 参数"),
19
+ maxResults: z.number().optional().default(20).describe("最大返回结果数量"),
20
+ contextLines: z
21
+ .number()
22
+ .optional()
23
+ .default(3)
24
+ .describe("匹配行前后的上下文行数,便于获取完整片段"),
25
+ });
26
+ server.registerTool("search_docs", {
27
+ title: "Search Docs",
28
+ description: `在 pitBusinessUi 和 pitElementUi 两个内部组件库的文档中搜索。当用户询问 pit 组件(如 pit-document-preview、pit-table、pit-button)的用法、参数、Props、示例时,必须使用此工具。
29
+
30
+ 本工具返回匹配片段及文档 URI。若需完整文档(如 Props 表格、示例代码),请使用返回的 pit-docs:// URI 调用 read_doc 获取全文。多关键词按 OR 语义匹配,如「pit-document-preview 参数」。`,
31
+ inputSchema: schema.shape,
32
+ }, async ({ keyword, maxResults, contextLines }) => {
33
+ const keywords = keyword.split(/\s+/).map((k) => k.trim()).filter(Boolean);
34
+ if (keywords.length === 0) {
35
+ return {
36
+ content: [
37
+ {
38
+ type: "text",
39
+ text: "请输入有效关键字。可尝试组件名(如 pit-document-preview)或技术词(如 props、参数)。",
40
+ },
41
+ ],
42
+ };
43
+ }
44
+ const results = await grepSearch(keywords, { contextLines });
45
+ const limited = results.slice(0, maxResults);
46
+ const lines = [];
47
+ for (const r of limited) {
48
+ let block = `- **${r.path}:${r.line}**\n \`${r.content}\``;
49
+ if (r.contextBefore?.length) {
50
+ block += `\n ← 上文: ${r.contextBefore.join(" | ")}`;
51
+ }
52
+ if (r.contextAfter?.length) {
53
+ block += `\n → 下文: ${r.contextAfter.join(" | ")}`;
54
+ }
55
+ lines.push(block);
56
+ }
57
+ const summary = results.length > maxResults
58
+ ? `\n\n... 共 ${results.length} 条匹配,仅显示前 ${maxResults} 条`
59
+ : "";
60
+ const hitPaths = [...new Set(limited.map((r) => r.path))];
61
+ const uris = hitPaths.map((p) => pathToUri(p));
62
+ const uriHint = uris.length > 0
63
+ ? `\n\n【读取完整文档】请使用 read_doc 工具,传入以下 URI 之一:\n${uris.map((u) => `- ${u}`).join("\n")}`
64
+ : "";
65
+ const propsSections = [];
66
+ for (const relPath of hitPaths.slice(0, 2)) {
67
+ try {
68
+ const content = await getCachedFileContent(relPath);
69
+ const props = extractPropsSection(content);
70
+ if (props) {
71
+ propsSections.push(`### ${relPath} - Props/参数\n\`\`\`\n${props}\n\`\`\``);
72
+ }
73
+ }
74
+ catch {
75
+ // 忽略
76
+ }
77
+ }
78
+ const propsBlock = propsSections.length > 0
79
+ ? `\n\n【Props/参数 摘要】\n${propsSections.join("\n\n")}`
80
+ : "";
81
+ const text = lines.length > 0
82
+ ? `找到 ${results.length} 条匹配:\n\n${lines.join("\n\n")}${summary}${uriHint}${propsBlock}`
83
+ : `未找到包含 "${keywords.join(" ")}" 的文档内容。可尝试其他关键字(如仅用组件名 pit-xxx),或使用 list_docs 获取文档目录清单。`;
84
+ return {
85
+ content: [{ type: "text", text }],
86
+ };
87
+ });
88
+ }
89
+ // --- read_doc ---
90
+ function registerReadDocTool(server) {
91
+ const schema = z.object({
92
+ pathOrUri: z
93
+ .string()
94
+ .describe("文档路径(如 pitBusinessUi/document-preview.md)或完整 URI(如 pit-docs:///pitBusinessUi/document-preview.md)"),
95
+ });
96
+ server.registerTool("read_doc", {
97
+ title: "Read Doc",
98
+ description: `读取 pitBusinessUi 或 pitElementUi 文档的完整内容。当 search_docs 找到匹配但片段不足以回答(如用户问「有哪些参数」)时,使用本工具传入 pit-docs:// URI 或文档路径(如 pitBusinessUi/document-preview.md)获取全文。`,
99
+ inputSchema: schema.shape,
100
+ }, async ({ pathOrUri }) => {
101
+ let relPath;
102
+ const trimmed = pathOrUri.trim();
103
+ if (trimmed.startsWith("pit-docs://")) {
104
+ try {
105
+ const fullPath = uriToFilePath(trimmed);
106
+ relPath = path.relative(DOCS_PATH, fullPath).replace(/\\/g, "/");
107
+ }
108
+ catch {
109
+ return {
110
+ content: [
111
+ {
112
+ type: "text",
113
+ text: `无效的 URI: ${trimmed}。请使用 pit-docs:///path/to/doc.md 格式。`,
114
+ },
115
+ ],
116
+ };
117
+ }
118
+ }
119
+ else {
120
+ relPath = trimmed.replace(/\\/g, "/");
121
+ if (relPath.startsWith("/"))
122
+ relPath = relPath.slice(1);
123
+ }
124
+ const fullPath = safePath(relPath);
125
+ try {
126
+ const content = await fs.readFile(fullPath, "utf-8");
127
+ const uri = pathToUri(relPath);
128
+ return {
129
+ content: [
130
+ {
131
+ type: "text",
132
+ text: `## ${relPath}\n\nURI: ${uri}\n\n---\n\n${content}`,
133
+ },
134
+ ],
135
+ };
136
+ }
137
+ catch (err) {
138
+ const msg = err instanceof Error ? err.message : String(err);
139
+ return {
140
+ content: [
141
+ {
142
+ type: "text",
143
+ text: `无法读取文档 "${relPath}": ${msg}。请检查路径或使用 list_docs 获取有效文档列表。`,
144
+ },
145
+ ],
146
+ };
147
+ }
148
+ });
149
+ }
150
+ // --- list_docs ---
151
+ function registerListDocsTool(server) {
152
+ const schema = z.object({
153
+ category: z
154
+ .string()
155
+ .optional()
156
+ .describe("按目录筛选。仅支持 pitBusinessUi 或 pitElementUi,不传则返回全部"),
157
+ offset: z.number().optional().default(0).describe("跳过前 N 条记录(分页偏移)"),
158
+ limit: z.number().optional().default(50).describe("最多返回条数,默认 50"),
159
+ });
160
+ server.registerTool("list_docs", {
161
+ title: "List Docs",
162
+ description: `获取 pitBusinessUi 和 pitElementUi 的文档目录清单。组件名按 pit-{name} 映射:如 pit-image 对应 image.md,pit-dic 对应 dic.md。返回组件名、文档路径及 pit-docs:// URI,便于定位并读取具体文档。`,
163
+ inputSchema: schema.shape,
164
+ }, async ({ category, offset, limit }) => {
165
+ const catalog = await getDocCatalog(category);
166
+ const total = catalog.length;
167
+ if (total === 0) {
168
+ return {
169
+ content: [
170
+ {
171
+ type: "text",
172
+ text: category
173
+ ? `目录 ${category} 下暂无组件文档。`
174
+ : "暂无可用的组件文档。",
175
+ },
176
+ ],
177
+ };
178
+ }
179
+ const paged = catalog.slice(offset, offset + limit);
180
+ const byCategory = new Map();
181
+ for (const item of paged) {
182
+ if (!byCategory.has(item.category)) {
183
+ byCategory.set(item.category, []);
184
+ }
185
+ byCategory.get(item.category).push(item);
186
+ }
187
+ const lines = [];
188
+ for (const [cat, items] of byCategory) {
189
+ lines.push(`\n### ${cat}`);
190
+ for (const item of items) {
191
+ lines.push(`- **${item.component}** → \`${item.docPath}\` (URI: ${item.uri})`);
192
+ }
193
+ }
194
+ const paginationHint = total > offset + limit
195
+ ? `\n\n... 共 ${total} 个文档,当前显示第 ${offset + 1}-${offset + paged.length} 条。传入 offset=${offset + limit} 获取下一页。`
196
+ : "";
197
+ return {
198
+ content: [
199
+ {
200
+ type: "text",
201
+ text: `PIT 组件文档清单(共 ${total} 个)\n${lines.join("\n")}${paginationHint}\n\n使用 read_doc 工具传入上述 URI 或路径可读取完整文档内容。`,
202
+ },
203
+ ],
204
+ };
205
+ });
206
+ }
207
+ // --- get_codegen_spec ---
208
+ function registerGetCodegenSpecTool(server) {
209
+ const pageTypes = ["standard", "left-tree", "flow", "all"];
210
+ const schema = z.object({
211
+ pageType: z
212
+ .enum(pageTypes)
213
+ .optional()
214
+ .default("all")
215
+ .describe('页面类型。standard=标准CRUD页面,left-tree=左树右表页面,flow=流程表单页面,all=返回全部规范。默认 all'),
216
+ includeComponentDocs: z
217
+ .boolean()
218
+ .optional()
219
+ .default(true)
220
+ .describe("是否附带相关组件的 Props/参数文档摘要,默认 true"),
221
+ });
222
+ server.registerTool("get_codegen_spec", {
223
+ title: "Get Codegen Spec",
224
+ description: `获取 pit-simplify-web 后台管理页面的代码生成规范。返回页面骨架模板、组件使用规范、数据准备规范、API 文件模板、反模式清单及验收基准。
225
+
226
+ 使用场景:当用户要求生成后台管理 CRUD 页面(标准页面、左树右表页面、流程审批页面)时,先调用此工具获取完整的编码规范和组件文档,然后按规范生成代码。
227
+
228
+ 输入格式提示:用户通常会提供「表结构 SQL + 服务名 + 模块名 + 字典字段信息」。
229
+
230
+ 若需要某个组件的更详细文档,可再用 search_docs 或 read_doc 工具补充查阅。`,
231
+ inputSchema: schema.shape,
232
+ }, async ({ pageType, includeComponentDocs }) => {
233
+ try {
234
+ const fullSpec = await loadCodegenSpec();
235
+ const spec = extractSections(fullSpec, pageType);
236
+ const parts = [];
237
+ parts.push(`# pit-simplify-web 代码生成规范(${pageType})\n\n${spec}`);
238
+ if (includeComponentDocs) {
239
+ const docs = await getComponentDocs(pageType);
240
+ parts.push(`\n\n---\n\n# 相关组件 Props/参数文档\n\n> 以下为 ${pageType} 页面涉及的核心组件文档摘要。若需完整文档,请使用 read_doc 工具。\n\n${docs}`);
241
+ }
242
+ return {
243
+ content: [{ type: "text", text: parts.join("") }],
244
+ };
245
+ }
246
+ catch (err) {
247
+ const msg = err instanceof Error ? err.message : String(err);
248
+ return {
249
+ content: [
250
+ {
251
+ type: "text",
252
+ text: `无法加载代码生成规范: ${msg}。请确认 docs/codegen/pit-simplify-web.md 文件存在。`,
253
+ },
254
+ ],
255
+ };
256
+ }
257
+ });
258
+ }
@@ -0,0 +1,26 @@
1
+ export declare function getCachedDocFiles(): Promise<string[]>;
2
+ export declare function getCachedFileContent(relPath: string): Promise<string>;
3
+ export declare function safePath(relPath: string): string;
4
+ export declare function uriToFilePath(uri: string): string;
5
+ export declare function pathToUri(relPath: string): string;
6
+ export declare function docNameToComponentName(docBasename: string, category?: string): string;
7
+ export declare function getDocCatalog(category?: string): Promise<{
8
+ category: string;
9
+ component: string;
10
+ docPath: string;
11
+ uri: string;
12
+ }[]>;
13
+ export declare function getComponentsInCategory(category: string): Promise<string[]>;
14
+ export type SearchMatch = {
15
+ path: string;
16
+ line: number;
17
+ content: string;
18
+ score: number;
19
+ contextBefore?: string[];
20
+ contextAfter?: string[];
21
+ };
22
+ export declare function grepSearch(keywords: string[], options?: {
23
+ contextLines?: number;
24
+ }): Promise<SearchMatch[]>;
25
+ export declare function extractPropsSection(content: string): string | null;
26
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAUA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAQ3D;AAID,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAc3E;AA6BD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMhD;AAID,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMjD;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGjD;AAID,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAGrF;AAID,wBAAsB,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAC7D;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,CACxE,CAuBA;AAED,wBAAsB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAcjF;AAID,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB,CAAC;AAcF,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAAE,EAClB,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAClC,OAAO,CAAC,WAAW,EAAE,CAAC,CAoCxB;AAID,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAwBlE"}
package/dist/utils.js ADDED
@@ -0,0 +1,198 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import { DOCS_PATH, DOC_EXTENSIONS, RESOURCE_URI_PREFIX } from "./constants.js";
4
+ // --- 缓存层 ---
5
+ const FILE_LIST_CACHE_TTL = 60_000;
6
+ const CONTENT_CACHE_MAX = 50;
7
+ let fileListCache = null;
8
+ export async function getCachedDocFiles() {
9
+ const now = Date.now();
10
+ if (fileListCache && now - fileListCache.ts < FILE_LIST_CACHE_TTL) {
11
+ return fileListCache.data;
12
+ }
13
+ const data = await getAllDocFiles(DOCS_PATH);
14
+ fileListCache = { data, ts: now };
15
+ return data;
16
+ }
17
+ const contentCache = new Map();
18
+ export async function getCachedFileContent(relPath) {
19
+ const now = Date.now();
20
+ const cached = contentCache.get(relPath);
21
+ if (cached && now - cached.ts < FILE_LIST_CACHE_TTL) {
22
+ return cached.text;
23
+ }
24
+ const fullPath = path.join(DOCS_PATH, relPath);
25
+ const text = await fs.readFile(fullPath, "utf-8");
26
+ if (contentCache.size >= CONTENT_CACHE_MAX) {
27
+ const oldest = [...contentCache.entries()].sort((a, b) => a[1].ts - b[1].ts)[0];
28
+ if (oldest)
29
+ contentCache.delete(oldest[0]);
30
+ }
31
+ contentCache.set(relPath, { text, ts: now });
32
+ return text;
33
+ }
34
+ // --- 文件扫描 ---
35
+ async function getAllDocFiles(dir, baseDir = dir) {
36
+ const files = [];
37
+ try {
38
+ const entries = await fs.readdir(dir, { withFileTypes: true });
39
+ for (const entry of entries) {
40
+ const fullPath = path.join(dir, entry.name);
41
+ if (entry.isDirectory()) {
42
+ if (entry.name === "node_modules" || entry.name.startsWith("."))
43
+ continue;
44
+ files.push(...(await getAllDocFiles(fullPath, baseDir)));
45
+ }
46
+ else if (entry.isFile()) {
47
+ const ext = path.extname(entry.name).toLowerCase();
48
+ if (DOC_EXTENSIONS.has(ext)) {
49
+ files.push(path.relative(baseDir, fullPath));
50
+ }
51
+ }
52
+ }
53
+ }
54
+ catch (err) {
55
+ console.error(`[pit-docs-mcp] 无法读取目录 ${dir}:`, err);
56
+ }
57
+ return files;
58
+ }
59
+ // --- 路径安全 ---
60
+ export function safePath(relPath) {
61
+ const resolved = path.resolve(DOCS_PATH, relPath);
62
+ if (!resolved.startsWith(path.resolve(DOCS_PATH) + path.sep) && resolved !== path.resolve(DOCS_PATH)) {
63
+ throw new Error(`路径越界,拒绝访问: ${relPath}`);
64
+ }
65
+ return resolved;
66
+ }
67
+ // --- URI 转换 ---
68
+ export function uriToFilePath(uri) {
69
+ const match = uri.match(/^pit-docs:\/\/(.+)$/);
70
+ if (!match)
71
+ throw new Error(`无效的文档 URI: ${uri}`);
72
+ let relPath = decodeURIComponent(match[1]);
73
+ if (relPath.startsWith("/"))
74
+ relPath = relPath.slice(1);
75
+ return safePath(relPath);
76
+ }
77
+ export function pathToUri(relPath) {
78
+ const normalized = relPath.replace(/\\/g, "/");
79
+ return `${RESOURCE_URI_PREFIX}/${normalized}`;
80
+ }
81
+ // --- 组件名映射 ---
82
+ export function docNameToComponentName(docBasename, category) {
83
+ const prefix = category === "pitElementUi" ? "el" : "pit";
84
+ return `${prefix}-${docBasename}`;
85
+ }
86
+ // --- 文档目录 ---
87
+ export async function getDocCatalog(category) {
88
+ const files = await getCachedDocFiles();
89
+ const mdFiles = files.filter((f) => f.endsWith(".md") && !f.endsWith("README.md"));
90
+ const catalog = [];
91
+ for (const relPath of mdFiles) {
92
+ const dir = path.dirname(relPath);
93
+ const basename = path.basename(relPath, ".md");
94
+ if (category && dir !== category)
95
+ continue;
96
+ const component = docNameToComponentName(basename, dir);
97
+ const normalizedPath = relPath.replace(/\\/g, "/");
98
+ catalog.push({
99
+ category: dir,
100
+ component,
101
+ docPath: normalizedPath,
102
+ uri: pathToUri(normalizedPath),
103
+ });
104
+ }
105
+ catalog.sort((a, b) => a.category.localeCompare(b.category) || a.component.localeCompare(b.component));
106
+ return catalog;
107
+ }
108
+ export async function getComponentsInCategory(category) {
109
+ const categoryPath = path.join(DOCS_PATH, category);
110
+ try {
111
+ const entries = await fs.readdir(categoryPath, { withFileTypes: true });
112
+ const components = [];
113
+ for (const entry of entries) {
114
+ if (entry.isFile() && entry.name.endsWith(".md") && entry.name !== "README.md") {
115
+ components.push(path.basename(entry.name, ".md"));
116
+ }
117
+ }
118
+ return components.sort();
119
+ }
120
+ catch {
121
+ return [];
122
+ }
123
+ }
124
+ function scoreMatch(line, keywords) {
125
+ let score = 1;
126
+ const lower = line.toLowerCase();
127
+ if (/^#+\s/.test(line))
128
+ score += 5;
129
+ if (/^\|/.test(line))
130
+ score += 2;
131
+ for (const kw of keywords) {
132
+ const count = lower.split(kw.toLowerCase()).length - 1;
133
+ if (count > 1)
134
+ score += count;
135
+ }
136
+ return score;
137
+ }
138
+ export async function grepSearch(keywords, options) {
139
+ const results = [];
140
+ const files = await getCachedDocFiles();
141
+ const ctx = options?.contextLines ?? 3;
142
+ const lowerKeywords = keywords.map((k) => k.trim().toLowerCase()).filter(Boolean);
143
+ if (lowerKeywords.length === 0)
144
+ return results;
145
+ const lineMatches = (line) => lowerKeywords.some((kw) => line.toLowerCase().includes(kw));
146
+ for (const relPath of files) {
147
+ try {
148
+ const content = await getCachedFileContent(relPath);
149
+ const lines = content.split("\n");
150
+ const normPath = relPath.replace(/\\/g, "/");
151
+ lines.forEach((line, index) => {
152
+ if (lineMatches(line)) {
153
+ const ctxBefore = ctx > 0 ? lines.slice(Math.max(0, index - ctx), index) : [];
154
+ const ctxAfter = ctx > 0 ? lines.slice(index + 1, index + 1 + ctx) : [];
155
+ results.push({
156
+ path: normPath,
157
+ line: index + 1,
158
+ content: line.trim(),
159
+ score: scoreMatch(line, lowerKeywords),
160
+ contextBefore: ctxBefore.length > 0 ? ctxBefore.map((l) => l.trim()) : undefined,
161
+ contextAfter: ctxAfter.length > 0 ? ctxAfter.map((l) => l.trim()) : undefined,
162
+ });
163
+ }
164
+ });
165
+ }
166
+ catch {
167
+ // 忽略无法读取的文件
168
+ }
169
+ }
170
+ results.sort((a, b) => b.score - a.score);
171
+ return results;
172
+ }
173
+ // --- Markdown 解析 ---
174
+ export function extractPropsSection(content) {
175
+ const lines = content.split("\n");
176
+ const propsHeaders = ["## Props", "## 参数", "### Props", "### 参数", "## 属性"];
177
+ let startIdx = -1;
178
+ for (let i = 0; i < lines.length; i++) {
179
+ const h = lines[i].trim();
180
+ if (propsHeaders.some((ph) => h === ph || h.startsWith(ph + " "))) {
181
+ startIdx = i;
182
+ break;
183
+ }
184
+ }
185
+ if (startIdx < 0)
186
+ return null;
187
+ const matchLevel = lines[startIdx].match(/^(#+)\s/)?.[1].length ?? 2;
188
+ const collected = [];
189
+ for (let i = startIdx; i < lines.length; i++) {
190
+ const line = lines[i];
191
+ const headingMatch = line.match(/^(#+)\s/);
192
+ if (i > startIdx && headingMatch && headingMatch[1].length <= matchLevel) {
193
+ break;
194
+ }
195
+ collected.push(line);
196
+ }
197
+ return collected.length > 0 ? collected.join("\n") : null;
198
+ }