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