From 93728557797c0b15cfc1734637833805c7afc877 Mon Sep 17 00:00:00 2001 From: liminghu <1037897418@qq.com> Date: Sun, 22 Jun 2025 00:45:09 +0800 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E6=B3=A8=E5=86=8C=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BF=9D?= =?UTF-8?q?=E8=AF=81=E6=9E=B6=E6=9E=84=E8=84=89=E7=BB=9C=E6=95=B4=E6=B4=81?= =?UTF-8?q?=E6=B8=85=E6=99=B0=EF=BC=8C=E6=B7=BB=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.ts | 315 +-------------------------------- package.json | 4 +- src/tools/getProjectInfo.ts | 65 +++++++ src/tools/index.ts | 9 + src/tools/initCodelf.ts | 100 +++++++++++ src/tools/updateProjectInfo.ts | 27 +++ src/utils/index.ts | 137 ++++++++++++++ tsconfig.json | 5 +- 8 files changed, 350 insertions(+), 312 deletions(-) create mode 100644 src/tools/getProjectInfo.ts create mode 100644 src/tools/index.ts create mode 100644 src/tools/initCodelf.ts create mode 100644 src/tools/updateProjectInfo.ts create mode 100644 src/utils/index.ts diff --git a/index.ts b/index.ts index cc89dd8..18f2bdc 100644 --- a/index.ts +++ b/index.ts @@ -2,77 +2,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { exec } from "child_process"; -import { existsSync, promises as fs } from "fs"; -import * as path from "path"; -import { promisify } from "util"; -import { z } from "zod"; -import * as gitignoreParser from "gitignore-parser"; - -const execPromise = promisify(exec); -// 默认黑名单,当.gitignore不存在时使用 -const folderBlackList = [ - "node_modules", - ".codelf", - ".git", - ".idea", - ".vscode", - "dist", - "build", - "out", - "target", - "bin", - "obj", - ".next", - "coverage", - "__pycache__", - ".DS_Store", - "tmp", - "temp", - "logs", - ".cache", - ".github", - ".gitlab", - "vendor", -]; - -const forceBlackList = [".git", ".codelf", ".vscode", ".idea"]; - -const GET_PROJECT_TEMPLATE = ` -This is the current project details, include project structure, dev attentions, and other important information: - -{{S}} - -Keep in mind: -1. after you finish modifying code to stisfy user requirements, you have to call 'update-project-info' which help you ensure the document remains up to date. -2. follow the response of 'update-project-info' to update .codelf/*.md files. -`; - -// 用于解析.gitignore文件的函数 -async function parseGitignore( - rootPath: string, - targetPath: string -): Promise { - const gitignorePath = path.join(rootPath, ".gitignore"); - - // 检查.gitignore文件是否存在 - if (!existsSync(gitignorePath)) { - return null; - } - - try { - // 读取.gitignore文件内容 - const content = await fs.readFile(gitignorePath, "utf-8"); - // 使用gitignore-parser的compile方法解析.gitignore内容 - const gitignore = gitignoreParser.compile(content); - - // 使用denies方法检查路径是否被拒绝(被忽略) - return gitignore.denies(targetPath); - } catch (error) { - console.error("Error parsing .gitignore:", error); - return null; - } -} +import { initTools } from './src/tools/index.ts' // Create server instance const server = new McpServer({ @@ -82,245 +12,8 @@ const server = new McpServer({ "Enables AI agents to better understand and modify code. Highly recommended for use under all circumstances", }); -// define project info context info tools -server.tool( - "get-project-info", - `Complete the project details and points to note. -its very important for LLM/Agent edit code. the more you konw, the more you can do. -its very useful for cursor or windsurf no martter in agent or edit mode. -**Highly recommended for use under all circumstances**. - `, - { - rootPath: z.string().describe( - `The root path of the project, - C:/User/name/codeProject in windows - /usr/name/codeProject/ in macos/linux` - ), - }, - async ({ rootPath }) => { - const content = await fs - .readdir(path.join(rootPath, ".codelf")) - .then(async (files) => { - const mdFiles = files.filter((f) => f.endsWith(".md")); - const contents = await Promise.all( - mdFiles.map(async (file) => { - // ignore files start with "_", like _changelog.md - if (file.startsWith("_")) { - return ""; - } - const content = await fs.readFile( - path.join(rootPath, ".codelf", file), - "utf-8" - ); - const name = path.basename(file, ".md"); - return `<${name}>\n\n${content}\n\n\n`; - }) - ); - return GET_PROJECT_TEMPLATE.replace("{{S}}", contents.join("\n")); - }) - .catch(() => ""); - return { - content: [ - { - type: "text", - text: content, - }, - ], - }; - } -); - -server.tool( - "update-project-info", - "when you have finished modifying code to stisfy user requirements, you have to update .codelf/*.md files. This tool help you ensure the document remains up to date.", - { - rootPath: z.string().describe( - `The root path of the project, - "C:/User/name/codeProject" in windows - "/usr/name/codeProject/" in macos/linux` - ), - }, - async ({ rootPath }) => { - return { - content: [ - { - type: "text", - text: `[ATTENTION] Next step you must do: -** Read files .codelf/project.md and .codelf/changelog.md and update them base on the changes you have just done.** - `, - }, - ], - }; - } -); - -server.tool( - "init-codelf", - `Initialize .codelf directory and files. which can help llm better understand your project. - - After init .codelf directory and files, you should: - 1. every file in .codelf directory is a markdown file, you can read them and update them. - 2. you have to follow the instructions in .codelf/*.md files and update them. - `, - { - rootPath: z.string().describe( - `The root path of the project, - "C:/User/name/codeProject" in windows - "/usr/name/codeProject/" in macos/linux` - ), - }, - async ({ rootPath }) => { - try { - await fs.mkdir(path.join(rootPath, ".codelf"), { recursive: true }); - - // 检查目标目录是否为空 - const files = await fs.readdir(path.join(rootPath, ".codelf")); - - if (files.length === 0) { - // 目录为空,执行 git clone - const { stdout, stderr } = await execPromise( - `git clone https://github.com/Disdjj/codelf-template ${path.join( - rootPath, - ".codelf" - )}` - ); - - // remove .git folder - await fs.rm(path.join(rootPath, ".codelf", ".git"), { - recursive: true, - }); - const fileTree = await getFileTree(rootPath); - - // append filetree to .codelf/project.md - await fs.appendFile( - path.join(rootPath, ".codelf", "project.md"), - `\n\`\`\`\n${fileTree}\n\`\`\`\n` - ); - - return { - content: [ - { - type: "text", - text: `Successfully initialized .codelf directory with template.\nOutput: ${stdout}\n${ - stderr ? `Error: ${stderr}` : "" - }`, - }, - { - type: "text", - text: `[Attention]\n - Next step you should do:\n - 1. every file in .codelf directory is a markdown file, you can read them and update them. - 2. you have to follow the instructions in .codelf/*.md files and update them. - 3. before you finish edit .codelf/*.md files, do not use update-project-info/get-project-info. - 4. after you finish edit .codelf/*.md files, just stop. - `, - }, - ], - }; - } else { - // 目录不为空,提示用户 - return { - content: [ - { - type: "text", - text: "The .codelf directory already exists and is not empty. Please remove or empty it before initializing.", - }, - ], - }; - } - } catch (error) { - return { - content: [ - { - type: "text", - text: `Failed to initialize .codelf directory: ${ - error instanceof Error ? error.message : String(error) - }`, - }, - ], - }; - } - } -); - -async function getFileTree(rootPath: string): Promise { - const indent = " "; - - // 递归处理单个路径(目录或文件) - const processEntry = async (entryPath: string, displayName: string, prefix: string, relativePath: string): Promise => { - const stat = await fs.stat(entryPath).catch(() => null); - const lines: string[] = []; - if (stat && stat.isDirectory()) { - lines.push(`${prefix}- ${displayName}`); - const entries = await fs.readdir(entryPath, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory() && forceBlackList.includes(entry.name)) continue; - const entryRelativePath = path.join(relativePath, entry.name).replace(/\\/g, "/"); - const subPath = path.join(entryPath, entry.name); - lines.push(...(await processEntry(subPath, entry.name, prefix + indent, entryRelativePath))); - } - } else if (stat && stat.isFile()) { - lines.push(`${prefix}- ${displayName}`); - } - return lines; - }; - - const buildTree = async ( - dir: string, - prefix: string, - relativePath: string = "" - ): Promise => { - const codelfPath = path.join(rootPath, ".codelf.config"); - const result: string[] = []; - const existsCodelfFile = existsSync(codelfPath) && !(await fs.stat(codelfPath)).isDirectory(); - - if (existsCodelfFile && dir === rootPath) { - // 读取 .codelf.config 文件内容 - const content = await fs.readFile(codelfPath, "utf-8"); - const lines = content - .split(/\r?\n/) - .map((l) => l.trim()) - .filter((l) => l && !l.startsWith("#")); - if (lines.length) { - for (const line of lines) { - const entryPath = path.join(rootPath, line); - result.push(...(await processEntry(entryPath, line, prefix, line.replace(/\\/g, "/")))); - } - return result; - } - } - - // 原有递归逻辑 - const entries = await fs.readdir(dir, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory() && forceBlackList.includes(entry.name)) { - continue; - } - - // 尝试解析.gitignore文件 - // 如果.gitignore存在且解析成功,使用其规则;否则使用默认黑名单 - const entryRelativePath = path - .join(relativePath, entry.name) - .replace(/\\/g, "/"); - const isIgnore = await parseGitignore(rootPath, entryRelativePath); - - // 使用.gitignore规则或默认黑名单进行过滤 - const shouldIgnore = - typeof isIgnore === "boolean" - ? isIgnore - : folderBlackList.includes(entry.name); - if (!shouldIgnore) { - const entryPath = path.join(dir, entry.name); - result.push(...(await processEntry(entryPath, entry.name, prefix, entryRelativePath))); - } - } - - return result; - }; - - const result = await buildTree(rootPath, "", ""); - return ["root", ...result].join("\n"); -} +// Register tools +initTools(server) async function main() { const transport = new StdioServerTransport(); @@ -332,3 +25,5 @@ main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); }); + + diff --git a/package.json b/package.json index 3060346..9b7c673 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,8 @@ }, "type": "module", "scripts": { - "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"" + "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", + "dev": "tsx watch index.ts", + "dev:nodemon": "nodemon --exec tsx index.ts" } } diff --git a/src/tools/getProjectInfo.ts b/src/tools/getProjectInfo.ts new file mode 100644 index 0000000..f8248c3 --- /dev/null +++ b/src/tools/getProjectInfo.ts @@ -0,0 +1,65 @@ + +// define project info context info tools +import { z } from "zod"; +import { promises as fs } from "fs"; +import * as path from "path"; + +const GET_PROJECT_TEMPLATE = ` +This is the current project details, include project structure, dev attentions, and other important information: + +{{S}} + +Keep in mind: +1. after you finish modifying code to stisfy user requirements, you have to call 'update-project-info' which help you ensure the document remains up to date. +2. follow the response of 'update-project-info' to update .codelf/*.md files. +`; + +export const getProjectInfo = (server: any) => { + server.tool( + "get-project-info", + `Complete the project details and points to note. + its very important for LLM/Agent edit code. the more you konw, the more you can do. + its very useful for cursor or windsurf no martter in agent or edit mode. + **Highly recommended for use under all circumstances**. + `, + { + rootPath: z.string().describe( + `The root path of the project, + C:/User/name/codeProject in windows + /usr/name/codeProject/ in macos/linux` + ), + }, + async ({ rootPath }: { rootPath: string }) => { + const content = await fs + .readdir(path.join(rootPath, ".codelf")) + .then(async (files) => { + const mdFiles = files.filter((f) => f.endsWith(".md")); + const contents = await Promise.all( + mdFiles.map(async (file) => { + // ignore files start with "_", like _changelog.md + if (file.startsWith("_")) { + return ""; + } + const content = await fs.readFile( + path.join(rootPath, ".codelf", file), + "utf-8" + ); + const name = path.basename(file, ".md"); + return `<${name}>\n\n${content}\n\n\n`; + }) + ); + return GET_PROJECT_TEMPLATE.replace("{{S}}", contents.join("\n")); + }) + .catch(() => ""); + return { + content: [ + { + type: "text", + text: content, + }, + ], + }; + } + ); + +} diff --git a/src/tools/index.ts b/src/tools/index.ts new file mode 100644 index 0000000..a60e3b9 --- /dev/null +++ b/src/tools/index.ts @@ -0,0 +1,9 @@ +import { initCodelf } from './initCodelf.ts' +import { getProjectInfo } from './getProjectInfo.ts' +import { updateProjectInfo } from './updateProjectInfo.ts' + +export const initTools = (server: any) => { + initCodelf(server) + getProjectInfo(server) + updateProjectInfo(server) +} \ No newline at end of file diff --git a/src/tools/initCodelf.ts b/src/tools/initCodelf.ts new file mode 100644 index 0000000..cb1949b --- /dev/null +++ b/src/tools/initCodelf.ts @@ -0,0 +1,100 @@ +// init-codelf +// get-project-info +// update-project-info +import { z } from "zod"; +import { promises as fs } from "fs"; +import * as path from "path"; +import { promisify } from "util"; +import { exec } from "child_process"; +import { getFileTree } from '../utils/index.ts' + +const execPromise = promisify(exec); + +export const initCodelf = (server: any) => { + server.tool( + "init-codelf", + `Initialize .codelf directory and files. which can help llm better understand your project. + + After init .codelf directory and files, you should: + 1. every file in .codelf directory is a markdown file, you can read them and update them. + 2. you have to follow the instructions in .codelf/*.md files and update them. + `, + { + rootPath: z.string().describe( + `The root path of the project, + "C:/User/name/codeProject" in windows + "/usr/name/codeProject/" in macos/linux` + ), + }, + async ({ rootPath }: { rootPath: string }) => { + try { + await fs.mkdir(path.join(rootPath, ".codelf"), { recursive: true }); + + // 检查目标目录是否为空 + const files = await fs.readdir(path.join(rootPath, ".codelf")); + + if (files.length === 0) { + // 目录为空,执行 git clone + const { stdout, stderr } = await execPromise( + `git clone https://github.com/Disdjj/codelf-template ${path.join( + rootPath, + ".codelf" + )}` + ); + + // remove .git folder + await fs.rm(path.join(rootPath, ".codelf", ".git"), { + recursive: true, + }); + const fileTree = await getFileTree(rootPath); + + // append filetree to .codelf/project.md + await fs.appendFile( + path.join(rootPath, ".codelf", "project.md"), + `\n\`\`\`\n${fileTree}\n\`\`\`\n` + ); + + return { + content: [ + { + type: "text", + text: `Successfully initialized .codelf directory with template.\nOutput: ${stdout}\n${stderr ? `Error: ${stderr}` : "" + }`, + }, + { + type: "text", + text: `[Attention]\n + Next step you should do:\n + 1. every file in .codelf directory is a markdown file, you can read them and update them. + 2. you have to follow the instructions in .codelf/*.md files and update them. + 3. before you finish edit .codelf/*.md files, do not use update-project-info/get-project-info. + 4. after you finish edit .codelf/*.md files, just stop. + `, + }, + ], + }; + } else { + // 目录不为空,提示用户 + return { + content: [ + { + type: "text", + text: "The .codelf directory already exists and is not empty. Please remove or empty it before initializing.", + }, + ], + }; + } + } catch (error) { + return { + content: [ + { + type: "text", + text: `Failed to initialize .codelf directory: ${error instanceof Error ? error.message : String(error) + }`, + }, + ], + }; + } + } + ); +} \ No newline at end of file diff --git a/src/tools/updateProjectInfo.ts b/src/tools/updateProjectInfo.ts new file mode 100644 index 0000000..3898190 --- /dev/null +++ b/src/tools/updateProjectInfo.ts @@ -0,0 +1,27 @@ +import { z } from "zod"; + +export const updateProjectInfo = (server: any) => { + server.tool( + "update-project-info", + "when you have finished modifying code to stisfy user requirements, you have to update .codelf/*.md files. This tool help you ensure the document remains up to date.", + { + rootPath: z.string().describe( + `The root path of the project, + "C:/User/name/codeProject" in windows + "/usr/name/codeProject/" in macos/linux` + ), + }, + async ({ rootPath }: { rootPath: string }) => { + return { + content: [ + { + type: "text", + text: `[ATTENTION] Next step you must do: + ** Read files .codelf/project.md and .codelf/changelog.md and update them base on the changes you have just done.** + `, + }, + ], + }; + } + ); +} \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..ece51c0 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,137 @@ +import { existsSync, promises as fs } from "fs"; +import * as path from "path"; +import * as gitignoreParser from "gitignore-parser"; + +// 默认黑名单,当.gitignore不存在时使用 +const folderBlackList = [ + "node_modules", + ".codelf", + ".git", + ".idea", + ".vscode", + "dist", + "build", + "out", + "target", + "bin", + "obj", + ".next", + "coverage", + "__pycache__", + ".DS_Store", + "tmp", + "temp", + "logs", + ".cache", + ".github", + ".gitlab", + "vendor", +]; + +const forceBlackList = [".git", ".codelf", ".vscode", ".idea"]; + + +// 用于解析.gitignore文件的函数 +export async function parseGitignore( + rootPath: string, + targetPath: string +): Promise { + const gitignorePath = path.join(rootPath, ".gitignore"); + + // 检查.gitignore文件是否存在 + if (!existsSync(gitignorePath)) { + return null; + } + + try { + // 读取.gitignore文件内容 + const content = await fs.readFile(gitignorePath, "utf-8"); + // 使用gitignore-parser的compile方法解析.gitignore内容 + const gitignore = gitignoreParser.compile(content); + + // 使用denies方法检查路径是否被拒绝(被忽略) + return gitignore.denies(targetPath); + } catch (error) { + console.error("Error parsing .gitignore:", error); + return null; + } +} + +export async function getFileTree(rootPath: string): Promise { + const indent = " "; + + // 递归处理单个路径(目录或文件) + const processEntry = async (entryPath: string, displayName: string, prefix: string, relativePath: string): Promise => { + const stat = await fs.stat(entryPath).catch(() => null); + const lines: string[] = []; + if (stat && stat.isDirectory()) { + lines.push(`${prefix}- ${displayName}`); + const entries = await fs.readdir(entryPath, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isDirectory() && forceBlackList.includes(entry.name)) continue; + const entryRelativePath = path.join(relativePath, entry.name).replace(/\\/g, "/"); + const subPath = path.join(entryPath, entry.name); + lines.push(...(await processEntry(subPath, entry.name, prefix + indent, entryRelativePath))); + } + } else if (stat && stat.isFile()) { + lines.push(`${prefix}- ${displayName}`); + } + return lines; + }; + + const buildTree = async ( + dir: string, + prefix: string, + relativePath: string = "" + ): Promise => { + const codelfPath = path.join(rootPath, ".codelf.config"); + const result: string[] = []; + const existsCodelfFile = existsSync(codelfPath) && !(await fs.stat(codelfPath)).isDirectory(); + + if (existsCodelfFile && dir === rootPath) { + // 读取 .codelf.config 文件内容 + const content = await fs.readFile(codelfPath, "utf-8"); + const lines = content + .split(/\r?\n/) + .map((l) => l.trim()) + .filter((l) => l && !l.startsWith("#")); + if (lines.length) { + for (const line of lines) { + const entryPath = path.join(rootPath, line); + result.push(...(await processEntry(entryPath, line, prefix, line.replace(/\\/g, "/")))); + } + return result; + } + } + + // 原有递归逻辑 + const entries = await fs.readdir(dir, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isDirectory() && forceBlackList.includes(entry.name)) { + continue; + } + + // 尝试解析.gitignore文件 + // 如果.gitignore存在且解析成功,使用其规则;否则使用默认黑名单 + const entryRelativePath = path + .join(relativePath, entry.name) + .replace(/\\/g, "/"); + const isIgnore = await parseGitignore(rootPath, entryRelativePath); + + // 使用.gitignore规则或默认黑名单进行过滤 + const shouldIgnore = + typeof isIgnore === "boolean" + ? isIgnore + : folderBlackList.includes(entry.name); + if (!shouldIgnore) { + const entryPath = path.join(dir, entry.name); + result.push(...(await processEntry(entryPath, entry.name, prefix, entryRelativePath))); + } + } + + return result; + }; + + const result = await buildTree(rootPath, "", ""); + return ["root", ...result].join("\n"); +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 7a220e2..ef7769f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,10 @@ "strict": true, "esModuleInterop": true, "skipLibCheck": true, - "forceConsistentCasingInFileNames": true + "forceConsistentCasingInFileNames": true, + "declaration": true, + "emitDeclarationOnly": true, + "allowImportingTsExtensions": true }, "include": ["./**/*"], "exclude": ["node_modules"] From 7d1ceae763723e7c08ac3bf10c31cc6615834310 Mon Sep 17 00:00:00 2001 From: liminghu <1037897418@qq.com> Date: Mon, 23 Jun 2025 14:54:18 +0800 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=94=9F?= =?UTF-8?q?=E6=88=90=E9=A1=B9=E7=9B=AEYAML=E5=92=8C=E5=8F=98=E6=9B=B4?= =?UTF-8?q?=E6=97=A5=E5=BF=97YAML=E7=9A=84=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=9B=AE=E5=BD=95=E7=BB=93=E6=9E=84=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/index.ts | 131 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 1 deletion(-) diff --git a/src/utils/index.ts b/src/utils/index.ts index ece51c0..4439084 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,6 +1,7 @@ import { existsSync, promises as fs } from "fs"; import * as path from "path"; import * as gitignoreParser from "gitignore-parser"; +import dayjs from "dayjs"; // 默认黑名单,当.gitignore不存在时使用 const folderBlackList = [ @@ -84,7 +85,7 @@ export async function getFileTree(rootPath: string): Promise { prefix: string, relativePath: string = "" ): Promise => { - const codelfPath = path.join(rootPath, ".codelf.config"); + const codelfPath = path.join(dir, ".codelf.config"); const result: string[] = []; const existsCodelfFile = existsSync(codelfPath) && !(await fs.stat(codelfPath)).isDirectory(); @@ -134,4 +135,132 @@ export async function getFileTree(rootPath: string): Promise { const result = await buildTree(rootPath, "", ""); return ["root", ...result].join("\n"); +} + +export async function generateProjectYaml(rootPath: string, analyzeDirectory?: string) { + if (!rootPath) return + console.log("rootPath", rootPath) + try { + // 设置默认的输出路径 + const outputPath = path.join(rootPath, ".codelf", "projectInfo.yaml"); + analyzeDirectory = analyzeDirectory || rootPath; + + // 递归解析目录结构 + const analyzeStructure = async (dirPath: string): Promise => { + const items: any[] = []; + + try { + const entries = await fs.readdir(dirPath, { withFileTypes: true }); + + for (const entry of entries) { + // 跳过黑名单中的文件和文件夹 + if (forceBlackList.includes(entry.name)) { + continue; + } + + const entryPath = path.join(dirPath, entry.name); + const relativePath = path.relative(dirPath, entryPath).replace(/\\/g, "/"); + + // 检查是否应该忽略此项 + const isIgnore = await parseGitignore(dirPath, relativePath); + const shouldIgnore = typeof isIgnore === "boolean" + ? isIgnore + : folderBlackList.includes(entry.name); + + if (!shouldIgnore) { + if (entry.isDirectory()) { + // 处理目录 + const subItems = await analyzeStructure(entryPath); + const dirItem = { + entry: entry.name, + ...(subItems.length > 0 && { subs: subItems }) + }; + items.push(dirItem); + } else if (entry.isFile()) { + // 处理文件 + const fileItem = { + entry: entry.name, + }; + items.push(fileItem); + } + } + } + } catch (error) { + console.error(`Error reading directory ${dirPath}:`, error); + } + + return items; + }; + + // 生成结构 + const structure = await analyzeStructure(analyzeDirectory); + + // 生成YAML内容 + const generateYamlContent = (obj: any, indent: number = 0): string => { + const spaces = " ".repeat(indent); + let result = ""; + + if (Array.isArray(obj)) { + for (const item of obj) { + result += `${spaces}- ${generateYamlContent(item, 0).trim()}\n`; + } + return result; + } + + if (typeof obj === "object" && obj !== null) { + const entries = Object.entries(obj); + for (let i = 0; i < entries.length; i++) { + const [key, value] = entries[i]; + if (key === "entry" && i === 0) { + result += `entry: "${value}"\n`; + } else if (typeof value === "string") { + result += `${spaces}${key}: "${value}"\n`; + } else if (Array.isArray(value)) { + if (key === "subs") { + result += `${spaces}${key}:\n`; + for (const sub of value) { + result += `${spaces} - ${generateYamlContent(sub, indent + 2).trim()}\n`; + } + } else { + result += `${spaces}${key}:\n${generateYamlContent(value, indent + 1)}`; + } + } else { + result += `${spaces}${key}:\n${generateYamlContent(value, indent + 1)}`; + } + } + return result; + } + + return String(obj); + }; + + // 获取最后一级目录名 + const dirName = path.basename(analyzeDirectory); + + // 构造完整的YAML内容,将最后一级目录作为根entry + console.log("outputPath", outputPath) + const yamlContent = `- entry: "${dirName}"\n subs:\n${generateYamlContent(structure, 2)}`; + + // 写入文件 + await fs.appendFile(outputPath, yamlContent, "utf-8"); + + console.log(`项目YAML文件已生成: ${outputPath}`); + return yamlContent; + + } catch (error) { + console.error("生成项目YAML文件时出错:", error); + throw error; + } +} + +export async function generateChangelogYaml(outputPath: string) { + if (!outputPath) return + const yamlContent = ` +${dayjs().format("YYYY-MM-DD HH:mm:ss")}: + title: "changelog_title" + content: "changelog_content" +`; + + // 写入文件 + fs.writeFile(outputPath, yamlContent, "utf-8"); } \ No newline at end of file From bd896a9de5137e9aee5f20ed4e08f602c9592505 Mon Sep 17 00:00:00 2001 From: liminghu <1037897418@qq.com> Date: Mon, 23 Jun 2025 14:54:49 +0800 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20=E5=9C=A8package.json=E4=B8=AD?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0dayjs=E4=BE=9D=E8=B5=96=E4=BB=A5=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=97=A5=E6=9C=9F=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 9b7c673..2452fe9 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "gitignore-parser": "^0.0.2", "minimist": "^1.2.8", "resend": "^4.1.2", - "zod": "^3.24.2" + "zod": "^3.24.2", + "dayjs": "^1.11.13" }, "devDependencies": { "@types/minimist": "^1.2.5", From 13d04e7e9e30e4353bea8d2e5e8c65b457d8b116 Mon Sep 17 00:00:00 2001 From: liminghu <1037897418@qq.com> Date: Mon, 23 Jun 2025 14:55:52 +0800 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0initCodelf?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=94=AF=E6=8C=81=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E7=9B=AE=E5=BD=95=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?.yaml=E6=96=87=E4=BB=B6=E7=9A=84=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E5=92=8C=E7=94=A8=E6=88=B7=E6=8F=90=E7=A4=BA=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tools/initCodelf.ts | 163 ++++++++++++++++++++++++---------------- 1 file changed, 99 insertions(+), 64 deletions(-) diff --git a/src/tools/initCodelf.ts b/src/tools/initCodelf.ts index cb1949b..d556b95 100644 --- a/src/tools/initCodelf.ts +++ b/src/tools/initCodelf.ts @@ -1,23 +1,23 @@ -// init-codelf -// get-project-info -// update-project-info import { z } from "zod"; import { promises as fs } from "fs"; import * as path from "path"; -import { promisify } from "util"; -import { exec } from "child_process"; -import { getFileTree } from '../utils/index.ts' - -const execPromise = promisify(exec); +import { generateProjectYaml, generateChangelogYaml } from '../utils/index.ts' export const initCodelf = (server: any) => { server.tool( "init-codelf", `Initialize .codelf directory and files. which can help llm better understand your project. - - After init .codelf directory and files, you should: - 1. every file in .codelf directory is a markdown file, you can read them and update them. - 2. you have to follow the instructions in .codelf/*.md files and update them. + + Before init .codelf directory and files, you should: + 1. If User provide the analyzeDirectorys, you need to convert to an absolute path to ensure correct function's invocation and push to a Array as an "analyzeDirectorys" before you read and update the yaml fileds. + 2. Tell users the conversion results and the analyzeDirectorys. + + During init .codelf directory and files, you should: + If some errors happen, output logs to users. + + After init .codelf directory and files, you should: + 1. Every file in .codelf directory is a yaml file, you can read them and update them. + 2. You have to follow the instructions in .codelf/*.yaml files and update them. `, { rootPath: z.string().describe( @@ -25,65 +25,100 @@ export const initCodelf = (server: any) => { "C:/User/name/codeProject" in windows "/usr/name/codeProject/" in macos/linux` ), + analyzeDirectorys: z.array(z.string()).describe( + `Catalog array to be analyzed. Each project is a absolute path String, will be like + "C:/User/name/codeProject" in windows + "/src" in windows + "/usr/name/codeProject/" in macos/linux + "/src" in macos/linux` + ), }, - async ({ rootPath }: { rootPath: string }) => { + async ({ rootPath, analyzeDirectorys }: { rootPath: string, analyzeDirectorys: string[] }) => { try { - await fs.mkdir(path.join(rootPath, ".codelf"), { recursive: true }); - - // 检查目标目录是否为空 - const files = await fs.readdir(path.join(rootPath, ".codelf")); - - if (files.length === 0) { - // 目录为空,执行 git clone - const { stdout, stderr } = await execPromise( - `git clone https://github.com/Disdjj/codelf-template ${path.join( - rootPath, - ".codelf" - )}` - ); + const codelfDir = path.join(rootPath, ".codelf"); + const projectinfoYaml = path.join(codelfDir, "projectInfo.yaml"); - // remove .git folder - await fs.rm(path.join(rootPath, ".codelf", ".git"), { - recursive: true, - }); - const fileTree = await getFileTree(rootPath); + // 检查 .codelf 目录是否存在 + const codelfExists = await fs.access(codelfDir).then(() => true).catch(() => false); + + if (codelfExists) { + // 检查 yaml 文件是否存在 + const yamlExists = await fs.access(projectinfoYaml).then(() => true).catch(() => false); + + if (yamlExists) { + return { + content: [ + { + type: "text", + text: "The .codelf directory and projectInfo.yaml file already exist.", + }, + { + type: "text", + text: `[Attention] + Next steps you should do: + 1. every file in .codelf directory is a yaml file, you can read them and update them. + 2. You can edit the prokectInfo.yaml file to customize project settings + 3. And then,just need to compare the directory structure and YAML in the actual project base on the ${analyzeDirectorys} + 4. Delete the excessive entries and related descriptions in YAML + 5. Complete the missing file directory entries and related descriptions + 6. You need to organize the indentation format of YAML files + 7. before you finish edit .codelf/*.yaml files, do not use update-project-info/get-project-info. + 6. After editing, you have to read files .codelf/changelog.yaml and update them base on the changes you have just done. + `, + }, + ], + }; + } + } else { + // 创建 .codelf 目录 + await fs.mkdir(codelfDir, { recursive: true }); + } - // append filetree to .codelf/project.md - await fs.appendFile( - path.join(rootPath, ".codelf", "project.md"), - `\n\`\`\`\n${fileTree}\n\`\`\`\n` - ); + // 创建 projectInfo.yaml 文件 + await fs.writeFile(projectinfoYaml, "structure/n", "utf-8"); - return { - content: [ - { - type: "text", - text: `Successfully initialized .codelf directory with template.\nOutput: ${stdout}\n${stderr ? `Error: ${stderr}` : "" - }`, - }, - { - type: "text", - text: `[Attention]\n - Next step you should do:\n - 1. every file in .codelf directory is a markdown file, you can read them and update them. - 2. you have to follow the instructions in .codelf/*.md files and update them. - 3. before you finish edit .codelf/*.md files, do not use update-project-info/get-project-info. - 4. after you finish edit .codelf/*.md files, just stop. - `, - }, - ], - }; + if (analyzeDirectorys?.length) { + let i = 0; + while(i < analyzeDirectorys?.length) { + i += 1; + await generateProjectYaml(rootPath, analyzeDirectorys[i]) + } } else { - // 目录不为空,提示用户 - return { - content: [ - { - type: "text", - text: "The .codelf directory already exists and is not empty. Please remove or empty it before initializing.", - }, - ], - }; + await generateProjectYaml(rootPath) } + + const changelogYaml = path.join(codelfDir, "changelog.yaml"); + + await generateChangelogYaml(changelogYaml) + + const temp = ` +description: "main.py_description" +attention: "some important thing about this file"" +` + + return { + content: [ + { + type: "text", + text: `Successfully initialized .codelf directory and created config.yaml file.`, + }, + { + type: "text", + text: `[Attention] +Next steps you should do: +1. every file in .codelf directory is a yaml file, you can read them and update them. +2. You can edit the projectInfo.yaml file to customize project settings +3. The file tree has been automatically included in the configuration +4. The entry field refers to the folder or file name. +5. Only the corresponding entry module in the current YAML needs to be updated, and new entries cannot be added base on the ${analyzeDirectorys} +6. You need to add a few lines of content after each entry in projectInfo.yaml, like "${temp}" +7. You need to organize the indentation format of YAML files +8. before you finish edit .codelf/*.yaml files, do not use update-project-info/get-project-info. +9. After editing, you have to read files .codelf/changelog.yaml and update them base on the changes you have just done. +`, + }, + ], + }; } catch (error) { return { content: [ From c423d3a30ec294b0d1c5a2dd0c60dc6b7ae34ff3 Mon Sep 17 00:00:00 2001 From: liminghu <1037897418@qq.com> Date: Mon, 23 Jun 2025 14:56:08 +0800 Subject: [PATCH 05/12] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0getProjectInfo?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=8C=E8=B0=83=E6=95=B4=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E6=A8=A1=E6=9D=BF=EF=BC=8C=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=89=A9=E5=B1=95=E5=90=8D=E5=A4=84=E7=90=86?= =?UTF-8?q?=E4=B8=BA.yaml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tools/getProjectInfo.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/tools/getProjectInfo.ts b/src/tools/getProjectInfo.ts index f8248c3..9bc627d 100644 --- a/src/tools/getProjectInfo.ts +++ b/src/tools/getProjectInfo.ts @@ -5,13 +5,13 @@ import { promises as fs } from "fs"; import * as path from "path"; const GET_PROJECT_TEMPLATE = ` -This is the current project details, include project structure, dev attentions, and other important information: +This is the current project details, include project structure: {{S}} Keep in mind: 1. after you finish modifying code to stisfy user requirements, you have to call 'update-project-info' which help you ensure the document remains up to date. -2. follow the response of 'update-project-info' to update .codelf/*.md files. +2. follow the response of 'update-project-info' to update .codelf/*.yaml files. `; export const getProjectInfo = (server: any) => { @@ -33,10 +33,10 @@ export const getProjectInfo = (server: any) => { const content = await fs .readdir(path.join(rootPath, ".codelf")) .then(async (files) => { - const mdFiles = files.filter((f) => f.endsWith(".md")); + const yamlFiles = files.filter((f) => f.endsWith(".yaml")); const contents = await Promise.all( - mdFiles.map(async (file) => { - // ignore files start with "_", like _changelog.md + yamlFiles.map(async (file) => { + // ignore files start with "_", like _changelog.yaml if (file.startsWith("_")) { return ""; } @@ -44,7 +44,7 @@ export const getProjectInfo = (server: any) => { path.join(rootPath, ".codelf", file), "utf-8" ); - const name = path.basename(file, ".md"); + const name = path.basename(file, ".yaml"); return `<${name}>\n\n${content}\n\n\n`; }) ); From 097af1336ce350635c4f7ea7ac6cc89ac36fee46 Mon Sep 17 00:00:00 2001 From: liminghu <1037897418@qq.com> Date: Mon, 23 Jun 2025 14:56:57 +0800 Subject: [PATCH 06/12] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0updateProjectIn?= =?UTF-8?q?fo=E5=8A=9F=E8=83=BD=E5=B9=B6=E6=B7=BB=E5=8A=A0=E9=87=8D?= =?UTF-8?q?=E8=A6=81=E7=AC=A6=E5=8F=B7=E5=92=8C=E7=9B=B8=E5=85=B3=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E8=AE=B0=E5=BD=95=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tools/updateProjectInfo.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/tools/updateProjectInfo.ts b/src/tools/updateProjectInfo.ts index 3898190..b98f8cb 100644 --- a/src/tools/updateProjectInfo.ts +++ b/src/tools/updateProjectInfo.ts @@ -3,7 +3,7 @@ import { z } from "zod"; export const updateProjectInfo = (server: any) => { server.tool( "update-project-info", - "when you have finished modifying code to stisfy user requirements, you have to update .codelf/*.md files. This tool help you ensure the document remains up to date.", + "when you have finished modifying code to stisfy user requirements, you have to update .codelf/*.yaml files. This tool help you ensure the document remains up to date.", { rootPath: z.string().describe( `The root path of the project, @@ -12,12 +12,28 @@ export const updateProjectInfo = (server: any) => { ), }, async ({ rootPath }: { rootPath: string }) => { + const temp = ` +important_symbols: + [ + "function_name": "what is this function?", + "class_name": "what is this class?", + "variable_name": "what is this variable?", + "constant_name": "what is this constant?", + "important_symbol_2": "what is this symbol?", + "important_symbol_2": "what is this symbol?", + ] +related: ["src/main/main.py"] +` return { content: [ { type: "text", text: `[ATTENTION] Next step you must do: - ** Read files .codelf/project.md and .codelf/changelog.md and update them base on the changes you have just done.** + 1. Read files .codelf/projectInfo.yaml and .codelf/changelog.yaml and update them base on the changes you have just done. + 2. You need to add a few lines of content after the modified "entry" by you in projectInfo.yaml, like "${temp}" + 3. Record the imported file in the "related" field + 4. Write important functions and variables into the "important_symbols" field + 5. And so on `, }, ], From ba3f49e889ea8947487ee9ce6ac6bf3cef18706a Mon Sep 17 00:00:00 2001 From: liminghu <1037897418@qq.com> Date: Mon, 23 Jun 2025 14:57:43 +0800 Subject: [PATCH 07/12] =?UTF-8?q?feat:=20=E6=B8=85=E7=90=86index.ts?= =?UTF-8?q?=E6=96=87=E4=BB=B6=EF=BC=8C=E7=A7=BB=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E7=9A=84=E7=A9=BA=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/index.ts b/index.ts index 18f2bdc..90aa84e 100644 --- a/index.ts +++ b/index.ts @@ -25,5 +25,3 @@ main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); }); - - From d9b842c1ad46932c1816be8cab5ca2fa8a31dbff Mon Sep 17 00:00:00 2001 From: liminghu <1037897418@qq.com> Date: Mon, 23 Jun 2025 14:58:44 +0800 Subject: [PATCH 08/12] =?UTF-8?q?feat:=20=E5=88=A0=E9=99=A4=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E6=96=87=E6=A1=A3=E4=B8=AD=E7=9A=84attention.md?= =?UTF-8?q?=E3=80=81changelog.md=E5=92=8Cproject.md=E6=96=87=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C=E6=B8=85=E7=90=86=E4=B8=8D=E5=BF=85=E8=A6=81=E7=9A=84?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .codelf/attention.md | 35 ---------------------------------- .codelf/changelog.md | 45 -------------------------------------------- .codelf/project.md | 31 ------------------------------ 3 files changed, 111 deletions(-) delete mode 100644 .codelf/attention.md delete mode 100644 .codelf/changelog.md delete mode 100644 .codelf/project.md diff --git a/.codelf/attention.md b/.codelf/attention.md deleted file mode 100644 index 8a40b78..0000000 --- a/.codelf/attention.md +++ /dev/null @@ -1,35 +0,0 @@ -## Language: TypeScript -> The project uses TypeScript as the main development language, adopting a modular structure, and using the Node.js runtime environment. - -**Formatter Library:** -- TypeScript (v5.7.3) -- ESLint/TSLint (implicitly used) - -**Usable Utilities and Components:** -> List the directories of existing common methods and components in the current project, along with a brief description of their functions. -``` -- index.ts // Main entry file, containing MCP server implementation and all tool functions -``` - -**Coding Conventions:** -> Clear separation of code responsibilities: routing, business logic, and utility functions are organized independently. -``` -- Tool functions // Defined in index.ts, including get-project-info, update-project-info, and init-codelf tools -- File operations // Using the fs module for file read and write operations -- Server logic // Using @modelcontextprotocol/sdk to create and run the MCP server -``` - -**Folder and Variable Naming Conventions:** -- Semantic naming -- Using camelCase for variable and function names -- Using PascalCase for class and interface names -- Reasonable length constraints - -**Error Monitoring and Logging:** -> Proper use of console for logging, ensuring removal of unnecessary debugging statements before production. -> Adding appropriate comments to enhance readability, but avoiding excessive annotation. - -**Special Considerations:** -- The project uses the folderBlackList array to define folders to be ignored when generating the file tree -- Using the Zod library for parameter validation -- Using TypeScript's type system to ensure type safety \ No newline at end of file diff --git a/.codelf/changelog.md b/.codelf/changelog.md deleted file mode 100644 index 5740fe3..0000000 --- a/.codelf/changelog.md +++ /dev/null @@ -1,45 +0,0 @@ -## 2025-04-13 15:33:57 - -1. 初始化项目文档 - ``` - root - - .codelf // add 项目文档目录,包含项目信息和变更记录 - - index.ts // - 主入口文件,包含MCP服务器和工具函数实现 - ``` - -2. 项目结构分析 - ``` - root - - index.ts // - 包含folderBlackList数组,定义了需要在生成文件树时忽略的文件夹 - - package.json // - 定义项目依赖和配置 - - tsconfig.json // - TypeScript配置文件 - ``` - -3. 主要功能识别 - ``` - root - - index.ts // - 实现了三个主要工具函数:get-project-info、update-project-info和init-codelf - ``` - -## 2025-04-13 15:37:52 - -1. 添加中文版README文档 - ``` - root - - README_CN.md // - 添加中文版项目说明文档,包含项目介绍、设置指南、核心功能和项目结构等内容 - ``` - -2. 更新项目结构文档 - ``` - root - - .codelf/project.md // - 更新项目结构文档,区分英文版和中文版README文件 - ``` - -## 2025-04-13 15:39:34 - -1. 增强README文档的多语言支持 - ``` - root - - README.md // - 添加切换到中文版的按钮(使用shields.io徽章) - - README_CN.md // - 添加切换到英文版的按钮(使用shields.io徽章) - ``` \ No newline at end of file diff --git a/.codelf/project.md b/.codelf/project.md deleted file mode 100644 index 032c4dd..0000000 --- a/.codelf/project.md +++ /dev/null @@ -1,31 +0,0 @@ -## Codelf -> 项目使用的技术、工具库及对应依赖版本如下: -> TypeScript(v5.7.3)、Node.js、@modelcontextprotocol/sdk(v1.5.0)、Zod(v3.24.2) - -## 项目结构 - -> 文件级别的分析对于理解项目至关重要。 - -> 以下是项目的目录结构,并对重要部分进行了注释说明。 - -root -- .codelf // 项目文档目录,存放项目相关的说明文档 -- .git // Git版本控制目录 -- .gitignore // Git忽略文件配置 -- index.ts // 主入口文件,包含MCP服务器实现和工具函数定义 -- node_modules // Node.js依赖包目录 -- package.json // 项目配置文件,定义依赖和脚本 -- pnpm-lock.yaml // pnpm包管理器锁定文件 -- README.md // 英文版项目说明文档 -- README_CN.md // 中文版项目说明文档 -- tsconfig.json // TypeScript配置文件 - -### 核心功能 - -该项目是一个MCP(Model Context Protocol)服务器实现,主要提供以下工具功能: - -1. `get-project-info` - 获取项目详细信息,帮助AI更好地理解代码 -2. `update-project-info` - 更新项目信息,维护.codelf目录下的文档 -3. `init-codelf` - 初始化.codelf目录和文件,帮助建立项目文档结构 - -项目通过Node.js实现,使用TypeScript进行开发,主要面向AI代码助手提供上下文信息。 From 4e9f8200693ee3ab90ac67e405c0db0b74da754e Mon Sep 17 00:00:00 2001 From: liminghu <1037897418@qq.com> Date: Mon, 23 Jun 2025 14:59:00 +0800 Subject: [PATCH 09/12] =?UTF-8?q?feat:=20=E5=9C=A8pnpm-lock.yaml=E4=B8=AD?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0dayjs@1.11.13=E4=BE=9D=E8=B5=96=EF=BC=8C?= =?UTF-8?q?=E7=A1=AE=E4=BF=9D=E6=97=A5=E6=9C=9F=E5=A4=84=E7=90=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E6=AD=A3=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pnpm-lock.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4cab167..a42a28e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: '@types/gitignore-parser': specifier: ^0.0.3 version: 0.0.3 + dayjs: + specifier: ^1.11.13 + version: 1.11.13 gitignore-parser: specifier: ^0.0.2 version: 0.0.2 @@ -308,6 +311,9 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + debug@4.3.6: resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} engines: {node: '>=6.0'} @@ -1083,6 +1089,8 @@ snapshots: csstype@3.1.3: {} + dayjs@1.11.13: {} + debug@4.3.6: dependencies: ms: 2.1.2 From 419888ae836d9249429e3d367222e9c40a396346 Mon Sep 17 00:00:00 2001 From: liminghu <1037897418@qq.com> Date: Sun, 29 Jun 2025 14:23:51 +0800 Subject: [PATCH 10/12] =?UTF-8?q?feat:=20=E5=9C=A8generateProjectYaml?= =?UTF-8?q?=E4=B8=AD=E5=BC=95=E5=85=A5js-yaml=E5=BA=93=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96YAML=E5=86=85=E5=AE=B9=E7=94=9F=E6=88=90=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E7=AE=80=E5=8C=96=E4=BB=A3=E7=A0=81=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/index.ts | 59 ++++++++++------------------------------------ 1 file changed, 13 insertions(+), 46 deletions(-) diff --git a/src/utils/index.ts b/src/utils/index.ts index 4439084..099c606 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -2,6 +2,7 @@ import { existsSync, promises as fs } from "fs"; import * as path from "path"; import * as gitignoreParser from "gitignore-parser"; import dayjs from "dayjs"; +import * as yaml from "js-yaml"; // 默认黑名单,当.gitignore不存在时使用 const folderBlackList = [ @@ -139,13 +140,12 @@ export async function getFileTree(rootPath: string): Promise { export async function generateProjectYaml(rootPath: string, analyzeDirectory?: string) { if (!rootPath) return - console.log("rootPath", rootPath) try { // 设置默认的输出路径 const outputPath = path.join(rootPath, ".codelf", "projectInfo.yaml"); analyzeDirectory = analyzeDirectory || rootPath; - // 递归解析目录结构 + // 分析项目结构 const analyzeStructure = async (dirPath: string): Promise => { const items: any[] = []; @@ -171,10 +171,12 @@ export async function generateProjectYaml(rootPath: string, analyzeDirectory?: s if (entry.isDirectory()) { // 处理目录 const subItems = await analyzeStructure(entryPath); - const dirItem = { + const dirItem: any = { entry: entry.name, - ...(subItems.length > 0 && { subs: subItems }) }; + if (subItems.length > 0) { + dirItem.subs = subItems; + } items.push(dirItem); } else if (entry.isFile()) { // 处理文件 @@ -195,51 +197,16 @@ export async function generateProjectYaml(rootPath: string, analyzeDirectory?: s // 生成结构 const structure = await analyzeStructure(analyzeDirectory); - // 生成YAML内容 - const generateYamlContent = (obj: any, indent: number = 0): string => { - const spaces = " ".repeat(indent); - let result = ""; - - if (Array.isArray(obj)) { - for (const item of obj) { - result += `${spaces}- ${generateYamlContent(item, 0).trim()}\n`; - } - return result; - } - - if (typeof obj === "object" && obj !== null) { - const entries = Object.entries(obj); - for (let i = 0; i < entries.length; i++) { - const [key, value] = entries[i]; - if (key === "entry" && i === 0) { - result += `entry: "${value}"\n`; - } else if (typeof value === "string") { - result += `${spaces}${key}: "${value}"\n`; - } else if (Array.isArray(value)) { - if (key === "subs") { - result += `${spaces}${key}:\n`; - for (const sub of value) { - result += `${spaces} - ${generateYamlContent(sub, indent + 2).trim()}\n`; - } - } else { - result += `${spaces}${key}:\n${generateYamlContent(value, indent + 1)}`; - } - } else { - result += `${spaces}${key}:\n${generateYamlContent(value, indent + 1)}`; - } - } - return result; - } - - return String(obj); - }; - // 获取最后一级目录名 const dirName = path.basename(analyzeDirectory); - // 构造完整的YAML内容,将最后一级目录作为根entry - console.log("outputPath", outputPath) - const yamlContent = `- entry: "${dirName}"\n subs:\n${generateYamlContent(structure, 2)}`; + const yamlData = [{ + entry: dirName, + subs: structure, + }]; + + // 使用 js-yaml 库生成 YAML 内容 + const yamlContent = yaml.dump(yamlData, { indent: 2 }); // 写入文件 await fs.appendFile(outputPath, yamlContent, "utf-8"); From 57e87f3157c5af9834af7059a7d9047e5f2d46d1 Mon Sep 17 00:00:00 2001 From: liminghu <1037897418@qq.com> Date: Sun, 29 Jun 2025 14:24:28 +0800 Subject: [PATCH 11/12] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0js-yaml?= =?UTF-8?q?=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 6 ++++-- pnpm-lock.yaml | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2452fe9..807b7d2 100644 --- a/package.json +++ b/package.json @@ -29,11 +29,13 @@ "@react-email/components": "^0.0.33", "@react-email/render": "^1.0.5", "@types/gitignore-parser": "^0.0.3", + "@types/js-yaml": "^4.0.9", + "dayjs": "^1.11.13", "gitignore-parser": "^0.0.2", + "js-yaml": "^4.1.0", "minimist": "^1.2.8", "resend": "^4.1.2", - "zod": "^3.24.2", - "dayjs": "^1.11.13" + "zod": "^3.24.2" }, "devDependencies": { "@types/minimist": "^1.2.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a42a28e..6dc32c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,12 +20,18 @@ importers: '@types/gitignore-parser': specifier: ^0.0.3 version: 0.0.3 + '@types/js-yaml': + specifier: ^4.0.9 + version: 4.0.9 dayjs: specifier: ^1.11.13 version: 1.11.13 gitignore-parser: specifier: ^0.0.2 version: 0.0.2 + js-yaml: + specifier: ^4.1.0 + version: 4.1.0 minimist: specifier: ^1.2.8 version: 1.2.8 @@ -207,6 +213,9 @@ packages: '@types/gitignore-parser@0.0.3': resolution: {integrity: sha512-sbdu1sG2pQcwjEYWTsX78OqJo5pKnonwC4FV3m2JeQRE2xYb3q0icHHopCHEvpn4uIBuvWBTpJUCJ76ISK24CA==} + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + '@types/minimist@1.2.5': resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} @@ -248,6 +257,9 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -528,6 +540,10 @@ packages: resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} engines: {node: '>=14'} + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + leac@0.6.0: resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} @@ -987,6 +1003,8 @@ snapshots: '@types/gitignore-parser@0.0.3': {} + '@types/js-yaml@4.0.9': {} + '@types/minimist@1.2.5': {} '@types/node@22.13.10': @@ -1021,6 +1039,8 @@ snapshots: ansi-styles@6.2.1: {} + argparse@2.0.1: {} + balanced-match@1.0.2: {} body-parser@2.1.0: @@ -1329,6 +1349,10 @@ snapshots: js-cookie@3.0.5: {} + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + leac@0.6.0: {} lru-cache@10.4.3: {} From a04e18c61aac2a1154683cd59cfbf8566025f67d Mon Sep 17 00:00:00 2001 From: liminghu <1037897418@qq.com> Date: Sun, 29 Jun 2025 14:25:08 +0800 Subject: [PATCH 12/12] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0initCodelf?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=A2=9E=E5=8A=A0=E5=AF=B9=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E7=BC=96=E7=A0=81=E7=9A=84=E5=A4=84=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=94=A8=E6=88=B7=E6=8F=90=E7=A4=BA=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E5=92=8CYAML=E6=96=87=E4=BB=B6=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tools/initCodelf.ts | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/tools/initCodelf.ts b/src/tools/initCodelf.ts index d556b95..d0d2d71 100644 --- a/src/tools/initCodelf.ts +++ b/src/tools/initCodelf.ts @@ -9,8 +9,9 @@ export const initCodelf = (server: any) => { `Initialize .codelf directory and files. which can help llm better understand your project. Before init .codelf directory and files, you should: - 1. If User provide the analyzeDirectorys, you need to convert to an absolute path to ensure correct function's invocation and push to a Array as an "analyzeDirectorys" before you read and update the yaml fileds. - 2. Tell users the conversion results and the analyzeDirectorys. + 1. When handling rootPath and analyzeDirectorys, if you encounter Percent-encoding (such as "%3A"), please decode it to the original Windows path format (e.g., "D:\code\codelf") to avoid path errors. + 2. If User provide the analyzeDirectorys, you need to convert to an absolute path to ensure correct function's invocation and push to an Array as an "analyzeDirectorys" before you read and update the yaml fileds. + 3. Tell users the conversion results and the analyzeDirectorys. During init .codelf directory and files, you should: If some errors happen, output logs to users. @@ -27,10 +28,8 @@ export const initCodelf = (server: any) => { ), analyzeDirectorys: z.array(z.string()).describe( `Catalog array to be analyzed. Each project is a absolute path String, will be like - "C:/User/name/codeProject" in windows - "/src" in windows - "/usr/name/codeProject/" in macos/linux - "/src" in macos/linux` + "C:/User/name/codeProject" or "/src" in windows + "/usr/name/codeProject/" or "/src" in macos/linux` ), }, async ({ rootPath, analyzeDirectorys }: { rootPath: string, analyzeDirectorys: string[] }) => { @@ -40,11 +39,11 @@ export const initCodelf = (server: any) => { // 检查 .codelf 目录是否存在 const codelfExists = await fs.access(codelfDir).then(() => true).catch(() => false); - + if (codelfExists) { // 检查 yaml 文件是否存在 const yamlExists = await fs.access(projectinfoYaml).then(() => true).catch(() => false); - + if (yamlExists) { return { content: [ @@ -58,7 +57,7 @@ export const initCodelf = (server: any) => { Next steps you should do: 1. every file in .codelf directory is a yaml file, you can read them and update them. 2. You can edit the prokectInfo.yaml file to customize project settings - 3. And then,just need to compare the directory structure and YAML in the actual project base on the ${analyzeDirectorys} + 3. And then, if you want to update the YAML and user has provided the analyzeDirectorys, just need to compare the directory structure and YAML in the actual project base on the ${analyzeDirectorys} 4. Delete the excessive entries and related descriptions in YAML 5. Complete the missing file directory entries and related descriptions 6. You need to organize the indentation format of YAML files @@ -75,18 +74,18 @@ export const initCodelf = (server: any) => { } // 创建 projectInfo.yaml 文件 - await fs.writeFile(projectinfoYaml, "structure/n", "utf-8"); + await fs.writeFile(projectinfoYaml, "structure:\n", "utf-8"); if (analyzeDirectorys?.length) { let i = 0; - while(i < analyzeDirectorys?.length) { - i += 1; + while (i < analyzeDirectorys?.length) { await generateProjectYaml(rootPath, analyzeDirectorys[i]) + i += 1; } } else { await generateProjectYaml(rootPath) } - + const changelogYaml = path.join(codelfDir, "changelog.yaml"); await generateChangelogYaml(changelogYaml)