Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .github/scripts/chinese-advice.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { execFileSync } from 'node:child_process'
import { appendFileSync, readFileSync } from 'node:fs'
import { pathToFileURL } from 'node:url'

export function pullRequestAdvice({ title = '', body = '' }) {
const advice = []
if (!/\p{Script=Han}/u.test(title)) {
advice.push('PR 标题未包含中文,建议补充中文标题,方便维护者阅读。')
}
const description = (body || '')
.replace(/<!--[\s\S]*?(?:-->|$)/g, '')
.replace(/^\s*(`{3,}|~{3,})[^\n]*\n[\s\S]*?^\s*\1\s*$/gm, '')
.replace(/`[^`\n]*`/g, '')
.replace(/^\s*#{1,6}\s+.*$/gm, '')
if (!/\p{Script=Han}/u.test(description)) {
advice.push('PR 正文未包含中文说明,建议补充中文的问题说明、改动内容和验证结果。')
}
return advice
}

export function commitAdvice(log) {
return log.split('\n').filter(Boolean).flatMap(line => {
const [sha, title] = line.split('\0')
return /\p{Script=Han}/u.test(title || '')
? []
: [`提交 ${sha} 的标题未包含中文,建议补充中文摘要。`]
})
}

function git(...args) {
return execFileSync('git', args, { encoding: 'utf8' }).trimEnd()
}

function main() {
const event = JSON.parse(readFileSync(process.env.GITHUB_EVENT_PATH, 'utf8'))
const advice = event.pull_request ? pullRequestAdvice(event.pull_request) : []
const base = process.env.BASE_SHA
const head = process.env.HEAD_SHA
let revisions
if (!base || /^0+$/.test(base)) {
// 新分支不追溯其他远程分支已有的提交。
const others = git('for-each-ref', '--format=%(objectname)', 'refs/remotes/origin/')
.split('\n').filter(sha => sha && sha !== head)
revisions = others.length ? [head, '--not', ...others] : ['-1', head]
} else {
revisions = [`${base}..${head}`]
}
advice.push(...commitAdvice(git('log', '--format=%h%x00%s', ...revisions)))
// 使用固定提示和 Git 生成的提交哈希,避免把 PR 原文作为工作流指令输出。
for (const message of advice) console.log(`::warning title=中文描述建议::${message}`)
const summary = advice.length
? advice.map(message => `- ${message}`).join('\n')
: '本次检查未发现需要补充中文的项目。'
appendFileSync(process.env.GITHUB_STEP_SUMMARY,
`## 中文描述建议\n\n${summary}\n\n以上仅为建议,不因缺少中文而阻断合并。\n`)
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) main()
30 changes: 30 additions & 0 deletions .github/scripts/chinese-advice.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { commitAdvice, pullRequestAdvice } from './chinese-advice.mjs'

test('标题和正文分别提醒,允许中英文混用', () => {
assert.equal(pullRequestAdvice({ title: 'Fix login', body: 'Fix a bug' }).length, 2)
assert.equal(pullRequestAdvice({ title: '修复登录', body: 'Fix a bug' }).length, 1)
assert.equal(pullRequestAdvice({ title: 'Fix login', body: '修复登录失败' }).length, 1)
assert.deepEqual(pullRequestAdvice({ title: 'Fix 登录', body: '修复 WebUI 登录' }), [])
})

test('空正文和仅有模板标题、注释或代码的正文仍给出建议', () => {
for (const body of [null, '', '## 改动说明\n<!-- 请填写中文 -->\nFix login',
'```text\n中文\n```', '~~~text\n中文\n~~~', '`中文`']) {
assert.equal(pullRequestAdvice({ title: '修复登录', body }).length, 1)
}
})

test('识别繁体和扩展汉字', () => {
assert.deepEqual(pullRequestAdvice({ title: '修復登入', body: '𠀀' }), [])
})

test('仅提醒缺少中文的提交,不回显不可信的标题', () => {
const log = 'abc123\0修复登录\ndef456\0Fix login\nghi789\0::error::bad'
assert.deepEqual(commitAdvice(log), [
'提交 def456 的标题未包含中文,建议补充中文摘要。',
'提交 ghi789 的标题未包含中文,建议补充中文摘要。',
])
assert.deepEqual(commitAdvice(''), [])
})
105 changes: 92 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,46 @@
name: Build and Test
name: 构建、测试与贡献建议

on:
pull_request:
types: [opened, synchronize, reopened, edited]
push:
branches: [main]
tags: ['**']
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
contents: read

env:
# 与便携包编译参数保持一致,复用测试和构建缓存。
CGO_ENABLED: '0'
GOFLAGS: -trimpath

jobs:
commit-language:
name: 中文描述建议
if: github.event_name == 'pull_request' || (github.event_name == 'push' && !github.event.deleted)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version: 22
- name: 验证建议规则
run: node --test .github/scripts/chinese-advice.test.mjs
- name: 提供中文描述建议
env:
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: node .github/scripts/chinese-advice.mjs

go:
strategy:
matrix:
Expand All @@ -22,7 +54,7 @@ jobs:
cache-dependency-path: dev/go.sum
- name: Test
working-directory: dev
run: go test ./...
run: go test -vet=off ./...
- name: Vet
working-directory: dev
run: go vet ./...
Expand All @@ -41,19 +73,66 @@ jobs:
release/*.zip
release/*.tar.gz
if-no-files-found: error
compression-level: 0

web:
mysql:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
version: ["8.4.11", "9.6.0"]
services:
mysql:
image: mysql:${{ matrix.version }}
env:
MYSQL_ROOT_PASSWORD: root-test-password
MYSQL_DATABASE: easybot_test
MYSQL_USER: easybot
MYSQL_PASSWORD: easybot-test-password
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping -h 127.0.0.1 -uroot -proot-test-password"
--health-interval=3s
--health-timeout=5s
--health-retries=40
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/setup-go@v5
with:
node-version: 22
cache: npm
cache-dependency-path: dev/web/package-lock.json
- name: Install dependencies
working-directory: dev/web
run: npm ci
- name: Build
working-directory: dev/web
run: npm run build
go-version-file: dev/go.mod
cache-dependency-path: dev/go.sum
- name: Test against MySQL
working-directory: dev
env:
EASYBOT_TEST_DATABASE_DSN: mysql://easybot:easybot-test-password@127.0.0.1:3306/easybot_test
# 其他包由跨平台任务覆盖;外部数据库测试每次连接新测试库执行。
run: go test -count=1 -vet=off ./internal/store

postgres:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:18.6
env:
POSTGRES_DB: easybot_test
POSTGRES_USER: easybot
POSTGRES_PASSWORD: easybot-test-password
ports:
- 5432:5432
options: >-
--health-cmd="pg_isready -h 127.0.0.1 -U easybot -d easybot_test"
--health-interval=3s
--health-timeout=5s
--health-retries=40
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: dev/go.mod
cache-dependency-path: dev/go.sum
- name: Test against PostgreSQL
working-directory: dev
env:
EASYBOT_TEST_DATABASE_DSN: postgres://easybot:easybot-test-password@127.0.0.1:5432/easybot_test?sslmode=disable
run: go test -count=1 -vet=off ./internal/store
44 changes: 0 additions & 44 deletions .github/workflows/commit-language.yml

This file was deleted.

71 changes: 0 additions & 71 deletions .github/workflows/database-integration.yml

This file was deleted.

3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
本文件适用于整个 Core 仓库及后续开发代理。

- 项目目标:在已获授权的范围内复刻闭源 EasyBot 的功能和使用流程;以可验证的协议、行为和测试为依据。
- 中文交流、中文开发文档、中文 commit 标题及正文。技术名、路径、类型标记可以保留英文,例如 `fix(web): 修复管理员登录失败`。
- 建议使用中文交流、编写开发文档及 PR、commit 标题和正文。技术名、路径、类型标记可以保留英文,例如 `fix(web): 修复管理员登录失败`。中文 CI 仅提供提醒,不因缺少中文阻断合并。
- 使用 AI 辅助贡献时,在 PR 中披露工具及用途;贡献者须理解并审阅改动,用自己的话说明问题、行为和验证结果,提交 PR 前完成人工测试。详细要求见 CONTRIBUTING.md;AI 披露及人工验证不纳入自动检测。
- 不得改写既有历史以追溯修改英文提交。
- 反编译参考仓库位于同组织的 EasyBot-Recovered。本地建议与 Core 并列;只用于功能、协议和接口对照。不得直接把私有参考源码或 DLL 提交到 Core。
- 不把配置 CRUD、页面入口或模拟器通过描述为真实运行时已完成。状态说明必须区分已实现、模拟验证、真实环境验证和待实现。
Expand Down
21 changes: 17 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
# 贡献规范

## 中文提交规范
## 中文交流建议

所有后续提交的标题和正文使用中文,技术术语、代码标识和类型前缀可保留英文。推荐格式:`类型(范围): 中文摘要`,例如 `feat(bridge): 增加连接身份校验`。正文说明原因、行为变化与验证结果。合并和回滚提交同样使用中文;不追溯重写历史英文提交。
建议 PR 和 commit 的标题、正文使用中文,方便维护者阅读;允许中英文混用,技术术语、代码标识和类型前缀可保留英文。推荐格式:`类型(范围): 中文摘要`,例如 `feat(bridge): 增加连接身份校验`。正文说明原因、行为变化与验证结果。不追溯重写历史英文提交。

CI 的中文提交检查校验每个新增提交标题是否含汉字;它只能做基本格式检查,正文语言和语义仍由人工评审。要强制所有贡献者遵守中文提交规范,需要在 GitHub 分支保护中将该检查设置为必需,并限制直接推送与绕过权限。
CI 在 PR 标题、正文说明或新增 commit 标题不含汉字时,通过警告及运行摘要建议补充中文,检查仍然通过。正文检测忽略 Markdown 标题、HTML 注释和代码,避免把模板提示当作实际说明。它只做字符检查,不判断语言质量;构建、测试和静态检查失败仍正常报告失败。

## AI 辅助贡献

参考 [GeyserMC 的 AI 使用政策](https://github.com/GeyserMC/.github/blob/master/AI_POLICY.md),本仓库采用以下协作要求:

- 使用 AI 辅助代码、测试或文档时,在 PR 正文中披露工具及用途,一句话即可,例如“使用 Codex 辅助调整 CI,已人工审阅配置并验证中文提醒逻辑”。
- 贡献者须理解并审阅所有改动,能够解释实现及验证结果,并对提交负责。PR 说明和评审回复应使用自己的话,不直接粘贴未经核实的 AI 回答。允许翻译和拼写检查;翻译时请附上原文,方便核对。
- 无论是否使用 AI,提交 PR 前都须完成人工测试,写清测试方式、结果及未验证部分。文档或 CI 改动可采用人工审阅和相关配置、脚本验证;不得把仅由 AI 声称通过的检查写成实测结果。
- PR 聚焦相关改动,不夹带大量无关格式修改或生成内容。不要提交 AI 生成的图片、视频或音频等媒体素材。

以上由贡献者披露、维护者评审,不加入 CI 自动检测,也不使用 AI 内容识别器。

## 开发与验证

当前实现位于 `dev/`:`cmd/easybot` 为入口,`internal` 为后端,`web` 为前端。使用 Go 1.24+ 与 Node.js 20.19+,依赖以 `go.mod/go.sum` 和 `package-lock.json` 为准。详细运行方法见 [开发说明](dev/README.md)。

后端在 `dev` 执行 `go test ./...` 和 `go vet ./...`;前端在 `dev/web` 执行 `npm ci` 和 `npm run build`。涉及 MySQL/PostgreSQL 时使用空的专用测试库执行集成检查。Windows 启动改动需要验证从其他工作目录启动 exe,并在验证后停止服务。

PR 使用中文说明问题、最终行为和验证结果。为非平凡修复保留能暴露原问题的测试;不要把默认跳过的外部数据库测试描述为已验证通过。
CI 在 PR、主分支推送、标签推送或手动触发时运行;普通开发分支请创建 PR 或手动运行。同一 PR 或分支的新运行会取消旧运行。跨平台任务完整执行 Go 测试和独立的 `go vet`,数据库任务只执行 `go test -count=1 -vet=off ./internal/store`,保证每次实际访问测试库。CI 使用与便携包一致的 `CGO_ENABLED=0` 和 `-trimpath` 编译参数,复用测试、静态检查与打包的编译缓存。

建议 PR 使用中文说明问题、最终行为和验证结果。为非平凡修复保留能暴露原问题的测试;不要把默认跳过的外部数据库测试描述为已验证通过。

## 复刻依据与边界

Expand Down
Loading
Loading