☰
用Claude打造能自我进化代码的本地App:Muse实战
2026/10/8 10:12:56 网站建设 项目流程

1. 从一句聊天指令到会自我进化的应用:这个项目到底在做什么

周末花了两天时间,我用 Claude 搭了一个能自己改自己代码的本地 App,取名叫 Muse。核心体验就一句话:打开聊天窗口,输入你想要的功能,它自己写代码、自己编译、自己热更新,下次打开就多了一个新模块。整个过程不需要我打开 IDE,不需要手动改一行代码。

这个项目的本质,是把Claude Code这类具备文件读写和命令执行能力的 agent,和一个本地 App 的运行时环境缝合在一起。用户输入自然语言需求,agent 解析意图后直接操作项目源码目录,修改代码、运行构建脚本、触发应用重载。App 本身是一个壳,真正的“进化引擎”是背后那套 agent 工作流。

适合谁来参考?如果你已经用过 Claude Code 或者类似的 agent 工具,想把它从“写代码的助手”升级成“应用的一部分”,这个思路可以直接抄。如果你只是听说过 agent 但没动手做过,建议先把 Claude Code 在本地跑通,理解它的工具调用机制再来看这篇。前端、全栈、独立开发者都能从中拿到可复用的模式,尤其是那些想做“个人专属工具”但懒得每次手动开发的人。

我踩过的最大坑是:一开始把 agent 和应用运行时放在同一个进程里,结果 agent 改代码时把正在运行的应用搞崩了,热更新直接变成冷重启。后来拆成两个独立进程,通过文件系统和 IPC 通信,才稳定下来。这个教训后面会详细展开。

2. 整体架构设计:为什么选择本地双进程 + 文件监听

2.1 核心思路:把 App 当成 agent 的工作区

传统 App 开发是“人写代码 → 构建 → 运行”,这个项目把它倒过来:“人提需求 → agent 写代码 → 构建 → 运行”。App 本身只是一个展示层和功能容器,真正的逻辑都在源码目录里,agent 有权限读写这个目录。

我选择本地运行而不是云端,原因有三个。第一,延迟低,agent 改完代码立刻就能看到效果,不需要等部署。第二,隐私可控,所有代码和数据都在自己机器上。第三,调试方便,出问题直接看本地日志和文件变化,不用去翻云函数日志。

架构上分成三层:交互层(聊天窗口)、agent 层(Claude Code 进程)、应用层(实际运行的 App)。交互层把用户输入传给 agent 层,agent 层操作应用层的源码目录,应用层通过文件监听感知变化并重载。

2.2 为什么用 Claude Code 而不是自己调 API

自己调 Claude API 当然可以,但你要自己实现文件读写、命令执行、多轮对话管理、错误重试这些基础设施。Claude Code 已经把这些封装好了,它内置了 Read、Write、Edit、Bash 等工具,agent 可以直接操作文件系统。你只需要给它一个工作目录和一句指令,它就能完成“读代码 → 改代码 → 运行测试”的完整闭环。

另一个原因是 Claude Code 支持 MCP(Model Context Protocol),可以挂载自定义工具。比如我给它加了一个reload_app工具,agent 改完代码后可以主动触发应用重载,而不是被动等文件监听。这个能力在自建 API 方案里要自己实现,工作量不小。

注意:Claude Code 默认会在当前工作目录下操作,一定要把它限制在项目源码目录内,避免误改系统文件。我是在启动时通过--cwd参数指定工作目录,并且在 agent 的 prompt 里明确写了“只能修改 src 目录下的文件”。

2.3 文件监听方案选型:chokidar vs fs.watch

应用层需要感知源码变化并重载。Node.js 原生有fs.watch,但它跨平台行为不一致,macOS 上偶尔会丢事件,Linux 上递归监听支持不好。我最后选了chokidar,它封装了底层差异,支持递归监听、防抖、忽略规则,稳定性好很多。

配置上关键就几个参数:ignoreInitial: true避免启动时触发一堆事件,awaitWriteFinish设置 200ms 稳定期,防止 agent 写文件写到一半就触发重载。实测下来,agent 连续修改多个文件时,chokidar 会把它们合并成一次重载,体验很顺。

