diff --git a/.ai-memory/MEMORY.md b/.ai-memory/MEMORY.md new file mode 100644 index 0000000..e49bdb4 --- /dev/null +++ b/.ai-memory/MEMORY.md @@ -0,0 +1,4 @@ +- [项目当前进度与待办](project_progress.md) — 三阶段状态、ICP 备案阻塞、出包前必须去掉 IP 覆盖等;**新对话先读这个** +- [生产服务器部署环境](project_server_deploy.md) — ethereal-realm.top 的路径/服务/MySQL/nginx 实况,自建 Git 代码服务器拓扑,及 ICP 拦截、pypi 不可达、MySQL 非 Docker、nginx 双 server 块等坑 +- [CAD 插件微信授权集成](project_cad_wxauth_integration.md) — d:/Ethereal-Realm 的 CadOcr+cadAgent 接入进度、两端分工、先编 CadOcr 的顺序约束、本机只能编 R230+ +- [用户的命令行习惯与协作方式](user_shell_workflow.md) — 用 PuTTY,长命令会被折行截断,sudo 操作要写成脚本交给用户执行 diff --git a/.ai-memory/README.md b/.ai-memory/README.md new file mode 100644 index 0000000..1ad3dd2 --- /dev/null +++ b/.ai-memory/README.md @@ -0,0 +1,47 @@ +# .ai-memory —— AI 助手记忆备份 + +本目录是 CodeBuddy Code 在本项目的**持久记忆备份**。 + +## 它是什么 + +CodeBuddy 的记忆原本存放在**本机用户目录**,不进版本控制: + +``` +C:\Users\<用户名>\.codebuddy\projects\d-wx-scan-authorize\memory\ +``` + +那里存的是「代码和 git 历史里看不出来」的东西:生产服务器实况、踩过的坑、 +协作习惯、当前进度与待办。换一台机器这些就没了,所以在仓库里留一份副本。 + +## 怎么用 + +**换机器 / 新环境恢复:** + +1. `git clone` 本仓库 +2. 对 CodeBuddy 说:「从 `.ai-memory/` 导入记忆」 +3. 助手会把本目录的文件写回本机记忆目录 + +> 注意:记忆目录名由**工作目录路径**派生(`d:\wx-scan-authorize` → +> `d-wx-scan-authorize`)。把仓库克隆到 `d:\wx-scan-authorize` 可保持目录名 +> 一致;放在别的路径也能用,导入时告诉助手实际路径即可。 + +**日常维护:** + +改完记忆后,把本机记忆目录的文件同步回本目录并提交。 +CodeBuddy 每次修改记忆时都会顺手同步,不需要你提醒。 + +## 里面有什么 + +| 文件 | 内容 | +|---|---| +| `MEMORY.md` | 索引,助手每次对话自动加载 | +| `project_progress.md` | 三阶段进度、阻塞项、待办(新对话先读这个) | +| `project_server_deploy.md` | 生产服务器实况与反直觉的坑 | +| `project_cad_wxauth_integration.md` | CAD 插件集成进度与构建约束 | +| `user_shell_workflow.md` | 协作习惯(PuTTY 长命令折行、sudo 走脚本等) | + +## 重要:不要在这里放密钥 + +本目录**会进 Git**。所有密码、token、私钥一律留在服务器的 `.env` 里, +不要写进这些记忆文件。当前文件里只有基础设施信息(IP、域名、用户名), +没有任何凭据。 diff --git a/.ai-memory/project_cad_wxauth_integration.md b/.ai-memory/project_cad_wxauth_integration.md new file mode 100644 index 0000000..82af847 --- /dev/null +++ b/.ai-memory/project_cad_wxauth_integration.md @@ -0,0 +1,43 @@ +--- +name: CAD 插件微信授权集成 +description: d:/Ethereal-Realm 里 CadOcr+cadAgent 接入微信扫码授权的进度、两端分工与构建顺序约束,以及本机只能编 R230/R243+ 工具集的事实 +type: project +--- + +把 `d:/Ethereal-Realm`(SVN 工作区)里的 CAD 插件从「本地时间毒药」授权换成 +服务端(本项目 d:/wx-scan-authorize)微信扫码授权。2026-09-27 完成编码 + +客户端侧端到端验证。 + +- 公众号后台「服务器配置」URL 已改到 Cloudflare 快速隧道,微信真实 + `subscribe`/`SCAN` 推送已打通(见 [生产服务器部署环境](project_server_deploy.md))。 +- **仍需用户本人做**:在 AutoCAD 里加载 `cadAgent.arx`,人工验证二维码对话框 + (我这边没有 AutoCAD)。用户 2026-09-27 表示暂不做。 +- `test_wxauth.exe` 已从控制台程序改造成 **Win32 对话框**:二维码显示区 + + 本地授权状态区 + 「清空令牌」「模拟新机器」两个按钮。为此 DLL 新增导出 + 序号 **44/45**(`ocr_wxAuthGetLocalState` / `ocr_wxAuthClearLocalState`)。 + +**分工**(用户拍板) +- `CadOcr`(DLL,无 MFC):全部网络 + 注册表 + 授权状态机(`src/wxAuth.cpp`)。 +- `cadAgent`(ARX,MFC):只做 UI(`WxAuthDlg.cpp`)。 +- 新增导出序号 **40~43**(`ocr_wxAuthBegin/Poll/Reset/FreeBuffer`),原有 2/5/13/25 + 一律不动。宿主侧 `ocr_check` 仍导出在序号 20,握手保留、时间毒药删除。 + +**关键约束** +- **必须先编 CadOcr 再编 cadAgent**:`CadOcr/include/*.h` 靠 PostBuildCopy 同步到 + `CADProject/dep_inc/cadOcr/`,顺序反了会用到旧头文件。 +- 服务端基址是编译期常量 `CADOCR_WX_BASE_URL`(默认域名),联调时用 + `cmake --preset msvc-ocr -DCADOCR_WX_BASE_URL=http://39.107.55.179` 覆盖。 + 刻意不做运行时覆盖,避免被指向假服务端绕过授权。 +- 本机 VS18 只装了 MSVC 14.16/14.29/14.42/14.44/14.51,**没有 v100(VS2010)、 + v140(VS2015)**,所以 `R190`/`R220` 这两个 preset 编不出来(`CADProject/out/R190` + 里连 vcxproj 都没生成)。实际能编的是 **R230/R243/R250/R260**;`build.bat` + 里写的 R220 会失败。 +- CadOcr 的 `test/test_wxauth.exe` 是独立验证程序,不装 AutoCAD 就能跑通整条链路。 + 它靠 `#pragma comment(linker, "/EXPORT:ocr_check,@20,NONAME")` 导出握手函数 + —— CMake 只对库目标识别 `.def`,对可执行目标会当普通文件忽略。 + +**Why:** 这些跨了两个工作区的耦合约束(构建顺序、共享头同步、导出序号、 +工具集可用性)从任一端的代码里都看不出来,踩了会很难查。 + +**How to apply:** 改 CadOcr 的公共头后务必先编 CadOcr;给用户的构建命令别用 +R190/R220;讨论授权链路时记得 UI 在 cadAgent、网络在 CadOcr。 diff --git a/.ai-memory/project_progress.md b/.ai-memory/project_progress.md new file mode 100644 index 0000000..c66049a --- /dev/null +++ b/.ai-memory/project_progress.md @@ -0,0 +1,44 @@ +--- +name: 项目当前进度与待办 +description: wx-scan-authorize 三阶段状态快照、当前阻塞项与下一步,用于跨对话续接——新对话应先读本文件 +type: project +--- + +`d:/wx-scan-authorize`(微信公众号扫码授权服务)截至 **2026-09-27 晚** 的状态。 + +**三阶段进度** + +| 阶段 | 内容 | 状态 | +|---|---|---| +| 1 | 扫码授权(`/wechat` 事件 + `/auth/*` + 首次关注送 7 天) | 已上线,微信真实推送已打通 | +| 2 | 使用扣减(`/usage/consume` + session_token + 节流记账) | 已上线 | +| 3 | 充值(微信公众号支付) | **未开始** | +| 附加 | 只读管理后台 `/admin`(计划外新增) | 2026-09-27 已上线 | + +代码状态:本地与服务器均在 `f167550`,工作区干净,远端是自建 Git 服务器 +(见 [生产服务器部署环境](project_server_deploy.md))。 + +客户端在 `d:/Ethereal-Realm`:CadOcr + cadAgent 已完成编码与客户端侧端到端验证, +`test_wxauth.exe` 已从控制台程序改造成 Win32 对话框。详见 +[CAD 插件微信授权集成](project_cad_wxauth_integration.md)。 + +**阻塞项与待办** + +1. **ICP 备案进行中**(用户 2026-09-27 已提交,等待通过)——当前最大的外部变量: + - 通过前:公众号后台「服务器配置」URL 指向 Cloudflare 快速隧道 + (URL 每次重启会变,捞取命令见 server_deploy 记忆) + - 通过后:切回 `http://ethereal-realm.top/wechat`,并撤掉隧道 +2. **阶段 3 充值暂缓**。用户原话:「阶段3现放着吧,因为微信公众号想要收费的话, + 需要营业执照,现在域名的备案也没有做好。」→ 等营业执照 + 备案。 +3. **正式出包前必须去掉编译覆盖** `-DCADOCR_WX_BASE_URL=http://39.107.55.179` + ——当前本地 DLL 是 IP 联调版,把服务端地址写死成了 IP。 +4. **AutoCAD 里人工验证 `cadAgent.arx`** ——我这边没有 AutoCAD,只能用户做。 + 用户 2026-09-27 明确表示暂不做。 +5. **IP 直连暴露 `/docs` 与 `/openapi.json`** ——用户选择暂不处理(详见 + server_deploy 记忆的 nginx 双 server 块章节)。 + +**Why:** 会话内的任务列表不跨对话持久化,关闭对话后这些状态不会自动带过去; +只有写进记忆才能在新对话里续上。用户 2026-09-27 明确问过「关掉对话后怎么重新开始」。 + +**How to apply:** 新对话开始时先读本文件确认进度。**动手前先问用户 ICP 备案是否 +已通过**——它决定公众号 URL 该配隧道还是配域名,是当前唯一会改变操作方式的变量。 diff --git a/.ai-memory/project_server_deploy.md b/.ai-memory/project_server_deploy.md new file mode 100644 index 0000000..7610646 --- /dev/null +++ b/.ai-memory/project_server_deploy.md @@ -0,0 +1,143 @@ +--- +name: 生产服务器部署环境 +description: ethereal-realm.top 的部署拓扑、自建 Git 代码服务器(47.93.46.28:1216,已弃用 GitHub)、MySQL/nginx 实际配置与几个反直觉的坑(pypi 不可达、MySQL 非 Docker、skip_name_resolve、nginx 只代理部分路径) +type: project +--- + +生产服务器 `deploy@ethereal-realm.top`(Alibaba Cloud Linux 3,公网 `39.107.55.179`,内网 `172.17.151.49`)的实际环境,与 REQUIREMENTS.md 的描述有出入。 + +**部署拓扑** +- 应用代码:`/home/deploy/app`(git 仓库,remote 指向自建 Git 服务器,见下) +- 虚拟环境:`/home/deploy/venv`(Python 3.11),**不是** 项目目录下的 venv +- 服务:systemd `wechat-api.service`(`/etc/systemd/system/`),`ExecStart=/home/deploy/venv/bin/python /home/deploy/app/run_server.py`,`Restart=always`,监听 `127.0.0.1:8000` +- 入口:systemd `cloudflared-quick.service`(`cloudflared tunnel --url http://127.0.0.1:80`, + 快速隧道,无配置文件、无路径限制,`Restart=always`,`User=deploy`)→ nginx:80 → 应用。 + **注意服务名是 `cloudflared-quick` 不是 `cloudflared`**——查 journal 时用错名字会显示空,误判成裸进程。 +- nginx 配置:`/etc/nginx/conf.d/wechat-api.conf`,`server_name ethereal-realm.top` +- `deploy` 在 `wheel` 组,`sudo` 需要密码 → 涉及 sudo 的操作必须让用户手动执行 + +**代码仓库拓扑(2026-09-27 起)** + +用户自建了一台 Linux 代码服务器 `gjm@47.93.46.28:1216`(CentOS 7.2,git 1.8.3.1), +裸仓库在 `/srv/git/wx-scan-authorize.git`(owner `gjm`)。 + +``` +本地 d:/wx-scan-authorize ──push──> 代码服务器 47.93.46.28 (唯一远端) ──pull──> 应用服务器 ~/app +``` + +- 本地:只有一个远端 `origin` = 代码服务器。`git push` / `git pull` 不带参数即可。 +- 应用服务器:`origin`=`ssh://gjm@47.93.46.28:1216/srv/git/wx-scan-authorize.git`, + 直接 `git pull`。两端读写都已验证。 +- **GitHub 已弃用**(2026-09-27 用户决定):国内访问不稳定,不再作备份。本地 remote + 已删除,`origin` 这个名字改指代码服务器。GitHub 上那个仓库(`LittleGuo/wx-scan-authorize`) + 是否删除由用户决定。 +- 代码服务器上 `gjm` **没有免密 sudo**,涉及 root 的操作(装软件等)要让用户做。 +- 该裸仓库 `HEAD` 曾误指 `refs/heads/master`(`git init --bare` 默认值),已改为 + `refs/heads/main`。以后新建裸仓库记得顺手改,否则 clone 会拿到空工作区。 + +**域名被阿里云 ICP 拦截(2026-09-27 确认)** + +> 用户已于 2026-09-27 提交 ICP 备案,预计不久下来。因此**决定不搞 Cloudflare 命名隧道** +> (那需要账号 + 把 ethereal-realm.top 的 NS 改到 Cloudflare,代价不值)。隧道只是过渡, +> 备案通过后应把公众号后台 URL 改回 `http://ethereal-realm.top/wechat` 并撤掉隧道。 + +`http://ethereal-realm.top` 在公网访问会返回阿里云的拦截页 +(403 `Non-compliance ICP Filing`),因为备案还没做。**直连公网 IP +`http://39.107.55.179` 一切正常**(nginx 的 `/auth/`、`/usage/` 都能打到应用)。 +影响面: + +- 微信服务器的回调推送也进不来 —— 公众号后台「服务器配置」URL 若指向该域名, + `subscribe`/`SCAN` 事件永远到不了应用(journal 里完全没有 `/wechat` POST)。 +- 可用的公网入口是 Cloudflare **快速隧道**(`cloudflared-quick.service`,已 enabled + + `Restart=always`,所以 SSH 断开/进程崩溃/服务器重启都能自动拉起)。隧道 URL 形如 + `https://<随机词>-<随机词>-<随机词>-<随机词>.trycloudflare.com`,**每次重启都会变** + (这是快速隧道的固有行为,不是配置问题),捞当前值: + `journalctl -u cloudflared-quick | grep -oE "https://[a-z0-9-]+\.trycloudflare\.com" | tail -1`。 + 公众号后台的服务器配置要填 `<隧道URL>/wechat`。若推送突然不来了,先查这个 URL 是否变过。 + +**微信真实推送已打通(2026-09-27 19:12 验证)** + +绕开 ICP 的办法:把测试号后台的服务器配置 URL 指向 Cloudflare 快速隧道, +`https://catering-thumbnails-pirates-workstation.trycloudflare.com/wechat`(Token 不变)。 +微信随即发来 GET 校验(来源 `162.62.81.123`,腾讯云网段),签名复算一致、返回 200, +后台配置成功。之后真实扫码产生了第一条**非模拟**推送: + +``` +162.62.80.57 - "POST /wechat?signature=...×tamp=...&nonce=...&openid=ohve625WiuQVosBDGLVwxGT68S4c" 200 OK +``` + +整条链路结果(库内):新建 `users.id=2`(真实 openid `ohve625WiuQVosBDGLVwxGT68S4c`), +发 7 天免费时间授权(`authorizations.id=2`,`2026-09-27 19:12:43 → 2026-10-04 19:12:43`), +`auth_scenes.id=9` 置 authorized,`usage_logs.id=2` 记账一条。 + +- **微信推送 URL 里会带一个 `openid=` 查询参数**(官方文档没写)。我们忽略它—— + `verify_signature()` 只用 token/timestamp/nonce,业务用 XML 正文的 `FromUserName`。 + 已确认代码里没有任何地方往 query 拼 openid,是微信自己加的。 +- 隧道**已经是 systemd 服务** `cloudflared-quick.service`(enabled + `Restart=always`), + 服务器重启/进程崩溃都会自动拉起,公众号后台配置不会因进程死掉而失效。 + 但快速隧道 URL **每次重启仍会变**——所以重启后仍要重新捞 URL 并回填公众号后台。 +- 库里的早期手工测试数据(`users.id=1 / openid='oTEST_CLIENT_01'`,其 + `authorizations.id=1` 的 `end_at` 早于 `start_at`)**已于 2026-09-27 20:19 清理干净**, + 连同 9 条无归属的过期测试场景。清理前整库备份在 + `/home/deploy/backup-cleanup-20260927-201903.sql`(11 KB)。 + 现在库里只剩真实数据:`users.id=2`(openid `ohve625WiuQVosBDGLVwxGT68S4c`)。 +- **`end_at` 倒挂不是 bug**(这条结论仍然成立):生产代码路径 + (`_grant_free_authorization`)在同一条 INSERT 里取 `NOW()` 算 start/end,不可能倒挂。 +- **清理用户数据时注意 `auth_scenes` 的删除规则是 `SET NULL` 不是 `CASCADE`** + (`authorizations`/`usage_logs`/`sessions` 才是 CASCADE)。直接删 `users` 会在 + `auth_scenes` 留下一条 `user_id=NULL` 的孤儿记录,状态可能还是 `authorized`—— + 客户端轮询时可能读到。**正确顺序是先删该用户的 `auth_scenes`,再删 `users`**。 + +**几个反直觉的坑** + +1. **服务器连不上 pypi.org**(超时),但阿里云镜像正常(0.06s)。装包必须加 + `-i https://mirrors.aliyun.com/pypi/simple/`,否则 pip 会长时间挂住。 + +2. **MySQL 是原生安装的 8.0.46,不是 REQUIREMENTS 里写的 Docker**。且 + `skip_name_resolve=ON` + `bind_address=127.0.0.1`,导致 `root@localhost` + 匹配不了 TCP 连接(报 `ERROR 1130 Host '127.0.0.1' is not allowed`)。 + 应用用专用账号 `wechat@127.0.0.1`(只授权 `wechat_api.*`),凭据在服务器 `.env` 里。 + 注意:本机 Windows 的 `.env` 仍是 `MYSQL_USER=root`,连的是本机 MySQL,两份不一样。 + +3. **nginx 有两个 server 块,访问行为取决于 Host 头**(2026-09-27 发现,很反直觉): + + | 入口 | 匹配的 server 块 | 行为 | + |---|---|---| + | `Host: ethereal-realm.top` | `wechat-api.conf` | **精确白名单**:只放行 `/wechat`、`/auth/`、`/usage/`、`/admin`,其余落到 `location /` 的占位响应 `return 200 'wechat api ok'` | + | `Host: 39.107.55.179`(IP 直连)| `app.conf` | **全量代理**:`listen 80 default_server` + `location / { proxy_pass http://fastapi; }`,所有路径都转发到 8000 | + + `app.conf` 是 Sep 25 就存在的早期配置(不是本次部署引入)。后果: + - **IP 直连会暴露 FastAPI 的 `/docs` 与 `/openapi.json`**(实测 200 + 真实 schema); + 域名路径下这两个路径返回占位文本,不暴露。 + - MFC 插件当前 `-DCADOCR_WX_BASE_URL=http://39.107.55.179` 正是依赖 IP 直连, + 所以**不能简单删掉 `app.conf`**(删了 IP 访问会落到 nginx.conf 内置的静态 + server,`/auth/`、`/usage/` 全部 404)。要收紧的话,应把 `wechat-api.conf` + 改成 `listen 80 default_server` 并删 `app.conf`,而不是单独删。 + + - 新增对外接口必须**同时**在 `wechat-api.conf` 加 `location` 块(给域名路径), + 否则备案后走域名会落到占位响应。 + - 改 nginx 的流程见 `user_shell_workflow.md`:**写成脚本 + `sudo bash ~/xxx.sh`**, + 不要给内联长命令。 + - **`/docs` 暴露问题用户 2026-09-27 决定暂不处理**(原话选了"暂不处理"):判断是 + 风险低(只泄露接口结构、不含数据,`/admin` 另有强密码),等 ICP 备案通过、插件 + 切回域名后再一并处理。**不要再自作主张去关 docs 或删 `app.conf`**——要动先问。 + + **`/admin` 已于 2026-09-27 20:13 上线**:`wechat-api.conf` 新增 `location /admin` + 块(显式透传 `Authorization` 头),应用侧 `ADMIN_USER=admin`、密码为 24 位随机串 + 存于服务器 `.env`(用户已自行保存)。`/admin` 需重启应用才生效——`ADMIN_PASSWORD` + 是进程启动时读入的模块级常量。 + +4. **GitHub 已弃用,不要再往那儿推**(2026-09-27 用户决定)。服务器到 github.com 的连接 + 本就间歇性失败(`git pull` 报 `Empty reply from server` 或超时;`curl -sI` 探到 200 + 也**不代表** git 协议可用,实测出现过 curl 200 而 pull 仍失败)。国内访问不稳定, + 用户明确不要 GitHub 备份了。所有代码走自建 Git 服务器。 + 若哪天需要从 GitHub 临时取东西(例如查旧仓库),仍可用 bundle 绕: + 本地 `git bundle create /tmp/x.bundle main`, + `ssh deploy@... 'cat > /tmp/x.bundle' < /tmp/x.bundle`,服务器上 + `git fetch /tmp/x.bundle main && git merge --ff-only FETCH_HEAD`。 + +**Why:** 这些都是服务器实际状态,仓库代码和 git 历史里看不出来;REQUIREMENTS.md 的 +技术栈描述(Docker MySQL)与实际不符,照着文档操作会踩坑。 + +**How to apply:** 涉及服务器操作时,直接用上面的真实路径/账号/镜像;给用户的命令要 +考虑他需要手动 sudo;新增对外路由时提醒同步改 nginx。 diff --git a/.ai-memory/user_shell_workflow.md b/.ai-memory/user_shell_workflow.md new file mode 100644 index 0000000..4076b07 --- /dev/null +++ b/.ai-memory/user_shell_workflow.md @@ -0,0 +1,26 @@ +--- +name: 用户的命令行习惯与协作方式 +description: 用户用 PuTTY 操作服务器,对多行粘贴/heredoc 不熟;偏好自己执行 sudo 操作,重要改动要先确认 +type: user +--- + +用户通过 **PuTTY** 连接服务器,对 Linux shell 的细节不算熟练。 + +**表现** +- 我给的 `sudo tee ... <<'EOF'` 多行 heredoc 命令,粘贴到 PuTTY 后卡在 `>` 二级提示符 + 没能执行(用户当时不确定自己是否改好了)。 +- **长单行命令也会被截断**(2026-09-27 实测):一条约 200 字符的 + `sudo cp A B && sudo cp C D && sudo nginx -t && ...` 粘贴后被 PuTTY 折成三行, + 前两行报 `cp: missing destination file operand`,第三行把配置文件路径当命令执行 + 报 `Permission denied`。**"单行"不等于安全**——长度也是风险。 +- 涉及 `.env` 这类含密钥的文件,用户会拒绝我直接编辑。 + +**How to apply:** +- 需要用户 sudo 时,**不要给内联命令**(无论单行还是多行)。正确做法: + 1. 我把脚本写到服务器 `~/xxx.sh`(用 `ssh ... 'cat > ~/xxx.sh' < 本地文件`,我有写权限); + 2. 让用户只执行 `sudo bash ~/xxx.sh` —— 短到不可能折行; + 3. 脚本里自带备份与失败回滚,并 `echo` 每一步进度,方便用户贴回显。 +- 若确实要给单行命令,控制在 **60 字符以内**,或拆成多条分别给。 +- 涉及 `sudo` 的操作(重启服务、改 nginx 配置等)用户需要自己执行——我没有密码, + 非交互 `sudo -n` 会失败。把命令准备好交给用户,并附上回滚方案。 +- 改动 `.env`、nginx 配置、重启服务这类动作,先说明清楚再让用户确认。 diff --git a/CODEBUDDY.md b/CODEBUDDY.md index 05f0543..d607654 100644 --- a/CODEBUDDY.md +++ b/CODEBUDDY.md @@ -83,6 +83,15 @@ sql/schema.sql # 建表脚本(5 张表,含索引与外键) - 外键:删除 user 会级联删除其 authorizations / usage_logs / sessions。 - 场景过期、授权到期/耗尽的惰性标记在读取时完成,没有后台定时任务。 +## AI 记忆备份(.ai-memory/) + +`.ai-memory/` 是 CodeBuddy 持久记忆的仓库副本——记忆本体存在本机 +`~/.codebuddy/projects/<工作目录名>/memory/`,不进版本控制。 + +- **改完记忆必须同步**:把记忆目录的文件复制到 `.ai-memory/` 并提交,否则换机器会丢。 +- 该目录**会进 Git**,因此**绝不可写入密码、token、私钥**——那些一律留在服务器 `.env`。 +- 新环境恢复:clone 后让助手「从 `.ai-memory/` 导入记忆」。 + ## 约定 - 代码注释与文档字符串使用中文。