部署 Afresto Next 是做什么的?
技术栈:后端 Go (推送→GHCR→Watchtower 约5分钟) · Web React (Vite→Cloudflare Worker,手动执行 wrangler deploy) · 移动端 RN/Expo 位于 独立仓库 <repo-mobile> (EAS OTA)。生产部署规则:始终先征得用户同意(用户每次操作都需批准)。
🔴 规则 #1 — 此仓库常被多个代理会话同时使用
git status 经常显示的是其他会话修改过的文件。因此:
- 不要执行
git add -a/git commit -a/git add .——这会将其他会话的半成品一并纳入你的提交。 - 选择性暂存属于你自己的每个文件,然后验证:
git diff --cached --name-only——确保没有混入不属于你的文件(常见误入的文件:web/src/pages/ChatPage.tsx、backend/internal/db/dbgen/models.go,除非这是你重新生成的)。 - 对于两个会话共享的文件(例如 ExamPage),而你只想暂存自己的部分代码(hunk):不要使用
stash或reset(这会从另一个代理脚下抽走文件)。应通过 Git 对象编辑索引:git show :path > tmp→ 清除tmp中他们的代码块 →SHA=$(git hash-object -w tmp)→git update-index --cacheinfo 100644,$SHA,path。磁盘上的文件不会受到影响。 - 🚨
cwd工具的 Bash 可能停留在之前调用的cd .../web(或 worktree)目录。之后执行git add backend/...会失败,出现pathspec did not match(或者暂存了错误的树)。在选择性暂存之前,先cd <repo-root> &&(或使用绝对路径)。使用git diff --cached --name-only验证是否显示了正确的根相对路径(而不是../backend或src/...)。 - 部署后:检查
git status——确保其他会话的工作文件仍然完好。
Go 质量关卡(在提交后端代码前)
类似 Laravel 中 Pint + PHPStan 的 Go 版本。在暂存后端文件之前,请在本地运行:
- 格式化:
cd backend && gofmt -l .→ 必须输出为空。有输出 → 执行gofmt -w .(自动修复),并暂存结果。(相当于pint --test。) - 构建 + vet:
go build ./... && go vet ./...显示绿色(成功)。 - 静态分析(目标):
golangci-lint run ./...(包含 gofmt/govet/staticcheck/errcheck/ineffassign/unused/misspell)。相当于phpstan analyse。 - ⚠️ 状态:CI 质量关卡尚未激活。 尚未设置
backend/.golangci.yml或.github/workflows/ci.yml,且staticcheck版本已过时 → 因此目前手动执行 format+build+vet。计划在 CI 中强制执行并添加完整配置:docs/plan/00-tooling-quality-gate.md。生成的代码目录internal/db/dbgen排除在 lint 检查之外。 - 测试需随变更一起执行 → 参考技能 afresto-testing (
go test ./...;若无TEST_DATABASE_URL则跳过集成测试)。
🔴 规则 #00 — 有意义的工作必须先生成 GitHub Issue(自 2026 年 9 月 5 日起生效)
此规则优先于规则 #0(分支和 PR):在触碰任何代码之前执行。
目的并非形式主义。通过聊天提出的请求只有双方知晓——团队无法看到,所有者无法分配任务,模块不会留下任何痕迹记录谁在做什么。
何时必须创建 Issue
新功能 · 学校可见的更改 · 数据库 schema 更改 · 任何需要产品决策的事项 · 跨越多个 PR 的工作。
何时无需创建
明确且范围较小的 Bug · 文本/标签修正 · 文档 · 直接响应 PR 审查意见的后续操作。
如有疑问 → 创建 Issue。成本低,且可以关闭。正是这个门槛维持了规则的生命力: 如果每个琐碎的请求都变成 Issue,人们会开始悄悄绕过它;一旦绕过而没有任何后果,规则就失效了。
代理需要执行的操作
- 充分探索代码,以填写“初步发现”并找出阻碍——不要凭空猜测。
- 创建 Issue,使用模板
.github/ISSUE_TEMPLATE/pekerjaan.md,添加标签modul:*(这有助于按模块记录),如果有待定事项,再加上butuh-keputusan标签。 - 停止。 不要编写代码,不要创建分支,不要自行分配任务。分配是项目所有者的行为;团队也需要空间提供意见或修改想法。
- 向项目所有者报告 Issue 编号,以及任何仍待定的决策(如有)。
🔑 项目所有者可以通过说“现在就开始做”来绕过此规则——Issue 仍然作为记录被创建,工作照常进行。此规则规定了默认行为,而不是限制。
在开发过程中发现的阻碍
在继续之前必须告知:在 Issue 中评论(以便团队看到)并且通知项目所有者。如果决策导致工作暂停 → 添加标签 butuh-keputusan。不要默默选择自己的解释然后继续——这会导致所谓“完成”但实际错误的工作。
关闭循环
PR 必须在其正文中包含 Closes #<编号>。这连接了 Issue → PR → 人员,使得 gh issue list --label modul:ujian --state all 成为该模块中谁在做什么的记录。没有这行,痕迹就会中断。
🔴 规则 #0 — 不要直接提交/推送到 main 分支(4 人团队,自 2026 年 7 月 25 日起生效)
所有工作通过分支 + 拉取请求进行;只有审查者才能合并到 main 分支(合并操作会触发部署)。
- 开始之前:
git checkout main && git pull——如果有新的迁移,先运行迁移。 - 在触碰代码之前,创建或切换到一个有主题的分支:
- 功能 →
feat/<名称>· Bug →bugfix/<名称>· 有 Issue 的 Bug → 创建新分支并仔细阅读 Issue。
- 功能 →
- 选择性提交 →
git push -u origin <分支>(不是推送到 main)。 - 向
main分支打开 PR:如果gh已登录,使用gh pr create;否则,向用户提供 URLhttps://github.com/Afresto-Next/next/compare/main...<分支>?expand=1。 - 审查者批准并合并 → 部署。不要自己合并 PR。
- 下面的“推送后端 / 部署 WEB”部分 = 在 PR 合并到 main 之后执行的机制(通常由负责部署的人执行),而不是允许直接推送到 main。
- 技能的更改也需通过 PR(有 CODEOWNERS)。
步骤顺序
1. 提交(Bash 工具 = Git Bash / sh,而不是 PowerShell)
- 多行消息:heredoc
git commit -q -F - <<'EOF' … EOF。不要使用@'…'@(这是 PowerShell 的 here-string → 标题会被污染为@ ...)。 - 在消息末尾添加:
Co-Authored-By: Claude Opus 4.8 <[email protected]>。 - 警告“CRLF will be replaced by LF” = 正常,可以忽略。
- 提交到你的任务分支(
feat/*·bugfix/*),不是main(参见规则 #0)。用户在请求时提交。
🔴 不要执行 git add -A 或 git add <dir>/ —— 已经三次误加了用户文件
此仓库包含许多用户未跟踪的工作文件:docs/bskap/(图片和 PDF)、docs/analisis butir soal/、elibrary/、cloudflare/**/.wrangler/(账户缓存)。一旦使用 -A,所有文件都会被添加。
历史事件:git add docs/ 误加了 BSKAP 的 PDF/xls/图片(7 月 25 日和 8 月 2 日);git add -A 误加了 446 个文件,包括 wrangler-account.json(8 月 9 日)。
git add path/ke/berkas1 path/ke/berkas2 # 逐个添加,始终保持
git commit -q -F - <<'EOF'
...
EOF
git show --name-only --format="" HEAD | wc -l # 检查数量是否符合预期?
如果数量不符:git reset --soft HEAD~1 && git reset,然后重新正确暂存。
如果已经推送到自己的分支,使用 git push --force-with-lease 是安全的。
2. 数据库迁移 — 必须位于最顶层的提交
.github/workflows/migrate.yml 通过 git diff --name-only HEAD~1 HEAD 检测迁移(仅检查最新的提交),在“构建并推送 Docker 镜像”步骤成功之后运行(workflow_run 事件)。
- 如果推送包含迁移 → 迁移文件必须位于最顶层的提交(一个提交同时包含迁移和代码是安全的)。
- 迁移会自动在自托管运行器 VM 上构建后执行。
run --rm migrate同步命令 → 如果出错,任务会硬失败(而不是静默失败)。 - 验证迁移:检查 Actions → “Migrate DB (self-hosted)” 标签页是否为绿色(针对该提交)(
ghCLI 可用:gh run list --workflow="Migrate DB (self-hosted)" --limit 1—— 代理可以自行检查,无需麻烦用户)。Watchtower 会在下一次轮询(约 5 分钟)时替换api镜像,通常会在迁移完成后进行。 - 在推送迁移之前,通过本地数据库上的 psql
BEGIN; … ROLLBACK;验证 SQL(v102 等同于生产环境)。请参考技能afresto-db-change/堵漏工程 §4。
🔴 迁移编号:在合并前重新检查,而不是在创建分支时
在你分支打开期间,团队成员可能合并了另一系列迁移。重复的编号会导致 goose 在 sortAndConnectMigrations 处崩溃——这不是 SQL 错误,因此容易被误读。
✅ 数据无损坏:崩溃发生在收集迁移列表时,在任何一个迁移被执行之前。
历史事件(2026 年 8 月 9 日):创建分支时选择了 00138;合并时,团队已经使用了 00138–00140。第一次修复将其重命名为 00140,但仍然冲突——因为又是猜测的结果。
计算,不要猜测:
git pull # 必须先执行,以查看他人的迁移
MAX=$(ls backend/migrations/ | sed 's/_.*//' | sort -n | tail -1)
NEXT=$(printf "%05d" $((10#$MAX + 1)))
ls backend/migrations/ | sed 's/_.*//' | sort | uniq -d # 必须为空
如果已经冲突:使用 git mv 重命名为新编号,创建一个小的 PR,合并,然后从 Actions 标签页重新运行 “Migrate DB (self-hosted)” 工作流。
3. 推送后端(在 PR 合并到 main 之后 — 参见规则 #0)
合并 PR → main 更新 → GHCR 构建 → Watchtower 约 5 分钟更新镜像。如果没有后端更改,则无需等待 Watchtower。(开发期间:使用 git push -u origin <分支> 然后创建 PR — 不要推送到 main。)
⚠️ 合并后端后:检查 502 错误(已复发 6 次)
重新创建 api/worker 容器经常导致完全中断——所有容器状态为 Up,但所有路由返回 502。实际上,每次后端合并都像是一场赌博。
curl -s -o /dev/null -w "%{http_code}\n" --max-time 15 https://next.afresto.co/api/v1/auth/me
# 401 = 健康(需要身份验证)· 502 = 中断
如果返回 502 → Actions → “Ops — pulihkan API” workflow → restart-docker(无需 SSH,1 分钟内恢复)。
不要分析刚部署的功能的细节:如果 502 且容器状态为 Up,则意味着需要重启 Docker 守护进程。
详细信息:docs/ops-auto-pulih.md。
4. 部署 WEB — 从干净的工作树进行(因为存在多个代理会话)
npm run build 会读取整个工作树 → 其他会话的半成品也会被包含在构建中。因此,请从你提交版本的独立工作树进行构建和部署:
rm -rf /c/wtd 2>/dev/null; git worktree add --detach /c/wtd <SHA-commit-mu>
cd /c/wtd && git status --short # 必须为空(干净)
cd /c/wtd/web && npm ci && npm run build
cd /c/wtd/cloudflare/web-worker && npx wrangler deploy
cd <repo-projectTwo> && git worktree remove --force /c/wtd && git worktree prune
- 🔥 工作树路径必须短 (
/c/wtd) —— 过长的暂存区路径加 DKI 日历长文件名会导致 Windows 错误 “Filename too long” 检出失败。 - 如有必要,验证构建包是否正确:
grep -rl "<penanda fiturmu>" /c/wtd/web/dist/assets/*.js—— 但要小心误报(公共标识可能来自其他功能)。 - Cloudflare 缓存资产约 20 秒 → 使用 Ctrl+F5 进行测试。(返回 200 但大小约为 397 字节 = 后备页面,而非实际资产。)
5. 移动端 OTA — 位于独立仓库,必须指定 --environment
cd "<repo-mobile>"
npx eas update --branch preview --environment preview --message "…" --non-interactive
- 🔥
--environment必须指定(preview 或 production)—— 不指定会导致更新到错误的环境或失败。 - 移动端仓库有远程 GitHub 仓库 (
Afresto-Next/afresto-next-mobile) → 因此规则 #0 适用:分支 → PR → 合并,不要直接推送到main。 - OTA 仅更新 JS/资产。原生模块的更改 → 通过 OTA 将导致崩溃 → 必须重新构建原生包。
🔥 eas update 发送的是工作树,而不是提交
这与步骤 4 中的 npm run build 完全一样——整个工作目录都被打包,包括其他会话的半成品。发布结果中 Commit <hash>* 行的 * 符号 = 工作树不干净。
必须在发布前立即执行——打印结果,然后与发布输出的 Commit 行进行核对:
echo "publish dari: $(git branch --show-current) @ $(git log --oneline -1 --format=%h)"
git log --oneline HEAD..origin/main # 为空 = 没有其他人未合并的工作
- 从
main分支发布,而不是功能分支。从功能分支发布,其他已合并人员的工作将丢失于渠道中——OTA 会替换整个包,而不是增量更新。 - 生产环境:先暂存其他会话的未提交更改(
git stash push -- <berkas>…git stash pop)。生产环境不应包含未经审查的代码。
🚨 分支可能在开发过程中变动(多个代理会话,单个克隆仓库)
2026 年 8 月 20 日事件:预览版发布明确来自 main @ d0938fa;但下一个命令发现 HEAD 已经指向另一个分支 @ 8126a50——另一个会话在同一个克隆仓库中切换了分支。生产环境因此运行了超前 3 个 PR 的代码,且没有任何错误。
➡️ 不要假设分支在命令之间保持不变。 每次都重新打印(使用上面的命令)。 ➡️ 真正的预防措施:从 WORKTREE 发布 OTA,类似于步骤 4 中的 Web 部署——其他会话无法移动它的分支:
rm -rf /c/wtm 2>/dev/null; git worktree add --detach /c/wtm <SHA-commit-mu>
cd /c/wtm && git status --short # 必须为空
npm ci && npx eas update --branch <ch> --environment <env> --message "…" --non-interactive
cd <repo-mobile> && git worktree remove --force /c/wtm && git worktree prune
🪤 工作树路径要短 (/c/wtm) —— 原因与步骤 4 相同。
🪤 在工作树中运行 npm ci 需要时间;对于小的 OTA,如果执行了上述分支+提交验证并且结果匹配,可以保留在主克隆仓库中。
🏢 GHCR 和 GitHub 组织 — 仓库现在位于组织 Afresto-Next
仓库 (next、afresto-next-mobile、afresto-claude-skills) 已从 ristology/* 移至组织 Afresto-Next(2026 年 7 月 20 日)。完整的迁移运行手册:docs/runbook-transfer-next-org.md。
- 🚨 仓库迁移到组织后,GHCR 路径的 owner 字段随之改变 → Watchtower 会静默冻结。 CI 为镜像添加标签
ghcr.io/${GITHUB_REPOSITORY_OWNER}/afresto-next-{api,web,migrate}— owner 字段跟随仓库所有者。自托管运行器的image:字段是硬编码的,因此如果 CI 标签推送到新路径,但运行器仍在拉取旧路径,Watchtower 会停留在旧镜像上,并且 Docker Compose 启动时会回退到本地缓存(如果未拉取新标签,则可能使用旧缓存)。解决方案:在所有镜像标签稳定后的下一次 PR 中,更新docker-compose.yml中的image:字段。 - 对于非
next仓库(技能、移动端):仅影响image:标签的含义。Docker Compose 未使用这些,因此无需更改。 - 组织级机密(例如
TEST_DATABASE_URL、DOPPLER_TOKEN)在Afresto-Next组织中的行为应与之前相同。