diff --git a/.gitignore b/.gitignore index 3a58136..cee51aa 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ # dependencies .cache node_modules +venv +.venv /.pnp .pnp.js diff --git a/config/AGENTS.md b/config/AGENTS.md new file mode 100644 index 0000000..b0baa29 --- /dev/null +++ b/config/AGENTS.md @@ -0,0 +1,524 @@ + + # 全局规则 + +## 交互语言 +- 所有交互一律使用中文(包括问题回答、代码解释、错误说明等) +- 代码注释使用中文 +- 变量名、函数名等代码标识符使用英文(遵循编程惯例) + +## 代码风格 +- 保持代码简洁、易读 +- 遵循项目现有的代码规范和约定 +- 不要添加不必要的注释或解释 +- 字符串排序(如日期 `YYYY-MM-DD`)用纯比较运算符 `a > b ? 1 : a < b ? -1 : 0`,不用 `localeCompare` —— 后者走 ICU 规则,与字典序不严格等价 + +## 工作原则 +- 优先理解用户意图,必要时主动提问澄清 +- 修改代码前先了解项目结构和现有模式 +- 完成修改后运行测试验证(如果项目有测试) +- 除非用户明确要求,否则不要提交代码变更 +- **代码查阅确认机制**:当需要阅读代码来确认 API 细节或具体用法时,必须先向用户说明: + 1. 为什么要阅读代码(目的) + 2. 准备阅读哪些代码(范围) + 3. 等待用户决定: + - 同意按方案继续阅读 + - 直接告知具体用法 + - 修正阅读范围,指定应该阅读哪些代码 + +### Git commit message 生成规范 + +生成 commit message 前,必须过滤非实质性变更: +- 先用 `git diff --ignore-all-space --stat` 检查是否有实质性变更 +- 使用 `git diff --ignore-cr-at-eol` 忽略行尾符(CRLF/LF)变化 +- 使用 `git diff --word-diff` 检查单词级别的实质性变更 +- 若忽略空白字符后无实质性变更,不应描述为"代码格式化"或"风格调整" +- 优先关注:新增/删除的代码逻辑、函数签名变更、配置项修改等 + +## 开发环境与脚本规范 + +### Python 环境 +- 当环境需要使用 Python 时,优先激活使用当前项目目录中的 venv 环境 +- 如果项目中没有 venv 环境,则在 `/root` 目录下创建虚拟环境后再激活 + - 创建命令示例:`python3 -m venv /root/venv` + - 激活命令:`source /root/venv/bin/activate` + +### 脚本文件管理 +- 为完成任务而编写的 Python 脚本,默认放入 `/tmp` 临时目录 +- 除非用户明确需要保存,则将脚本移至项目目录下或用户指定的目录下 + +## 代码查阅原则 + +- 当 AGENTS.md 中已提供了完整的代码模板和规范时(如 Entity、Dao、Service、MyBatis XML 等模板), + 必须直接按照模板和规范编写代码,**不得**先去阅读现有代码来"学习风格" +- 只有在以下情况才允许阅读现有代码: + 1. 确认某个框架 API 的具体用法或参数 + 2. 了解某个模块的具体业务逻辑或实现细节 + 3. 模板和规范中未覆盖的特殊场景 +- 简单说:规范已覆盖的内容 → 直接按规范写;规范未覆盖的内容 → 再去看代码 +- **禁止在规划阶段启动 agent 或工具去阅读现有代码**。 + 规划必须仅基于两份 AGENTS.md 中的信息进行。 + 只有在进入实现阶段、且需要确认模板未覆盖的框架 API 细节时, + 才允许按「代码查阅确认机制」经用户同意后阅读代码。 + +## 项目初始化规则 + +当用户要求"了解项目"、"熟悉规范"或类似指令时: + +### 必须做的: +1. 阅读并理解**全局 AGENTS.md**(`/root/.config/opencode/AGENTS.md`,即本文件)和**项目级 AGENTS.md**(`/workspace/AGENTS.md`,位于项目根目录)中的所有规范 +2. 基于两份 AGENTS.md 中的信息回答项目相关问题 +3. 直接使用两份 AGENTS.md 中的代码模板和规范进行开发 + +### 禁止做的: +1. 不要主动阅读实际代码文件来"学习风格" +2. 不要查看现有模块来"理解代码组织" +3. 不要假设 AGENTS.md 中的信息不够用 +4. 不要只阅读其中一份 AGENTS.md 而忽略另一份——两份都必须阅读 + +### 标准回复: +"已阅读全局和项目级 AGENTS.md,项目采用 [技术栈],包含 [模块数量] 个模块。 +命名规范:[简述]。代码模板已就绪,可以开始开发。" + +## 前端项目开发原则(仅限 React 项目) + +> **适用范围说明:** 以下原则仅适用于前端 React 项目开发。对于后端项目(Java、Python 等)或其他类型项目,这些规则不适用。 + +### 1. 架构思维 + +**分层计算原则** +- 数据计算按层级划分:原始数据 → 单实体衍生 → 跨实体汇总 → 展示 +- 每一层只做自己该做的事,不越界 +- 体现"关注点分离"的核心思想 + +**响应式数据流** +- 状态变化自动触发相关更新,而非手动调用 +- 使用观察者模式(如 autorun)实现自动响应 +- 体现"声明式编程"优于"命令式编程"的理念 + +### 2. 命名哲学 + +**语义优先,避免冗余** +- 命名要清晰表达含义,但不重复上下文 +- 在已知上下文中,省略冗余前缀 +- 嵌套结构表达关联关系(如 `compare.scale.daily`) +- 体现"代码即文档"的自解释原则 + +### 3. 职责分离 + +**逻辑与展示分离** +- 业务逻辑集中在状态管理层 +- 组件只负责展示和 UI 配置 +- 体现"单一职责原则" + +**全局与局部分离** +- 全局参数集中管理(日期、汇率等) +- 业务数据按领域分离 +- 体现"高内聚、低耦合"的设计原则 + +### 4. 工程实践 + +**渐进式改进** +- 逐步消除技术债务(如硬编码数据) +- 暂时不处理的部分明确标注,不阻塞整体进度 +- 体现"持续重构"的工程思维 + +**一致性原则** +- 相关文件集中组织(Store、API、Mock 一一对应) +- 统一的命名规范和文件结构 +- 体现"约定优于配置"的理念 + +### 5. 数据驱动 + +**以最细粒度实体为基础** +- 所有数据围绕最小业务单元组织 +- 汇总数据从细粒度数据派生 +- 体现"数据规范化"的思想 + +**确定性 Mock** +- Mock 数据基于种子生成,保证可重复性 +- 体现"可测试性"和"可预测性"的要求 + +### 6. 组件编码约定 + +**observer 使用规范** + +`observer` 必须跟随 `export`,而非组件定义: + +```javascript +// ✅ 正确:定义与导出分离 +const MyComponent = () => { ... }; +export default observer(MyComponent); + +// ✅ 正确:定义与导出合一时 +export const MyComponent = observer(() => { ... }); + +// ❌ 错误:observer 包裹定义,单独导出 +const MyComponent = observer(() => { ... }); +export default MyComponent; +``` + +## 代码模板 + +### Store 模板 + +```javascript +import {action, computed, observable} from "mobx"; +import {LocalStore} from "./local.store"; // 如需本地存储继承 +import {XxxApi} from "../api"; + +export class XxxStore extends LocalStore { + + @observable + list = [] + + @observable + detail = null + + @computed + get derivedData() { + // 计算衍生数据 + return this.list.map(item => ({ + ...item, + computedField: item.field * 2 + })); + } + + get storeName() { + return "xxx"; // 本地存储 key + } + + @action + fetchData = async (params) => { + this.list = await XxxApi.getList(params); + } + + @action + fetchDetail = async (id) => { + this.detail = await XxxApi.getDetail(id); + } + + @action + reset = () => { + this.list = []; + this.detail = null; + } +} + +export default new XxxStore(); +``` + +### API 模板 + +```javascript +import request from "../utils/request"; +import qs from "query-string"; + +// GET 请求 +export const getList = async (query) => { + return request.get(qs.stringifyUrl({ + url: '/xxx', query + }, {skipNull: true, skipEmptyString: true})); +} + +// GET 详情 +export const getDetail = async (id) => { + return request.get(`/xxx/${id}`); +} + +// POST 创建 +export const create = async (data) => { + return request.post('/xxx', {data}); +} + +// PUT 更新 +export const update = async (id, data) => { + return request.put(`/xxx/${id}`, {data}); +} + +// DELETE 删除 +export const remove = async (id) => { + return request.delete(`/xxx/${id}`); +} +``` + +### Page 组件模板 + +```javascript +import React, {useEffect} from "react"; +import {observer} from 'mobx-react'; +import {PageContainer} from "@ant-design/pro-layout"; +import {useStores} from "../../hooks"; + +const XxxPage = () => { + const {xxxStore} = useStores(); + + useEffect(() => { + xxxStore.fetchData(); + }, []); + + return ( + + {/* 页面内容 */} + + ); +} + +export default observer(XxxPage); +``` + +### Hook 模板 + +```javascript +import {useState, useEffect, useCallback} from "react"; + +export const useXxx = (initialValue) => { + const [value, setValue] = useState(initialValue); + + const doSomething = useCallback(() => { + // 逻辑 + }, []); + + return {value, setValue, doSomething}; +} +``` + +### Mock 数据模板 + +```javascript +// mock/xxx.js +module.exports = { + _apis: { + getList: "GET /api/v1.0/xxx", + getDetail: "GET /api/v1.0/xxx/:id", + create: "POST /api/v1.0/xxx", + }, + + "GET /api/v1.0/xxx": (req, res) => { + res.send({ + status: "success", + body: { + content: [ + {id: 1, name: "示例1", field: "value1"}, + {id: 2, name: "示例2", field: "value2"}, + ], + total: 2, + page: 0, + size: 20, + last: true + }, + success: true + }); + }, + + "GET /api/v1.0/xxx/:id": { + status: "success", + body: {id: 1, name: "示例", field: "value"}, + success: true + }, + + "POST /api/v1.0/xxx": { + status: "success", + body: {id: 3}, + success: true + } +} +``` + +**模板约定说明:** + +- **`_apis`**:别名 → 路由字符串的映射,供环境变量细粒度屏蔽指定接口(如 `REACT_APP_MOCK_IGNORE=xxx.create`)。**别名必须与 API 层的导出方法名完全一致**,聚合入口依赖此对应关系做过滤 +- **`_getData()`**:返回模块内数据数组,供其他 mock 模块交叉引用(如创建时校验关联实体是否存在)。仅在模块间有引用需求时提供 +- **路由注册顺序**:具体路由必须**先于**参数路由注册,否则 `:id` 会吞掉字面量路径(如 `/xxx/settings` 必须先于 `/xxx/:id`) +- **聚合入口**:若用统一入口(如 `mock/index.js`)合并多模块,展开时必须剔除非路由键(`_apis`、`_getData`),否则同名键会被后注册模块覆盖;模块间交叉引用应直接 `require` 目标模块,不经聚合对象 + +### 图表组件模板 + +```javascript +import React from "react"; +import {observer} from 'mobx-react'; +import {Line, Column, Pie} from "@ant-design/charts"; +import {Card, Row, Col, Statistic} from "antd"; +import {useStores} from "../../hooks"; + +const XxxChart = () => { + const {xxxStore} = useStores(); + const {chartData, summary} = xxxStore; + + // 折线图配置 + const lineConfig = { + data: chartData.lineData, + xField: 'date', + yField: 'value', + seriesField: 'type', + }; + + // 饼图配置 + const pieConfig = { + data: chartData.pieData, + angleField: 'value', + colorField: 'type', + }; + + return ( + + + + + + + + + + + + + + + + + + ); +} + +export default observer(XxxChart); +``` + +## 装饰器语法 + +项目使用 MobX 5,需要以下 Babel 配置: + +```javascript +// craco.config.js +babel: { + plugins: [ + ['@babel/plugin-proposal-decorators', {legacy: true}], + ["@babel/plugin-proposal-class-properties", {loose: true}], + ] +} +``` + +常用装饰器: +- `@observable` - 声明可观察状态 +- `@action` - 声明修改状态的方法 +- `@computed` - 声明计算属性 + +## 样式规范 + +### CSS Modules +- 文件命名:`xxx.scss` 或 `xxx.module.scss` +- 自动启用:`src/` 和 `@chaosoft/` 下的 `.scss` 文件 +- 类名转换:kebab-case → camelCase + +```scss +// component.scss +.container { + padding: 16px; +} + +.title-text { + font-size: 16px; +} +``` + +```javascript +// component.js +import styles from './component.scss'; + +const Component = () => ( +
+ 标题 +
+); +``` + +## VERSION.md 编写规范 + +### 文件位置 +项目根目录:`{project-root}/VERSION.md` + +### 版本号规则 + +采用语义化版本(Semantic Versioning):`MAJOR.MINOR.PATCH` + +| 部分 | 说明 | 示例 | +|------|------|------| +| MAJOR | 不兼容的 API 变更 | 1.0.0 → 2.0.0 | +| MINOR | 向下兼容的功能新增 | 1.0.0 → 1.1.0 | +| PATCH | 向下兼容的问题修复 | 1.0.0 → 1.0.1 | + +### 内容结构 + +```markdown +# 项目版本记录 + +## 当前版本 +v1.0.0 (2026-09-22) + +## 版本历史 + +| 版本 | 日期 | 说明 | +|------|------|------| +| v1.0.0 | 2026-09-22 | 初始发布版本 | + +## 版本明细 + +### 待发布 + +**发布日期:** 待定 + +**新增功能:** +- 无 + +**修复问题:** +- 无 + +**变更说明:** +- 无 + +--- + +### v1.0.0 + +**发布日期:** 2026-09-22 + +**新增功能:** +- 客户管理模块(CRUD) +- 租约管理模块(CRUD) + +**修复问题:** +- 无 + +**变更说明:** +- 无 +``` + +### 待发布章节 + +- **位置**:固定位于「版本明细」最上面,在所有已发布版本章节之上;全文有且仅有一个 +- **结构**:与版本章节同构 —— `**发布日期:** 待定` + 新增功能 / 修复问题 / 变更说明,以 `---` 与下一章节分隔 +- **用途**:累积当前未发布周期内用户可感知的净变化,同受「记录范围(产品视角)」与「对比基线(净变化原则)」约束 +- **发版迁移**:发版时将其内容整体转为新版本章节(标题改为版本号、发布日期改为实际日期,并同步「当前版本」与「版本历史」表格),随后在其上方重建一个空的「待发布」章节,保证其恒在最上面 +- **常驻保留**:即使本周期无待记录内容,也保留空的「待发布」章节(三项均填「无」),不得删除 + +### 编写要求 + +1. **当前版本**:始终显示最新版本号和发布日期 +2. **版本历史表格**:按时间倒序排列(最新版本在最上面) +3. **版本明细**: + - 每个版本一个章节,标题为版本号(如 `### v1.0.0`) + - 包含:发布日期、新增功能、修复问题、变更说明 + - 按时间倒序排列(最新版本在最上面) + - 「待发布」章节恒置于最上面(在所有已发布版本章节之上),详见「待发布章节」 +4. **更新时机**:每次发布新版本时更新此文件 +5. **记录范围(产品视角)**:只记录用户可感知的产品功能变化,按最终功能归类为新增 / 修复 / 变更 + - ✅ 记录:新增页面与菜单、新增图表与指标卡、功能行为与数据口径变化、界面呈现调整、缺陷修复、交付形态变更 + - ❌ 不记录:技术实现细节(Store / API / 组件拆分、状态管理调整)、代码重构与规范修正、 + Mock 数据与辅助文档(AGENTS.md、docs/)调整、构建脚本与依赖变更、 + API 数据结构变更(仅后端可感知的字段调整、接口重构等) + - 判断标准:以最终用户(产品使用者)是否可感知为唯一标准,后端开发者的感知不纳入记录范围 + - 描述以功能名称与用户可见效果表述,不堆砌文件路径、类名、行数等技术标识 +6. **对比基线(净变化原则)**:所有条目必须是「当前状态 vs 上一个已发布版本」的净变化 + - 同一未发布周期内先新增、后又修复或调整的内容,不单独记录修复/变更,只记录最终功能形态 + - 判断方法:若某缺陷或行为在上一发布版本中不存在(属本周期新增功能自带),则不写入「修复问题」「变更说明」 + - 「新增功能」与「变更说明」不重复描述同一动作 + - 发版前按上一版本 tag 重新逐条核对 diff --git a/config/opencode.json b/config/opencode.json new file mode 100644 index 0000000..4debba5 --- /dev/null +++ b/config/opencode.json @@ -0,0 +1,80 @@ +{ + "$schema": "https://opencode.ai/config.json", + "model": "alibaba-cn/qwen3.8-max", + "provider": { + "bailian-payg": { + "npm": "@ai-sdk/anthropic", + "name": "Alibaba Cloud Model Studio", + "options": { + "baseURL": "https://llm-725mrgwyn1rzglgb.cn-beijing.maas.aliyuncs.com/apps/anthropic/v1", + "apiKey": "{env:ALIBABA_API_KEY}" + }, + "models": { + "qwen3.7-max": { + "name": "Qwen3.7 Max", + "options": { + "thinking": { + "type": "enabled", + "budgetTokens": 8192 + } + } + }, + "qwen3.7-plus": { + "name": "Qwen3.7 Plus", + "modalities": { + "input": ["text", "image"], + "output": ["text"] + }, + "options": { + "thinking": { + "type": "enabled", + "budgetTokens": 8192 + } + } + }, + "qwen3.6-plus": { + "name": "Qwen3.6 Plus", + "modalities": { + "input": ["text", "image"], + "output": ["text"] + }, + "options": { + "thinking": { + "type": "enabled", + "budgetTokens": 8192 + } + } + } + } + }, + "alibaba-cn": { + "options": { + "apiKey": "{env:ALIBABA_API_KEY}", + "baseURL": "https://dashscope.aliyuncs.com/compatible-mode/v1" + } + } + }, + "mcp": { + "chrome-devtools": { + "type": "local", + "command": [ + "docker", "run", "--rm", "-i", + "chrome-devtools-mcp:node24-1.12.0", + "--browserUrl=http://192.168.3.95:9222" + ], + "enabled": false + } + }, + "server": { + "port": 4096, + "hostname": "0.0.0.0" + }, + "permission": { + "external_directory": { + "/var/store/**": "allow" + }, + "edit": { + "/var/store/**": "deny" + } + } +} diff --git a/python/docker-compose.yml b/python/docker-compose.yml new file mode 100644 index 0000000..33729f6 --- /dev/null +++ b/python/docker-compose.yml @@ -0,0 +1,22 @@ +services: + opencode-python: + image: opencode:py3-1.14.20 + container_name: opencode-python + environment: + - TZ=Asia/Shanghai + - ALIBABA_API_KEY=sk-6026a1b171b84c2985c316d063136105 + - HTTP_PROXY=http://host.docker.internal:${PROXY_PORT} + - HTTPS_PROXY=http://host.docker.internal:${PROXY_PORT} + - NO_PROXY=localhost,127.0.0.1,host.docker.internal,nexus.chaosoft.com.cn + volumes: + - ../config:/root/.config/opencode + - ./pip:/root/.pip + - /var/run/docker.sock:/var/run/docker.sock + - ${SRC_PATH}:/workspace + - ./projects/${PROJECT_NAME}/venv:/root/venv + working_dir: /workspace + networks: + - "opencode-net" +networks: + opencode-net: + driver: bridge diff --git a/python/pip/pip.conf b/python/pip/pip.conf new file mode 100644 index 0000000..835eeec --- /dev/null +++ b/python/pip/pip.conf @@ -0,0 +1,4 @@ +[global] +index-url = https://nexus.chaosoft.com.cn/repository/pypi-public/simple +trusted-host = nexus.chaosoft.com.cn + diff --git a/python/projects/hemin/.env b/python/projects/hemin/.env new file mode 100644 index 0000000..c81ede3 --- /dev/null +++ b/python/projects/hemin/.env @@ -0,0 +1 @@ +PROJECT_NAME=hemin \ No newline at end of file diff --git a/python/projects/hemin/startup.bat b/python/projects/hemin/startup.bat new file mode 100644 index 0000000..135b7ef --- /dev/null +++ b/python/projects/hemin/startup.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul +setlocal enabledelayedexpansion + +set "COMPOSE_CMD=docker compose" + +set "PROJECT_ROOT=%~dp0" +set "PROJECT_ROOT=%PROJECT_ROOT:~0,-1%" +for %%i in ("%PROJECT_ROOT%\..\..") do set "PYTHON_ROOT=%%~fi" +for %%i in ("%PYTHON_ROOT%\..") do set "SYSTEM_ROOT=%%~fi" + +echo %PROJECT_ROOT% +echo %PYTHON_ROOT% +echo %SYSTEM_ROOT% + +for %%R in ("%SYSTEM_ROOT%" "%PYTHON_ROOT%" "%PROJECT_ROOT%") do ( + if exist "%%R\.env" ( + set "COMPOSE_CMD=!COMPOSE_CMD! --env-file %%R\.env" + echo [FOUND] %%R\.env + ) + if exist "%%R\.env.local" ( + set "COMPOSE_CMD=!COMPOSE_CMD! --env-file %%R\.env.local" + echo [FOUND] %%R\.env.local + ) +) + +echo. + +cd %PYTHON_ROOT% + +::端口跳转,为了使用chrome mcp + +netsh interface portproxy add v4tov4 listenport=9222 listenaddress=0.0.0.0 connectport=9222 connectaddress=127.0.0.1 +netsh interface portproxy show v4tov4 + +echo 执行命令: !COMPOSE_CMD! run --rm opencode-python +!COMPOSE_CMD! run --rm opencode-python + +netsh interface portproxy delete v4tov4 listenport=9222 listenaddress=0.0.0.0 \ No newline at end of file diff --git a/yarn/config/AGENTS.md b/yarn/config/AGENTS.md deleted file mode 100644 index 82fd57b..0000000 --- a/yarn/config/AGENTS.md +++ /dev/null @@ -1,512 +0,0 @@ - - # 全局规则 - -## 交互语言 -- 所有交互一律使用中文(包括问题回答、代码解释、错误说明等) -- 代码注释使用中文 -- 变量名、函数名等代码标识符使用英文(遵循编程惯例) - -## 代码风格 -- 保持代码简洁、易读 -- 遵循项目现有的代码规范和约定 -- 不要添加不必要的注释或解释 -- 字符串排序(如日期 `YYYY-MM-DD`)用纯比较运算符 `a > b ? 1 : a < b ? -1 : 0`,不用 `localeCompare` —— 后者走 ICU 规则,与字典序不严格等价 - -## 工作原则 -- 优先理解用户意图,必要时主动提问澄清 -- 修改代码前先了解项目结构和现有模式 -- 完成修改后运行测试验证(如果项目有测试) -- 除非用户明确要求,否则不要提交代码变更 -- **代码查阅确认机制**:当需要阅读代码来确认 API 细节或具体用法时,必须先向用户说明: - 1. 为什么要阅读代码(目的) - 2. 准备阅读哪些代码(范围) - 3. 等待用户决定: - - 同意按方案继续阅读 - - 直接告知具体用法 - - 修正阅读范围,指定应该阅读哪些代码 - -### Git commit message 生成规范 - -生成 commit message 前,必须过滤非实质性变更: -- 先用 `git diff --ignore-all-space --stat` 检查是否有实质性变更 -- 使用 `git diff --ignore-cr-at-eol` 忽略行尾符(CRLF/LF)变化 -- 使用 `git diff --word-diff` 检查单词级别的实质性变更 -- 若忽略空白字符后无实质性变更,不应描述为"代码格式化"或"风格调整" -- 优先关注:新增/删除的代码逻辑、函数签名变更、配置项修改等 - -## 代码查阅原则 - -- 当 AGENTS.md 中已提供了完整的代码模板和规范时(如 Entity、Dao、Service、MyBatis XML 等模板), - 必须直接按照模板和规范编写代码,**不得**先去阅读现有代码来"学习风格" -- 只有在以下情况才允许阅读现有代码: - 1. 确认某个框架 API 的具体用法或参数 - 2. 了解某个模块的具体业务逻辑或实现细节 - 3. 模板和规范中未覆盖的特殊场景 -- 简单说:规范已覆盖的内容 → 直接按规范写;规范未覆盖的内容 → 再去看代码 -- **禁止在规划阶段启动 agent 或工具去阅读现有代码**。 - 规划必须仅基于两份 AGENTS.md 中的信息进行。 - 只有在进入实现阶段、且需要确认模板未覆盖的框架 API 细节时, - 才允许按「代码查阅确认机制」经用户同意后阅读代码。 - -## 项目初始化规则 - -当用户要求"了解项目"、"熟悉规范"或类似指令时: - -### 必须做的: -1. 阅读并理解**全局 AGENTS.md**(`/root/.config/opencode/AGENTS.md`,即本文件)和**项目级 AGENTS.md**(`/workspace/AGENTS.md`,位于项目根目录)中的所有规范 -2. 基于两份 AGENTS.md 中的信息回答项目相关问题 -3. 直接使用两份 AGENTS.md 中的代码模板和规范进行开发 - -### 禁止做的: -1. 不要主动阅读实际代码文件来"学习风格" -2. 不要查看现有模块来"理解代码组织" -3. 不要假设 AGENTS.md 中的信息不够用 -4. 不要只阅读其中一份 AGENTS.md 而忽略另一份——两份都必须阅读 - -### 标准回复: -"已阅读全局和项目级 AGENTS.md,项目采用 [技术栈],包含 [模块数量] 个模块。 -命名规范:[简述]。代码模板已就绪,可以开始开发。" - -## 前端项目开发原则(仅限 React 项目) - -> **适用范围说明:** 以下原则仅适用于前端 React 项目开发。对于后端项目(Java、Python 等)或其他类型项目,这些规则不适用。 - -### 1. 架构思维 - -**分层计算原则** -- 数据计算按层级划分:原始数据 → 单实体衍生 → 跨实体汇总 → 展示 -- 每一层只做自己该做的事,不越界 -- 体现"关注点分离"的核心思想 - -**响应式数据流** -- 状态变化自动触发相关更新,而非手动调用 -- 使用观察者模式(如 autorun)实现自动响应 -- 体现"声明式编程"优于"命令式编程"的理念 - -### 2. 命名哲学 - -**语义优先,避免冗余** -- 命名要清晰表达含义,但不重复上下文 -- 在已知上下文中,省略冗余前缀 -- 嵌套结构表达关联关系(如 `compare.scale.daily`) -- 体现"代码即文档"的自解释原则 - -### 3. 职责分离 - -**逻辑与展示分离** -- 业务逻辑集中在状态管理层 -- 组件只负责展示和 UI 配置 -- 体现"单一职责原则" - -**全局与局部分离** -- 全局参数集中管理(日期、汇率等) -- 业务数据按领域分离 -- 体现"高内聚、低耦合"的设计原则 - -### 4. 工程实践 - -**渐进式改进** -- 逐步消除技术债务(如硬编码数据) -- 暂时不处理的部分明确标注,不阻塞整体进度 -- 体现"持续重构"的工程思维 - -**一致性原则** -- 相关文件集中组织(Store、API、Mock 一一对应) -- 统一的命名规范和文件结构 -- 体现"约定优于配置"的理念 - -### 5. 数据驱动 - -**以最细粒度实体为基础** -- 所有数据围绕最小业务单元组织 -- 汇总数据从细粒度数据派生 -- 体现"数据规范化"的思想 - -**确定性 Mock** -- Mock 数据基于种子生成,保证可重复性 -- 体现"可测试性"和"可预测性"的要求 - -### 6. 组件编码约定 - -**observer 使用规范** - -`observer` 必须跟随 `export`,而非组件定义: - -```javascript -// ✅ 正确:定义与导出分离 -const MyComponent = () => { ... }; -export default observer(MyComponent); - -// ✅ 正确:定义与导出合一时 -export const MyComponent = observer(() => { ... }); - -// ❌ 错误:observer 包裹定义,单独导出 -const MyComponent = observer(() => { ... }); -export default MyComponent; -``` - -## 代码模板 - -### Store 模板 - -```javascript -import {action, computed, observable} from "mobx"; -import {LocalStore} from "./local.store"; // 如需本地存储继承 -import {XxxApi} from "../api"; - -export class XxxStore extends LocalStore { - - @observable - list = [] - - @observable - detail = null - - @computed - get derivedData() { - // 计算衍生数据 - return this.list.map(item => ({ - ...item, - computedField: item.field * 2 - })); - } - - get storeName() { - return "xxx"; // 本地存储 key - } - - @action - fetchData = async (params) => { - this.list = await XxxApi.getList(params); - } - - @action - fetchDetail = async (id) => { - this.detail = await XxxApi.getDetail(id); - } - - @action - reset = () => { - this.list = []; - this.detail = null; - } -} - -export default new XxxStore(); -``` - -### API 模板 - -```javascript -import request from "../utils/request"; -import qs from "query-string"; - -// GET 请求 -export const getList = async (query) => { - return request.get(qs.stringifyUrl({ - url: '/xxx', query - }, {skipNull: true, skipEmptyString: true})); -} - -// GET 详情 -export const getDetail = async (id) => { - return request.get(`/xxx/${id}`); -} - -// POST 创建 -export const create = async (data) => { - return request.post('/xxx', {data}); -} - -// PUT 更新 -export const update = async (id, data) => { - return request.put(`/xxx/${id}`, {data}); -} - -// DELETE 删除 -export const remove = async (id) => { - return request.delete(`/xxx/${id}`); -} -``` - -### Page 组件模板 - -```javascript -import React, {useEffect} from "react"; -import {observer} from 'mobx-react'; -import {PageContainer} from "@ant-design/pro-layout"; -import {useStores} from "../../hooks"; - -const XxxPage = () => { - const {xxxStore} = useStores(); - - useEffect(() => { - xxxStore.fetchData(); - }, []); - - return ( - - {/* 页面内容 */} - - ); -} - -export default observer(XxxPage); -``` - -### Hook 模板 - -```javascript -import {useState, useEffect, useCallback} from "react"; - -export const useXxx = (initialValue) => { - const [value, setValue] = useState(initialValue); - - const doSomething = useCallback(() => { - // 逻辑 - }, []); - - return {value, setValue, doSomething}; -} -``` - -### Mock 数据模板 - -```javascript -// mock/xxx.js -module.exports = { - _apis: { - getList: "GET /api/v1.0/xxx", - getDetail: "GET /api/v1.0/xxx/:id", - create: "POST /api/v1.0/xxx", - }, - - "GET /api/v1.0/xxx": (req, res) => { - res.send({ - status: "success", - body: { - content: [ - {id: 1, name: "示例1", field: "value1"}, - {id: 2, name: "示例2", field: "value2"}, - ], - total: 2, - page: 0, - size: 20, - last: true - }, - success: true - }); - }, - - "GET /api/v1.0/xxx/:id": { - status: "success", - body: {id: 1, name: "示例", field: "value"}, - success: true - }, - - "POST /api/v1.0/xxx": { - status: "success", - body: {id: 3}, - success: true - } -} -``` - -**模板约定说明:** - -- **`_apis`**:别名 → 路由字符串的映射,供环境变量细粒度屏蔽指定接口(如 `REACT_APP_MOCK_IGNORE=xxx.create`)。**别名必须与 API 层的导出方法名完全一致**,聚合入口依赖此对应关系做过滤 -- **`_getData()`**:返回模块内数据数组,供其他 mock 模块交叉引用(如创建时校验关联实体是否存在)。仅在模块间有引用需求时提供 -- **路由注册顺序**:具体路由必须**先于**参数路由注册,否则 `:id` 会吞掉字面量路径(如 `/xxx/settings` 必须先于 `/xxx/:id`) -- **聚合入口**:若用统一入口(如 `mock/index.js`)合并多模块,展开时必须剔除非路由键(`_apis`、`_getData`),否则同名键会被后注册模块覆盖;模块间交叉引用应直接 `require` 目标模块,不经聚合对象 - -### 图表组件模板 - -```javascript -import React from "react"; -import {observer} from 'mobx-react'; -import {Line, Column, Pie} from "@ant-design/charts"; -import {Card, Row, Col, Statistic} from "antd"; -import {useStores} from "../../hooks"; - -const XxxChart = () => { - const {xxxStore} = useStores(); - const {chartData, summary} = xxxStore; - - // 折线图配置 - const lineConfig = { - data: chartData.lineData, - xField: 'date', - yField: 'value', - seriesField: 'type', - }; - - // 饼图配置 - const pieConfig = { - data: chartData.pieData, - angleField: 'value', - colorField: 'type', - }; - - return ( - - - - - - - - - - - - - - - - - - ); -} - -export default observer(XxxChart); -``` - -## 装饰器语法 - -项目使用 MobX 5,需要以下 Babel 配置: - -```javascript -// craco.config.js -babel: { - plugins: [ - ['@babel/plugin-proposal-decorators', {legacy: true}], - ["@babel/plugin-proposal-class-properties", {loose: true}], - ] -} -``` - -常用装饰器: -- `@observable` - 声明可观察状态 -- `@action` - 声明修改状态的方法 -- `@computed` - 声明计算属性 - -## 样式规范 - -### CSS Modules -- 文件命名:`xxx.scss` 或 `xxx.module.scss` -- 自动启用:`src/` 和 `@chaosoft/` 下的 `.scss` 文件 -- 类名转换:kebab-case → camelCase - -```scss -// component.scss -.container { - padding: 16px; -} - -.title-text { - font-size: 16px; -} -``` - -```javascript -// component.js -import styles from './component.scss'; - -const Component = () => ( -
- 标题 -
-); -``` - -## VERSION.md 编写规范 - -### 文件位置 -项目根目录:`{project-root}/VERSION.md` - -### 版本号规则 - -采用语义化版本(Semantic Versioning):`MAJOR.MINOR.PATCH` - -| 部分 | 说明 | 示例 | -|------|------|------| -| MAJOR | 不兼容的 API 变更 | 1.0.0 → 2.0.0 | -| MINOR | 向下兼容的功能新增 | 1.0.0 → 1.1.0 | -| PATCH | 向下兼容的问题修复 | 1.0.0 → 1.0.1 | - -### 内容结构 - -```markdown -# 项目版本记录 - -## 当前版本 -v1.0.0 (2026-09-22) - -## 版本历史 - -| 版本 | 日期 | 说明 | -|------|------|------| -| v1.0.0 | 2026-09-22 | 初始发布版本 | - -## 版本明细 - -### 待发布 - -**发布日期:** 待定 - -**新增功能:** -- 无 - -**修复问题:** -- 无 - -**变更说明:** -- 无 - ---- - -### v1.0.0 - -**发布日期:** 2026-09-22 - -**新增功能:** -- 客户管理模块(CRUD) -- 租约管理模块(CRUD) - -**修复问题:** -- 无 - -**变更说明:** -- 无 -``` - -### 待发布章节 - -- **位置**:固定位于「版本明细」最上面,在所有已发布版本章节之上;全文有且仅有一个 -- **结构**:与版本章节同构 —— `**发布日期:** 待定` + 新增功能 / 修复问题 / 变更说明,以 `---` 与下一章节分隔 -- **用途**:累积当前未发布周期内用户可感知的净变化,同受「记录范围(产品视角)」与「对比基线(净变化原则)」约束 -- **发版迁移**:发版时将其内容整体转为新版本章节(标题改为版本号、发布日期改为实际日期,并同步「当前版本」与「版本历史」表格),随后在其上方重建一个空的「待发布」章节,保证其恒在最上面 -- **常驻保留**:即使本周期无待记录内容,也保留空的「待发布」章节(三项均填「无」),不得删除 - -### 编写要求 - -1. **当前版本**:始终显示最新版本号和发布日期 -2. **版本历史表格**:按时间倒序排列(最新版本在最上面) -3. **版本明细**: - - 每个版本一个章节,标题为版本号(如 `### v1.0.0`) - - 包含:发布日期、新增功能、修复问题、变更说明 - - 按时间倒序排列(最新版本在最上面) - - 「待发布」章节恒置于最上面(在所有已发布版本章节之上),详见「待发布章节」 -4. **更新时机**:每次发布新版本时更新此文件 -5. **记录范围(产品视角)**:只记录用户可感知的产品功能变化,按最终功能归类为新增 / 修复 / 变更 - - ✅ 记录:新增页面与菜单、新增图表与指标卡、功能行为与数据口径变化、界面呈现调整、缺陷修复、交付形态变更 - - ❌ 不记录:技术实现细节(Store / API / 组件拆分、状态管理调整)、代码重构与规范修正、 - Mock 数据与辅助文档(AGENTS.md、docs/)调整、构建脚本与依赖变更、 - API 数据结构变更(仅后端可感知的字段调整、接口重构等) - - 判断标准:以最终用户(产品使用者)是否可感知为唯一标准,后端开发者的感知不纳入记录范围 - - 描述以功能名称与用户可见效果表述,不堆砌文件路径、类名、行数等技术标识 -6. **对比基线(净变化原则)**:所有条目必须是「当前状态 vs 上一个已发布版本」的净变化 - - 同一未发布周期内先新增、后又修复或调整的内容,不单独记录修复/变更,只记录最终功能形态 - - 判断方法:若某缺陷或行为在上一发布版本中不存在(属本周期新增功能自带),则不写入「修复问题」「变更说明」 - - 「新增功能」与「变更说明」不重复描述同一动作 - - 发版前按上一版本 tag 重新逐条核对 diff --git a/yarn/config/opencode.json b/yarn/config/opencode.json deleted file mode 100644 index c6aea2c..0000000 --- a/yarn/config/opencode.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "$schema": "https://opencode.ai/config.json", - "model": "alibaba-cn/qwen3.8-max", - "provider": { - "bailian-payg": { - "npm": "@ai-sdk/anthropic", - "name": "Alibaba Cloud Model Studio", - "options": { - "baseURL": "https://llm-725mrgwyn1rzglgb.cn-beijing.maas.aliyuncs.com/apps/anthropic/v1", - "apiKey": "{env:ALIBABA_API_KEY}" - }, - "models": { - "qwen3.7-max": { - "name": "Qwen3.7 Max", - "options": { - "thinking": { - "type": "enabled", - "budgetTokens": 8192 - } - } - }, - "qwen3.7-plus": { - "name": "Qwen3.7 Plus", - "modalities": { - "input": ["text", "image"], - "output": ["text"] - }, - "options": { - "thinking": { - "type": "enabled", - "budgetTokens": 8192 - } - } - }, - "qwen3.6-plus": { - "name": "Qwen3.6 Plus", - "modalities": { - "input": ["text", "image"], - "output": ["text"] - }, - "options": { - "thinking": { - "type": "enabled", - "budgetTokens": 8192 - } - } - } - } - }, - "alibaba-cn": { - "options": { - "apiKey": "{env:ALIBABA_API_KEY}", - "baseURL": "https://dashscope.aliyuncs.com/compatible-mode/v1" - } - } - }, - "mcp": { - "chrome-devtools": { - "type": "local", - "command": [ - "docker", "run", "--rm", "-i", - "-v", "E:/Work/jftech/findash/srcs/findash-admin:/workspace", - "chrome-devtools-mcp:node24-v1.1.1", - "--browserUrl=http://192.168.3.236:9222" - ], - "enabled": false - } - }, - "server": { - "port": 4096, - "hostname": "0.0.0.0" - }, - "permission": { - "external_directory": { - "/var/store/**": "allow" - }, - "edit": { - "/var/store/**": "deny" - } - } -} diff --git a/yarn/docker-compose.yml b/yarn/docker-compose.yml index 98a9fbb..f529cc0 100644 --- a/yarn/docker-compose.yml +++ b/yarn/docker-compose.yml @@ -9,7 +9,7 @@ - HTTPS_PROXY=http://host.docker.internal:${PROXY_PORT} - NO_PROXY=localhost,127.0.0.1,host.docker.internal volumes: - - ./config:/root/.config/opencode + - ../config:/root/.config/opencode - /var/run/docker.sock:/var/run/docker.sock - ./node/.cache:/usr/local/share/.cache - ./node/.yarnrc:/usr/local/share/.yarnrc