diff --git a/.claude/commands/20-code-review.md b/.claude/commands/20-code-review.md new file mode 100644 index 0000000000..f0477a2183 --- /dev/null +++ b/.claude/commands/20-code-review.md @@ -0,0 +1,329 @@ +--- +name: code-review +description: 通用代码审查命令,基于业务需求进行白盒逻辑正确性审查和技术架构评估 +--- + +# 代码审查命令 + +## 审查任务 + +请对当前工作区的代码修改进行全面审查,重点关注业务逻辑的正确性和技术架构的合理性。 + +## 审查维度 + +### 1. 业务层面审查(BLOCKING级别) +- **需求来源验证**:寻找并验证业务需求来源(不能基于代码反推需求) +- **需求实现完整性**:验证代码是否100%满足业务需求 +- **业务流程逻辑正确性**:检查状态转换、数据流、条件判断的逻辑 +- **数据结构正确性**:验证模型定义、字段约束、业务规则映射 +- **Edge Case处理**:评估边界条件和异常场景的处理合理性 + +### 2. 技术层面审查 +- **架构合理性**:模块化、职责分离、依赖关系 +- **KISS原则遵循**:避免过度工程化 +- **扩展性评估**:未来功能添加的容易程度 +- **非Adhoc修改验证**:是否遵循现有代码模式 +- **性能问题检测**:查找明显的性能问题(如N+1查询等) +- **单元测试完备性**:核心逻辑90%覆盖率要求 + +### 3. 契约与连通性专项检查(BLOCKING) +- 端点一致性:前端端点集中配置;路径/动态段/大小写/末尾分隔符与后端路由完全一致;HTTP 方法语义匹配(幂等/副作用)。 +- 认证与跨域:统一的认证机制(会话/令牌);前端传递方式与后端期望一致(Cookie/Authorization 等);跨域与 CSRF 策略匹配。 +- 请求/响应 Schema:字段名、类型、必选/可选一致;时间/数值/布尔的编码一致;分页/排序参数与响应元信息对齐。 +- 错误与状态码:4xx/5xx 使用合理;错误负载结构稳定且可解析;前端根据错误类型提供可恢复提示。 +- 异步/事件(如有):事件类型与载荷字段与文档一致;开始/结束/错误/心跳等语义完整;增量合并无丢失/重复;存在降级路径。 +- 端到端透传:API→Service→下游(存储/第三方)参数(上下文/会话/区域/幂等键)无遗漏;写操作具备幂等/去重策略。 +- 日志与隐私:日志含必要上下文(追踪/用户/会话),敏感信息脱敏;UI 默认不展示内部实现细节。 + +#### 全链路 Contract Crosswalk(必做) +- 选取关键用户流,建立“参数对齐矩阵”: + - 前端请求 → 后端路由/处理 → 服务层 → 下游(存储/第三方)→ 持久化字段。 + - 列出每个参数:`name | type | required | default | source-of-truth(文件:行)`。 + - 如存在事件/流式契约,补充事件类型与载荷字段,以及必要的状态回写。 +- 输出差异点与最小修复建议(谁改、改哪、如何不破坏现有行为)。 + +### 4. 高发问题清单(优先排查) +- 命名错位:不同层对同一概念使用了不同命名风格或名称(例如 `camelCase` ↔ `snake_case`、标识符命名不一致)。 +- 路径错位:端点路径或动态段命名不一致;末尾分隔符导致重定向或方法失败。 +- 类型漂移:数字/字符串/布尔/时间类型在层间编码不一致或未正确转换。 +- 认证混用:请求在不同处使用了不同认证方式;跨域请求未按需携带凭证;CSRF 保护缺失。 +- 事件缺口:后端新增事件类型或字段未在前端解析;增量合并逻辑导致重复/丢失。 +- 可见性/状态:后端状态位或可见性字段被忽略,导致 UI 展示与真实状态不一致。 + +### 5. 零假设验证(VERIFY-FIRST Gate,BLOCKING) +- 禁止基于“猜测的字段/API/事件”写逻辑。每个关键元素必须给出证据: + - 模型/字段定义位置(文件:行) + - 路由/处理方法签名(文件:行) + - 前端类型/解析/调用代码(文件:行) +- 无证据的假设一律不通过。 + +### 6. 用户视角 E2E 可见性审计(BLOCKING) +⚠️ **这是最关键的检查** - "代码完美但功能不工作"的主要原因就是跳过了这一步! + +**MANDATORY USER FLOW VERIFICATION (必须执行):** +- **完整点击路径追踪**:从用户点击开始,逐步追踪到最终状态 + - 用户点击X → 调用函数Y → 导航到页面Z → 显示内容W + - **必须验证每个步骤都正确执行** +- **URL路由验证**:所有导航路径在路由配置中存在且正确处理参数 +- **状态传递验证**:点击后的状态变化是否正确反映在UI中 +- **错误场景测试**:参数缺失、网络错误、权限不足等场景的处理 + +**具体检查项目:** +- 入口可见:对应功能的入口(按钮/导航/控件)在默认场景与目标设备上可达;不被误判条件隐藏。 +- **链接落地(核心)**:页面路由/回跳/深链接一致;从入口到完成形成闭环。 + - 点击通知 → 是否真的跳转到预期页面? + - 分享链接 → 是否真的加载预期内容? + - 所有导航路径都必须实际追踪验证! +- 状态完整:加载/空数据/错误/权限不足 均有清晰呈现与可恢复路径。 +- 角色/开关:与权限/Feature Flag 的可见性符合预期;默认值不阻断主流程。 + +**⛔ 禁止行为:** +- ❌ 只看代码结构,不追踪实际执行流程 +- ❌ 假设navigate()调用就等于用户到达了目标页面 +- ❌ 不验证URL参数处理逻辑 +- ❌ 说"看起来正确"而不验证"实际正确" + +### 7. Web3 AI 交易系统安全审查(BLOCKING 级别) +🔐 **资金安全是生死线** - 一个安全漏洞可能导致所有资金损失! + +#### 7.1 私钥与密钥管理(CRITICAL) +- **零泄露原则**: + - ❌ 禁止:私钥/助记词出现在日志、错误消息、前端代码、Git 历史中 + - ❌ 禁止:明文存储私钥(环境变量、配置文件、数据库) + - ✅ 必需:使用硬件钱包、HSM、或加密密钥管理服务(AWS KMS/Vault) + - ✅ 必需:API Key、密钥材料必须加密存储,运行时解密 +- **最小权限原则**: + - 交易签名密钥与只读查询密钥分离 + - 每个功能使用独立的子账户/权限 + - 定期轮换 API Key 和访问令牌 +- **验证检查**: + - [ ] grep 搜索 `private_key`、`mnemonic`、`seed` 等关键词,确保无硬编码 + - [ ] 检查所有密钥存储位置的加密状态 + - [ ] 验证密钥访问日志和审计追踪 + +#### 7.2 交易安全(CRITICAL) +- **签名验证**: + - ✅ 必需:所有交易必须经过签名验证 + - ✅ 必需:验证交易发起者身份(防止伪造) + - ✅ 必需:使用 nonce/序列号防止重放攻击 +- **交易参数验证**: + - ✅ 必需:验证接收地址合法性(checksum、白名单) + - ✅ 必需:金额/价格/滑点限制(防止异常大额交易) + - ✅ 必需:Gas Price/Gas Limit 上限保护(防止 Gas 耗尽攻击) + - ✅ 必需:Deadline/超时保护(防止过期交易执行) +- **滑点与价格保护**: + - ✅ 必需:设置合理的滑点容忍度(如 0.5%-2%) + - ✅ 必需:价格预言机验证(多源对比、时间戳检查) + - ✅ 必需:异常价格波动拒绝交易 +- **验证检查**: + - [ ] 所有交易调用都有 nonce 或幂等键 + - [ ] 金额/价格参数都有上下限验证 + - [ ] Gas 费用有最大限制 + - [ ] 滑点保护代码存在且正确 + +#### 7.3 AI 决策安全(CRITICAL) +- **提示注入防护**: + - ❌ 禁止:直接将用户输入拼接到 AI prompt 中 + - ✅ 必需:用户输入消毒/转义(防止 prompt injection) + - ✅ 必需:系统提示与用户输入明确分离(使用角色隔离) + - ✅ 必需:敏感操作需要用户明确确认,AI 不能自主决定大额交易 +- **决策审计**: + - ✅ 必需:记录所有 AI 决策的完整上下文(输入、输出、时间戳、模型版本) + - ✅ 必需:决策可追溯、可回放、可审计 + - ✅ 必需:异常决策告警(如突然的大额交易建议) +- **模型安全**: + - ✅ 必需:使用官方 API,避免第三方代理(防止中间人攻击) + - ✅ 必需:API 响应验证(检测异常输出、格式错误) + - ✅ 必需:模型输出不直接执行,必须经过参数验证 +- **验证检查**: + - [ ] 搜索用户输入拼接点,确保有消毒处理 + - [ ] 检查决策日志是否完整(包含所有关键参数) + - [ ] 验证大额交易需要额外确认机制 + +#### 7.4 智能合约交互安全(CRITICAL) +- **授权范围控制**: + - ❌ 禁止:无限授权(`approve(spender, type(uint256).max)`) + - ✅ 必需:按需授权,每次交易前计算精确授权额度 + - ✅ 必需:定期清理过期授权 + - ✅ 必需:监控授权事件,异常授权告警 +- **合约调用验证**: + - ✅ 必需:合约地址白名单(只与已审计合约交互) + - ✅ 必需:函数选择器验证(防止调用错误函数) + - ✅ 必需:调用参数类型/范围验证 + - ✅ 必需:模拟执行(dry-run)后再真实执行 +- **重入与异常处理**: + - ✅ 必需:处理合约调用失败情况(revert、out of gas) + - ✅ 必需:检查返回值,不假设调用成功 + - ✅ 必需:避免在外部调用后修改关键状态(防重入) +- **验证检查**: + - [ ] grep `approve` 确保无无限授权 + - [ ] 所有合约地址来自配置/白名单,无硬编码 + - [ ] 调用失败有完整的错误处理和回退逻辑 + +#### 7.5 资金保护机制(BLOCKING) +- **限额控制**: + - ✅ 必需:单笔交易金额上限(如 $1000) + - ✅ 必需:日/周/月累计限额 + - ✅ 必需:异常交易频率限制(防止快速耗尽资金) + - ✅ 必需:大额交易需要多重签名或延迟执行 +- **紧急暂停**: + - ✅ 必需:全局紧急停止按钮(kill switch) + - ✅ 必需:异常检测自动暂停(如价格异常、Gas 费暴涨) + - ✅ 必需:暂停后资金安全提取机制 +- **余额监控**: + - ✅ 必需:实时余额监控,低于阈值告警 + - ✅ 必需:异常资金流出告警(大额转出、未知接收方) + - ✅ 必需:定期对账(链上余额 vs 系统记录) +- **验证检查**: + - [ ] 限额配置存在且合理 + - [ ] 紧急暂停功能可测试且有权限控制 + - [ ] 余额监控代码存在且接入告警系统 + +#### 7.6 链上数据验证(CRITICAL) +- **预言机安全**: + - ❌ 禁止:单一数据源(可被操纵) + - ✅ 必需:多预言机对比(Chainlink、Band、UMA 等) + - ✅ 必需:价格偏差检测(多源价格差异超阈值拒绝) + - ✅ 必需:时间戳验证(数据新鲜度检查,拒绝过期数据) +- **区块确认**: + - ✅ 必需:等待足够的区块确认(主网建议 ≥12 块,L2 根据实际情况) + - ✅ 必需:处理链重组可能(pending → confirmed → finalized) + - ✅ 必需:交易回执验证(status=1 成功) +- **数据完整性**: + - ✅ 必需:事件日志完整性检查(topic、参数匹配) + - ✅ 必需:合约状态一致性验证(链上 vs 本地缓存) + - ✅ 必需:MEV 保护(使用私有内存池或 Flashbots) +- **验证检查**: + - [ ] 价格数据来自多个预言机 + - [ ] 区块确认数配置合理 + - [ ] 交易状态检查包含 finalized 状态 + +## 审查结果 + +请给出以下三种结果之一: +- ✅ **通过**:可以直接提交 +- ❌ **不通过**:存在BLOCKING问题,必须修复 +- ⚠️ **需要修复**:有改进空间,建议修复 + +## 核心原则 + +1. **白盒逻辑正确性是根本**:业务逻辑错误是生死线 +2. **需求驱动**:必须找到真实需求来源 +3. **客观分析**:基于实际代码和需求,不自我欺骗 +4. **actionable建议**:提供具体的修复指导 + +## 评审交付物(必须包含) +- **问题清单**:逐条指出"谁与谁不一致"(路径/参数/字段/事件/状态码),附最小复现样本。 +- **最小修复建议**:明确"谁改、改哪里、如何不破坏现有调用"(可附 1-3 行级 diff 建议)。 +- **兼容/过渡策略**:必要时说明双解析/版本前缀/灰度开关/降级方案。 +- **🚨 E2E验证报告**:对每个用户交互流程的完整追踪验证(MANDATORY) + +## 强制性E2E验证清单(必须逐项检查) +在给出审查结果前,必须完成以下验证: + +### ✅ 用户点击验证 +- [ ] 所有onClick处理器都能正确执行 +- [ ] 处理器中的navigate()调用指向正确的路径 +- [ ] 目标路径在路由配置中存在 +- [ ] 目标页面能正确处理URL参数 + +### ✅ 导航流程验证 +- [ ] 从点击到页面加载的完整路径畅通 +- [ ] URL参数正确传递和解析 +- [ ] 页面状态正确初始化 +- [ ] 用户看到预期的内容和界面 + +### ✅ 状态一致性验证 +- [ ] 点击后应用状态正确更新 +- [ ] UI界面反映状态变化 +- [ ] 没有状态不同步的问题 + +### ✅ 安全验证(Web3 AI 交易系统 - MANDATORY) +- [ ] **密钥安全**:无私钥泄露(日志/错误/前端/Git) +- [ ] **密钥管理**:私钥加密存储,无明文环境变量 +- [ ] **交易验证**:所有交易有签名验证、nonce、金额限制 +- [ ] **滑点保护**:价格/滑点验证存在且合理 +- [ ] **AI 安全**:用户输入有消毒处理,无直接拼接到 prompt +- [ ] **决策审计**:AI 决策有完整日志(输入/输出/时间戳) +- [ ] **合约安全**:无无限授权,合约地址来自白名单 +- [ ] **限额保护**:存在单笔/累计交易限额 +- [ ] **紧急机制**:有 kill switch 或暂停功能 +- [ ] **预言机安全**:价格数据来自多源,有偏差检测 +- [ ] **确认机制**:大额交易需要用户明确确认 + +### ⛔ 审查失败条件 +如果以下任一项为真,审查必须标记为❌不通过: + +**功能性问题:** +- 存在navigate()指向不存在或错误的路径 +- 用户点击后无法到达预期页面 +- 状态更新不完整导致UI不一致 +- 关键用户流程无法完成 + +**安全性问题(Web3 AI 系统):** +- 私钥/助记词出现在日志、错误消息、前端代码、Git 历史中 +- 私钥明文存储(环境变量/配置文件/数据库) +- 交易缺少签名验证、nonce、或金额限制 +- 存在无限授权(`approve(spender, type(uint256).max)`) +- 用户输入直接拼接到 AI prompt(prompt injection 风险) +- AI 可以自主决定大额交易(无用户确认) +- 缺少紧急暂停机制 +- 单一预言机数据源(可被操纵) +- 大额交易无多重签名或延迟执行 + +**记住:代码编译通过 ≠ 功能正确工作 ≠ 资金安全** + +## 技术验证方法(MANDATORY) + +### 🔍 导航路径验证脚本 +执行以下检查来验证导航逻辑: +```bash +# 1. 找出所有navigate()调用 +grep -r "navigate(" frontend/src --include="*.tsx" --include="*.ts" -n + +# 2. 找出所有路由定义 +grep -r "path=" frontend/src --include="*.tsx" --include="*.ts" -n + +# 3. 检查URL参数处理 +grep -r "useSearchParams\|URLSearchParams" frontend/src --include="*.tsx" --include="*.ts" -n +``` + +### 🔍 状态管理验证 +```bash +# 检查状态更新逻辑 +grep -r "useState\|useEffect.*navigate" frontend/src --include="*.tsx" --include="*.ts" -n + +# 检查onClick处理器 +grep -r "onClick.*=>" frontend/src --include="*.tsx" --include="*.ts" -n +``` + +### 🚨 必须回答的验证问题 +对于每个用户交互,审查者必须回答: + +1. **点击发生什么?** + - onClick处理器具体做了什么操作? + - 调用了哪些函数?传递了什么参数? + +2. **导航去哪里?** + - navigate()的目标路径是什么? + - 这个路径在路由配置中存在吗? + - 路径参数格式正确吗? + +3. **目标页面做什么?** + - 目标页面/组件如何处理URL参数? + - 是否正确提取和使用参数? + - 用户最终看到什么内容? + +4. **状态是否一致?** + - 点击后应用状态如何变化? + - UI是否正确反映状态变化? + - 有没有状态不同步问题? + +**如果审查者无法回答这些问题,审查必须标记为❌不通过** + +## 快速验证提示 +- 端点集中来源:前端禁止硬编码 URL;新增/变更端点已同步到常量/SDK。 +- 认证一致:跨域/跨端口请求按需携带凭证(Cookie/Token),不依赖未声明的自定义头。 +- 异步降级:在不支持事件/流式或网络异常时具备降级路径与用户提示。 +- 可见性扫描:关键入口在默认态与目标设备上可见;空/错误/加载可复现且可恢复。 +- 自动化检查:加入简单脚本/CI 规则检查硬编码端点、路径格式、必需认证头/凭证的使用一致性。 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..9b9ec6700b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,50 @@ +# Git +.git +.gitignore +.github + +# Docker +Dockerfile +docker-compose.yml +.dockerignore + +# IDE +.idea +.vscode +*.swp +*.swo +*~ + +# Build artifacts +nofx +nofx_test +*.exe +*.dll +*.so +*.dylib + +# Test files +*_test.go +test_* + +# Documentation +*.md +!README.md +docs/ + +# Runtime data +decision_logs/ +coin_pool_cache/ +*.log + +# Config files (should be mounted) +config.json + +# Web build artifacts (but include source for multi-stage build) +web/node_modules/ +web/dist/ + +# Temporary files +tmp/ +temp/ +*.tmp diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000..cd64fe4e68 --- /dev/null +++ b/.env.example @@ -0,0 +1,14 @@ +# NOFX Environment Variables Template +# Copy this file to .env and modify the values as needed + +# Ports Configuration +# Backend API server port (internal: 8080, external: configurable) +NOFX_BACKEND_PORT=8080 + +# Frontend web interface port (Nginx listens on port 80 internally) +NOFX_FRONTEND_PORT=3000 + +# Timezone Setting +# System timezone for container time synchronization +NOFX_TIMEZONE=Asia/Shanghai + diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..46a2981448 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,135 @@ +# CODEOWNERS +# +# This file defines code ownership and automatic reviewer assignment. +# When a PR touches files matching these patterns, the listed users/teams +# will be automatically requested for review. +# +# 此文件定义代码所有权和自动 reviewer 分配。 +# 当 PR 涉及匹配这些模式的文件时,列出的用户/团队将自动被请求审查。 +# +# Syntax | 语法: +# pattern @username @org/team-name +# +# More specific patterns override less specific ones +# 更具体的模式会覆盖不太具体的模式 +# +# Documentation: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners + +# ============================================================================= +# Global Owners | 全局所有者 +# These users will be requested for review on ALL pull requests +# 这些用户将被请求审查所有 PR +# ============================================================================= + +* @hzb1115 @Icyoung @tangmengqiu @xqliu @SkywalkerJi + +# ============================================================================= +# Specific Component Owners | 特定组件所有者 +# Additional reviewers based on file paths (in addition to global owners) +# 基于文件路径的额外 reviewers(在全局 owners 之外) +# ============================================================================= + +# Backend / Go Code | 后端 / Go 代码 +# Go files and backend logic +*.go @xqliu @SkywalkerJi @hzb1115 @Icyoung @tangmengqiu +go.mod @xqliu @SkywalkerJi @hzb1115 @Icyoung @tangmengqiu +go.sum @xqliu @SkywalkerJi @hzb1115 @Icyoung @tangmengqiu + + +# Frontend / Web | 前端 / Web +# React/TypeScript frontend code +/web/ @0xEmberZz @hzb1115 @xqliu @tangmengqiu +/web/src/ @0xEmberZz @hzb1115 @xqliu @tangmengqiu +*.tsx @0xEmberZz @hzb1115 @xqliu @tangmengqiu +*.ts @0xEmberZz @hzb1115 @xqliu @tangmengqiu (frontend TypeScript only) +*.jsx @0xEmberZz @hzb1115 @xqliu @tangmengqiu +*.css @0xEmberZz @hzb1115 @xqliu @tangmengqiu +*.scss @0xEmberZz @hzb1115 @xqliu @tangmengqiu + +# Configuration Files | 配置文件 +*.json @0xEmberZz @hzb1115 @xqliu @tangmengqiu +*.yaml @0xEmberZz @hzb1115 @xqliu @tangmengqiu +*.yml @0xEmberZz @hzb1115 @xqliu @tangmengqiu +*.toml @0xEmberZz @hzb1115 @xqliu @tangmengqiu +*.ini @0xEmberZz @hzb1115 @xqliu @tangmengqiu + +# Documentation | 文档 +# Markdown and documentation files +*.md @hzb1115 @tangmengqiu +/docs/ @hzb1115 @tangmengqiu +README.md @hzb1115 @tangmengqiu + +# GitHub Workflows & Actions | GitHub 工作流和 Actions +# CI/CD configuration and automation +/.github/ @hzb1115 +/.github/workflows/ @hzb1115 +/.github/workflows/*.yml @hzb1115 + +# Docker | Docker 配置 +Dockerfile @tangmengqiu +docker-compose.yml @tangmengqiu +.dockerignore @tangmengqiu + +# Database | 数据库 +# Database migrations and schemas +/migrations/ @SkywalkerJi @hzb1115 @Icyoung @tangmengqiu +/db/ @SkywalkerJi @hzb1115 @Icyoung @tangmengqiu +*.sql @SkywalkerJi @hzb1115 @Icyoung @tangmengqiu + +# Scripts | 脚本 +/scripts/ @hzb1115 @xqliu @tangmengqiu +*.sh @hzb1115 @xqliu @tangmengqiu +*.bash @hzb1115 @tangmengqiu +*.py @hzb1115 @tangmengqiu (if Python scripts exist) + +# Tests | 测试 +# Test files require review from component owners +*_test.go @xqliu @SkywalkerJi @heronsbillC +/tests/ @xqliu @SkywalkerJi @Icyoung @heronsbillC +/web/tests/ @Icyoung @hzb1115 @heronsbillC + +# Security & Dependencies | 安全和依赖 +# Security-sensitive files require extra attention +.env.example @hzb1115 @tangmengqiu +.gitignore @hzb1115 @tangmengqiu +go.sum @xqliu @hzb1115 @tangmengqiu +package-lock.json @Icyoung @hzb1115 @tangmengqiu +yarn.lock @Icyoung @hzb1115 @tangmengqiu + +# Build Configuration | 构建配置 +Makefile @hzb1115 @xqliu @tangmengqiu +/build/ @hzb1115 @xqliu @tangmengqiu +/dist/ @hzb1115 @tangmengqiu + +# License & Legal | 许可证和法律文件 +LICENSE @hzb1115 +COPYING @hzb1115 + +# ============================================================================= +# Notes | 注意事项 +# ============================================================================= +# +# 1. All PRs will be assigned to the 5 global owners +# 所有 PR 都会分配给这 5 个全局 owners +# +# 2. Specific paths may add additional reviewers +# 特定路径可能会添加额外的 reviewers +# +# 3. PR author will NOT be requested for review (GitHub handles this) +# PR 作者不会被请求审查(GitHub 自动处理) +# +# 4. You can adjust patterns and owners as needed +# 你可以根据需要调整模式和 owners +# +# 5. To require multiple approvals, configure branch protection rules +# 要求多个批准,请配置分支保护规则 +# +# ⚠️ IMPORTANT - Permission Requirements | 重要 - 权限要求: +# - Users listed here will ONLY be auto-requested if they have Write+ permission +# 这里列出的用户只有在拥有 Write 或以上权限时才会被自动请求 +# - GitHub will silently skip users without proper permissions +# GitHub 会静默跳过没有适当权限的用户 +# - See CODEOWNERS_PERMISSIONS.md for details +# 详见 CODEOWNERS_PERMISSIONS.md +# +# ============================================================================= diff --git a/.github/ISSUE_TEMPLATE/bounty_claim.md b/.github/ISSUE_TEMPLATE/bounty_claim.md new file mode 100644 index 0000000000..1f76c15929 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bounty_claim.md @@ -0,0 +1,93 @@ +--- +name: Bounty Claim +about: Claim a bounty task or propose a new bounty +title: '[BOUNTY CLAIM] ' +labels: bounty +assignees: '' +--- + +## 💰 Bounty Information + +**Claiming existing bounty?** +- Issue #: +- Bounty amount: + +**OR proposing new bounty?** +- Proposed feature/fix: +- Estimated effort: [Small / Medium / Large] + +--- + +## 👤 About You + +**Name/Username:** + +**Contact:** +- GitHub: @your_username +- Telegram: @your_telegram (optional) +- Email: your@email.com (optional) + +**Relevant Experience:** +- +- +- + +--- + +## 📋 Implementation Plan + +### 1. Approach + +- How will you implement this? +- What components will be affected? +- Any dependencies or libraries needed? + +### 2. Timeline +- **Start date:** +- **Estimated completion:** +- **Milestones:** + - [ ] Week 1: ... + - [ ] Week 2: ... + - [ ] Week 3: ... + +### 3. Deliverables +- [ ] Working code (merged PR) +- [ ] Unit tests (if applicable) +- [ ] Documentation updates +- [ ] Demo video/screenshots + +--- + +## 🔍 Questions for Maintainers + + + +1. +2. +3. + +--- + +## 📚 References + + + +- + +--- + +## ✅ Acknowledgment + +By claiming this bounty, I acknowledge that: +- [ ] I have read the [Contributing Guide](../../CONTRIBUTING.md) +- [ ] I will follow the [Code of Conduct](../../CODE_OF_CONDUCT.md) +- [ ] I understand the acceptance criteria +- [ ] My contribution will be licensed under AGPL-3.0 License +- [ ] Payment is subject to successful PR merge + +--- + +**For maintainers:** +- [ ] Bounty claim approved +- [ ] Issue assigned to claimant +- [ ] Timeline agreed upon diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..d26f1d0a14 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,167 @@ +--- +name: Bug Report +about: Report a bug to help us improve NOFX +title: '[BUG] ' +labels: bug +assignees: '' +--- + +> **⚠️ Before submitting:** Please check the [Troubleshooting Guide](../../docs/guides/TROUBLESHOOTING.md) ([中文版](../../docs/guides/TROUBLESHOOTING.zh-CN.md)) to see if your issue can be resolved quickly. + +## 🐛 Bug Description + + + +## 🔍 Bug Category + +- [ ] Trading execution (orders not executing, wrong position size, etc.) +- [ ] AI decision issues (unexpected decisions, only opening one direction, etc.) +- [ ] Exchange connection (API errors, authentication failures, etc.) +- [ ] UI/Frontend (display issues, buttons not working, data not updating, etc.) +- [ ] Backend/API (server errors, crashes, performance issues, etc.) +- [ ] Configuration (settings not saving, database errors, etc.) +- [ ] Other: _________________ + +## 📋 Steps to Reproduce +1. Go to '...' +2. Click on '...' / Run command '...' +3. Configure '...' +4. See error + +## ✅ Expected Behavior + + + +## ❌ Actual Behavior + + + +## 📸 Screenshots & Logs + +### Frontend Error (if applicable) + + + + + + +**Browser Console Screenshot:** + + +**Network Tab (failed requests):** + + +### Backend Logs (if applicable) + + +**Docker users:** +```bash +# View backend logs +docker compose logs backend --tail=100 + +# OR continuously follow logs +docker compose logs -f backend +``` + +**Manual/PM2 users:** +```bash +# Terminal output where you ran: ./nofx +# OR PM2 logs: +pm2 logs nofx --lines 100 +``` + +**Backend Log Output:** +``` +Paste backend logs here (last 50-100 lines around the error) +``` + +### Trading/Decision Logs (if trading issue) + + + +**Decision Log Path:** `decision_logs/{trader_id}/{timestamp}.json` + +```json +{ + "paste relevant decision log here if applicable" +} +``` + +## 💻 Environment + +**System:** +- **OS:** [e.g. macOS 13, Ubuntu 22.04, Windows 11] +- **Deployment:** [Docker / Manual / PM2] + +**Backend:** +- **Go Version:** [run: `go version`] +- **NOFX Version:** [run: `git log -1 --oneline` or check release tag] + +**Frontend:** +- **Browser:** [e.g. Chrome 120, Firefox 121, Safari 17] +- **Node.js Version:** [run: `node -v`] + +**Trading Setup:** +- **Exchange:** [Binance / Hyperliquid / Aster] +- **Account Type:** [Main Account / Subaccount] +- **Position Mode:** [Hedge Mode (Dual) / One-way Mode] ← **Important for trading bugs!** +- **AI Model:** [DeepSeek / Qwen / Custom] +- **Number of Traders:** [e.g. 1, 2, etc.] + +## 🔧 Configuration (if relevant) + + + +**Leverage Settings:** +```json +{ + "btc_eth_leverage": 5, + "altcoin_leverage": 5 +} +``` + +**Any custom settings:** + + + +## 📊 Additional Context + +**Frequency:** +- [ ] Happens every time +- [ ] Happens randomly +- [ ] Happened once + +**Timeline:** +- Did this work before? [ ] Yes [ ] No +- When did it break? [e.g. after upgrade to v3.0.0, after changing config, etc.] +- Recent changes? [e.g. updated dependencies, changed exchange, etc.] + +**Impact:** +- [ ] System cannot start +- [ ] Trading stopped/broken +- [ ] UI broken but trading works +- [ ] Minor visual issue +- [ ] Other: _________________ + +## 💡 Possible Solution + + + +--- + +## 📝 Quick Tips for Faster Resolution + +**For Trading Issues:** +1. ✅ Check Binance position mode: Go to Futures → ⚙️ Preferences → Position Mode → Must be **Hedge Mode** +2. ✅ Verify API permissions: Futures trading must be enabled +3. ✅ Check decision logs in `decision_logs/{trader_id}/` for AI reasoning + +**For Connection Issues:** +4. ✅ Test API connectivity: `curl http://localhost:8080/api/health` +5. ✅ Check API rate limits on exchange +6. ✅ Verify API keys are not expired + +**For UI Issues:** +7. ✅ Hard refresh: Ctrl+Shift+R (or Cmd+Shift+R on Mac) +8. ✅ Check browser console (F12) for errors +9. ✅ Verify backend is running: `docker compose ps` or `ps aux | grep nofx` diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..7e508790ee --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,27 @@ +--- +name: Feature Request +about: Suggest a new feature for NOFX +title: '[FEATURE] ' +labels: enhancement +assignees: '' +--- + +## 📋 Feature Description + + +## 🎯 Problem to Solve + + +## 💡 Proposed Solution + + +## 🔧 Technical Details + + +## ✅ Acceptance Criteria + +- [ ] Item 1 +- [ ] Item 2 + +## 📚 Additional Context + diff --git a/.github/PR_TITLE_GUIDE.md b/.github/PR_TITLE_GUIDE.md new file mode 100644 index 0000000000..95fbc12c7c --- /dev/null +++ b/.github/PR_TITLE_GUIDE.md @@ -0,0 +1,322 @@ +# PR 标题指南 + +## 📋 概述 + +我们使用 **Conventional Commits** 格式来保持 PR 标题的一致性,但这是**建议性的**,不会阻止你的 PR 被合并。 + +## ✅ 推荐格式 + +``` +type(scope): description +``` + +### 示例 + +``` +feat(trader): add new trading strategy +fix(api): resolve authentication issue +docs: update README +chore(deps): update dependencies +ci(workflow): improve GitHub Actions +``` + +--- + +## 📖 详细说明 + +### Type(类型)- 必需 + +描述这次变更的类型: + +| Type | 说明 | 示例 | +|------|------|------| +| `feat` | 新功能 | `feat(trader): add stop-loss feature` | +| `fix` | Bug 修复 | `fix(api): handle null response` | +| `docs` | 文档变更 | `docs: update installation guide` | +| `style` | 代码格式(不影响代码运行) | `style: format code with prettier` | +| `refactor` | 重构(既不是新功能也不是修复) | `refactor(exchange): simplify connection logic` | +| `perf` | 性能优化 | `perf(ai): optimize prompt processing` | +| `test` | 添加或修改测试 | `test(trader): add unit tests` | +| `chore` | 构建过程或辅助工具的变动 | `chore: update dependencies` | +| `ci` | CI/CD 相关变更 | `ci: add test coverage report` | +| `security` | 安全相关修复 | `security: update vulnerable dependencies` | +| `build` | 构建系统或外部依赖项变更 | `build: upgrade webpack to v5` | + +### Scope(范围)- 可选 + +描述这次变更影响的范围: + +| Scope | 说明 | +|-------|------| +| `exchange` | 交易所相关 | +| `trader` | 交易员/交易策略 | +| `ai` | AI 模型相关 | +| `api` | API 接口 | +| `ui` | 用户界面 | +| `frontend` | 前端代码 | +| `backend` | 后端代码 | +| `security` | 安全相关 | +| `deps` | 依赖项 | +| `workflow` | GitHub Actions workflows | +| `github` | GitHub 配置 | +| `actions` | GitHub Actions | +| `config` | 配置文件 | +| `docker` | Docker 相关 | +| `build` | 构建相关 | +| `release` | 发布相关 | + +**注意:** 如果变更影响多个范围,可以省略 scope 或选择最主要的。 + +### Description(描述)- 必需 + +- 使用现在时态("add" 而不是 "added") +- 首字母小写 +- 结尾不加句号 +- 简洁明了地描述变更内容 + +--- + +## 🎯 完整示例 + +### ✅ 好的 PR 标题 + +``` +feat(trader): add risk management system +fix(exchange): resolve connection timeout issue +docs: add API documentation for trading endpoints +style: apply consistent code formatting +refactor(ai): simplify prompt processing logic +perf(backend): optimize database queries +test(api): add integration tests for auth +chore(deps): update TypeScript to 5.0 +ci(workflow): add automated security scanning +security(api): fix SQL injection vulnerability +build(docker): optimize Docker image size +``` + +### ⚠️ 需要改进的标题 + +| 不好的标题 | 问题 | 改进后 | +|-----------|------|--------| +| `update code` | 太模糊 | `refactor(trader): simplify order execution logic` | +| `Fixed bug` | 首字母大写,不够具体 | `fix(api): handle edge case in login` | +| `Add new feature.` | 有句号,不够具体 | `feat(ui): add dark mode toggle` | +| `changes` | 完全不符合格式 | `chore: update dependencies` | +| `feat: Added new trading algo` | 时态错误 | `feat(trader): add new trading algorithm` | + +--- + +## 🤖 自动检查行为 + +### 当 PR 标题不符合格式时 + +1. **不会阻止合并** ✅ + - 检查会标记为"建议" + - PR 仍然可以被审查和合并 + +2. **会收到友好提示** 💬 + - 机器人会在 PR 中留言 + - 提供格式说明和示例 + - 建议如何改进标题 + +3. **可以随时更新** 🔄 + - 更新 PR 标题后会重新检查 + - 无需关闭和重新打开 PR + +### 示例评论 + +如果你的 PR 标题是 `update workflow`,你会收到这样的评论: + +```markdown +## ⚠️ PR Title Format Suggestion + +Your PR title doesn't follow the Conventional Commits format, +but this won't block your PR from being merged. + +**Current title:** `update workflow` + +**Recommended format:** `type(scope): description` + +### Valid types: +feat, fix, docs, style, refactor, perf, test, chore, ci, security, build + +### Common scopes (optional): +exchange, trader, ai, api, ui, frontend, backend, security, deps, +workflow, github, actions, config, docker, build, release + +### Examples: +- feat(trader): add new trading strategy +- fix(api): resolve authentication issue +- docs: update README +- chore(deps): update dependencies +- ci(workflow): improve GitHub Actions + +**Note:** This is a suggestion to improve consistency. +Your PR can still be reviewed and merged. +``` + +--- + +## 🔧 配置详情 + +### 支持的 Types + +在 `.github/workflows/pr-checks.yml` 中配置: + +```yaml +types: | + feat + fix + docs + style + refactor + perf + test + chore + ci + security + build +``` + +### 支持的 Scopes + +```yaml +scopes: | + exchange + trader + ai + api + ui + frontend + backend + security + deps + workflow + github + actions + config + docker + build + release +``` + +### 添加新的 Scope + +如果你需要添加新的 scope,请: + +1. 在 `.github/workflows/pr-checks.yml` 的 `scopes` 部分添加 +2. 在 `.github/workflows/pr-checks-run.yml` 更新正则表达式(可选) +3. 更新本文档 + +--- + +## 📚 为什么使用 Conventional Commits? + +### 优点 + +1. **自动化 Changelog** 📝 + - 可以自动生成版本更新日志 + - 清晰地分类各种变更 + +2. **语义化版本** 🔢 + - `feat` → MINOR 版本(1.1.0) + - `fix` → PATCH 版本(1.0.1) + - `BREAKING CHANGE` → MAJOR 版本(2.0.0) + +3. **更好的可读性** 👀 + - 一眼看出 PR 的目的 + - 更容易浏览 Git 历史 + +4. **团队协作** 🤝 + - 统一的提交风格 + - 降低沟通成本 + +### 示例:自动生成的 Changelog + +```markdown +## v1.2.0 (2025-11-02) + +### Features +- **trader**: add risk management system (#123) +- **ui**: add dark mode toggle (#125) + +### Bug Fixes +- **api**: resolve authentication issue (#124) +- **exchange**: fix connection timeout (#126) + +### Documentation +- update API documentation (#127) +``` + +--- + +## 🎓 学习资源 + +- **Conventional Commits 官网:** https://www.conventionalcommits.org/ +- **Angular Commit Guidelines:** https://github.com/angular/angular/blob/main/CONTRIBUTING.md#commit +- **Semantic Versioning:** https://semver.org/ + +--- + +## ❓ FAQ + +### Q: 我必须遵循这个格式吗? + +**A:** 不必须。这是建议性的,不会阻止你的 PR 被合并。但遵循格式可以提高项目的可维护性。 + +### Q: 如果我忘记了怎么办? + +**A:** 机器人会在 PR 中提醒你,你可以随时更新标题。 + +### Q: 我可以在一个 PR 中做多种类型的变更吗? + +**A:** 可以,但建议: +- 选择最主要的类型 +- 或者考虑拆分成多个 PR(更易于审查) + +### Q: Scope 可以省略吗? + +**A:** 可以。`requireScope: false` 表示 scope 是可选的。 + +示例:`docs: update README` (没有 scope 也可以) + +### Q: 我想添加新的 type 或 scope,怎么做? + +**A:** 提一个 PR 修改 `.github/workflows/pr-checks.yml`,并在本文档中说明新增项的用途。 + +### Q: Breaking Changes 怎么表示? + +**A:** 在描述中添加 `BREAKING CHANGE:` 或在 type 后加 `!`: + +``` +feat!: remove deprecated API +feat(api)!: change authentication method + +BREAKING CHANGE: The old /auth endpoint is removed +``` + +--- + +## 📊 统计 + +想看项目的 commit 类型分布?运行: + +```bash +git log --oneline --no-merges | \ + grep -oE '^[a-f0-9]+ (feat|fix|docs|style|refactor|perf|test|chore|ci|security|build)' | \ + cut -d' ' -f2 | sort | uniq -c | sort -rn +``` + +--- + +## ✅ 快速检查清单 + +在提交 PR 前,检查你的标题是否: + +- [ ] 包含有效的 type(feat, fix, docs 等) +- [ ] 使用小写字母开头 +- [ ] 使用现在时态("add" 而不是 "added") +- [ ] 简洁明了(最好在 50 字符内) +- [ ] 准确描述了变更内容 + +**记住:** 这些都是建议,不是强制要求! diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..4ca3deb330 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,104 @@ +# Pull Request | PR 提交 + +> **📋 选择专用模板 | Choose Specialized Template** +> +> 我们现在提供了针对不同类型PR的专用模板,帮助你更快速地填写PR信息: +> We now offer specialized templates for different types of PRs to help you fill out the information faster: +> +> - 🔧 **[Backend PR Template](./PULL_REQUEST_TEMPLATE/backend.md)** | 后端PR模板 - For Go/API/Trading changes +> - 🎨 **[Frontend PR Template](./PULL_REQUEST_TEMPLATE/frontend.md)** | 前端PR模板 - For UI/UX changes +> - 📝 **[Documentation PR Template](./PULL_REQUEST_TEMPLATE/docs.md)** | 文档PR模板 - For documentation updates +> - 📦 **[General PR Template](./PULL_REQUEST_TEMPLATE/general.md)** | 通用PR模板 - For mixed or other changes +> +> **如何使用?| How to use?** +> - 创建PR时,在URL中添加 `?template=backend.md` 或其他模板名称 +> - When creating a PR, add `?template=backend.md` or other template name to the URL +> - 或者直接复制粘贴对应模板的内容 +> - Or simply copy and paste the content from the corresponding template + +--- + +> **💡 提示 Tip:** 推荐 PR 标题格式 `type(scope): description` +> 例如: `feat(trader): add new strategy` | `fix(api): resolve auth issue` + +--- + +## 📝 Description | 描述 + +**English:** | **中文:** + + + +--- + +## 🎯 Type of Change | 变更类型 + +- [ ] 🐛 Bug fix | 修复 Bug +- [ ] ✨ New feature | 新功能 +- [ ] 💥 Breaking change | 破坏性变更 +- [ ] 📝 Documentation update | 文档更新 +- [ ] 🎨 Code style update | 代码样式更新 +- [ ] ♻️ Refactoring | 重构 +- [ ] ⚡ Performance improvement | 性能优化 +- [ ] ✅ Test update | 测试更新 +- [ ] 🔧 Build/config change | 构建/配置变更 +- [ ] 🔒 Security fix | 安全修复 + +--- + +## 🔗 Related Issues | 相关 Issue + +- Closes # | 关闭 # +- Related to # | 相关 # + +--- + +## 📋 Changes Made | 具体变更 + +**English:** | **中文:** +- +- + +--- + +## 🧪 Testing | 测试 + +- [ ] Tested locally | 本地测试通过 +- [ ] Tests pass | 测试通过 +- [ ] Verified no existing functionality broke | 确认没有破坏现有功能 + +--- + +## ✅ Checklist | 检查清单 + +### Code Quality | 代码质量 +- [ ] Code follows project style | 代码遵循项目风格 +- [ ] Self-review completed | 已完成代码自查 +- [ ] Comments added for complex logic | 已添加必要注释 + +### Documentation | 文档 +- [ ] Updated relevant documentation | 已更新相关文档 + +### Git +- [ ] Commits follow conventional format | 提交遵循 Conventional Commits 格式 +- [ ] Rebased on latest `dev` branch | 已 rebase 到最新 `dev` 分支 +- [ ] No merge conflicts | 无合并冲突 + +--- + +## 📚 Additional Notes | 补充说明 + +**English:** | **中文:** + + +--- + +**By submitting this PR, I confirm | 提交此 PR,我确认:** + +- [ ] I have read the [Contributing Guidelines](../CONTRIBUTING.md) | 已阅读贡献指南 +- [ ] I agree to the [Code of Conduct](../CODE_OF_CONDUCT.md) | 同意行为准则 +- [ ] My contribution is licensed under AGPL-3.0 | 贡献遵循 AGPL-3.0 许可证 + +--- + +🌟 **Thank you for your contribution! | 感谢你的贡献!** diff --git a/.github/PULL_REQUEST_TEMPLATE/README.md b/.github/PULL_REQUEST_TEMPLATE/README.md new file mode 100644 index 0000000000..f0478ba78f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/README.md @@ -0,0 +1,213 @@ +# PR Templates | PR 模板 + +## 📋 模板概述 | Template Overview + +我们提供了4种针对不同类型PR的专用模板,帮助贡献者快速填写PR信息: +We offer 4 specialized templates for different types of PRs to help contributors quickly fill out PR information: + +### 1. 🔧 Backend Template | 后端模板 +**文件:** `backend.md` + +**适用于 | Use for:** +- Go代码变更 | Go code changes +- API端点开发 | API endpoint development +- 交易逻辑实现 | Trading logic implementation +- 后端性能优化 | Backend performance optimization +- 数据库相关改动 | Database-related changes + +**包含 | Includes:** +- Go测试环境配置 | Go test environment +- 安全考虑检查 | Security considerations +- 性能影响评估 | Performance impact assessment +- `go fmt` 和 `go build` 检查 | `go fmt` and `go build` checks + +### 2. 🎨 Frontend Template | 前端模板 +**文件:** `frontend.md` + +**适用于 | Use for:** +- UI/UX变更 | UI/UX changes +- React/Vue组件开发 | React/Vue component development +- 前端样式更新 | Frontend styling updates +- 浏览器兼容性修复 | Browser compatibility fixes +- 前端性能优化 | Frontend performance optimization + +**包含 | Includes:** +- 截图/演示要求 | Screenshots/demo requirements +- 浏览器测试清单 | Browser testing checklist +- 国际化检查 | Internationalization checks +- 响应式设计验证 | Responsive design verification +- `npm run lint` 和 `npm run build` 检查 | Linting and build checks + +### 3. 📝 Documentation Template | 文档模板 +**文件:** `docs.md` + +**适用于 | Use for:** +- README更新 | README updates +- API文档编写 | API documentation +- 教程和指南 | Tutorials and guides +- 代码注释改进 | Code comment improvements +- 翻译工作 | Translation work + +**包含 | Includes:** +- 文档类型分类 | Documentation type classification +- 内容质量检查 | Content quality checks +- 双语要求(中英文)| Bilingual requirements (EN/CN) +- 链接有效性验证 | Link validity verification + +### 4. 📦 General Template | 通用模板 +**文件:** `general.md` + +**适用于 | Use for:** +- 混合类型变更 | Mixed-type changes +- 跨多个领域的PR | Cross-domain PRs +- 构建配置变更 | Build configuration changes +- 依赖更新 | Dependency updates +- 不确定使用哪个模板时 | When unsure which template to use + +## 🤖 自动模板建议 | Automatic Template Suggestion + +我们的GitHub Action会自动分析你的PR并建议最合适的模板: +Our GitHub Action automatically analyzes your PR and suggests the most suitable template: + +### 工作原理 | How it works: + +1. **文件分析 | File Analysis** + - 检测PR中所有变更的文件类型 + - Detects all changed file types in the PR + +2. **智能判断 | Smart Detection** + - 如果 >50% 是 `.go` 文件 → 建议**后端模板** + - If >50% are `.go` files → Suggests **Backend template** + - 如果 >50% 是 `.js/.ts/.tsx/.vue` 文件 → 建议**前端模板** + - If >50% are `.js/.ts/.tsx/.vue` files → Suggests **Frontend template** + - 如果 >70% 是 `.md` 文件 → 建议**文档模板** + - If >70% are `.md` files → Suggests **Documentation template** + +3. **自动评论 | Auto-comment** + - 如果检测到你使用了默认模板,但应该用专用模板 + - If it detects you're using the default template but should use a specialized one + - 会自动添加友好的评论建议 + - It will automatically add a friendly comment suggestion + +4. **自动标签 | Auto-labeling** + - 自动添加对应的标签:`backend`、`frontend`、`documentation` + - Automatically adds corresponding labels: `backend`, `frontend`, `documentation` + +## 📖 使用方法 | How to Use + +### 方法1: URL参数(推荐) | Method 1: URL Parameter (Recommended) + +创建PR时,在URL末尾添加模板参数: +When creating a PR, add the template parameter to the URL: + +``` +https://github.com/YOUR_ORG/nofx/compare/dev...YOUR_BRANCH?template=backend.md +``` + +替换 `backend.md` 为: +Replace `backend.md` with: +- `backend.md` - 后端模板 | Backend template +- `frontend.md` - 前端模板 | Frontend template +- `docs.md` - 文档模板 | Documentation template +- `general.md` - 通用模板 | General template + +### 方法2: 手动选择 | Method 2: Manual Selection + +1. 创建PR时,默认模板会显示 + When creating a PR, the default template will be shown + +2. 根据顶部的指引链接,点击查看对应的模板 + Follow the guidance links at the top to view the corresponding template + +3. 复制模板内容到PR描述中 + Copy the template content into the PR description + +### 方法3: 跟随自动建议 | Method 3: Follow Auto-suggestion + +1. 使用任何模板创建PR + Create a PR with any template + +2. GitHub Action会自动分析并评论建议 + GitHub Action will automatically analyze and comment with a suggestion + +3. 根据建议更新PR描述 + Update the PR description based on the suggestion + +## 🎯 最佳实践 | Best Practices + +1. **提前选择 | Choose in Advance** + - 在创建PR前确定变更类型 + - Determine the change type before creating the PR + +2. **完整填写 | Complete Filling** + - 不要跳过必填项(标记为 required) + - Don't skip required items + +3. **保持简洁 | Keep it Concise** + - 描述清晰但简洁 + - Keep descriptions clear but concise + +4. **添加截图 | Add Screenshots** + - 对于UI变更,务必添加截图 + - For UI changes, always add screenshots + +5. **测试证明 | Test Evidence** + - 提供测试通过的证据 + - Provide evidence that tests pass + +## 🔧 自定义 | Customization + +如果需要修改模板或自动检测逻辑: +If you need to modify templates or auto-detection logic: + +1. **修改模板** | **Modify Templates** + - 编辑 `.github/PULL_REQUEST_TEMPLATE/*.md` 文件 + - Edit `.github/PULL_REQUEST_TEMPLATE/*.md` files + +2. **调整检测阈值** | **Adjust Detection Threshold** + - 编辑 `.github/workflows/pr-template-suggester.yml` + - Edit `.github/workflows/pr-template-suggester.yml` + - 修改文件类型占比阈值(当前:50%后端,50%前端,70%文档) + - Modify file type percentage thresholds (current: 50% backend, 50% frontend, 70% docs) + +3. **添加新模板** | **Add New Template** + - 在 `PULL_REQUEST_TEMPLATE/` 目录创建新的 `.md` 文件 + - Create a new `.md` file in the `PULL_REQUEST_TEMPLATE/` directory + - 更新工作流以支持新的文件类型检测 + - Update the workflow to support new file type detection + +## ❓ FAQ + +**Q: 我的PR既有前端又有后端代码,用哪个模板?** +**Q: My PR has both frontend and backend code, which template should I use?** + +A: 使用**通用模板**(`general.md`),或选择主要变更类型的模板。 +A: Use the **General template** (`general.md`), or choose the template for the primary change type. + +--- + +**Q: 自动建议的模板不合适怎么办?** +**Q: What if the automatically suggested template is not suitable?** + +A: 你可以忽略建议,继续使用当前模板。自动建议仅供参考。 +A: You can ignore the suggestion and continue using the current template. Auto-suggestions are for reference only. + +--- + +**Q: 可以不使用任何模板吗?** +**Q: Can I not use any template?** + +A: 不推荐。模板帮助确保PR包含必要信息,加快审查速度。 +A: Not recommended. Templates help ensure PRs contain necessary information and speed up reviews. + +--- + +**Q: 如何禁用自动模板建议?** +**Q: How to disable automatic template suggestions?** + +A: 删除或禁用 `.github/workflows/pr-template-suggester.yml` 文件。 +A: Delete or disable the `.github/workflows/pr-template-suggester.yml` file. + +--- + +🌟 **感谢使用我们的PR模板系统!| Thank you for using our PR template system!** diff --git a/.github/PULL_REQUEST_TEMPLATE/backend.md b/.github/PULL_REQUEST_TEMPLATE/backend.md new file mode 100644 index 0000000000..dfc354c30b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/backend.md @@ -0,0 +1,121 @@ +# Pull Request - Backend | 后端 PR + +> **💡 提示 Tip:** 推荐 PR 标题格式 `type(scope): description` +> 例如: `feat(trader): add new strategy` | `fix(api): resolve auth issue` + +--- + +## 📝 Description | 描述 + +**English:** | **中文:** + + + +--- + +## 🎯 Type of Change | 变更类型 + +- [ ] 🐛 Bug fix | 修复 Bug +- [ ] ✨ New feature | 新功能 +- [ ] 💥 Breaking change | 破坏性变更 +- [ ] ♻️ Refactoring | 重构 +- [ ] ⚡ Performance improvement | 性能优化 +- [ ] 🔒 Security fix | 安全修复 +- [ ] 🔧 Build/config change | 构建/配置变更 + +--- + +## 🔗 Related Issues | 相关 Issue + +- Closes # | 关闭 # +- Related to # | 相关 # + +--- + +## 📋 Changes Made | 具体变更 + +**English:** | **中文:** +- +- + +--- + +## 🧪 Testing | 测试 + +### Test Environment | 测试环境 +- **OS | 操作系统:** +- **Go Version | Go 版本:** +- **Exchange | 交易所:** [if applicable | 如适用] + +### Manual Testing | 手动测试 +- [ ] Tested locally | 本地测试通过 +- [ ] Tested on testnet | 测试网测试通过(交易所集成相关) +- [ ] Unit tests pass | 单元测试通过 +- [ ] Verified no existing functionality broke | 确认没有破坏现有功能 + +### Test Results | 测试结果 +``` +Test output here | 测试输出 +``` + +--- + +## 🔒 Security Considerations | 安全考虑 + +- [ ] No API keys or secrets hardcoded | 没有硬编码 API 密钥 +- [ ] User inputs properly validated | 用户输入已正确验证 +- [ ] No SQL injection vulnerabilities | 无 SQL 注入漏洞 +- [ ] Authentication/authorization properly handled | 认证/授权正确处理 +- [ ] Sensitive data is encrypted | 敏感数据已加密 +- [ ] N/A (not security-related) | 不适用 + +--- + +## ⚡ Performance Impact | 性能影响 + +- [ ] No significant performance impact | 无显著性能影响 +- [ ] Performance improved | 性能提升 +- [ ] Performance may be impacted (explain below) | 性能可能受影响 + +**If impacted, explain | 如果受影响,请说明:** + + +--- + +## ✅ Checklist | 检查清单 + +### Code Quality | 代码质量 +- [ ] Code follows project style | 代码遵循项目风格 +- [ ] Self-review completed | 已完成代码自查 +- [ ] Comments added for complex logic | 已添加必要注释 +- [ ] Code compiles successfully | 代码编译成功 (`go build`) +- [ ] Ran `go fmt` | 已运行 `go fmt` + +### Documentation | 文档 +- [ ] Updated relevant documentation | 已更新相关文档 +- [ ] Added inline comments where necessary | 已添加必要的代码注释 +- [ ] Updated API documentation (if applicable) | 已更新 API 文档 + +### Git +- [ ] Commits follow conventional format | 提交遵循 Conventional Commits 格式 +- [ ] Rebased on latest `dev` branch | 已 rebase 到最新 `dev` 分支 +- [ ] No merge conflicts | 无合并冲突 + +--- + +## 📚 Additional Notes | 补充说明 + +**English:** | **中文:** + + +--- + +**By submitting this PR, I confirm | 提交此 PR,我确认:** + +- [ ] I have read the [Contributing Guidelines](../../CONTRIBUTING.md) | 已阅读贡献指南 +- [ ] I agree to the [Code of Conduct](../../CODE_OF_CONDUCT.md) | 同意行为准则 +- [ ] My contribution is licensed under AGPL-3.0 | 贡献遵循 AGPL-3.0 许可证 + +--- + +🌟 **Thank you for your contribution! | 感谢你的贡献!** diff --git a/.github/PULL_REQUEST_TEMPLATE/docs.md b/.github/PULL_REQUEST_TEMPLATE/docs.md new file mode 100644 index 0000000000..2ce9a90cd1 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/docs.md @@ -0,0 +1,97 @@ +# Pull Request - Documentation | 文档 PR + +> **💡 提示 Tip:** 推荐 PR 标题格式 `docs(scope): description` +> 例如: `docs(api): update trading endpoints` | `docs(readme): add setup guide` + +--- + +## 📝 Description | 描述 + +**English:** | **中文:** + + +--- + +## 📚 Type of Documentation | 文档类型 + +- [ ] 📖 README update | README 更新 +- [ ] 📋 API documentation | API 文档 +- [ ] 🎓 Tutorial/Guide | 教程/指南 +- [ ] 📝 Code comments | 代码注释 +- [ ] 🔧 Configuration docs | 配置文档 +- [ ] 🐛 Fix typo/error | 修复拼写/错误 +- [ ] 🌍 Translation | 翻译 + +--- + +## 🔗 Related Issues | 相关 Issue + +- Closes # | 关闭 # +- Related to # | 相关 # + +--- + +## 📋 Changes Made | 具体变更 + +**English:** | **中文:** +- +- + +--- + +## 📸 Screenshots (if applicable) | 截图(如适用) + + + + + +--- + +## 🌐 Internationalization | 国际化 + +- [ ] English version complete | 英文版本完整 +- [ ] Chinese version complete | 中文版本完整 +- [ ] Both versions are consistent | 两个版本内容一致 +- [ ] N/A (only one language needed) | 不适用(只需要一种语言) + +--- + +## ✅ Checklist | 检查清单 + +### Content Quality | 内容质量 +- [ ] Information is accurate and up-to-date | 信息准确且最新 +- [ ] Language is clear and concise | 语言清晰简洁 +- [ ] No spelling or grammar errors | 无拼写或语法错误 +- [ ] Links are valid and working | 链接有效且可用 +- [ ] Code examples are tested and working | 代码示例已测试且可用 +- [ ] Formatting is consistent | 格式一致 + +### Documentation Standards | 文档标准 +- [ ] Follows project documentation style | 遵循项目文档风格 +- [ ] Includes necessary examples | 包含必要的示例 +- [ ] Technical terms are explained | 技术术语已解释 +- [ ] Self-review completed | 已完成自查 + +### Git +- [ ] Commits follow conventional format | 提交遵循 Conventional Commits 格式 +- [ ] Rebased on latest `dev` branch | 已 rebase 到最新 `dev` 分支 +- [ ] No merge conflicts | 无合并冲突 + +--- + +## 📚 Additional Notes | 补充说明 + +**English:** | **中文:** + + +--- + +**By submitting this PR, I confirm | 提交此 PR,我确认:** + +- [ ] I have read the [Contributing Guidelines](../../CONTRIBUTING.md) | 已阅读贡献指南 +- [ ] I agree to the [Code of Conduct](../../CODE_OF_CONDUCT.md) | 同意行为准则 +- [ ] My contribution is licensed under AGPL-3.0 | 贡献遵循 AGPL-3.0 许可证 + +--- + +🌟 **Thank you for your contribution! | 感谢你的贡献!** diff --git a/.github/PULL_REQUEST_TEMPLATE/frontend.md b/.github/PULL_REQUEST_TEMPLATE/frontend.md new file mode 100644 index 0000000000..b95a20d09e --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/frontend.md @@ -0,0 +1,119 @@ +# Pull Request - Frontend | 前端 PR + +> **💡 提示 Tip:** 推荐 PR 标题格式 `type(scope): description` +> 例如: `feat(ui): add dark mode toggle` | `fix(form): resolve validation bug` + +--- + +## 📝 Description | 描述 + +**English:** | **中文:** + + +--- + +## 🎯 Type of Change | 变更类型 + +- [ ] 🐛 Bug fix | 修复 Bug +- [ ] ✨ New feature | 新功能 +- [ ] 💥 Breaking change | 破坏性变更 +- [ ] 🎨 Code style update | 代码样式更新 +- [ ] ♻️ Refactoring | 重构 +- [ ] ⚡ Performance improvement | 性能优化 + +--- + +## 🔗 Related Issues | 相关 Issue + +- Closes # | 关闭 # +- Related to # | 相关 # + +--- + +## 📋 Changes Made | 具体变更 + +**English:** | **中文:** +- +- + +--- + +## 📸 Screenshots / Demo | 截图/演示 + + + + +**Before | 变更前:** + + +**After | 变更后:** + + +--- + +## 🧪 Testing | 测试 + +### Test Environment | 测试环境 +- **OS | 操作系统:** +- **Node Version | Node 版本:** +- **Browser(s) | 浏览器:** + +### Manual Testing | 手动测试 +- [ ] Tested in development mode | 开发模式测试通过 +- [ ] Tested production build | 生产构建测试通过 +- [ ] Tested on multiple browsers | 多浏览器测试通过 +- [ ] Tested responsive design | 响应式设计测试通过 +- [ ] Verified no existing functionality broke | 确认没有破坏现有功能 + +--- + +## 🌐 Internationalization | 国际化 + +- [ ] All user-facing text supports i18n | 所有面向用户的文本支持国际化 +- [ ] Both English and Chinese versions provided | 提供了中英文版本 +- [ ] N/A | 不适用 + +--- + +## ✅ Checklist | 检查清单 + +### Code Quality | 代码质量 +- [ ] Code follows project style | 代码遵循项目风格 +- [ ] Self-review completed | 已完成代码自查 +- [ ] Comments added for complex logic | 已添加必要注释 +- [ ] Code builds successfully | 代码构建成功 (`npm run build`) +- [ ] Ran `npm run lint` | 已运行 `npm run lint` +- [ ] No console errors or warnings | 无控制台错误或警告 + +### Testing | 测试 +- [ ] Component tests added/updated | 已添加/更新组件测试 +- [ ] Tests pass locally | 测试在本地通过 + +### Documentation | 文档 +- [ ] Updated relevant documentation | 已更新相关文档 +- [ ] Updated type definitions (TypeScript) | 已更新类型定义 +- [ ] Added JSDoc comments where necessary | 已添加 JSDoc 注释 + +### Git +- [ ] Commits follow conventional format | 提交遵循 Conventional Commits 格式 +- [ ] Rebased on latest `dev` branch | 已 rebase 到最新 `dev` 分支 +- [ ] No merge conflicts | 无合并冲突 + +--- + +## 📚 Additional Notes | 补充说明 + +**English:** | **中文:** + + +--- + +**By submitting this PR, I confirm | 提交此 PR,我确认:** + +- [ ] I have read the [Contributing Guidelines](../../CONTRIBUTING.md) | 已阅读贡献指南 +- [ ] I agree to the [Code of Conduct](../../CODE_OF_CONDUCT.md) | 同意行为准则 +- [ ] My contribution is licensed under AGPL-3.0 | 贡献遵循 AGPL-3.0 许可证 + +--- + +🌟 **Thank you for your contribution! | 感谢你的贡献!** diff --git a/.github/PULL_REQUEST_TEMPLATE/general.md b/.github/PULL_REQUEST_TEMPLATE/general.md new file mode 100644 index 0000000000..23773e4ce4 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/general.md @@ -0,0 +1,98 @@ +# Pull Request - General | 通用 PR + +> **💡 提示 Tip:** 推荐 PR 标题格式 `type(scope): description` +> 例如: `feat(trader): add new strategy` | `fix(api): resolve auth issue` | `docs(readme): update` + +--- + +## 📝 Description | 描述 + +**English:** | **中文:** + + +--- + +## 🎯 Type of Change | 变更类型 + +- [ ] 🐛 Bug fix | 修复 Bug +- [ ] ✨ New feature | 新功能 +- [ ] 💥 Breaking change | 破坏性变更 +- [ ] 📝 Documentation update | 文档更新 +- [ ] 🎨 Code style update | 代码样式更新 +- [ ] ♻️ Refactoring | 重构 +- [ ] ⚡ Performance improvement | 性能优化 +- [ ] ✅ Test update | 测试更新 +- [ ] 🔧 Build/config change | 构建/配置变更 +- [ ] 🔒 Security fix | 安全修复 + +--- + +## 🔗 Related Issues | 相关 Issue + +- Closes # | 关闭 # +- Related to # | 相关 # + +--- + +## 📋 Changes Made | 具体变更 + +**English:** | **中文:** +- +- + +--- + +## 🧪 Testing | 测试 + +- [ ] Tested locally | 本地测试通过 +- [ ] Tests pass | 测试通过 +- [ ] Verified no existing functionality broke | 确认没有破坏现有功能 + +**Test details | 测试详情:** + + +--- + +## ✅ Checklist | 检查清单 + +### Code Quality | 代码质量 +- [ ] Code follows project style | 代码遵循项目风格 +- [ ] Self-review completed | 已完成代码自查 +- [ ] Comments added for complex logic | 已添加必要注释 +- [ ] No new warnings or errors | 无新的警告或错误 + +### Documentation | 文档 +- [ ] Updated relevant documentation | 已更新相关文档 +- [ ] Added inline comments where necessary | 已添加必要的代码注释 + +### Git +- [ ] Commits follow conventional format | 提交遵循 Conventional Commits 格式 +- [ ] Rebased on latest `dev` branch | 已 rebase 到最新 `dev` 分支 +- [ ] No merge conflicts | 无合并冲突 + +--- + +## 🔒 Security (if applicable) | 安全(如适用) + +- [ ] No API keys or secrets hardcoded | 没有硬编码 API 密钥 +- [ ] User inputs properly validated | 用户输入已正确验证 +- [ ] N/A | 不适用 + +--- + +## 📚 Additional Notes | 补充说明 + +**English:** | **中文:** + + +--- + +**By submitting this PR, I confirm | 提交此 PR,我确认:** + +- [ ] I have read the [Contributing Guidelines](../../CONTRIBUTING.md) | 已阅读贡献指南 +- [ ] I agree to the [Code of Conduct](../../CODE_OF_CONDUCT.md) | 同意行为准则 +- [ ] My contribution is licensed under AGPL-3.0 | 贡献遵循 AGPL-3.0 许可证 + +--- + +🌟 **Thank you for your contribution! | 感谢你的贡献!** diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000000..89bcae3fb0 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,127 @@ +# Auto-labeler configuration +# Automatically adds labels based on changed files + +# Area: Frontend +'area: frontend': + - changed-files: + - any-glob-to-any-file: + - 'web/**/*' + - '*.tsx' + - '*.ts' + - '*.jsx' + - '*.js' + - '*.css' + +# Area: Backend +'area: backend': + - changed-files: + - any-glob-to-any-file: + - '**/*.go' + - 'go.mod' + - 'go.sum' + - 'cmd/**/*' + - 'internal/**/*' + - 'pkg/**/*' + +# Area: Exchange +'area: exchange': + - changed-files: + - any-glob-to-any-file: + - 'internal/exchange/**/*' + - 'pkg/exchange/**/*' + - '**/binance*.go' + - '**/hyperliquid*.go' + - '**/aster*.go' + - '**/okx*.go' + - '**/bybit*.go' + +# Area: AI +'area: ai': + - changed-files: + - any-glob-to-any-file: + - 'internal/ai/**/*' + - 'pkg/ai/**/*' + - '**/deepseek*.go' + - '**/qwen*.go' + - '**/openai*.go' + - '**/claude*.go' + +# Area: API +'area: api': + - changed-files: + - any-glob-to-any-file: + - 'internal/api/**/*' + - 'pkg/api/**/*' + - '**/handler*.go' + - '**/router*.go' + +# Area: Security +'area: security': + - changed-files: + - any-glob-to-any-file: + - '**/auth*.go' + - '**/jwt*.go' + - '**/encryption*.go' + - '**/crypto*.go' + - 'SECURITY.md' + +# Area: Database +'area: database': + - changed-files: + - any-glob-to-any-file: + - 'internal/database/**/*' + - 'internal/db/**/*' + - '**/migration*.go' + - '**/*.sql' + - '**/schema*.go' + +# Area: UI/UX +'area: ui/ux': + - changed-files: + - any-glob-to-any-file: + - 'web/src/components/**/*' + - 'web/src/pages/**/*' + - '**/*.css' + - '**/style*.ts' + +# Area: Deployment +'area: deployment': + - changed-files: + - any-glob-to-any-file: + - 'Dockerfile' + - 'docker-compose*.yml' + - '.github/workflows/**/*' + - 'start.sh' + - '**/*deploy*.md' + +# Type: Documentation +'type: documentation': + - changed-files: + - any-glob-to-any-file: + - 'docs/**/*' + - '*.md' + - 'README*' + - 'CHANGELOG*' + - 'CONTRIBUTING.md' + - 'CODE_OF_CONDUCT.md' + +# Type: Test +'type: test': + - changed-files: + - any-glob-to-any-file: + - '**/*_test.go' + - 'test/**/*' + - '**/*.test.ts' + - '**/*.test.tsx' + - '**/*.spec.ts' + +# Dependencies +'dependencies': + - changed-files: + - any-glob-to-any-file: + - 'go.mod' + - 'go.sum' + - 'package.json' + - 'package-lock.json' + - 'web/package.json' + - 'web/package-lock.json' diff --git a/.github/labels.yml b/.github/labels.yml new file mode 100644 index 0000000000..26cadba424 --- /dev/null +++ b/.github/labels.yml @@ -0,0 +1,180 @@ +# GitHub Labels Configuration +# Use https://github.com/crazy-max/ghaction-github-labeler to sync labels + +# Priority Labels +- name: "priority: critical" + color: "d73a4a" + description: "Critical priority - requires immediate attention" + +- name: "priority: high" + color: "ff6b6b" + description: "High priority - should be addressed soon" + +- name: "priority: medium" + color: "fbca04" + description: "Medium priority - normal queue" + +- name: "priority: low" + color: "0e8a16" + description: "Low priority - nice to have" + +# Type Labels +- name: "type: bug" + color: "d73a4a" + description: "Something isn't working" + +- name: "type: feature" + color: "a2eeef" + description: "New feature or request" + +- name: "type: enhancement" + color: "84b6eb" + description: "Improvement to existing feature" + +- name: "type: documentation" + color: "0075ca" + description: "Documentation improvements" + +- name: "type: security" + color: "ee0701" + description: "Security-related changes" + +- name: "type: performance" + color: "f9d0c4" + description: "Performance improvements" + +- name: "type: refactor" + color: "fbca04" + description: "Code refactoring" + +- name: "type: test" + color: "c5def5" + description: "Test-related changes" + +# Status Labels +- name: "status: needs review" + color: "fbca04" + description: "PR is ready for review" + +- name: "status: needs changes" + color: "d93f0b" + description: "PR needs changes based on review" + +- name: "status: on hold" + color: "fef2c0" + description: "PR/issue is on hold" + +- name: "status: in progress" + color: "0e8a16" + description: "Currently being worked on" + +- name: "status: blocked" + color: "d93f0b" + description: "Blocked by another issue/PR" + +# Area Labels (aligned with roadmap) +- name: "area: security" + color: "ee0701" + description: "Security enhancements (Phase 1.1)" + +- name: "area: ai" + color: "7057ff" + description: "AI capabilities and models (Phase 1.2)" + +- name: "area: exchange" + color: "0075ca" + description: "Exchange integrations (Phase 1.3)" + +- name: "area: architecture" + color: "d4c5f9" + description: "Project structure refactoring (Phase 1.4)" + +- name: "area: ui/ux" + color: "c2e0c6" + description: "User experience improvements (Phase 1.5)" + +- name: "area: frontend" + color: "bfdadc" + description: "Frontend (React/TypeScript)" + +- name: "area: backend" + color: "c5def5" + description: "Backend (Go)" + +- name: "area: api" + color: "0e8a16" + description: "API endpoints" + +- name: "area: database" + color: "f9d0c4" + description: "Database changes" + +- name: "area: deployment" + color: "fbca04" + description: "Deployment and CI/CD" + +# Special Labels +- name: "good first issue" + color: "7057ff" + description: "Good for newcomers" + +- name: "help wanted" + color: "008672" + description: "Extra attention is needed" + +- name: "bounty" + color: "1d76db" + description: "Bounty available for this issue" + +- name: "bounty: claimed" + color: "5319e7" + description: "Bounty has been claimed" + +- name: "bounty: paid" + color: "0e8a16" + description: "Bounty has been paid" + +- name: "RFC" + color: "d4c5f9" + description: "Request for Comments - needs discussion" + +- name: "breaking change" + color: "d73a4a" + description: "Includes breaking changes" + +- name: "duplicate" + color: "cfd3d7" + description: "This issue or pull request already exists" + +- name: "invalid" + color: "e4e669" + description: "This doesn't seem right" + +- name: "wontfix" + color: "ffffff" + description: "This will not be worked on" + +- name: "dependencies" + color: "0366d6" + description: "Dependency updates" + +# Roadmap Phases +- name: "roadmap: phase-1" + color: "0e8a16" + description: "Core Infrastructure Enhancement" + +- name: "roadmap: phase-2" + color: "fbca04" + description: "Testing & Stability" + +- name: "roadmap: phase-3" + color: "0075ca" + description: "Universal Market Expansion" + +- name: "roadmap: phase-4" + color: "7057ff" + description: "Advanced AI & Automation" + +- name: "roadmap: phase-5" + color: "d73a4a" + description: "Enterprise & Scaling" diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000000..5eb6f9852e --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,176 @@ +# GitHub Actions Workflows + +This directory contains the GitHub Actions workflows for the NOFX project. + +## 📚 Documentation Index + +- **[README.md](./README.md)** - This file, overview of all workflows +- **[PERMISSIONS.md](./PERMISSIONS.md)** - Detailed permission analysis and security model +- **[TRIGGERS.md](./TRIGGERS.md)** - Comparison of event triggers (pull_request vs pull_request_target vs workflow_run) +- **[FORK_PR_FLOW.md](./FORK_PR_FLOW.md)** - Complete analysis of what happens when a fork PR is submitted +- **[FLOW_DIAGRAM.md](./FLOW_DIAGRAM.md)** - Visual flow diagrams and quick reference +- **[SECRETS_SCANNING.md](./SECRETS_SCANNING.md)** - Secrets scanning solutions and TruffleHog setup + +## 🚀 Quick Start + +**Want to understand how fork PRs work?** → Read [FLOW_DIAGRAM.md](./FLOW_DIAGRAM.md) + +**Need security details?** → Read [PERMISSIONS.md](./PERMISSIONS.md) + +**Confused about triggers?** → Read [TRIGGERS.md](./TRIGGERS.md) + +## PR Check Workflows + +We use a **two-workflow pattern** to safely handle PR checks from both internal and fork PRs: + +### 1. `pr-checks-run.yml` - Execute Checks + +**Trigger:** On pull request (opened, synchronize, reopened) + +**Permissions:** Read-only + +**Purpose:** Executes all PR checks with read-only permissions, making it safe for fork PRs. + +**What it does:** +- ✅ Checks PR title format (Conventional Commits) +- ✅ Calculates PR size +- ✅ Runs backend checks (Go formatting, vet, tests) +- ✅ Runs frontend checks (linting, type checking, build) +- ✅ Saves all results as artifacts + +**Security:** Safe for fork PRs because it only has read permissions and cannot access secrets or modify the repository. + +### 2. `pr-checks-comment.yml` - Post Results + +**Trigger:** When `pr-checks-run.yml` completes (workflow_run) + +**Permissions:** Write (pull-requests, issues) + +**Purpose:** Posts check results as PR comments, running in the main repository context. + +**What it does:** +- ✅ Downloads artifacts from `pr-checks-run.yml` +- ✅ Reads check results +- ✅ Posts a comprehensive comment to the PR + +**Security:** Safe because: +- Runs in the main repository context (not fork context) +- Has write permissions but doesn't execute untrusted code +- Only reads pre-generated results from artifacts + +### 3. `pr-checks.yml` - Strict Checks + +**Trigger:** On pull request + +**Permissions:** Read + conditional write + +**Purpose:** Runs mandatory checks that must pass before PR can be merged. + +**What it does:** +- ✅ Validates PR title (blocks merge if invalid) +- ✅ Auto-labels PR based on size and files changed (non-fork only) +- ✅ Runs backend tests (Go) +- ✅ Runs frontend tests (React/TypeScript) +- ✅ Security scanning (Trivy, Gitleaks) + +**Security:** +- Fork PRs: Only runs read-only operations (tests, security scans) +- Non-fork PRs: Can add labels and comments +- Uses `continue-on-error` for operations that may fail on forks + +## Why Two Workflows for PR Checks? + +### The Problem + +When a PR comes from a forked repository: +- GitHub restricts `GITHUB_TOKEN` permissions for security +- Fork PRs cannot write comments, add labels, or access secrets +- This prevents malicious contributors from: + - Stealing repository secrets + - Modifying workflow files to execute malicious code + - Spamming issues/PRs with automated comments + +### The Solution + +**Two-Workflow Pattern:** + +``` +Fork PR Submitted + ↓ +[pr-checks-run.yml] + - Runs with read-only permissions + - Executes all checks safely + - Saves results to artifacts + ↓ +[pr-checks-comment.yml] + - Triggered by workflow_run + - Runs in main repo context (has write permissions) + - Downloads artifacts + - Posts comment with results +``` + +This approach: +- ✅ Allows fork PRs to run checks +- ✅ Safely posts results as comments +- ✅ Prevents security vulnerabilities +- ✅ Follows GitHub's best practices + +### Can workflow_run Comment on Fork PRs? + +**Yes! ✅ The permissions are sufficient.** + +**Key Understanding:** +- `workflow_run` executes in the **base repository** context +- Fork PRs exist in the **base repository** (not in the fork) +- The base repository's `GITHUB_TOKEN` has write permissions +- Therefore, `workflow_run` can comment on fork PRs + +**Security:** +- Fork PR code runs in isolated environment (read-only) +- Comment workflow doesn't execute fork code +- Only reads pre-generated artifact data + +**For detailed permission analysis, see:** [PERMISSIONS.md](./PERMISSIONS.md) + +## Workflow Comparison + +| Workflow | Fork PRs | Write Access | Blocks Merge | Purpose | +|----------|----------|--------------|--------------|---------| +| `pr-checks-run.yml` | ✅ Yes | ❌ No | ❌ No | Advisory checks | +| `pr-checks-comment.yml` | ✅ Yes | ✅ Yes* | ❌ No | Post results | +| `pr-checks.yml` | ✅ Yes | ⚠️ Partial | ✅ Yes | Mandatory checks | + +\* Write access only in main repo context, not available to fork PR code + +## File History + +- `pr-checks-advisory.yml.old` - Old advisory workflow that failed on fork PRs (deprecated) +- Now replaced by the two-workflow pattern (`pr-checks-run.yml` + `pr-checks-comment.yml`) + +## Testing the Workflows + +### Test with a Fork PR + +1. Fork the repository +2. Make changes in your fork +3. Create a PR to the main repository +4. Observe: + - `pr-checks-run.yml` runs successfully with read-only access + - `pr-checks-comment.yml` posts results as a comment + - `pr-checks.yml` runs tests but skips labeling + +### Test with a Branch PR + +1. Create a branch in the main repository +2. Make changes +3. Create a PR +4. Observe: + - All workflows run with full permissions + - Labels are added automatically + - Comments are posted + +## References + +- [GitHub Actions: Keeping your GitHub Actions and workflows secure Part 1](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/) +- [Safely posting comments from untrusted workflows](https://securitylab.github.com/research/github-actions-building-blocks/) +- [GitHub Actions: workflow_run trigger](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_run) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 0000000000..8c5403d19b --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,181 @@ +name: Build and Push Docker Images + +on: + push: + branches: + - main + - dev + tags: + - 'v*' + pull_request: + branches: + - main + - dev + workflow_dispatch: + +env: + REGISTRY_GHCR: ghcr.io + +jobs: + prepare: + name: Prepare repository metadata + runs-on: ubuntu-22.04 + outputs: + image_base: ${{ steps.lowercase.outputs.image_base }} + steps: + - name: Convert repository name to lowercase + id: lowercase + run: | + REPO_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') + echo "image_base=${REPO_LOWER}" >> $GITHUB_OUTPUT + echo "Lowercase repository: ${REPO_LOWER}" + + build-and-push: + name: Build ${{ matrix.name }} (${{ matrix.arch_tag }}) + needs: prepare + runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + include: + - name: backend + dockerfile: ./docker/Dockerfile.backend + image_suffix: backend + platform: linux/amd64 + arch_tag: amd64 + runner: ubuntu-22.04 + - name: backend + dockerfile: ./docker/Dockerfile.backend + image_suffix: backend + platform: linux/arm64 + arch_tag: arm64 + runner: ubuntu-22.04-arm + - name: frontend + dockerfile: ./docker/Dockerfile.frontend + image_suffix: frontend + platform: linux/amd64 + arch_tag: amd64 + runner: ubuntu-22.04 + - name: frontend + dockerfile: ./docker/Dockerfile.frontend + image_suffix: frontend + platform: linux/arm64 + arch_tag: arm64 + runner: ubuntu-22.04-arm + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY_GHCR }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + continue-on-error: true + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.REGISTRY_GHCR }}/${{ needs.prepare.outputs.image_base }}/nofx-${{ matrix.image_suffix }} + ${{ secrets.DOCKERHUB_USERNAME && format('{0}/nofx-{1}', secrets.DOCKERHUB_USERNAME, matrix.image_suffix) || '' }} + tags: | + type=ref,event=branch,suffix=-${{ matrix.arch_tag }} + type=semver,pattern={{version}},suffix=-${{ matrix.arch_tag }} + type=semver,pattern={{major}}.{{minor}},suffix=-${{ matrix.arch_tag }} + type=semver,pattern={{major}},suffix=-${{ matrix.arch_tag }} + type=sha,prefix={{branch}}-,suffix=-${{ matrix.arch_tag }} + + - name: Build and push ${{ matrix.name }}-${{ matrix.arch_tag }} image + uses: docker/build-push-action@v5 + with: + context: . + file: ${{ matrix.dockerfile }} + platforms: ${{ matrix.platform }} + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=${{ matrix.name }}-${{ matrix.arch_tag }} + cache-to: type=gha,mode=max,scope=${{ matrix.name }}-${{ matrix.arch_tag }} + build-args: | + BUILD_DATE=${{ github.event.head_commit.timestamp }} + VCS_REF=${{ github.sha }} + VERSION=${{ github.ref_name }} + + - name: Image digest + run: | + echo "✅ Built: ${{ matrix.name }}-${{ matrix.arch_tag }}" + echo "Platform: ${{ matrix.platform }}" + echo "Tags: ${{ steps.meta.outputs.tags }}" + + create-manifest: + name: Create multi-arch manifests + if: github.event_name != 'pull_request' + needs: [prepare, build-and-push] + runs-on: ubuntu-22.04 + permissions: + contents: read + packages: write + strategy: + matrix: + image_suffix: [backend, frontend] + steps: + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY_GHCR }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + continue-on-error: true + + - name: Create and push multi-arch manifest + env: + IMAGE_BASE: ${{ needs.prepare.outputs.image_base }} + run: | + REF_NAME="${{ github.ref_name }}" + GHCR_IMAGE="${{ env.REGISTRY_GHCR }}/${IMAGE_BASE}/nofx-${{ matrix.image_suffix }}" + + echo "📦 Creating manifest for ${{ matrix.image_suffix }}" + echo "Repository: ${IMAGE_BASE}" + echo "Image: ${GHCR_IMAGE}" + + docker buildx imagetools create -t "${GHCR_IMAGE}:${REF_NAME}" \ + "${GHCR_IMAGE}:${REF_NAME}-amd64" \ + "${GHCR_IMAGE}:${REF_NAME}-arm64" + + if [[ "${{ github.ref }}" == "refs/heads/main" ]] || [[ "${{ github.ref }}" == "refs/heads/dev" ]]; then + docker buildx imagetools create -t "${GHCR_IMAGE}:latest" \ + "${GHCR_IMAGE}:${REF_NAME}-amd64" \ + "${GHCR_IMAGE}:${REF_NAME}-arm64" + echo "✅ Created latest tag" + fi + + if [[ -n "${{ secrets.DOCKERHUB_USERNAME }}" ]]; then + DOCKERHUB_IMAGE="${{ secrets.DOCKERHUB_USERNAME }}/nofx-${{ matrix.image_suffix }}" + docker buildx imagetools create -t "${DOCKERHUB_IMAGE}:${REF_NAME}" \ + "${DOCKERHUB_IMAGE}:${REF_NAME}-amd64" \ + "${DOCKERHUB_IMAGE}:${REF_NAME}-arm64" || true + echo "✅ Created Docker Hub manifest" + fi + + echo "🎉 Multi-arch manifest created successfully!" diff --git a/.github/workflows/pr-checks-advisory.yml.old b/.github/workflows/pr-checks-advisory.yml.old new file mode 100644 index 0000000000..2fb62b4290 --- /dev/null +++ b/.github/workflows/pr-checks-advisory.yml.old @@ -0,0 +1,331 @@ +name: PR Checks (Advisory) + +on: + pull_request: + types: [opened, synchronize, reopened] + branches: [main, dev] + +# These checks are advisory only - they won't block PR merging +# Results will be posted as comments to help contributors improve their PRs + +permissions: + contents: write + pull-requests: write + checks: write + issues: write + +jobs: + pr-info: + name: PR Information + runs-on: ubuntu-latest + steps: + - name: Check PR title format + id: check-title + run: | + PR_TITLE="${{ github.event.pull_request.title }}" + + # Check if title follows conventional commits + if echo "$PR_TITLE" | grep -qE "^(feat|fix|docs|style|refactor|perf|test|chore|ci|security)(\(.+\))?: .+"; then + echo "status=✅ Good" >> $GITHUB_OUTPUT + echo "message=PR title follows Conventional Commits format" >> $GITHUB_OUTPUT + else + echo "status=⚠️ Suggestion" >> $GITHUB_OUTPUT + echo "message=Consider using Conventional Commits format: type(scope): description" >> $GITHUB_OUTPUT + fi + + - name: Calculate PR size + id: pr-size + run: | + ADDITIONS=${{ github.event.pull_request.additions }} + DELETIONS=${{ github.event.pull_request.deletions }} + TOTAL=$((ADDITIONS + DELETIONS)) + + if [ $TOTAL -lt 100 ]; then + echo "size=🟢 Small" >> $GITHUB_OUTPUT + echo "label=size: small" >> $GITHUB_OUTPUT + elif [ $TOTAL -lt 500 ]; then + echo "size=🟡 Medium" >> $GITHUB_OUTPUT + echo "label=size: medium" >> $GITHUB_OUTPUT + else + echo "size=🔴 Large" >> $GITHUB_OUTPUT + echo "label=size: large" >> $GITHUB_OUTPUT + echo "suggestion=Consider breaking this into smaller PRs for easier review" >> $GITHUB_OUTPUT + fi + echo "lines=$TOTAL" >> $GITHUB_OUTPUT + + - name: Post advisory comment + uses: actions/github-script@v7 + with: + script: | + const titleStatus = '${{ steps.check-title.outputs.status }}'; + const titleMessage = '${{ steps.check-title.outputs.message }}'; + const prSize = '${{ steps.pr-size.outputs.size }}'; + const prLines = '${{ steps.pr-size.outputs.lines }}'; + const sizeSuggestion = '${{ steps.pr-size.outputs.suggestion }}' || ''; + + let comment = '## 🤖 PR Advisory Feedback\n\n'; + comment += 'Thank you for your contribution! Here\'s some automated feedback to help improve your PR:\n\n'; + comment += '### PR Title\n'; + comment += titleStatus + ' ' + titleMessage + '\n\n'; + comment += '### PR Size\n'; + comment += prSize + ' (' + prLines + ' lines changed)\n'; + if (sizeSuggestion) { + comment += '\n💡 **Suggestion:** ' + sizeSuggestion + '\n'; + } + comment += '\n---\n\n'; + comment += '### 📖 New PR Management System\n\n'; + comment += 'We\'re introducing a new PR management system! These checks are **advisory only** and won\'t block your PR.\n\n'; + comment += '**Want to check your PR against new standards?**\n'; + comment += '```bash\n'; + comment += '# Run the PR health check tool\n'; + comment += './scripts/pr-check.sh\n'; + comment += '```\n\n'; + comment += 'This tool will:\n'; + comment += '- 🔍 Analyze your PR (doesn\'t modify anything)\n'; + comment += '- ✅ Show what\'s already good\n'; + comment += '- ⚠️ Point out issues\n'; + comment += '- 💡 Give specific suggestions on how to fix\n\n'; + comment += '**Learn more:**\n'; + comment += '- [Migration Guide](https://github.com/tinkle-community/nofx/blob/dev/docs/community/MIGRATION_ANNOUNCEMENT.md)\n'; + comment += '- [Contributing Guidelines](https://github.com/tinkle-community/nofx/blob/dev/CONTRIBUTING.md)\n\n'; + comment += '**Questions?** Just ask in the comments! We\'re here to help. 🙏\n\n'; + comment += '---\n\n'; + comment += '*This is an automated message. It won\'t affect your PR being merged.*'; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); + + backend-checks: + name: Backend Checks (Advisory) + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.21' + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y libta-lib-dev || true + go mod download || true + + - name: Check Go formatting + id: go-fmt + continue-on-error: true + run: | + UNFORMATTED=$(gofmt -l . 2>/dev/null || echo "") + if [ -n "$UNFORMATTED" ]; then + echo "status=⚠️ Needs formatting" >> $GITHUB_OUTPUT + echo "files<> $GITHUB_OUTPUT + echo "$UNFORMATTED" | head -10 >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + else + echo "status=✅ Good" >> $GITHUB_OUTPUT + echo "files=" >> $GITHUB_OUTPUT + fi + + - name: Run go vet + id: go-vet + continue-on-error: true + run: | + if go vet ./... 2>&1 | tee vet-output.txt; then + echo "status=✅ Good" >> $GITHUB_OUTPUT + echo "output=" >> $GITHUB_OUTPUT + else + echo "status=⚠️ Issues found" >> $GITHUB_OUTPUT + echo "output<> $GITHUB_OUTPUT + cat vet-output.txt | head -20 >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + + - name: Run tests + id: go-test + continue-on-error: true + run: | + if go test ./... -v 2>&1 | tee test-output.txt; then + echo "status=✅ Passed" >> $GITHUB_OUTPUT + echo "output=" >> $GITHUB_OUTPUT + else + echo "status=⚠️ Failed" >> $GITHUB_OUTPUT + echo "output<> $GITHUB_OUTPUT + cat test-output.txt | tail -30 >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + + - name: Post backend feedback + if: always() + uses: actions/github-script@v7 + with: + script: | + const fmtStatus = '${{ steps.go-fmt.outputs.status }}' || '⚠️ Skipped'; + const vetStatus = '${{ steps.go-vet.outputs.status }}' || '⚠️ Skipped'; + const testStatus = '${{ steps.go-test.outputs.status }}' || '⚠️ Skipped'; + const fmtFiles = `${{ steps.go-fmt.outputs.files }}`; + const vetOutput = `${{ steps.go-vet.outputs.output }}`; + const testOutput = `${{ steps.go-test.outputs.output }}`; + + let comment = '## 🔧 Backend Checks (Advisory)\n\n'; + comment += '### Go Formatting\n'; + comment += fmtStatus + '\n'; + if (fmtFiles) { + comment += '\nFiles needing formatting:\n```\n' + fmtFiles + '\n```\n'; + } + comment += '\n### Go Vet\n'; + comment += vetStatus + '\n'; + if (vetOutput) { + comment += '\n```\n' + vetOutput.substring(0, 500) + '\n```\n'; + } + comment += '\n### Tests\n'; + comment += testStatus + '\n'; + if (testOutput) { + comment += '\n```\n' + testOutput.substring(0, 1000) + '\n```\n'; + } + comment += '\n---\n\n'; + comment += '💡 **To fix locally:**\n'; + comment += '```bash\n'; + comment += '# Format code\n'; + comment += 'go fmt ./...\n\n'; + comment += '# Check for issues\n'; + comment += 'go vet ./...\n\n'; + comment += '# Run tests\n'; + comment += 'go test ./...\n'; + comment += '```\n\n'; + comment += '*These checks are advisory and won\'t block merging. Need help? Just ask!*'; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); + + frontend-checks: + name: Frontend Checks (Advisory) + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Check if web directory exists + id: check-web + run: | + if [ -d "web" ]; then + echo "exists=true" >> $GITHUB_OUTPUT + else + echo "exists=false" >> $GITHUB_OUTPUT + fi + + - name: Install dependencies + if: steps.check-web.outputs.exists == 'true' + working-directory: ./web + continue-on-error: true + run: npm ci + + - name: Run linter + if: steps.check-web.outputs.exists == 'true' + id: lint + working-directory: ./web + continue-on-error: true + run: | + if npm run lint 2>&1 | tee lint-output.txt; then + echo "status=✅ Good" >> $GITHUB_OUTPUT + echo "output=" >> $GITHUB_OUTPUT + else + echo "status=⚠️ Issues found" >> $GITHUB_OUTPUT + echo "output<> $GITHUB_OUTPUT + cat lint-output.txt | head -20 >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + + - name: Type check + if: steps.check-web.outputs.exists == 'true' + id: typecheck + working-directory: ./web + continue-on-error: true + run: | + if npm run type-check 2>&1 | tee typecheck-output.txt; then + echo "status=✅ Good" >> $GITHUB_OUTPUT + echo "output=" >> $GITHUB_OUTPUT + else + echo "status=⚠️ Issues found" >> $GITHUB_OUTPUT + echo "output<> $GITHUB_OUTPUT + cat typecheck-output.txt | head -20 >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + + - name: Build + if: steps.check-web.outputs.exists == 'true' + id: build + working-directory: ./web + continue-on-error: true + run: | + if npm run build 2>&1 | tee build-output.txt; then + echo "status=✅ Success" >> $GITHUB_OUTPUT + echo "output=" >> $GITHUB_OUTPUT + else + echo "status=⚠️ Failed" >> $GITHUB_OUTPUT + echo "output<> $GITHUB_OUTPUT + cat build-output.txt | tail -20 >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + + - name: Post frontend feedback + if: always() && steps.check-web.outputs.exists == 'true' + uses: actions/github-script@v7 + with: + script: | + const lintStatus = '${{ steps.lint.outputs.status }}' || '⚠️ Skipped'; + const typecheckStatus = '${{ steps.typecheck.outputs.status }}' || '⚠️ Skipped'; + const buildStatus = '${{ steps.build.outputs.status }}' || '⚠️ Skipped'; + const lintOutput = `${{ steps.lint.outputs.output }}`; + const typecheckOutput = `${{ steps.typecheck.outputs.output }}`; + const buildOutput = `${{ steps.build.outputs.output }}`; + + let comment = '## ⚛️ Frontend Checks (Advisory)\n\n'; + comment += '### Linting\n'; + comment += lintStatus + '\n'; + if (lintOutput) { + comment += '\n```\n' + lintOutput.substring(0, 500) + '\n```\n'; + } + comment += '\n### Type Checking\n'; + comment += typecheckStatus + '\n'; + if (typecheckOutput) { + comment += '\n```\n' + typecheckOutput.substring(0, 500) + '\n```\n'; + } + comment += '\n### Build\n'; + comment += buildStatus + '\n'; + if (buildOutput) { + comment += '\n```\n' + buildOutput.substring(0, 500) + '\n```\n'; + } + comment += '\n---\n\n'; + comment += '💡 **To fix locally:**\n'; + comment += '```bash\n'; + comment += 'cd web\n\n'; + comment += '# Fix linting issues\n'; + comment += 'npm run lint -- --fix\n\n'; + comment += '# Check types\n'; + comment += 'npm run type-check\n\n'; + comment += '# Test build\n'; + comment += 'npm run build\n'; + comment += '```\n\n'; + comment += '*These checks are advisory and won\'t block merging. Need help? Just ask!*'; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); diff --git a/.github/workflows/pr-checks-comment.yml b/.github/workflows/pr-checks-comment.yml new file mode 100644 index 0000000000..8e46508fca --- /dev/null +++ b/.github/workflows/pr-checks-comment.yml @@ -0,0 +1,362 @@ +name: PR Checks - Comment + +# This workflow posts ADVISORY check results as comments +# Runs in the main repo context with write permissions (SAFE) +# Triggered after pr-checks-run.yml completes +# +# NOTE: PR title and size checks are handled by pr-checks.yml (no duplication) +# This workflow only posts backend/frontend advisory check results + +on: + workflow_run: + workflows: ["PR Checks - Run"] + types: [completed] + +# Write permissions - SAFE because runs in main repo context +# This token has write access to the base repository +# Fork PRs exist in the base repo, so we can comment on them +permissions: + pull-requests: write + issues: write + actions: read # Needed to download artifacts + +jobs: + comment: + name: Post Advisory Check Results + runs-on: ubuntu-latest + # Only run if the workflow was triggered by a pull_request event + if: github.event.workflow_run.event == 'pull_request' + steps: + - name: Download artifacts + id: download-artifacts + continue-on-error: true + uses: actions/download-artifact@v4 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + path: artifacts + + - name: Debug workflow run info + run: | + echo "=== Workflow Run Debug Info ===" + echo "Workflow Run ID: ${{ github.event.workflow_run.id }}" + echo "Workflow Run Event: ${{ github.event.workflow_run.event }}" + echo "Workflow Run Conclusion: ${{ github.event.workflow_run.conclusion }}" + echo "Workflow Run Head SHA: ${{ github.event.workflow_run.head_sha }}" + + - name: List downloaded artifacts + run: | + echo "=== Checking downloaded artifacts ===" + ls -la artifacts/ || echo "⚠️ No artifacts directory found" + find artifacts/ -type f || echo "⚠️ No files found in artifacts" + echo "" + echo "Artifact download result: ${{ steps.download-artifacts.outcome }}" + + - name: Read backend results + id: backend + continue-on-error: true + run: | + if [ -f artifacts/backend-results/backend-results.json ]; then + echo "=== Backend Results JSON ===" + cat artifacts/backend-results/backend-results.json + echo "pr_number=$(jq -r '.pr_number' artifacts/backend-results/backend-results.json)" >> $GITHUB_OUTPUT + echo "fmt_status=$(jq -r '.fmt_status' artifacts/backend-results/backend-results.json)" >> $GITHUB_OUTPUT + echo "vet_status=$(jq -r '.vet_status' artifacts/backend-results/backend-results.json)" >> $GITHUB_OUTPUT + echo "test_status=$(jq -r '.test_status' artifacts/backend-results/backend-results.json)" >> $GITHUB_OUTPUT + + # Read output files + if [ -f artifacts/backend-results/fmt-files.txt ]; then + echo "fmt_files<> $GITHUB_OUTPUT + cat artifacts/backend-results/fmt-files.txt >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + if [ -f artifacts/backend-results/vet-output-short.txt ]; then + echo "vet_output<> $GITHUB_OUTPUT + cat artifacts/backend-results/vet-output-short.txt >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + if [ -f artifacts/backend-results/test-output-short.txt ]; then + echo "test_output<> $GITHUB_OUTPUT + cat artifacts/backend-results/test-output-short.txt >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + else + echo "pr_number=0" >> $GITHUB_OUTPUT + echo "⚠️ Backend results artifact not found" + fi + + - name: Read frontend results + id: frontend + continue-on-error: true + run: | + if [ -f artifacts/frontend-results/frontend-results.json ]; then + echo "=== Frontend Results JSON ===" + cat artifacts/frontend-results/frontend-results.json + echo "build_status=$(jq -r '.build_status' artifacts/frontend-results/frontend-results.json)" >> $GITHUB_OUTPUT + + # Read output files + if [ -f artifacts/frontend-results/build-output-short.txt ]; then + echo "build_output<> $GITHUB_OUTPUT + cat artifacts/frontend-results/build-output-short.txt >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + else + echo "⚠️ Frontend results artifact not found" + fi + + - name: Get PR information + id: pr-info + if: steps.backend.outputs.pr_number != '0' + uses: actions/github-script@v7 + with: + script: | + const prNumber = ${{ steps.backend.outputs.pr_number }}; + + // Get PR details + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber + }); + + // Check PR title format (Conventional Commits) + const prTitle = pr.title; + const conventionalCommitPattern = /^(feat|fix|docs|style|refactor|perf|test|chore|ci|security|build)(\(.+\))?: .+/; + const titleValid = conventionalCommitPattern.test(prTitle); + + core.setOutput('pr_title', prTitle); + core.setOutput('title_valid', titleValid); + + // Calculate PR size + const additions = pr.additions; + const deletions = pr.deletions; + const total = additions + deletions; + + let size = ''; + let sizeEmoji = ''; + if (total < 300) { + size = 'Small'; + sizeEmoji = '🟢'; + } else if (total < 1000) { + size = 'Medium'; + sizeEmoji = '🟡'; + } else { + size = 'Large'; + sizeEmoji = '🔴'; + } + + core.setOutput('pr_size', size); + core.setOutput('size_emoji', sizeEmoji); + core.setOutput('total_lines', total); + core.setOutput('additions', additions); + core.setOutput('deletions', deletions); + + - name: Post advisory results comment + if: steps.backend.outputs.pr_number != '0' + uses: actions/github-script@v7 + with: + script: | + const prNumber = ${{ steps.backend.outputs.pr_number }}; + + let comment = '## 🤖 Advisory Check Results\n\n'; + comment += 'These are **advisory** checks to help improve code quality. They won\'t block your PR from being merged.\n\n'; + + // PR Information section + const prTitle = '${{ steps.pr-info.outputs.pr_title }}'; + const titleValid = '${{ steps.pr-info.outputs.title_valid }}' === 'true'; + const prSize = '${{ steps.pr-info.outputs.pr_size }}'; + const sizeEmoji = '${{ steps.pr-info.outputs.size_emoji }}'; + const totalLines = '${{ steps.pr-info.outputs.total_lines }}'; + const additions = '${{ steps.pr-info.outputs.additions }}'; + const deletions = '${{ steps.pr-info.outputs.deletions }}'; + + comment += '### 📋 PR Information\n\n'; + + // Title check + if (titleValid) { + comment += '**Title Format:** ✅ Good - Follows Conventional Commits\n'; + } else { + comment += '**Title Format:** ⚠️ Suggestion - Consider using `type(scope): description`\n'; + comment += '
Recommended format\n\n'; + comment += '**Valid types:** `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`, `ci`, `security`, `build`\n\n'; + comment += '**Examples:**\n'; + comment += '- `feat(trader): add new trading strategy`\n'; + comment += '- `fix(api): resolve authentication issue`\n'; + comment += '- `docs: update README`\n'; + comment += '
\n\n'; + } + + // Size check + comment += `**PR Size:** ${sizeEmoji} ${prSize} (${totalLines} lines: +${additions} -${deletions})\n`; + + if (prSize === 'Large') { + comment += '\n💡 **Suggestion:** This is a large PR. Consider breaking it into smaller, focused PRs for easier review.\n'; + } + + comment += '\n'; + + // Backend checks + const fmtStatus = '${{ steps.backend.outputs.fmt_status }}'; + const vetStatus = '${{ steps.backend.outputs.vet_status }}'; + const testStatus = '${{ steps.backend.outputs.test_status }}'; + + if (fmtStatus || vetStatus || testStatus) { + comment += '\n### 🔧 Backend Checks\n\n'; + + if (fmtStatus) { + comment += '**Go Formatting:** ' + fmtStatus + '\n'; + const fmtFiles = `${{ steps.backend.outputs.fmt_files }}`; + if (fmtFiles && fmtFiles.trim()) { + comment += '
Files needing formatting\n\n```\n' + fmtFiles + '\n```\n
\n\n'; + } + } + + if (vetStatus) { + comment += '**Go Vet:** ' + vetStatus + '\n'; + const vetOutput = `${{ steps.backend.outputs.vet_output }}`; + if (vetOutput && vetOutput.trim()) { + comment += '
Issues found\n\n```\n' + vetOutput.substring(0, 1000) + '\n```\n
\n\n'; + } + } + + if (testStatus) { + comment += '**Tests:** ' + testStatus + '\n'; + const testOutput = `${{ steps.backend.outputs.test_output }}`; + if (testOutput && testOutput.trim()) { + comment += '
Test output\n\n```\n' + testOutput.substring(0, 1000) + '\n```\n
\n\n'; + } + } + + comment += '\n**Fix locally:**\n'; + comment += '```bash\n'; + comment += 'go fmt ./... # Format code\n'; + comment += 'go vet ./... # Check for issues\n'; + comment += 'go test ./... # Run tests\n'; + comment += '```\n'; + } + + // Frontend checks + const buildStatus = '${{ steps.frontend.outputs.build_status }}'; + + if (buildStatus) { + comment += '\n### ⚛️ Frontend Checks\n\n'; + + comment += '**Build & Type Check:** ' + buildStatus + '\n'; + const buildOutput = `${{ steps.frontend.outputs.build_output }}`; + if (buildOutput && buildOutput.trim()) { + comment += '
Build output\n\n```\n' + buildOutput.substring(0, 1000) + '\n```\n
\n\n'; + } + + comment += '\n**Fix locally:**\n'; + comment += '```bash\n'; + comment += 'cd web\n'; + comment += 'npm run build # Test build (includes type checking)\n'; + comment += '```\n'; + } + + comment += '\n---\n\n'; + comment += '### 📖 Resources\n\n'; + comment += '- [Contributing Guidelines](https://github.com/tinkle-community/nofx/blob/dev/CONTRIBUTING.md)\n'; + comment += '- [Migration Guide](https://github.com/tinkle-community/nofx/blob/dev/docs/community/MIGRATION_ANNOUNCEMENT.md)\n\n'; + comment += '**Questions?** Feel free to ask in the comments! 🙏\n\n'; + comment += '---\n\n'; + comment += '*These checks are advisory and won\'t block your PR from being merged. This comment is automatically generated from [pr-checks-run.yml](https://github.com/tinkle-community/nofx/blob/dev/.github/workflows/pr-checks-run.yml).*'; + + // Post comment + await github.rest.issues.createComment({ + issue_number: prNumber, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); + + - name: Post fallback comment if no results + if: steps.backend.outputs.pr_number == '0' + uses: actions/github-script@v7 + with: + script: | + // Try to get PR number from the workflow_run event + const pulls = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + head: `${context.repo.owner}:${{ github.event.workflow_run.head_branch }}` + }); + + if (pulls.data.length === 0) { + console.log('⚠️ Could not find PR for this workflow run'); + return; + } + + const pr = pulls.data[0]; + const prNumber = pr.number; + + // Get PR information for fallback comment + const prTitle = pr.title; + const conventionalCommitPattern = /^(feat|fix|docs|style|refactor|perf|test|chore|ci|security|build)(\(.+\))?: .+/; + const titleValid = conventionalCommitPattern.test(prTitle); + + const additions = pr.additions || 0; + const deletions = pr.deletions || 0; + const total = additions + deletions; + + let size = ''; + let sizeEmoji = ''; + if (total < 300) { + size = 'Small'; + sizeEmoji = '🟢'; + } else if (total < 1000) { + size = 'Medium'; + sizeEmoji = '🟡'; + } else { + size = 'Large'; + sizeEmoji = '🔴'; + } + + let comment = '## ⚠️ Advisory Checks - Results Unavailable\n\n'; + comment += 'The advisory checks workflow completed, but results could not be retrieved.\n\n'; + + // Add PR Information + comment += '### 📋 PR Information\n\n'; + + if (titleValid) { + comment += '**Title Format:** ✅ Good - Follows Conventional Commits\n'; + } else { + comment += '**Title Format:** ⚠️ Suggestion - Consider using `type(scope): description`\n'; + } + + comment += `**PR Size:** ${sizeEmoji} ${size} (${total} lines: +${additions} -${deletions})\n\n`; + + if (size === 'Large') { + comment += '💡 **Suggestion:** This is a large PR. Consider breaking it into smaller, focused PRs for easier review.\n\n'; + } + + comment += '---\n\n'; + comment += '### ⚠️ Backend/Frontend Check Results\n\n'; + comment += 'Results could not be retrieved.\n\n'; + comment += '**Possible reasons:**\n'; + comment += '- Artifacts were not uploaded successfully\n'; + comment += '- Artifacts expired (retention: 1 day)\n'; + comment += '- Permission issues\n\n'; + comment += '**What to do:**\n'; + comment += `1. Check the [PR Checks - Run workflow](${context.payload.workflow_run?.html_url || 'logs'}) logs\n`; + comment += '2. Ensure your code passes local checks:\n'; + comment += '```bash\n'; + comment += '# Backend\n'; + comment += 'go fmt ./...\n'; + comment += 'go vet ./...\n'; + comment += 'go build\n'; + comment += 'go test ./...\n\n'; + comment += '# Frontend (if applicable)\n'; + comment += 'cd web\n'; + comment += 'npm run build\n'; + comment += '```\n\n'; + comment += '---\n\n'; + comment += '*This is an automated fallback message. The advisory checks ran but results are not available.*'; + + await github.rest.issues.createComment({ + issue_number: prNumber, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); diff --git a/.github/workflows/pr-checks-run.yml b/.github/workflows/pr-checks-run.yml new file mode 100644 index 0000000000..48d23afe15 --- /dev/null +++ b/.github/workflows/pr-checks-run.yml @@ -0,0 +1,160 @@ +name: PR Checks - Run + +# This workflow runs advisory PR checks with read-only permissions +# Safe for fork PRs - results are saved as artifacts +# Companion workflow (pr-checks-comment.yml) will post comments +# +# NOTE: This workflow provides ADVISORY checks (non-blocking) +# Main blocking checks are in pr-checks.yml +# PR title and size checks are handled by pr-checks.yml (no duplication) + +on: + pull_request: + types: [opened, synchronize, reopened] + branches: [main, dev] + +# Read-only permissions - safe for fork PRs +permissions: + contents: read + +jobs: + # Backend advisory checks + # Different from pr-checks.yml: these use continue-on-error and generate reports + backend-checks: + name: Backend Checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.21' + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y libta-lib-dev || true + go mod download || true + + - name: Check Go formatting + id: go-fmt + continue-on-error: true + run: | + UNFORMATTED=$(gofmt -l . 2>/dev/null || echo "") + if [ -n "$UNFORMATTED" ]; then + echo "status=⚠️ Needs formatting" >> $GITHUB_OUTPUT + echo "$UNFORMATTED" | head -10 > fmt-files.txt + else + echo "status=✅ Good" >> $GITHUB_OUTPUT + echo "" > fmt-files.txt + fi + + - name: Run go vet + id: go-vet + continue-on-error: true + run: | + if go vet ./... 2>&1 | tee vet-output.txt; then + echo "status=✅ Good" >> $GITHUB_OUTPUT + else + echo "status=⚠️ Issues found" >> $GITHUB_OUTPUT + cat vet-output.txt | head -20 > vet-output-short.txt + fi + + - name: Run tests + id: go-test + continue-on-error: true + run: | + if go test ./... -v 2>&1 | tee test-output.txt; then + echo "status=✅ Passed" >> $GITHUB_OUTPUT + else + echo "status=⚠️ Failed" >> $GITHUB_OUTPUT + cat test-output.txt | tail -30 > test-output-short.txt + fi + + - name: Save backend results + if: always() + run: | + cat > backend-results.json <> $GITHUB_OUTPUT + else + echo "exists=false" >> $GITHUB_OUTPUT + fi + + - name: Install dependencies + if: steps.check-web.outputs.exists == 'true' + working-directory: ./web + continue-on-error: true + run: npm ci + + - name: Build and Type Check + if: steps.check-web.outputs.exists == 'true' + id: build + working-directory: ./web + continue-on-error: true + run: | + # build script includes: tsc && vite build + if npm run build 2>&1 | tee build-output.txt; then + echo "status=✅ Success" >> $GITHUB_OUTPUT + else + echo "status=⚠️ Failed" >> $GITHUB_OUTPUT + cat build-output.txt | tail -30 > build-output-short.txt + fi + + - name: Save frontend results + if: always() && steps.check-web.outputs.exists == 'true' + working-directory: ./web + run: | + cat > frontend-results.json <= 1000) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + body: comment + }); + } + } catch (error) { + console.log('Failed to add label/comment (expected for fork PRs):', error.message); + } + } else { + console.log('Fork PR detected - skipping label/comment (will be handled by pr-checks-comment.yml)'); + } + + # Backend checks (simplified - no TA-Lib required) + backend-checks: + name: Backend Code Quality (Go) + runs-on: ubuntu-latest + permissions: + contents: read # Only need read access for testing + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.21' + + - name: Cache Go modules + uses: actions/cache@v4 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Download dependencies + run: go mod download + + - name: Run go fmt + continue-on-error: true # Don't block PR if formatting issues found + run: | + if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then + echo "⚠️ Code formatting issues found. Please run 'go fmt ./...' locally." + echo "" + echo "Files needing formatting:" + gofmt -s -l . + echo "" + echo "This is a warning and won't block your PR from being merged." + exit 1 + else + echo "✅ All Go files are properly formatted" + fi + + - name: Run go vet + run: go vet ./... + + - name: Build + run: go build -v -o nofx + + # Frontend tests + frontend-tests: + name: Frontend Tests (React/TypeScript) + runs-on: ubuntu-latest + permissions: + contents: read # Only need read access for testing + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Cache Node modules + uses: actions/cache@v4 + with: + path: web/node_modules + key: ${{ runner.os }}-node-${{ hashFiles('web/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Install dependencies + working-directory: ./web + run: npm ci + + - name: Build and Type Check + working-directory: ./web + run: npm run build + # Note: build script runs "tsc && vite build" which includes type checking + + # Auto-label based on files changed + auto-label: + name: Auto Label PR + runs-on: ubuntu-latest + # Only run for non-fork PRs (fork PRs don't have write permission) + if: github.event.pull_request.head.repo.full_name == github.repository + permissions: + contents: read + pull-requests: write + issues: write # Required: PRs are issues, labeler needs to modify issue labels + steps: + - uses: actions/labeler@v5 + with: + configuration-path: .github/labeler.yml + repo-token: ${{ secrets.GITHUB_TOKEN }} + + # Check for security issues + security-check: + name: Security Scan + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write # Required: Upload SARIF results to GitHub Security + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + scan-type: 'fs' + scan-ref: '.' + format: 'sarif' + output: 'trivy-results.sarif' + + - name: Upload Trivy results + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: 'trivy-results.sarif' + + # Check for secrets in code + secrets-check: + name: Check for Secrets + runs-on: ubuntu-latest + permissions: + contents: read # Only need read access for scanning + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Run TruffleHog OSS + uses: trufflesecurity/trufflehog@main + with: + path: ./ + base: ${{ github.event.pull_request.base.sha }} + head: ${{ github.event.pull_request.head.sha }} + extra_args: --debug --only-verified + + # All checks passed + all-checks: + name: All Checks Passed + runs-on: ubuntu-latest + needs: [validate-pr, backend-checks, frontend-tests, security-check, secrets-check] + if: always() + permissions: + contents: read # Only need read access for status checking + steps: + - name: Check all jobs + run: | + # Note: validate-pr uses continue-on-error, so it won't block even if title format is invalid + # We only care about actual test failures + echo "validate-pr: ${{ needs.validate-pr.result }}" + echo "backend-checks: ${{ needs.backend-checks.result }}" + echo "frontend-tests: ${{ needs.frontend-tests.result }}" + echo "security-check: ${{ needs.security-check.result }}" + echo "secrets-check: ${{ needs.secrets-check.result }}" + + # Check if any critical checks failed (excluding validate-pr which is advisory) + if [[ "${{ needs.backend-checks.result }}" == "failure" ]] || \ + [[ "${{ needs.frontend-tests.result }}" == "failure" ]] || \ + [[ "${{ needs.security-check.result }}" == "failure" ]] || \ + [[ "${{ needs.secrets-check.result }}" == "failure" ]]; then + echo "❌ Critical checks failed" + exit 1 + else + echo "✅ All critical checks passed!" + if [[ "${{ needs.validate-pr.result }}" != "success" ]]; then + echo "ℹ️ Note: PR title format check is advisory only and doesn't block merging" + fi + fi diff --git a/.github/workflows/pr-docker-check.yml b/.github/workflows/pr-docker-check.yml new file mode 100644 index 0000000000..9399db5909 --- /dev/null +++ b/.github/workflows/pr-docker-check.yml @@ -0,0 +1,246 @@ +name: PR Docker Build Check + +# PR 时只做轻量级构建检查,不推送镜像 +# 策略: 快速验证 amd64 + 抽样检查 arm64 (backend only) +on: + pull_request: + branches: + - main + - dev + paths: + - 'docker/**' + - 'Dockerfile*' + - 'go.mod' + - 'go.sum' + - '**.go' + - 'web/**' + - '.github/workflows/docker-build.yml' + - '.github/workflows/pr-docker-check.yml' + +jobs: + # 快速检查: 所有镜像的 amd64 版本 + docker-build-amd64: + name: Build Check (amd64) + runs-on: ubuntu-22.04 + permissions: + contents: read + + strategy: + fail-fast: false + matrix: + include: + - name: backend + dockerfile: ./docker/Dockerfile.backend + test_run: true # 需要测试运行 + - name: frontend + dockerfile: ./docker/Dockerfile.frontend + test_run: true + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build ${{ matrix.name }} image (amd64) + id: build + uses: docker/build-push-action@v5 + with: + context: . + file: ${{ matrix.dockerfile }} + platforms: linux/amd64 + push: false + load: true # 加载到本地 Docker,用于测试运行 + tags: nofx-${{ matrix.name }}:pr-test + cache-from: type=gha,scope=${{ matrix.name }}-amd64 + cache-to: type=gha,mode=max,scope=${{ matrix.name }}-amd64 + build-args: | + BUILD_DATE=${{ github.event.pull_request.updated_at }} + VCS_REF=${{ github.event.pull_request.head.sha }} + VERSION=pr-${{ github.event.pull_request.number }} + + - name: Test run container (smoke test) + if: matrix.test_run + timeout-minutes: 2 + run: | + echo "🧪 Testing container startup..." + + # 启动容器 + docker run -d --name test-${{ matrix.name }} \ + --health-cmd="exit 0" \ + nofx-${{ matrix.name }}:pr-test + + # 等待容器启动 (最多 30 秒) + for i in {1..30}; do + if docker ps | grep -q test-${{ matrix.name }}; then + echo "✅ Container started successfully" + docker logs test-${{ matrix.name }} + docker stop test-${{ matrix.name }} || true + exit 0 + fi + sleep 1 + done + + echo "❌ Container failed to start" + docker logs test-${{ matrix.name }} || true + exit 1 + + - name: Check image size + run: | + SIZE=$(docker image inspect nofx-${{ matrix.name }}:pr-test --format='{{.Size}}') + SIZE_MB=$((SIZE / 1024 / 1024)) + + echo "📦 Image size: ${SIZE_MB} MB" + + # 警告阈值 + if [ "${{ matrix.name }}" = "backend" ] && [ $SIZE_MB -gt 500 ]; then + echo "⚠️ Warning: Backend image is larger than 500MB" + elif [ "${{ matrix.name }}" = "frontend" ] && [ $SIZE_MB -gt 200 ]; then + echo "⚠️ Warning: Frontend image is larger than 200MB" + else + echo "✅ Image size is reasonable" + fi + + # ARM64 原生构建检查: 使用 GitHub 原生 ARM64 runner (快速!) + docker-build-arm64-native: + name: Build Check (arm64 native - backend) + runs-on: ubuntu-22.04-arm # 原生 ARM64 runner + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + # 原生 ARM64 不需要 QEMU,直接构建 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build backend image (arm64 native) + uses: docker/build-push-action@v5 + timeout-minutes: 15 # 原生构建更快! + with: + context: . + file: ./docker/Dockerfile.backend + platforms: linux/arm64 + push: false + load: true # 加载到本地,用于测试 + tags: nofx-backend:pr-test-arm64 + cache-from: type=gha,scope=backend-arm64 + cache-to: type=gha,mode=max,scope=backend-arm64 + build-args: | + BUILD_DATE=${{ github.event.pull_request.updated_at }} + VCS_REF=${{ github.event.pull_request.head.sha }} + VERSION=pr-${{ github.event.pull_request.number }} + + - name: Test run ARM64 container + timeout-minutes: 2 + run: | + echo "🧪 Testing ARM64 container startup..." + + # 启动容器 + docker run -d --name test-backend-arm64 \ + --health-cmd="exit 0" \ + nofx-backend:pr-test-arm64 + + # 等待启动 + for i in {1..30}; do + if docker ps | grep -q test-backend-arm64; then + echo "✅ ARM64 container started successfully" + docker logs test-backend-arm64 + docker stop test-backend-arm64 || true + exit 0 + fi + sleep 1 + done + + echo "❌ ARM64 container failed to start" + docker logs test-backend-arm64 || true + exit 1 + + - name: ARM64 build summary + run: | + echo "✅ Backend ARM64 native build successful!" + echo "Using GitHub native ARM64 runner - no QEMU needed!" + echo "Build time is ~3x faster than emulation" + + # 汇总检查结果 + check-summary: + name: Docker Build Summary + needs: [docker-build-amd64, docker-build-arm64-native] + runs-on: ubuntu-22.04 + if: always() + permissions: + pull-requests: write # 用于发布评论 + steps: + - name: Check build results + id: check + run: | + echo "## 🐳 Docker Build Check Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # 检查 amd64 构建 + if [[ "${{ needs.docker-build-amd64.result }}" == "success" ]]; then + echo "✅ **AMD64 builds**: All passed" >> $GITHUB_STEP_SUMMARY + AMD64_OK=true + else + echo "❌ **AMD64 builds**: Failed" >> $GITHUB_STEP_SUMMARY + AMD64_OK=false + fi + + # 检查 arm64 构建 + if [[ "${{ needs.docker-build-arm64-native.result }}" == "success" ]]; then + echo "✅ **ARM64 build** (native): Backend passed (frontend will be verified after merge)" >> $GITHUB_STEP_SUMMARY + ARM64_OK=true + else + echo "❌ **ARM64 build** (native): Backend failed" >> $GITHUB_STEP_SUMMARY + ARM64_OK=false + fi + + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "$AMD64_OK" = true ] && [ "$ARM64_OK" = true ]; then + echo "### 🎉 All checks passed!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "After merge:" >> $GITHUB_STEP_SUMMARY + echo "- Full multi-arch builds (amd64 + arm64) will run in parallel" >> $GITHUB_STEP_SUMMARY + echo "- Estimated time: 15-20 minutes" >> $GITHUB_STEP_SUMMARY + exit 0 + else + echo "### ❌ Build checks failed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Please check the build logs above and fix the errors." >> $GITHUB_STEP_SUMMARY + exit 1 + fi + + - name: Comment on PR + if: always() && github.event.pull_request.head.repo.full_name == github.repository + uses: actions/github-script@v7 + with: + script: | + const amd64Status = '${{ needs.docker-build-amd64.result }}'; + const arm64Status = '${{ needs.docker-build-arm64-native.result }}'; + + const successIcon = '✅'; + const failIcon = '❌'; + + const comment = [ + '## 🐳 Docker Build Check Results', + '', + `**AMD64 builds**: ${amd64Status === 'success' ? successIcon : failIcon} ${amd64Status}`, + `**ARM64 build** (native runner): ${arm64Status === 'success' ? successIcon : failIcon} ${arm64Status}`, + '', + amd64Status === 'success' && arm64Status === 'success' + ? '### 🎉 All Docker builds passed!\n\n✨ Using GitHub native ARM64 runners - 3x faster than emulation!\n\nAfter merge, full multi-arch builds will run in ~10-12 minutes.' + : '### ⚠️ Some builds failed\n\nPlease check the Actions tab for details.', + '', + 'Checked: Backend (amd64 + arm64 native), Frontend (amd64) | Powered by GitHub ARM64 Runners' + ].join('\n'); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: comment + }); diff --git a/.github/workflows/pr-docker-compose-healthcheck.yml b/.github/workflows/pr-docker-compose-healthcheck.yml new file mode 100644 index 0000000000..139477e569 --- /dev/null +++ b/.github/workflows/pr-docker-compose-healthcheck.yml @@ -0,0 +1,152 @@ +name: PR Docker Compose Healthcheck + +# 驗證 docker-compose.yml 的 healthcheck 配置在 Alpine 容器中正常工作 +on: + pull_request: + branches: + - main + - dev + paths: + - 'docker-compose.yml' + - 'docker/Dockerfile.backend' + - 'docker/Dockerfile.frontend' + - '.github/workflows/pr-docker-compose-healthcheck.yml' + +jobs: + healthcheck-test: + name: Test Docker Compose Healthcheck + runs-on: ubuntu-22.04 + timeout-minutes: 10 + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Create minimal .env for testing + run: | + cat > .env < config.json < 0.5) { + suggestedTemplate = 'backend'; templateEmoji = '🔧'; templateLabel = 'backend'; + } else if ((jsFiles + tsFiles) / totalFiles > 0.5) { + suggestedTemplate = 'frontend'; templateEmoji = '🎨'; templateLabel = 'frontend'; + } else if (mdFiles / totalFiles > 0.7) { + suggestedTemplate = 'docs'; templateEmoji = '📝'; templateLabel = 'documentation'; + } + + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + }); + + const prBody = pr.body || ''; + const usesBackendTemplate = prBody.includes('Pull Request - Backend'); + const usesFrontendTemplate = prBody.includes('Pull Request - Frontend'); + const usesDocsTemplate = prBody.includes('Pull Request - Documentation'); + const usesGeneralTemplate = prBody.includes('Pull Request - General'); + const usingDefaultTemplate = !usesBackendTemplate && !usesFrontendTemplate && !usesDocsTemplate && !usesGeneralTemplate; + + if (templateLabel) { + try { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: [templateLabel] + }); + console.log('Added label: ' + templateLabel); + } catch (error) { + console.log('Label might not exist, skipping...'); + } + } + + function isPRBodyEmpty(body) { + if (!body || body.trim().length < 100) return true; + const hasEmptyDescription = body.includes('**English:**') && body.match(/\*\*English:\*\*\s*\n\s*\n\s*\n/); + const hasEmptyChanges = body.includes('具体变更') && body.match(/\*\*中文:\*\*\s*\n\s*-\s*\n\s*-\s*\n/); + if (hasEmptyDescription || hasEmptyChanges) return true; + const descMatch = body.match(/\*\*English:\*\*[||]\s*\*\*中文:\*\*\s*\n\s*(.+)/); + if (!descMatch || descMatch[1].trim().length < 10) return true; + return false; + } + + if (suggestedTemplate && usingDefaultTemplate) { + const shouldAutoApply = isPRBodyEmpty(prBody); + const templatePath = '.github/PULL_REQUEST_TEMPLATE/' + suggestedTemplate + '.md'; + + if (shouldAutoApply) { + try { + const { data: templateFile } = await github.rest.repos.getContent({ + owner: context.repo.owner, + repo: context.repo.repo, + path: templatePath, + ref: context.payload.pull_request.head.ref + }); + + const templateContent = Buffer.from(templateFile.content, 'base64').toString('utf-8'); + + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + body: templateContent + }); + + console.log('Auto-applied ' + suggestedTemplate + ' template'); + + let fileStats = []; + if (goFiles > 0) fileStats.push('- 🔧 Go files: ' + goFiles); + if (jsFiles > 0) fileStats.push('- 🎨 JavaScript files: ' + jsFiles); + if (tsFiles > 0) fileStats.push('- 🎨 TypeScript files: ' + tsFiles); + if (mdFiles > 0) fileStats.push('- 📝 Markdown files: ' + mdFiles); + if (otherFiles > 0) fileStats.push('- 📦 Other files: ' + otherFiles); + const fileStatsText = fileStats.join('\n'); + + const notifyComment = '## ' + templateEmoji + ' 已自动应用专用模板 | Auto-Applied Template\n\n' + + '检测到您的PR主要包含 **' + suggestedTemplate + '** 相关的变更,系统已自动为您应用相应的模板。\n\n' + + 'Detected that your PR primarily contains **' + suggestedTemplate + '** changes. The appropriate template has been automatically applied.\n\n' + + '**文件统计 | File Statistics**\n' + fileStatsText + '\n\n' + + '**已应用模板 | Applied Template**\n`' + templatePath + '`\n\n' + + '✨ 您现在可以直接在PR描述中填写相关信息了!\n\n' + + '✨ You can now fill in the relevant information in the PR description!'; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: notifyComment + }); + + } catch (error) { + console.log('Failed to fetch or apply template: ' + error.message); + const templateUrl = 'https://raw.githubusercontent.com/' + context.repo.owner + '/' + context.repo.repo + '/dev/.github/PULL_REQUEST_TEMPLATE/' + suggestedTemplate + '.md'; + const fallbackComment = '## ' + templateEmoji + ' 建议使用专用模板 | Suggested Template\n\n' + + '您的PR主要包含 **' + suggestedTemplate + '** 相关的变更。\n\n' + + '**推荐模板 | Recommended Template:** `.github/PULL_REQUEST_TEMPLATE/' + suggestedTemplate + '.md`\n\n' + + '**如何使用 | How to use:** [点击查看模板内容](' + templateUrl + ')'; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: fallbackComment + }); + } + } else { + console.log('PR body has content, sending suggestion only'); + + let fileStats = []; + if (goFiles > 0) fileStats.push('- 🔧 Go files: ' + goFiles); + if (jsFiles > 0) fileStats.push('- 🎨 JavaScript files: ' + jsFiles); + if (tsFiles > 0) fileStats.push('- 🎨 TypeScript files: ' + tsFiles); + if (mdFiles > 0) fileStats.push('- 📝 Markdown files: ' + mdFiles); + if (otherFiles > 0) fileStats.push('- 📦 Other files: ' + otherFiles); + const fileStatsText = fileStats.join('\n'); + + const templateUrl = 'https://raw.githubusercontent.com/' + context.repo.owner + '/' + context.repo.repo + '/dev/.github/PULL_REQUEST_TEMPLATE/' + suggestedTemplate + '.md'; + + const comment = '## ' + templateEmoji + ' 建议使用专用模板 | Suggested Template\n\n' + + '您的PR主要包含 **' + suggestedTemplate + '** 相关的变更。我们建议使用更适合的模板以简化填写。\n\n' + + 'Your PR primarily contains **' + suggestedTemplate + '** changes. We suggest using a more suitable template to simplify filling.\n\n' + + '**文件统计 | File Statistics**\n' + fileStatsText + '\n\n' + + '**推荐模板 | Recommended Template**\n```\n.github/PULL_REQUEST_TEMPLATE/' + suggestedTemplate + '.md\n```\n\n' + + '**如何使用 | How to use**\n' + + '1. 编辑PR描述 | Edit PR description\n' + + '2. 复制 [' + suggestedTemplate + ' 模板内容](' + templateUrl + ') | Copy [' + suggestedTemplate + ' template content](' + templateUrl + ')\n' + + '3. 或在创建PR时使用URL参数 | Or use URL parameter when creating PR\n' + + ' `?template=' + suggestedTemplate + '.md`\n\n' + + '_这是一个自动建议,您可以继续使用当前模板。_\n\n' + + '_This is an automated suggestion. You may continue using the current template._'; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: comment + }); + } + } else if (suggestedTemplate && !usingDefaultTemplate) { + console.log('PR already uses a specific template'); + } else { + console.log('No specific template suggestion needed - mixed changes'); + } diff --git a/.github/workflows/scripts/calculate_coverage.py b/.github/workflows/scripts/calculate_coverage.py new file mode 100755 index 0000000000..735da873d0 --- /dev/null +++ b/.github/workflows/scripts/calculate_coverage.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +""" +Calculate Go test coverage and generate reports. + +This script parses the coverage.out file generated by `go test -coverprofile`, +extracts coverage statistics, and generates formatted reports. +""" + +import sys +import re +import os +from typing import Dict, List, Tuple + + +def parse_coverage_file(coverage_file: str) -> Tuple[float, Dict[str, float]]: + """ + Parse coverage output file and extract coverage data. + + Args: + coverage_file: Path to coverage.out file + + Returns: + Tuple of (total_coverage, package_coverage_dict) + """ + if not os.path.exists(coverage_file): + print(f"Error: Coverage file {coverage_file} not found", file=sys.stderr) + sys.exit(1) + + # Run go tool cover to get coverage data + import subprocess + + try: + result = subprocess.run( + ['go', 'tool', 'cover', '-func', coverage_file], + capture_output=True, + text=True, + check=True + ) + except subprocess.CalledProcessError as e: + print(f"Error running go tool cover: {e}", file=sys.stderr) + sys.exit(1) + + lines = result.stdout.strip().split('\n') + package_coverage = {} + total_coverage = 0.0 + + for line in lines: + # Skip empty lines + if not line.strip(): + continue + + # Check for total coverage line + if line.startswith('total:'): + # Extract percentage from "total: (statements) XX.X%" + match = re.search(r'(\d+\.\d+)%', line) + if match: + total_coverage = float(match.group(1)) + continue + + # Parse package/file coverage + # Format: "package/file.go:function statements coverage%" + parts = line.split() + if len(parts) >= 3: + file_path = parts[0] + coverage_str = parts[-1] + + # Extract package name from file path + package = file_path.split(':')[0] + package_name = '/'.join(package.split('/')[:-1]) if '/' in package else package + + # Extract coverage percentage + match = re.search(r'(\d+\.\d+)%', coverage_str) + if match: + coverage_pct = float(match.group(1)) + + # Aggregate by package + if package_name not in package_coverage: + package_coverage[package_name] = [] + package_coverage[package_name].append(coverage_pct) + + # Calculate average coverage per package + package_avg = { + pkg: sum(coverages) / len(coverages) + for pkg, coverages in package_coverage.items() + } + + return total_coverage, package_avg + + +def get_coverage_status(coverage: float) -> Tuple[str, str, str]: + """ + Get coverage status based on percentage. + + Args: + coverage: Coverage percentage + + Returns: + Tuple of (emoji, status_text, badge_color) + """ + if coverage >= 80: + return '🟢', 'excellent', 'brightgreen' + elif coverage >= 60: + return '🟡', 'good', 'yellow' + elif coverage >= 40: + return '🟠', 'fair', 'orange' + else: + return '🔴', 'needs improvement', 'red' + + +def generate_coverage_report(coverage_file: str, output_file: str) -> None: + """ + Generate a detailed coverage report in markdown format. + + Args: + coverage_file: Path to coverage.out file + output_file: Path to output markdown file + """ + import subprocess + + try: + result = subprocess.run( + ['go', 'tool', 'cover', '-func', coverage_file], + capture_output=True, + text=True, + check=True + ) + except subprocess.CalledProcessError as e: + print(f"Error generating coverage report: {e}", file=sys.stderr) + sys.exit(1) + + with open(output_file, 'w') as f: + f.write("## Coverage by Package\n\n") + f.write("```\n") + f.write(result.stdout) + f.write("```\n") + + +def set_github_output(name: str, value: str) -> None: + """ + Set GitHub Actions output variable. + + Args: + name: Output variable name + value: Output variable value + """ + github_output = os.environ.get('GITHUB_OUTPUT') + if github_output: + with open(github_output, 'a') as f: + f.write(f"{name}={value}\n") + else: + print(f"::set-output name={name}::{value}") + + +def main(): + """Main entry point.""" + if len(sys.argv) < 2: + print("Usage: calculate_coverage.py [output_file]", file=sys.stderr) + sys.exit(1) + + coverage_file = sys.argv[1] + output_file = sys.argv[2] if len(sys.argv) > 2 else 'coverage_report.md' + + # Parse coverage data + total_coverage, package_coverage = parse_coverage_file(coverage_file) + + # Get coverage status + emoji, status, badge_color = get_coverage_status(total_coverage) + + # Generate detailed report + generate_coverage_report(coverage_file, output_file) + + # Output results + print(f"Total Coverage: {total_coverage}%") + print(f"Status: {status}") + print(f"Badge Color: {badge_color}") + + # Set GitHub Actions outputs + set_github_output('coverage', f'{total_coverage}%') + set_github_output('coverage_num', str(total_coverage)) + set_github_output('status', status) + set_github_output('emoji', emoji) + set_github_output('badge_color', badge_color) + + # Print package breakdown + if package_coverage: + print("\nCoverage by Package:") + for package, coverage in sorted(package_coverage.items()): + print(f" {package}: {coverage:.1f}%") + + +if __name__ == '__main__': + main() diff --git a/.github/workflows/scripts/comment_pr.py b/.github/workflows/scripts/comment_pr.py new file mode 100755 index 0000000000..a40d8a16f4 --- /dev/null +++ b/.github/workflows/scripts/comment_pr.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python3 +""" +Post or update coverage report comment on GitHub Pull Request. + +This script generates a formatted coverage report comment and posts it to a PR, +or updates an existing coverage comment if one already exists. +""" + +import os +import sys +import json +import requests +from typing import Optional + + +def read_file(file_path: str) -> str: + """Read file content.""" + try: + with open(file_path, 'r') as f: + return f.read() + except FileNotFoundError: + print(f"Warning: File {file_path} not found", file=sys.stderr) + return "" + + +def generate_comment_body(coverage: str, emoji: str, status: str, + badge_color: str, coverage_report_path: str) -> str: + """ + Generate the PR comment body. + + Args: + coverage: Coverage percentage (e.g., "75.5%") + emoji: Status emoji + status: Status text + badge_color: Badge color + coverage_report_path: Path to detailed coverage report + + Returns: + Formatted comment body in markdown + """ + coverage_report = read_file(coverage_report_path) + + # URL encode the coverage percentage for the badge + coverage_encoded = coverage.replace('%', '%25') + + comment = f"""## {emoji} Go Test Coverage Report + +**Total Coverage:** `{coverage}` ({status}) + +![Coverage](https://img.shields.io/badge/coverage-{coverage_encoded}-{badge_color}) + +
+📊 Detailed Coverage Report (click to expand) + +{coverage_report} + +
+ +### Coverage Guidelines +- 🟢 >= 80%: Excellent +- 🟡 >= 60%: Good +- 🟠 >= 40%: Fair +- 🔴 < 40%: Needs improvement + +--- +*This is an automated coverage report. The coverage requirement is advisory and does not block PR merging.* +""" + return comment + + +def find_existing_comment(token: str, repo: str, pr_number: int) -> Optional[int]: + """ + Find existing coverage comment in the PR. + + Args: + token: GitHub token + repo: Repository in format "owner/repo" + pr_number: Pull request number + + Returns: + Comment ID if found, None otherwise + """ + url = f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments" + headers = { + 'Authorization': f'token {token}', + 'Accept': 'application/vnd.github.v3+json' + } + + try: + response = requests.get(url, headers=headers) + response.raise_for_status() + comments = response.json() + + # Look for existing coverage comment + for comment in comments: + if (comment.get('user', {}).get('type') == 'Bot' and + 'Go Test Coverage Report' in comment.get('body', '')): + return comment['id'] + + except requests.exceptions.RequestException as e: + print(f"Error fetching comments: {e}", file=sys.stderr) + + return None + + +def post_comment(token: str, repo: str, pr_number: int, body: str) -> bool: + """ + Post a new comment to the PR. + + Args: + token: GitHub token + repo: Repository in format "owner/repo" + pr_number: Pull request number + body: Comment body + + Returns: + True if successful, False otherwise + """ + url = f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments" + headers = { + 'Authorization': f'token {token}', + 'Accept': 'application/vnd.github.v3+json' + } + data = {'body': body} + + try: + response = requests.post(url, headers=headers, json=data) + response.raise_for_status() + print("✅ Coverage comment posted successfully") + return True + except requests.exceptions.RequestException as e: + print(f"Error posting comment: {e}", file=sys.stderr) + if hasattr(e, 'response') and e.response is not None: + print(f"Response: {e.response.text}", file=sys.stderr) + return False + + +def update_comment(token: str, repo: str, comment_id: int, body: str) -> bool: + """ + Update an existing comment. + + Args: + token: GitHub token + repo: Repository in format "owner/repo" + comment_id: Comment ID to update + body: New comment body + + Returns: + True if successful, False otherwise + """ + url = f"https://api.github.com/repos/{repo}/issues/comments/{comment_id}" + headers = { + 'Authorization': f'token {token}', + 'Accept': 'application/vnd.github.v3+json' + } + data = {'body': body} + + try: + response = requests.patch(url, headers=headers, json=data) + response.raise_for_status() + print("✅ Coverage comment updated successfully") + return True + except requests.exceptions.RequestException as e: + print(f"Error updating comment: {e}", file=sys.stderr) + if hasattr(e, 'response') and e.response is not None: + print(f"Response: {e.response.text}", file=sys.stderr) + return False + + +def is_fork_pr(event_path: str) -> bool: + """ + Check if the PR is from a fork. + + Args: + event_path: Path to GitHub event JSON file + + Returns: + True if fork PR, False otherwise + """ + try: + with open(event_path, 'r') as f: + event = json.load(f) + + pr = event.get('pull_request', {}) + head_repo = pr.get('head', {}).get('repo', {}).get('full_name') + base_repo = pr.get('base', {}).get('repo', {}).get('full_name') + + return head_repo != base_repo + except (FileNotFoundError, json.JSONDecodeError, KeyError) as e: + print(f"Warning: Could not determine if fork PR: {e}", file=sys.stderr) + return False + + +def main(): + """Main entry point.""" + # Get environment variables + token = os.environ.get('GITHUB_TOKEN') + repo = os.environ.get('GITHUB_REPOSITORY') + event_path = os.environ.get('GITHUB_EVENT_PATH', '') + + # Get arguments + if len(sys.argv) < 6: + print("Usage: comment_pr.py [coverage_report_path]", + file=sys.stderr) + sys.exit(1) + + pr_number = int(sys.argv[1]) + coverage = sys.argv[2] + emoji = sys.argv[3] + status = sys.argv[4] + badge_color = sys.argv[5] + coverage_report_path = sys.argv[6] if len(sys.argv) > 6 else 'coverage_report.md' + + # Validate environment + if not token: + print("Error: GITHUB_TOKEN environment variable not set", file=sys.stderr) + sys.exit(1) + + if not repo: + print("Error: GITHUB_REPOSITORY environment variable not set", file=sys.stderr) + sys.exit(1) + + # Check if fork PR + if event_path and is_fork_pr(event_path): + print("ℹ️ Fork PR detected - skipping comment (no write permissions)") + sys.exit(0) + + # Generate comment body + comment_body = generate_comment_body(coverage, emoji, status, badge_color, coverage_report_path) + + # Check for existing comment + existing_comment_id = find_existing_comment(token, repo, pr_number) + + # Post or update comment + if existing_comment_id: + print(f"Found existing comment (ID: {existing_comment_id}), updating...") + success = update_comment(token, repo, existing_comment_id, comment_body) + else: + print("No existing comment found, creating new one...") + success = post_comment(token, repo, pr_number, comment_body) + + sys.exit(0 if success else 1) + + +if __name__ == '__main__': + main() diff --git a/.github/workflows/scripts/requirements.txt b/.github/workflows/scripts/requirements.txt new file mode 100644 index 0000000000..c606cb501a --- /dev/null +++ b/.github/workflows/scripts/requirements.txt @@ -0,0 +1,2 @@ +# Python dependencies for GitHub Actions scripts +requests>=2.31.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..31b11c12ab --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,54 @@ +name: Test + +on: + push: + branches: [main, dev] + pull_request: + branches: [main, dev] + +jobs: + backend-tests: + name: Backend Tests + runs-on: ubuntu-latest + continue-on-error: true # Don't block PRs + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + + - name: Download dependencies + run: go mod download + + - name: Run tests + run: go test -v ./... + + - name: Generate coverage + run: go test -coverprofile=coverage.out ./... + continue-on-error: true + + frontend-tests: + name: Frontend Tests + runs-on: ubuntu-latest + continue-on-error: true # Don't block PRs + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: web/package-lock.json + + - name: Install dependencies + run: cd web && npm ci + + - name: Run tests + run: cd web && npm run test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..05dd17f492 --- /dev/null +++ b/.gitignore @@ -0,0 +1,125 @@ +# IDE 配置文件 +.idea/ +*.iml +*.xml + +# AI 工具 +.claude/ +CLAUDE.md + +# 编译产物 +nofx-auto +*.exe +nofx +nofx_test + +# Go 相关 +*.test +*.out + +# 操作系统 +.DS_Store +Thumbs.db + +# 临时文件 +*.log +*.tmp +*.bak +*.backup + +# 环境变量 +.env +config.json +config.db* +nofx.db +configbak.json + +# 决策日志 +decision_logs/ +coin_pool_cache/ +nofx_test + +# Node.js +web/node_modules/ +node_modules/ +web/dist/ +web/.vite/ + +# ESLint 临时报告文件(调试时生成,不纳入版本控制) +eslint-*.json + +# VS code +.vscode + +# 密钥和敏感文件 +# 注意:crypto目录包含加密服务代码,应该被提交 +# 只忽略密钥文件本身 +secrets/ +*.key +*.pem +*.p12 +*.pfx +rsa_key* + +# 加密相关 +DATA_ENCRYPTION_KEY=* +*.enc + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Python 虚拟环境 +.venv/ +venv/ +ENV/ +env/ +.env/ + +# uv +.uv/ +uv.lock + +# Pytest +.pytest_cache/ +.coverage +htmlcov/ +*.cover +.hypothesis/ + +# Jupyter Notebook +.ipynb_checkpoints +*.ipynb + +# pyenv +.python-version + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ +PR_DESCRIPTION.md diff --git a/.husky/_/husky.sh b/.husky/_/husky.sh new file mode 100755 index 0000000000..cec959a6b9 --- /dev/null +++ b/.husky/_/husky.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env sh +if [ -z "$husky_skip_init" ]; then + debug () { + if [ "$HUSKY_DEBUG" = "1" ]; then + echo "husky (debug) - $1" + fi + } + + readonly hook_name="$(basename -- "$0")" + debug "starting $hook_name..." + + if [ "$HUSKY" = "0" ]; then + debug "HUSKY env variable is set to 0, skipping hook" + exit 0 + fi + + if [ -f ~/.huskyrc ]; then + debug "sourcing ~/.huskyrc" + . ~/.huskyrc + fi + + readonly husky_skip_init=1 + export husky_skip_init + sh -e "$0" "$@" + exitCode="$?" + + if [ $exitCode != 0 ]; then + echo "husky - $hook_name hook exited with code $exitCode (error)" + fi + + if [ $exitCode = 127 ]; then + echo "husky - command not found in PATH=$PATH" + fi + + exit $exitCode +fi diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000000..25b3e6b78a --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +cd web && npx lint-staged diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..063b7b4b0d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,203 @@ +# Changelog + +All notable changes to the NOFX project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +**Languages:** [English](CHANGELOG.md) | [中文](CHANGELOG.zh-CN.md) + +--- + +## [Unreleased] + +### Added +- Documentation system with multi-language support (EN/CN/RU/UK) +- Complete getting-started guides (Docker, Custom API) +- Architecture documentation with system design details +- User guides with FAQ and troubleshooting +- Community documentation with bounty programs + +### Changed +- Reorganized documentation structure into logical categories +- Updated all README files with proper navigation links + +--- + +## [3.0.0] - 2025-10-30 + +### Added - Major Architecture Transformation 🚀 + +**Complete System Redesign - Web-Based Configuration Platform** + +This is a **major breaking update** that completely transforms NOFX from a static config-based system to a modern web-based trading platform. + +#### Database-Driven Architecture +- SQLite integration replacing static JSON config +- Persistent storage with automatic timestamps +- Foreign key relationships and triggers for data consistency +- Separate tables for AI models, exchanges, traders, and system config + +#### Web-Based Configuration Interface +- Complete web-based configuration management (no more JSON editing) +- AI Model setup through web interface (DeepSeek/Qwen API keys) +- Exchange management (Binance/Hyperliquid credentials) +- Dynamic trader creation (combine any AI model with any exchange) +- Real-time control (start/stop traders without system restart) + +#### Flexible Architecture +- Separation of concerns (AI models and exchanges independent) +- Mix & match capability (unlimited combinations) +- Scalable design (support for unlimited traders) +- Clean slate approach (no default traders) + +#### Enhanced API Layer +- RESTful design with complete CRUD operations +- New endpoints: + - `GET/PUT /api/models` - AI model configuration + - `GET/PUT /api/exchanges` - Exchange configuration + - `POST/DELETE /api/traders` - Trader management + - `POST /api/traders/:id/start|stop` - Trader control +- Updated documentation for all API endpoints + +#### Modernized Codebase +- Type safety with proper separation of configuration types +- Database abstraction with prepared statements +- Comprehensive error handling and validation +- Better code organization (database, API, business logic) + +### Changed +- **BREAKING**: Old `config.json` files no longer used +- Configuration must be done through web interface +- Much easier setup and better UX +- No more server restarts for configuration changes + +### Why This Matters +- 🎯 **User Experience**: Much easier to configure and manage +- 🔧 **Flexibility**: Create any combination of AI models and exchanges +- 📊 **Scalability**: Support for complex multi-trader setups +- 🔒 **Reliability**: Database ensures data persistence and consistency +- 🚀 **Future-Proof**: Foundation for advanced features + +--- + +## [2.0.2] - 2025-10-29 + +### Fixed - Critical Bug Fixes: Trade History & Performance Analysis + +#### PnL Calculation - Major Error Fixed +- **Fixed**: PnL now calculated as actual USDT amount instead of percentage only +- Previously ignored position size and leverage (e.g., 100 USDT @ 5% = 1000 USDT @ 5%) +- Now: `PnL (USDT) = Position Value × Price Change % × Leverage` +- Impact: Win rate, profit factor, and Sharpe ratio now accurate + +#### Position Tracking - Missing Critical Data +- **Fixed**: Open position records now store quantity and leverage +- Previously only stored price and time +- Essential for accurate PnL calculations + +#### Position Key Logic - Long/Short Conflict +- **Fixed**: Changed from `symbol` to `symbol_side` format +- Now properly distinguishes between long and short positions +- Example: `BTCUSDT_long` vs `BTCUSDT_short` + +#### Sharpe Ratio Calculation - Code Optimization +- **Changed**: Replaced custom Newton's method with `math.Sqrt` +- More reliable, maintainable, and efficient + +### Why This Matters +- Historical trade statistics now show real USDT profit/loss +- Performance comparison between different leverage trades is accurate +- AI self-learning mechanism receives correct feedback +- Multi-position tracking (long + short simultaneously) works correctly + +--- + +## [2.0.2] - 2025-10-29 + +### Fixed - Aster Exchange Precision Error + +- Fixed Aster exchange precision error (code -1111) +- Improved price and quantity formatting to match exchange requirements +- Added detailed precision processing logs for debugging +- Enhanced all order functions with proper precision handling + +#### Technical Details +- Added `formatFloatWithPrecision` function +- Price and quantity formatted according to exchange specifications +- Trailing zeros removed to optimize API requests + +--- + +## [2.0.1] - 2025-10-29 + +### Fixed - ComparisonChart Data Processing + +- Fixed ComparisonChart data processing logic +- Switched from cycle_number to timestamp grouping +- Resolved chart freezing issue when backend restarts +- Improved chart data display (shows all historical data chronologically) +- Enhanced debugging logs + +--- + +## [2.0.0] - 2025-10-28 + +### Added - Major Updates + +- AI self-learning mechanism (historical feedback, performance analysis) +- Multi-trader competition mode (Qwen vs DeepSeek) +- Binance-style UI (complete interface imitation) +- Performance comparison charts (real-time ROI comparison) +- Risk control optimization (per-coin position limit adjustment) + +### Fixed + +- Fixed hardcoded initial balance issue +- Fixed multi-trader data sync issue +- Optimized chart data alignment (using cycle_number) + +--- + +## [1.0.0] - 2025-10-27 + +### Added - Initial Release + +- Basic AI trading functionality +- Decision logging system +- Simple Web interface +- Support for Binance Futures +- DeepSeek and Qwen AI model integration + +--- + +## How to Use This Changelog + +### For Users +- Check the [Unreleased] section for upcoming features +- Review version sections to understand what changed +- Follow migration guides for breaking changes + +### For Contributors +When making changes, add them to the [Unreleased] section under appropriate categories: +- **Added** - New features +- **Changed** - Changes to existing functionality +- **Deprecated** - Features that will be removed +- **Removed** - Features that were removed +- **Fixed** - Bug fixes +- **Security** - Security fixes + +When releasing a new version, move [Unreleased] items to a new version section with date. + +--- + +## Links + +- [Documentation](docs/README.md) +- [Contributing Guidelines](CONTRIBUTING.md) +- [Security Policy](SECURITY.md) +- [GitHub Repository](https://github.com/tinkle-community/nofx) + +--- + +**Last Updated:** 2025-11-01 diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md new file mode 100644 index 0000000000..92c7840c4c --- /dev/null +++ b/CHANGELOG.zh-CN.md @@ -0,0 +1,203 @@ +# 更新日志 + +NOFX 项目的所有重要更改都将记录在此文件中。 + +本文件格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/), +本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。 + +**语言:** [English](CHANGELOG.md) | [中文](CHANGELOG.zh-CN.md) + +--- + +## [未发布] + +### 新增 +- 多语言文档系统(英文/中文/俄语/乌克兰语) +- 完整的快速开始指南(Docker、自定义 API) +- 架构文档,包含系统设计细节 +- 用户指南,包含 FAQ 和故障排除 +- 社区文档,包含悬赏计划 + +### 变更 +- 重组文档结构为逻辑分类 +- 更新所有 README 文件,添加适当的导航链接 + +--- + +## [3.0.0] - 2025-10-30 + +### 新增 - 重大架构变革 🚀 + +**系统完全重新设计 - 基于 Web 的配置平台** + +这是一个**重大破坏性更新**,将 NOFX 从基于静态配置的系统完全转变为现代化的 Web 交易平台。 + +#### 数据库驱动架构 +- SQLite 集成,取代静态 JSON 配置 +- 持久化存储,自动时间戳 +- 外键关系和触发器确保数据一致性 +- 为 AI 模型、交易所、交易员和系统配置分离表结构 + +#### 基于 Web 的配置界面 +- 完整的 Web 配置管理(无需编辑 JSON) +- 通过 Web 界面设置 AI 模型(DeepSeek/Qwen API 密钥) +- 交易所管理(Binance/Hyperliquid 凭证) +- 动态创建交易员(结合任意 AI 模型和交易所) +- 实时控制(无需重启即可启动/停止交易员) + +#### 灵活架构 +- 关注点分离(AI 模型和交易所独立) +- 混合搭配能力(无限组合) +- 可扩展设计(支持无限交易员) +- 清洁起点(无默认交易员) + +#### 增强的 API 层 +- RESTful 设计,完整的 CRUD 操作 +- 新端点: + - `GET/PUT /api/models` - AI 模型配置 + - `GET/PUT /api/exchanges` - 交易所配置 + - `POST/DELETE /api/traders` - 交易员管理 + - `POST /api/traders/:id/start|stop` - 交易员控制 +- 更新所有 API 端点文档 + +#### 现代化代码库 +- 类型安全,适当分离配置类型 +- 数据库抽象,使用预处理语句 +- 全面的错误处理和验证 +- 更好的代码组织(数据库、API、业务逻辑) + +### 变更 +- **破坏性变更**:不再使用旧的 `config.json` 文件 +- 必须通过 Web 界面进行配置 +- 设置更简单,用户体验更好 +- 配置更改无需重启服务器 + +### 为什么重要 +- 🎯 **用户体验**:配置和管理更容易 +- 🔧 **灵活性**:创建 AI 模型和交易所的任意组合 +- 📊 **可扩展性**:支持复杂的多交易员设置 +- 🔒 **可靠性**:数据库确保数据持久性和一致性 +- 🚀 **面向未来**:为高级功能奠定基础 + +--- + +## [2.0.2] - 2025-10-29 + +### 修复 - 关键错误修复:交易历史和性能分析 + +#### 盈亏计算 - 重大错误修复 +- **修复**:盈亏现在计算为实际 USDT 金额,而不是仅百分比 +- 之前忽略了仓位大小和杠杆(例如,100 USDT @ 5% = 1000 USDT @ 5%) +- 现在:`盈亏 (USDT) = 仓位价值 × 价格变化 % × 杠杆` +- 影响:胜率、盈利因子和夏普比率现在准确 + +#### 仓位跟踪 - 缺失关键数据 +- **修复**:持仓记录现在存储数量和杠杆 +- 之前只存储价格和时间 +- 这对准确的盈亏计算至关重要 + +#### 仓位键逻辑 - 多空冲突 +- **修复**:从 `symbol` 改为 `symbol_side` 格式 +- 现在正确区分多头和空头仓位 +- 示例:`BTCUSDT_long` vs `BTCUSDT_short` + +#### 夏普比率计算 - 代码优化 +- **变更**:用 `math.Sqrt` 替换自定义牛顿法 +- 更可靠、可维护和高效 + +### 为什么重要 +- 历史交易统计现在显示真实的 USDT 盈亏 +- 不同杠杆交易之间的性能比较准确 +- AI 自学习机制接收正确的反馈 +- 多仓位跟踪(同时多空)正常工作 + +--- + +## [2.0.2] - 2025-10-29 + +### 修复 - Aster 交易所精度错误 + +- 修复 Aster 交易所精度错误(代码 -1111) +- 改进价格和数量格式化以匹配交易所要求 +- 添加详细的精度处理日志用于调试 +- 增强所有订单函数的精度处理 + +#### 技术细节 +- 添加 `formatFloatWithPrecision` 函数 +- 根据交易所规范格式化价格和数量 +- 删除尾随零以优化 API 请求 + +--- + +## [2.0.1] - 2025-10-29 + +### 修复 - ComparisonChart 数据处理 + +- 修复 ComparisonChart 数据处理逻辑 +- 从 cycle_number 切换到时间戳分组 +- 解决后端重启时图表冻结问题 +- 改进图表数据显示(按时间顺序显示所有历史数据) +- 增强调试日志 + +--- + +## [2.0.0] - 2025-10-28 + +### 新增 - 重大更新 + +- AI 自学习机制(历史反馈、性能分析) +- 多交易员竞赛模式(Qwen vs DeepSeek) +- 币安风格 UI(完整界面仿制) +- 性能比较图表(实时 ROI 比较) +- 风险控制优化(每币种仓位限制调整) + +### 修复 + +- 修复硬编码初始余额问题 +- 修复多交易员数据同步问题 +- 优化图表数据对齐(使用 cycle_number) + +--- + +## [1.0.0] - 2025-10-27 + +### 新增 - 初始版本 + +- 基础 AI 交易功能 +- 决策日志系统 +- 简单的 Web 界面 +- 支持币安合约 +- DeepSeek 和 Qwen AI 模型集成 + +--- + +## 如何使用本更新日志 + +### 用户 +- 查看 [未发布] 部分了解即将推出的功能 +- 查看版本部分了解变更内容 +- 遵循破坏性变更的迁移指南 + +### 贡献者 +进行更改时,将它们添加到 [未发布] 部分的相应类别下: +- **新增** - 新功能 +- **变更** - 现有功能的变更 +- **弃用** - 即将删除的功能 +- **移除** - 已删除的功能 +- **修复** - 错误修复 +- **安全** - 安全修复 + +发布新版本时,将 [未发布] 项目移动到带日期的新版本部分。 + +--- + +## 链接 + +- [文档](docs/README.md) +- [贡献指南](CONTRIBUTING.md) +- [安全策略](SECURITY.md) +- [GitHub 仓库](https://github.com/tinkle-community/nofx) + +--- + +**最后更新:** 2025-11-01 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..d1c3406599 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,237 @@ +# Contributor Covenant Code of Conduct / 贡献者公约行为准则 + +**Languages:** [English](#english) | [中文](#中文) + +--- + +# English + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at: + + +You can also report via: +- **Telegram:** Direct message to [@Web3Tinkle](https://t.me/Web3Tinkle) +- **Twitter:** DM to [@nofx_ai](https://x.com/nofx_ai) + +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations + +--- + +# 中文 + +## 我们的承诺 + +作为成员、贡献者和领导者,我们承诺使社区中的每个人都不受骚扰,无论其年龄、体型、明显或不明显的残疾、种族、性别特征、性别认同和表达、经验水平、教育程度、社会经济地位、国籍、个人外貌、种族、种姓、肤色、宗教或性认同和取向如何。 + +我们承诺以有助于开放、友好、多元、包容和健康社区的方式行事和互动。 + +## 我们的标准 + +有助于为我们的社区创造积极环境的行为示例包括: + +* 对他人表现出同理心和善意 +* 尊重不同的意见、观点和经验 +* 给予并优雅地接受建设性反馈 +* 接受责任并向受我们错误影响的人道歉,并从经验中学习 +* 关注不仅对我们个人最好,而且对整个社区最好的事情 + +不可接受的行为示例包括: + +* 使用性化的语言或图像,以及任何形式的性关注或性挑逗 +* 挑衅、侮辱性或贬损性评论,以及人身或政治攻击 +* 公开或私下骚扰 +* 未经他人明确许可,发布他人的私人信息,如物理地址或电子邮件地址 +* 在专业环境中可能被合理认为不适当的其他行为 + +## 执行责任 + +社区领导者负责阐明和执行我们可接受行为的标准,并将对他们认为不适当、威胁性、冒犯性或有害的任何行为采取适当和公平的纠正措施。 + +社区领导者有权利和责任删除、编辑或拒绝不符合本行为准则的评论、提交、代码、wiki 编辑、问题和其他贡献,并在适当时传达审核决定的原因。 + +## 范围 + +本行为准则适用于所有社区空间,也适用于个人在公共空间正式代表社区的情况。代表我们社区的示例包括使用官方电子邮件地址、通过官方社交媒体账户发布信息,或在线上或线下活动中担任指定代表。 + +## 执行 + +可以向负责执行的社区领导者报告滥用、骚扰或其他不可接受行为的实例: + + +您也可以通过以下方式报告: +- **Telegram:** 直接消息 [@Web3Tinkle](https://t.me/Web3Tinkle) +- **Twitter:** 私信 [@nofx_ai](https://x.com/nofx_ai) + +所有投诉都将得到迅速和公正的审查和调查。 + +所有社区领导者都有义务尊重任何事件报告者的隐私和安全。 + +## 执行指南 + +社区领导者将遵循这些社区影响指南来确定他们认为违反本行为准则的任何行动的后果: + +### 1. 纠正 + +**社区影响**:使用不适当的语言或其他被认为在社区中不专业或不受欢迎的行为。 + +**后果**:社区领导者的私下书面警告,说明违规的性质和解释为什么行为不适当。可能要求公开道歉。 + +### 2. 警告 + +**社区影响**:通过单一事件或一系列行动违规。 + +**后果**:警告并说明持续行为的后果。在指定时间内不与相关人员互动,包括不主动与执行行为准则的人互动。这包括避免在社区空间以及外部渠道(如社交媒体)的互动。违反这些条款可能导致临时或永久禁令。 + +### 3. 临时禁令 + +**社区影响**:严重违反社区标准,包括持续的不当行为。 + +**后果**:在指定时间内临时禁止与社区进行任何形式的互动或公开交流。在此期间,不允许与相关人员进行公开或私下互动,包括不主动与执行行为准则的人互动。违反这些条款可能导致永久禁令。 + +### 4. 永久禁令 + +**社区影响**:表现出违反社区标准的模式,包括持续的不当行为、对个人的骚扰,或对个人类别的攻击或贬低。 + +**后果**:永久禁止在社区内进行任何形式的公开互动。 + +## 归属 + +本行为准则改编自 [贡献者公约][homepage] 2.1 版,可在 +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1] 获取。 + +社区影响指南受到 [Mozilla 行为准则执行阶梯][Mozilla CoC] 的启发。 + +有关本行为准则的常见问题解答,请参阅 [https://www.contributor-covenant.org/faq][FAQ]。翻译版本可在 [https://www.contributor-covenant.org/translations][translations] 获取。 + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..553a0399ff --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,480 @@ +# 🤝 Contributing to NOFX + +**Language:** [English](CONTRIBUTING.md) | [中文](docs/i18n/zh-CN/CONTRIBUTING.md) + +Thank you for your interest in contributing to NOFX! This document provides guidelines and workflows for contributing to the project. + +--- + +## 📑 Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [How Can I Contribute?](#how-can-i-contribute) +- [Development Workflow](#development-workflow) +- [PR Submission Guidelines](#pr-submission-guidelines) +- [Coding Standards](#coding-standards) +- [Commit Message Guidelines](#commit-message-guidelines) +- [Review Process](#review-process) +- [Bounty Program](#bounty-program) + +--- + +## 📜 Code of Conduct + +This project adheres to the [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. + +--- + +## 🎯 How Can I Contribute? + +### 1. Report Bugs 🐛 + +- Use the [Bug Report Template](.github/ISSUE_TEMPLATE/bug_report.md) +- Check if the bug has already been reported +- Include detailed reproduction steps +- Provide environment information (OS, Go version, etc.) + +### 2. Suggest Features ✨ + +- Use the [Feature Request Template](.github/ISSUE_TEMPLATE/feature_request.md) +- Explain the use case and benefits +- Check if it aligns with the [project roadmap](docs/roadmap/README.md) + +### 3. Submit Pull Requests 🔧 + +Before submitting a PR, please check the following: + +#### ✅ **Accepted Contributions** + +**High Priority** (aligned with roadmap): +- 🔒 Security enhancements (encryption, authentication, RBAC) +- 🧠 AI model integrations (GPT-4, Claude, Gemini Pro) +- 🔗 Exchange integrations (OKX, Bybit, Lighter, EdgeX) +- 📊 Trading data APIs (AI500, OI analysis, NetFlow) +- 🎨 UI/UX improvements (mobile responsiveness, charts) +- ⚡ Performance optimizations +- 🐛 Bug fixes +- 📝 Documentation improvements + +**Medium Priority:** +- ✅ Test coverage improvements +- 🌐 Internationalization (new language support) +- 🔧 Build/deployment tooling +- 📈 Monitoring and logging enhancements + +#### ❌ **Not Accepted** (without prior discussion) + +- Major architectural changes without RFC (Request for Comments) +- Features not aligned with project roadmap +- Breaking changes without migration path +- Code that introduces new dependencies without justification +- Experimental features without opt-in flag + +**⚠️ Important:** For major features, please open an issue for discussion **before** starting work. + +--- + +## 🛠️ Development Workflow + +### 1. Fork and Clone + +```bash +# Fork the repository on GitHub +# Then clone your fork +git clone https://github.com/YOUR_USERNAME/nofx.git +cd nofx + +# Add upstream remote +git remote add upstream https://github.com/tinkle-community/nofx.git +``` + +### 2. Create a Feature Branch + +```bash +# Update your local dev branch +git checkout dev +git pull upstream dev + +# Create a new branch +git checkout -b feature/your-feature-name +# or +git checkout -b fix/your-bug-fix +``` + +**Branch Naming Convention:** +- `feature/` - New features +- `fix/` - Bug fixes +- `docs/` - Documentation updates +- `refactor/` - Code refactoring +- `perf/` - Performance improvements +- `test/` - Test updates +- `chore/` - Build/config changes + +### 3. Set Up Development Environment + +```bash +# Install Go dependencies +go mod download + +# Install frontend dependencies +cd web +npm install +cd .. + +# Install TA-Lib (required) +# macOS: +brew install ta-lib + +# Ubuntu/Debian: +sudo apt-get install libta-lib0-dev +``` + +### 4. Make Your Changes + +- Follow the [coding standards](#coding-standards) +- Write tests for new features +- Update documentation as needed +- Keep commits focused and atomic + +### 5. Test Your Changes + +```bash +# Run backend tests +go test ./... + +# Build backend +go build -o nofx + +# Run frontend in dev mode +cd web +npm run dev + +# Build frontend +npm run build +``` + +### 6. Commit Your Changes + +Follow the [commit message guidelines](#commit-message-guidelines): + +```bash +git add . +git commit -m "feat: add support for OKX exchange integration" +``` + +### 7. Push and Create PR + +```bash +# Push to your fork +git push origin feature/your-feature-name + +# Go to GitHub and create a Pull Request +# Use the PR template and fill in all sections +``` + +--- + +## 📝 PR Submission Guidelines + +### Before Submitting + +- [ ] Code compiles successfully (`go build` and `npm run build`) +- [ ] All tests pass (`go test ./...`) +- [ ] No linting errors (`go fmt`, `go vet`) +- [ ] Documentation is updated +- [ ] Commits follow conventional commits format +- [ ] Branch is rebased on latest `dev` + +### PR Title Format + +Use [Conventional Commits](https://www.conventionalcommits.org/) format: + +``` +(): + +Examples: +feat(exchange): add OKX exchange integration +fix(trader): resolve position tracking bug +docs(readme): update installation instructions +perf(ai): optimize prompt generation +refactor(core): extract common exchange interface +``` + +**Types:** +- `feat` - New feature +- `fix` - Bug fix +- `docs` - Documentation +- `style` - Code style (formatting, no logic change) +- `refactor` - Code refactoring +- `perf` - Performance improvement +- `test` - Test updates +- `chore` - Build/config changes +- `ci` - CI/CD changes +- `security` - Security improvements + +### PR Description + +Use the [PR template](.github/PULL_REQUEST_TEMPLATE.md) and ensure: + +1. **Clear description** of what and why +2. **Type of change** is marked +3. **Related issues** are linked +4. **Testing steps** are documented +5. **Screenshots** for UI changes +6. **All checkboxes** are completed + +### PR Size + +Keep PRs focused and reasonably sized: + +- ✅ **Small PR** (< 300 lines): Ideal, fast review +- ⚠️ **Medium PR** (300-1000 lines): Acceptable, may take longer +- ❌ **Large PR** (> 1000 lines): Please break into smaller PRs + +--- + +## 💻 Coding Standards + +### Go Code + +```go +// ✅ Good: Clear naming, proper error handling +func ConnectToExchange(apiKey, secret string) (*Exchange, error) { + if apiKey == "" || secret == "" { + return nil, fmt.Errorf("API credentials are required") + } + + client, err := createClient(apiKey, secret) + if err != nil { + return nil, fmt.Errorf("failed to create client: %w", err) + } + + return &Exchange{client: client}, nil +} + +// ❌ Bad: Poor naming, no error handling +func ce(a, s string) *Exchange { + c := createClient(a, s) + return &Exchange{client: c} +} +``` + +**Best Practices:** +- Use meaningful variable names +- Handle all errors explicitly +- Add comments for complex logic +- Follow Go idioms and conventions +- Run `go fmt` before committing +- Use `go vet` and `golangci-lint` + +### TypeScript/React Code + +```typescript +// ✅ Good: Type-safe, clear naming +interface TraderConfig { + id: string; + exchange: 'binance' | 'hyperliquid' | 'aster'; + aiModel: string; + enabled: boolean; +} + +const TraderCard: React.FC<{ trader: TraderConfig }> = ({ trader }) => { + const [isRunning, setIsRunning] = useState(false); + + const handleStart = async () => { + try { + await startTrader(trader.id); + setIsRunning(true); + } catch (error) { + console.error('Failed to start trader:', error); + } + }; + + return
...
; +}; + +// ❌ Bad: No types, unclear naming +const TC = (props) => { + const [r, setR] = useState(false); + const h = () => { startTrader(props.t.id); setR(true); }; + return
...
; +}; +``` + +**Best Practices:** +- Use TypeScript strict mode +- Define interfaces for all data structures +- Avoid `any` type +- Use functional components with hooks +- Follow React best practices +- Run `npm run lint` before committing + +### File Structure + +``` +NOFX/ +├── cmd/ # Main applications +├── internal/ # Private code +│ ├── exchange/ # Exchange adapters +│ ├── trader/ # Trading logic +│ ├── ai/ # AI integrations +│ └── api/ # API handlers +├── pkg/ # Public libraries +├── web/ # Frontend +│ ├── src/ +│ │ ├── components/ +│ │ ├── pages/ +│ │ ├── hooks/ +│ │ └── utils/ +│ └── public/ +└── docs/ # Documentation +``` + +--- + +## 📋 Commit Message Guidelines + +### Format + +``` +(): + + + +