const watcher = chokidar.watch('./src', { ignored: /node_modules/, persistent: true, ignoreInitial: true, awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 } }); watcher.on('all', (event, path) => { console.log(`[watcher] ${event} ${path}`); scheduleReload(); });

scheduleReload里再加一层 300ms 防抖,确保连续变更只触发一次重载。这个双层防抖是我试出来的,单靠 chokidar 的awaitWriteFinish在大量文件同时变更时还是会有多次触发。

3. 核心细节拆解:agent 如何理解需求并改代码

3.1 需求解析:从自然语言到文件操作

用户输入“给我加一个深色模式切换按钮”,agent 需要做几件事:找到 UI 组件目录、确定用哪个框架的组件写法、修改对应的文件、可能还要加样式和状态管理。Claude Code 的做法是先读项目结构,再读相关文件,然后生成修改方案。

这里的关键是prompt 设计。我在系统 prompt 里写了几条硬规则:先读后写、每次只改一个功能、改完必须运行构建命令验证、失败要回滚。这些规则不是随便写的,是踩坑踩出来的。有一次 agent 一口气改了五个文件,结果构建失败,它自己也不知道哪里错了,最后我手动回滚了整个目录。

你是一个应用代码修改助手。工作目录是 ./src。 规则: 1. 修改前必须先读取目标文件,理解现有代码结构。 2. 每次只实现一个功能,不要批量修改。 3. 修改后运行 `npm run build` 验证,失败则回滚本次修改。 4. 只能修改 ./src 下的文件,禁止操作其他目录。 5. 如果需求不明确,先问清楚再动手。

这套规则让 agent 的成功率从大概六成提升到九成以上。尤其是“先读后写”这条,能避免 agent 凭想象改代码,改出来的东西跟现有风格完全对不上。

3.2 代码生成:模板 + 约束比自由发挥更稳

完全让 agent 自由生成代码,风格会飘。今天用函数组件,明天用类组件,后天又换个状态管理库。我的做法是给项目定一套模板和约束,在 prompt 里明确写出来。

比如 UI 组件必须用函数式写法,状态用useState,样式用 CSS Modules,文件命名用 PascalCase。这些约束写进 prompt 后,agent 生成的代码风格就统一了。我还在项目里放了一个COMPONENT_TEMPLATE.tsx,让 agent 参考这个模板来写新组件。

// COMPONENT_TEMPLATE.tsx import styles from './ComponentName.module.css'; interface ComponentNameProps { // props 定义 } export function ComponentName({}: ComponentNameProps) { return ( <div className={styles.container}> {/* 内容 */} </div> ); }

实测下来,给了模板之后,agent 生成的组件一次通过率明显提高,因为结构、导入、导出方式都是它见过的模式,不需要自己发明。

3.3 构建与热更新:让改动立刻可见

agent 改完代码后,需要触发构建。我用的是 Vite,它的 HMR(热模块替换)本身就很快,但 agent 修改文件后 HMR 不一定能正确捕获,尤其是新增文件的情况。所以我加了一个显式的构建触发:agent 改完代码后调用npm run build,构建成功后通过 IPC 通知应用层重载。

重载策略分两种:热重载和冷重启。热重载只刷新改动的模块,速度快但有时状态会丢;冷重启整个应用,慢一点但状态干净。我的策略是:如果改动只涉及样式或单个组件,走热重载;如果涉及路由、全局状态或新增依赖,走冷重启。判断逻辑放在 agent 的 prompt 里,让它根据改动范围选择。

实操心得:冷重启时一定要先杀掉旧进程再启动新进程,否则端口占用会导致启动失败。我一开始没做进程管理,重启几次后端口就被占满了,后来加了一个killPort函数,重启前先清理端口。

4. 实操过程:从零搭一个会进化的本地 App

4.1 环境准备与依赖安装

先确保本地有 Node.js 18+ 和 npm。然后安装 Claude Code,官方推荐用 npm 全局安装。安装完成后运行claude命令,按提示完成登录授权。这一步网络环境要正常,否则授权会失败。

node -v # 确认 >= 18 npm install -g @anthropic-ai/claude-code claude --version # 确认安装成功

接着创建项目目录,初始化一个 Vite + React + TypeScript 的项目。这个组合构建快、HMR 稳定、类型检查能帮 agent 提前发现错误。

