huiwings 的目录、域名,以及 Agent 怎么推代码看 CI
最近把 huiwings 的目录又对了一遍,在这里记录一下。开发、部署、测试都在 Agent 里做。人要看清的是代码在哪一个 git 根,push 到哪,以及哪一条 CI 真的会跑。
角色从写代码转到定边界,之前记过12。认知上的六条见《AI 辅助编程中沉淀的六个核心洞见》3。下面是现在磁盘上的布局和流水线。
一、这台机器上的目录
工作区叫 huiwings。以前的笔记里同一个目录写作 huiwing-migration。当前布局如下:
huiwings/
├── monorepo/ # 唯一的私有 git 根,分支 main
│ ├── AGENTS.md -> plan_skills/AGENTS.md
│ ├── AuthCore -> ../AuthCore
│ ├── AuthCoreJS -> ../AuthCoreJS
│ ├── ai-api/
│ ├── etl-tasks/
│ ├── htysso-web/
│ ├── htyteacher/
│ ├── htyts-web/
│ ├── huike-back/
│ ├── huike-e2e-moicen/
│ ├── huike-front/
│ ├── official-website/
│ ├── plan_skills/
│ └── resty_functions/
├── AuthCore/ # GitHub alchemy-studio/AuthCore
├── AuthCoreJS/
├── authcoreadmin/ # GitHub alchemy-studio/AuthCoreAdmin
├── task_server/ # 旧 Java,不进每日循环
├── proc_server/
├── task_commons/
└── midi2musicxml、transcript、tsv2
可以看到,私有业务只有一个 git 根:monorepo。远端是 ci.moicen.com 上的 weli/monorepo。plan_skills 是给 Agent 读的手册,根上的 AGENTS.md 是指过去的符号链接。
并列的三个目录各自有 .git,远端在 GitHub:
monorepo/AuthCore 和 monorepo/AuthCoreJS 是指向上级目录的符号链接,不是第二份历史。huike-back 用 path patch 指到这条链接上:
# huike-back/Cargo.toml:14-19
# Path must stay *outside* this workspace so patched crates keep AuthCore's own [workspace.dependencies].
# Clone/symlink: monorepo `AuthCore` as sibling `../AuthCore` (see `.github/workflows/ci.yml`).
[patch."https://github.com/alchemy-studio/AuthCore.git"]
htycommons = { path = "../AuthCore/htycommons" }
htyuc_models = { path = "../AuthCore/htyuc_models" }
htyuc_remote = { path = "../AuthCore/htyuc_remote" }
htysso-web 的 package.json 里,@authcore/commons 是 file:../AuthCoreJS,同样靠这根链接。
洞见三说,一个仓库里 Agent 才能同时看见接口、调用方和测试,一次提交改完3。私有代码收进 monorepo 就是这件事:网关、后端、前端、E2E 在同一个 main 上。AuthCore 三仓留在 GitHub,因为它们是公开协议仓,历史不并进私有仓库。这是那篇「多仓库难协调」的例外,例外的原因是协议要公开。
task_server、proc_server、task_commons 还在磁盘上,远端仍是 GitHub 上的同名仓库。现网任务栈已经是 Rust 的 htyts / htyproc,迁移过程见 3 月那篇4。midi2musicxml、transcript、tsv2 也不进每日业务循环。
flowchart TB
subgraph ac [AuthCore]
UcBin[htyuc]
AcCrates["htycommons htyuc_models htyuc_remote"]
UcBin --> AcCrates
end
subgraph acjs [AuthCoreJS]
Sdk["@authcore/commons"]
end
subgraph hb [huike-back]
Ws[htyws]
WsModels[htyws_models]
Kc[htykc]
Ts[htyts]
TsModels[htyts_models]
Proc[htyproc]
Ws --> WsModels
WsModels --> AcCrates
Kc --> WsModels
Kc --> AcCrates
Ts --> TsModels
TsModels --> AcCrates
Proc --> TsModels
Proc --> AcCrates
end
Front[huike-front] --> Sdk
TsWeb[htyts-web] --> Sdk
Sso[htysso-web] --> Sdk
可以看到,箭头是 Cargo 和 package.json 里的依赖,不是目录的并列。htyws、htykc 用到 AuthCore 的 htyuc_models 和 htyuc_remote,htykc 再依赖 htyws_models。htyts、htyproc 用 htycommons,htyproc 再依赖 htyts_models。huike-front、htyts-web、htysso-web 的 @authcore/commons 是 file:../AuthCoreJS。htyteacher 和 authcoreadmin 没有这层编译依赖,它们只在运行时打 HTTP。resty_functions 是 OpenResty 配置,也不在这张依赖图里。
二、两套域名
测试机对外是 moicen.com,正式机对外是 alchemy-studio.cn。两边的网关都是 OpenResty,配置在 resty_functions。浏览器先到网关,再进静态目录或后面的进程。
| 站点 | 测试 | 正式 | 代码 |
|---|---|---|---|
| 音乐教室 | music-room.moicen.com |
music-room.huiwings.cn |
huike-front |
| 管理端 | admin.moicen.com |
admin.huiwings.cn |
authcoreadmin |
| 教师 | teacher.moicen.com |
teacher.huiwings.cn |
htyteacher |
| 任务 | ts.moicen.com |
ts.huiwings.cn |
htyts-web |
| 登录 | sso.moicen.com |
sso.huiwings.cn |
htysso-web |
| AI | ai.moicen.com |
ai.alchemy-studio.cn |
ai-api |
| 处理 | proc.moicen.com |
proc.huiwings.cn |
htyproc |
另外两个名字只在测试机上:ci.moicen.com 是 Gitea,ticket.moicen.com 是工单。ai.moicen.com 的 /api/v1/ai/ 反代到本机的 ai-api。它不是本站那些 Spring AI、LangChain 的 Demo。
进程来自上面那张依赖图:htyuc 是 AuthCore 的二进制,htyws、htykc、htyts、htyproc 是 huike-back 的二进制。两台机器的监听端口一样。htyproc 的 env 把本机地址写死了:
# huike-back/envs/moicen/htyproc.env 与 envs/huiwings/htyproc.env
TS_URL=http://127.0.0.1:3003
HTYUC_URL=http://127.0.0.1:3000
HTYWS_URL=http://127.0.0.1:3001
AI_URL=https://ai.moicen.com
PROC_PORT=3004
OpenResty 的 admin.conf 在测试服和正式服都把 /api/v1/uc、/api/v1/ws、/api/v1/kc 转到 3000、3001、3002,把 htyts、htyproc 转到 127.0.0.1:3003、127.0.0.1:3004。ai.conf 把 /api/v1/ai/ 转到 127.0.0.1:5000。
音乐教室的 vite.config.ts 不把 UC、WS、KC 打到自己的域名上,两种构建都指向 admin.*。任务和 AI 的地址两边不一样:
// huike-front/vite.config.ts
// moicen
kc_server: 'https://admin.moicen.com',
ts_server: '',
ai_server: 'https://ai.alchemy-studio.cn',
assets_base: "/"
// huiwing
kc_server: 'https://admin.huiwings.cn',
ts_server: 'https://ts.huiwings.cn',
ai_server: 'https://ai.alchemy-studio.cn',
assets_base: "https://static.huiwings.cn"
ts_server 为空时,测试服的任务轮询走 music-room.moicen.com 同域的 /api/v2/ts/,由这份站点自己的 nginx 转到 3003。htyteacher、htysso-web 的接口也指向 admin.*。htyts-web 在测试服把 TS、UC、WS 都指到 admin.moicen.com,在正式服把 TS 指到 ts.huiwings.cn,UC 和 WS 仍指到 admin.huiwings.cn。
flowchart TB
subgraph testBox [测试服 moicen.com]
direction TB
Tspa["huike-front htyteacher htysso-web htyts-web authcoreadmin"]
Tgw[OpenResty]
Tuc["htyuc :3000"]
Tws["htyws :3001"]
Tkc["htykc :3002"]
Tts["htyts :3003"]
Tproc["htyproc :3004"]
Tai["ai-api :5000"]
Tdata["PostgreSQL Redis"]
Tspa --> Tgw
Tgw -->|/api/v1/uc| Tuc
Tgw -->|/api/v1/ws| Tws
Tgw -->|/api/v1/kc| Tkc
Tgw -->|/api/v2/ts| Tts
Tgw -->|/api/v2/proc| Tproc
Tgw -->|ai.moicen.com| Tai
Tts --> Tuc
Tts --> Tkc
Tproc --> Tts
Tproc --> Tuc
Tproc --> Tws
Tuc --> Tdata
Tws --> Tdata
Tkc --> Tdata
Tts --> Tdata
end
subgraph prodBox [正式服 alchemy-studio.cn]
direction TB
Pspa["同五个前端 index 在本机"]
Pcdn["static.huiwings.cn"]
Pgw[OpenResty]
Puc["htyuc :3000"]
Pws["htyws :3001"]
Pkc["htykc :3002"]
Pts["htyts :3003"]
Pproc["htyproc :3004"]
Pai["ai-api :5000"]
Pdata["PostgreSQL Redis"]
Pspa --> Pcdn
Pspa --> Pgw
Pgw -->|admin.huiwings.cn| Puc
Pgw -->|admin.huiwings.cn| Pws
Pgw -->|admin.huiwings.cn| Pkc
Pgw -->|ts.huiwings.cn| Pts
Pgw -->|/api/v2/proc| Pproc
Pgw -->|ai.alchemy-studio.cn| Pai
Pts --> Puc
Pts --> Pkc
Pproc --> Pts
Pproc --> Puc
Pproc --> Pws
Puc --> Pdata
Pws --> Pdata
Pkc --> Pdata
Pts --> Pdata
end
Tspa -->|ai_server| Pai
Pproc -->|AI_URL| Tai
如上所示,每台机器自己跑一套进程,不是两台共用一组。页面先到本机 OpenResty。UC、WS、KC 汇合到 admin.*。测试服的任务轮询留在 music-room 同域,正式服改走 ts.huiwings.cn。正式服音乐教室的 JS/CSS 在 static.huiwings.cn,测试服的 assets_base 是 /,文件跟页面在同一台 OpenResty。
跨机的只有两条。音乐教室两种构建的 ai_server 都写成 ai.alchemy-studio.cn,所以测试服页面打的是正式服的 ai-api。htyproc 两份 env 的 AI_URL 都是 ai.moicen.com,正式服的处理进程反过来打测试服的 AI。Gitea 和工单只在测试服,不在这条业务链上。
构建模式跟域名绑在一起:测试 build:moicen,正式 build:huiwing。静态目录对照写在 plan_skills/guide/project-work-handbook.md。上线是那台机器上 git pull --ff-only 再构建:私有仓从 ci.moicen.com 拉,AuthCore 从 GitHub 拉。
测试服相对正式服,就是洞见四里说的那份可复刻环境3。页面是否真的出来,用 Playwright 看,也是那一节写的观测。
三、Agent 一天怎么走
Agent 先读 plan_skills/AGENTS.md 和 plan_skills/guide/huiwing-agent-harness.md。3 月那篇把计划写成边界4,现在这份边界就在 plan_skills 里。联网之前先挂代理,git push 和 gh 都走它:
$ eval "$(huiwing-proxy-export)"
改完按仓推。私有子目录:
$ git push origin main
远端是 weli/monorepo。开源仓以 AuthCore 为例:
$ git push origin main
$ gh run list --repo alchemy-studio/AuthCore --limit 5
$ gh run watch --repo alchemy-studio/AuthCore
AuthCore 的 Rust workflow 在 push 或 PR 到 main 时跑 cargo build,并在 Postgres、Redis 上跑 htyuc 的 e2e。gh 看的是这条。AuthCoreJS 的 workflow 文件写的触发分支是 master,当前检出的分支是 main,两边对不上,push main 不会匹配那份 on。authcoreadmin 仓库里没有 Actions 文件,管理端在服务器上构建。
sequenceDiagram
participant Agent as Agent
participant Git as 本地 git
participant Gitea as Gitea
participant Hub as GitHub
participant Gh as gh
Agent->>Git: 提交
alt 私有 monorepo
Git->>Gitea: push main
Note over Gitea: 仓库根没有 workflow 目录
else AuthCore
Git->>Hub: push main
Hub-->>Gh: Rust workflow
Agent->>Gh: gh run watch
end
私有仓里其实放着 Playwright。huike-e2e-moicen/.gitea/workflows/playwright-music-room.yml 写的是 push 到 main 默认跑 --project=smoke,会改数据的 project 用 workflow_dispatch 指定。这样是为了避免每次 push 都去创建打卡、发微信通知。同目录的 .github/workflows/playwright-production.yml 文件头写着:正式服冒烟只手动触发,加每天一次的 cron,不跟随 push。
这两份文件现在不在 git 根上。Gitea 找 workflow 的函数在 modules/actions/workflows.go 的 listWorkflowsInDirs:
// modules/actions/workflows.go:83-86
for _, workflowDir = range dirs {
tree, err = commit.SubTree(ctx, gitRepo, workflowDir)
if err == nil {
break
}
如上所示,循环在第一个存在的目录上停。默认目录顺序写在 modules/setting/actions.go 里,是 .gitea/workflows,然后才是 .github/workflows。维护者在 issue 里也说过:先看 .gitea,没有再退回 .github,不把两个目录合并跑5。
weli/monorepo 的根上这两个目录都没有。Playwright 的 yml 在 huike-e2e-moicen/ 下面。official-website、htyteacher、etl-tasks 子目录里的 .github/workflows 同样不在查找路径上。所以 push monorepo 的 main,Gitea 不会把这些文件排队。测试机上的 act_runner 只跑它真正领到的任务。
flowchart TD
Root[仓库根]
Root --> D1[".gitea/workflows"]
D1 -->|目录存在| Run1[采用该目录并停止]
D1 -->|不存在| D2[".github/workflows"]
D2 -->|目录存在| Run2[采用该目录并停止]
D2 -->|不存在| None[这次 push 没有 workflow]
Nested["子目录里的 yml"] -.-> None
站点更新走另一条。在对应的机器上 git pull --ff-only,再用那台机器的构建模式产出静态文件或重启进程。2 月那张「合并后自动部署」的图是抽象版2。这里的门禁是 AuthCore 的 cargo test,以及 Playwright 文件里写的 smoke。现网没有 SonarQube,也没有 push 之后自动合并。正式服冒烟的 cron 写在子目录那份 yml 里,按上面的查找规则,它现在也不会被 monorepo 的 push 或仓库根的定时带起来。
工单在 Gitea 的 weli/tickets。带 approved 的单子由 Agent 领走,改完推送,再关单。git push 用人的账号,改工单用另一个机器人账号。循环的运行时是 tmux,定时脚本往会话里送按键。命令和原因见 5 月那篇第二节6。
对照洞见四:Observation 是 push 之后看得到的结果3。AuthCore 这条结果是 gh run watch。私有仓这条,在 workflow 还停在子目录里的时候,push 本身不会给出 Playwright 的结果。洞见六的两种模式同时在用3。Cursor 里对着一处改,是辅助者。从领单到推送、看结果、关单,是执行者。
CI 日志按公开处理。会进 GitHub Actions 的输出里不放连接串和密钥。这条写在 plan_skills/guide/huiwing-agent-harness.md。
四、对照
| 改动 | 远端 | push 之后 |
|---|---|---|
monorepo 里的业务目录 |
ci.moicen.com 的 weli/monorepo |
服务器上 pull,按环境构建。仓库根没有 workflow,子目录里的 Playwright 不会被这次 push 排队 |
| AuthCore | GitHub main |
Rust workflow,用 gh run watch 看 |
| AuthCoreJS | GitHub | workflow 的 on 写的是 master,当前分支是 main |
| authcoreadmin | GitHub | 没有 Actions 文件,在服务器上构建 |
后续把 Playwright 的 yml 放到 monorepo 根的 .gitea/workflows/,push 才会重新变成私有仓的那次 Observation。
References
-
参见《One Year of AI-Assisted Programming: Insights, Practices, and Reflections》,2026-01-31。https://weinan.tech/2026/01/31/one-year-with-ai.html。人握全局,AI 做局部。 ↩
-
参见《AI Reshaping Software Development Workflow: From Code Writer to AI Conductor》,2026-02-10。https://weinan.tech/2026/02/10/ai-reshaping-software-development-workflow.html。文末那张合并前 / 合并后的图是抽象门禁。该文已
noindex。本文的门禁是cargo test和 Playwright smoke,部署是服务器上的git pull再构建。 ↩ ↩2 -
参见《AI 辅助编程中沉淀的六个核心洞见》,2026-06-09。https://weinan.tech/2026/06/09/ai-coding-six-core-insights.html。洞见三写 monorepo 里的一次跨项目提交;洞见四写 Observation、可复刻的测试环境和 Playwright;洞见六写执行者与辅助者。本文把这三节落到 huiwings 现在的目录和 CI 上。 ↩ ↩2 ↩3 ↩4 ↩5
-
参见《从 Java task_server 到 Rust(htyts / htyproc):用 AI 推进迁移,用 GitHub CI 与基础设施兜住 E2E》,2026-03-22。https://weinan.tech/2026/03/22/rust-task-server-migration-ai-ci-e2e.html。进程边界、计划即边界,以及轻量 E2E 和手动重联调的分法。本文不重讲 crate,只补上后来的仓库根和 Gitea 查找规则。 ↩ ↩2
-
Gitea issue Query both
.giteaand.githubfor workflows。维护者说明先查.gitea/workflows,没有再退回.github/workflows,不合并两个目录。实现见listWorkflowsInDirs,默认目录见WorkflowDirs。 ↩ -
参见《大模型工程化的自举迭代框架:从 Demo 到可持续生产系统的五层实践》,2026-05-04。https://weinan.tech/2026/05/04/llm-engineering-bootstrapping-iteration-framework.html。第二节把 tmux 当作长期 Agent 的运行时。工单循环用的就是这种
send-keys。 ↩