npm create vite@latest muse-app -- --template react-ts cd muse-app npm install npm install chokidar

项目结构大概是这样:src/放源码,agent/放 agent 相关脚本,scripts/放构建和重载脚本。agent 的工作目录限定在src/,其他目录它碰不到。

4.2 agent 启动脚本与工作目录配置

写一个agent/start.js,负责启动 Claude Code 进程并传入工作目录和系统 prompt。这里用child_process.spawn启动,把 stdout 和 stderr 转发到主进程,方便调试。

const { spawn } = require('child_process'); const path = require('path'); const SRC_DIR = path.resolve(__dirname, '../src'); const SYSTEM_PROMPT = `你是一个应用代码修改助手...`; // 前面写的规则 const agent = spawn('claude', ['--cwd', SRC_DIR, '--prompt', SYSTEM_PROMPT], { stdio: ['pipe', 'pipe', 'pipe'] }); agent.stdout.on('data', (data) => { process.stdout.write(`[agent] ${data}`); }); agent.stderr.on('data', (data) => { process.stderr.write(`[agent:err] ${data}`); }); module.exports = { agent };

启动后,agent 就待命了。用户输入通过agent.stdin.write()传进去,agent 的输出通过 stdout 读出来展示在聊天窗口。

4.3 聊天窗口与 agent 的通信实现

聊天窗口用 React 写,一个输入框加一个消息列表。用户输入后,通过 IPC 发给主进程,主进程转发给 agent 进程。agent 的回复实时推回渲染进程,展示在消息列表里。

function ChatWindow() { const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const sendMessage = async () => { setMessages(prev => [...prev, { role: 'user', content: input }]); const reply = await window.electron.invoke('agent:send', input); setMessages(prev => [...prev, { role: 'agent', content: reply }]); setInput(''); }; return ( <div className="chat"> {messages.map((m, i) => ( <div key={i} className={`msg ${m.role}`}>{m.content}</div> ))} <input value={input} onChange={e => setInput(e.target.value)} /> <button onClick={sendMessage}>发送</button> </div> ); }

这里用的是 Electron 的 IPC 通道,如果你用 Tauri 或其他框架,换成对应的通信方式即可。核心是保持 agent 进程和 UI 进程分离,UI 只负责展示和转发,不直接操作文件。

4.4 文件监听与自动重载的完整配置

前面提过用 chokidar 监听src/目录。完整配置还要加上重载逻辑:监听事件触发后,先判断改动类型,再决定热重载还是冷重启。

let reloadTimer = null; function scheduleReload() { clearTimeout(reloadTimer); reloadTimer = setTimeout(async () => { const changedFiles = getChangedFiles(); // 从 watcher 事件里收集 const needsColdRestart = changedFiles.some(f => f.includes('router') || f.includes('store') || f.endsWith('package.json') ); if (needsColdRestart) { await killPort(5173); await startApp(); } else { await triggerHMR(); } }, 300); }

killPort用lsof -ti:5173 | xargs kill -9实现,startApp用spawn启动 Vite。这套逻辑跑通后,agent 改完代码大概 2 到 3 秒就能看到界面变化,体验很流畅。

注意:文件监听要忽略node_modules和构建产物目录,否则构建时会产生大量事件,导致无限重载循环。我一开始没忽略dist,结果构建触发监听、监听触发重载、重载又触发构建,死循环了。

5. 常见问题与排查技巧实录

5.1 agent 改代码后应用崩溃怎么办

最常见的原因是 agent 生成的代码有语法错误或类型错误。排查步骤:先看构建日志,Vite 会报具体哪个文件哪一行出错;再看 agent 的修改记录,对比改动前后的差异;最后手动回滚到上一个可用版本。

预防措施是在 prompt 里强制要求 agent 改完后运行npm run build,构建失败就自己回滚。我还在项目里加了 git 自动提交,每次 agent 修改前先 commit 一个快照,出问题直接git reset --hard回滚。

# agent 修改前的快照 git add -A && git commit -m "snapshot before agent edit"

5.2 热更新不生效的几种情况

热更新失效通常有三个原因:文件监听没触发、HMR 客户端断连、改动类型不支持热更新。排查时先看 watcher 日志有没有输出,再看浏览器控制台的 HMR 连接状态,最后确认改动是否涉及需要冷重启的模块。

我遇到过一次 watcher 没触发,原因是 agent 用fs.writeFileSync写文件时,chokidar 的awaitWriteFinish还没稳定就触发了事件,导致重载时读到的是半截文件。后来把stabilityThreshold从 100ms 调到 200ms 就解决了。

5.3 agent 理解错需求怎么纠正

agent 理解错需求时,不要直接说“你错了”,而是给它更具体的上下文。比如它把按钮加到了错误的位置,你可以说“按钮应该放在顶部导航栏右侧,参考 Header.tsx 里现有按钮的写法”。给它一个具体的参考文件,比抽象描述有效得多。

我总结了一个纠正话术模板:指出问题 + 给出参考 + 明确期望。比如“深色模式切换按钮现在在页面底部,应该移到顶部导航栏,参考 Header.tsx 里主题按钮的位置和样式”。这样 agent 能快速定位并修正。

5.4 常见问题速查表

问题现象可能原因排查方法解决方案
应用启动后白屏agent 生成的代码有运行时错误看浏览器控制台报错回滚到上一个快照
热更新不触发watcher 未监听或防抖配置不当看 watcher 日志调整 awaitWriteFinish 参数
端口被占用旧进程未退出lsof -ti:5173重启前先 killPort
agent 无响应进程卡死或网络问题看 agent stderr重启 agent 进程
构建失败类型错误或依赖缺失看构建日志让 agent 修复或手动回滚
重载循环监听了构建产物目录看 watcher 事件频率忽略 dist 和 node_modules

5.5 几个提升稳定性的独家技巧

第一个技巧是给 agent 加超时。agent 有时候会陷入死循环,一直读文件不改代码。我在启动脚本里加了 60 秒超时,超时后自动终止并返回错误信息。

第二个技巧是限制单次修改的文件数量。在 prompt 里写“每次最多修改 3 个文件”,超过就让它分步执行。这样出问题时影响范围可控,回滚也容易。

第三个技巧是保留 agent 的操作日志。每次 agent 的输入输出都写到agent/logs/下,按日期分文件。出问题时翻日志比凭记忆靠谱得多,也能用来分析 agent 的行为模式,优化 prompt。

第四个技巧是用 git 分支隔离实验性修改。agent 要加新功能时,先切一个新分支,改完验证通过再合并回主分支。这样主分支始终是可用的,不会因为 agent 的实验性修改而崩溃。

6. 这个模式还能怎么扩展

把 agent 嵌入应用运行时这个思路,能玩的花样比想象中多。我现在跑通的是“聊天改代码”这一条路径,但同样的架构可以支持更多场景。

比如定时自进化:加一个 cron 任务,每天凌晨让 agent 根据用户行为日志自动优化 UI。用户经常点某个按钮但找不到,agent 就把它移到更显眼的位置。这个需要给 agent 加一个日志分析工具,让它能读取用户行为数据。

再比如多 agent 协作:一个 agent 负责写代码,另一个负责审查,第三个负责测试。三个 agent 通过文件系统交换产物,形成一条自动化流水线。这个模式在复杂功能开发上比单 agent 更稳,因为审查 agent 能发现写代码 agent 忽略的问题。

还有跨应用复用:把这套 agent 工作流抽成一个独立的服务,任何本地应用都可以接入。你做一个笔记应用、一个记账应用、一个健身打卡应用,都共用同一个进化引擎。这样你只需要维护一套 agent 逻辑,应用本身只负责 UI 和业务数据。

我目前正在尝试的是agent 记忆持久化。让 agent 记住之前做过哪些修改、用户偏好是什么、哪些方案被否决过。这样它下次改代码时能参考历史决策,不会重复犯同样的错误。实现方式是用一个 JSON 文件存记忆,agent 每次启动时读取,修改后写回。这个还在调试中,稳定了再单独写一篇。

最后分享一个我在实际使用中的体会:agent 改代码的能力很强,但它的“审美”和“产品感”需要你来引导。同样的需求,你给的约束越具体、参考越明确,它产出的质量越高。不要指望一句话就能让它做出完美功能,把它当成一个执行力很强但需要明确指令的初级工程师,你的 prompt 就是你的管理能力。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询