6 Commits

Author SHA1 Message Date
湛兮 8415291772 feat: 补齐员工端 agent skill 规范
- .agents: 新增中文提交与注释同步 skill 及项目索引

- AGENTS.md: 增加本地 skill 入口并保留 RTK 引用

- README.md: 同步 Agent 文档入口
2026-06-11 17:48:08 +08:00
湛兮 f8386d7b02 feat: 完善环境标识配置 2026-06-05 15:35:51 +08:00
湛兮 5fe2e2c75c chore: move Jenkins pipeline config out of repo 2026-06-05 15:08:11 +08:00
湛兮 3e1789e124 ci: harden production tag selection 2026-06-05 14:45:44 +08:00
湛兮 3efb2182b2 ci: add production tag pipeline 2026-06-05 14:38:25 +08:00
湛兮 6953f48344 chore: split dev and production deployment env 2026-06-05 12:32:16 +08:00
14 changed files with 273 additions and 6 deletions
+21
View File
@@ -0,0 +1,21 @@
# role-user Agent Skills
本目录存放 `role-user` 的项目级 Agent skill。规则参考 SeaCloud 项目的 agent skill 组织方式,并按当前员工端 Next.js、TypeScript、移动端工作台场景保留最小必要规范。
## 使用入口
1. 进入仓库后先读 `AGENTS.md``RTK.md`
2. 创建或修改代码文件时,使用 `header-comment-sync` 保持中文文件头、导出声明和复杂逻辑注释同步。
3. 提交或推送代码时,使用 `chinese-commit-message` 生成英文 type 前缀加中文摘要的 commit message。
4. 所有 skill 的触达文件补齐规则只处理本次任务相关文件,不要求为了单次任务全仓扫描。
## 当前项目事实
- 应用类型:员工端 C 端移动优先工作台。
- 技术栈:Next.js App Router、React、TypeScript。
- 关键边界:BFF Route Handlers 负责会话与后端转发,前端不持久化 JWT 或明文密码。
## Skill 索引
- `header-comment-sync`:中文文件头、导出声明、复杂逻辑和风险边界注释。
- `chinese-commit-message`:中文提交信息格式。
@@ -0,0 +1,54 @@
---
name: chinese-commit-message
description: 在本仓库执行 git commit、整理提交说明或准备推送时使用,保持提交信息使用英文类型前缀加中文描述。
---
# 中文提交信息
## 何时使用
- 用户要求提交、推送或整理 commit message。
- 你准备执行 `git commit`
- 需要总结当前 diff 的提交说明。
## 核心要求
1. 提交信息必须使用英文 type 加中文摘要,例如 `feat: 补齐员工端 agent skill 规范`
2. 常用 type 优先使用 `feat``fix``refactor``docs``chore`
3. 不强制 scope;只有模块边界非常清楚时才使用 `feat(auth): ...`
4. 标题部分使用简洁中文,直接说明改动,不写“修改一下”“更新代码”这类空泛描述。
5. 非 trivial 改动建议补中文正文,用扁平 bullet 按文件或内容块说明关键动作。
6. 正文与标题之间空一行;正文每条 bullet 对应一个独立文件或明确内容块。
7. 如果用户指定提交信息,优先尊重用户原意,只做必要格式整理。
## 触达文件补齐
- 不要求为了提交信息单独全仓扫描。
- 提交前如果 diff 中的触达文件明显违反对应 skill 的触达补齐要求,应先修正当前相关链路,再整理 commit message。
- commit 正文应概括本次触达补齐,例如“补齐触达文件注释”“同步 README 目录说明”。
## 推荐结构
```text
feat: 补齐员工端 agent skill 规范
- .agents/README.md: 新增项目级 skill 索引和使用入口
- .agents/skills: 补充中文提交与注释同步规则
- AGENTS.md: 增加本地 skill 入口
```
## 不推荐写法
- `新增规则`
- `feat: add skills`
- `fix bug`
- `update`
- 复杂改动只有标题没有正文。
- 正文写成长段流水账或嵌套列表。
## 落地检查
- type 是否合理。
- 中文摘要是否覆盖主改动。
- 是否需要正文。
- 正文是否按文件或内容块归纳。
@@ -0,0 +1,68 @@
---
name: header-comment-sync
description: 在本仓库创建或修改 ts、tsx、js、jsx、mjs、cjs、vue、astro 文件时使用,保持中文文件头、导出声明、复杂逻辑和风险边界注释准确。
---
# 注释规范与同步
## 目标
让下一次进入文件的维护者能快速理解当前职责、关键约束和风险边界。注释解释“为什么”和“边界”,不复述代码表面行为。
## 适用场景
- 新增或修改页面、组件、Hook、store、service、utils、类型、配置或脚本文件。
- 修改 BFF Route Handler、鉴权、Cookie、权限、后端协议转换、缓存或错误兜底逻辑。
- 发现旧注释与当前代码行为不一致。
## 核心原则
- 注释使用中文,描述当前事实。
- 简单局部变量不强行注释。
- 文件头说明文件当前职责,1 到 2 行即可。
- 导出的函数、组件、Hook、类型、配置对象、service method 应有用途说明。
- 复杂兼容逻辑、后端协议、竞态锁、缓存、权限映射和会话安全边界需要短注释。
- TODO/FIXME 必须说明触发条件、剩余动作和可删除条件。
## 文件头规则
- 每个适用文件都要有准确文件头。
- 如果文件必须以 `"use client"``"use server"` 开头,文件头注释放在指令之后、导入之前。
- Vue 文件不为了补头注释重排模板结构;在 `<script>``<script setup>` 顶部补当前职责说明。
- 文件头不要写“本文件用于...”这类空话,直接说明业务角色。
```ts
"use client";
/**
* 员工端任务详情页,负责加载任务、发起流转动作并展示备注时间线。
*/
```
## JSDoc 与局部注释
- 导出的函数、类、组件、Hook、类型、常量和配置对象优先使用 JSDoc。
- 非导出但复杂的解析、格式化、请求构造、状态派生、回调工厂也要补。
- 只在局部逻辑确实有约束时使用行内注释,例如竞态锁、兼容字段、后端协议和临时迁移。
- 不要在每一行、每个 JSX 片段、每个短生命周期变量上堆注释。
## 触达文件补齐
- 不要求为了注释规范单独全仓扫描。
- 只要本次任务修改了适用文件,就顺手补齐明显缺失或过时的文件头、导出声明、共享类型、复杂回调和协议注释。
- 大文件可按触达区域优先,但同文件内裸露的顶层导出和共享类型应一起补。
- 如果一次补齐整个大文件会明显超出需求范围,至少补齐当前需求链路和顶层声明,并在交付说明里说明剩余范围。
## 不推荐的写法
- 注释只写“处理数据”“点击事件”这种无信息内容。
- 注释描述旧方案,和当前代码矛盾。
- 为每个 JSX 片段、简单赋值、短生命周期变量写注释。
- 用英文口号、emoji 或情绪化标记替代项目内中文说明。
## 落地检查
- 修改后的文件头是否准确。
- 新增/修改的导出声明是否有必要说明。
- 复杂逻辑是否解释了约束而不是复述代码。
- 旧注释是否仍然可信。
+6
View File
@@ -0,0 +1,6 @@
ACCESS_MANAGE_API_BASE_URL=http://127.0.0.1:3501/api
ROLE_USER_SESSION_COOKIE=role_user_session_develop
APP_ENV=develop
APP_ENV_LABEL=测试环境
PORT=3211
HOSTNAME=0.0.0.0
+5 -1
View File
@@ -1,2 +1,6 @@
ACCESS_MANAGE_API_BASE_URL=http://localhost:3500/api
ROLE_USER_SESSION_COOKIE=role_user_session
ROLE_USER_SESSION_COOKIE=role_user_session_local
APP_ENV=local
APP_ENV_LABEL=本地环境
PORT=3210
HOSTNAME=0.0.0.0
+6
View File
@@ -0,0 +1,6 @@
ACCESS_MANAGE_API_BASE_URL=http://127.0.0.1:3500/api
ROLE_USER_SESSION_COOKIE=role_user_session
APP_ENV=production
APP_ENV_LABEL=生产环境
PORT=3210
HOSTNAME=0.0.0.0
+6
View File
@@ -0,0 +1,6 @@
ACCESS_MANAGE_API_BASE_URL=http://127.0.0.1:3501/api
ROLE_USER_SESSION_COOKIE=role_user_session_develop
APP_ENV=develop
APP_ENV_LABEL=测试环境
PORT=3211
HOSTNAME=0.0.0.0
+13
View File
@@ -0,0 +1,13 @@
@RTK.md
# role-user Agent 入口
## 必用 Skill
- 创建或修改 `ts``tsx``js``jsx``mjs``cjs``vue``astro` 文件时,使用 `./.agents/skills/header-comment-sync/SKILL.md`
- 执行 `git commit`、整理提交说明或准备推送时,使用 `./.agents/skills/chinese-commit-message/SKILL.md`
## 本地规则
- 先读 `RTK.md`,再按任务读取 `.agents/README.md` 中匹配的 skill。
- skill 的触达文件补齐只处理本次修改相关文件,不为单次任务全仓扫描。
+21 -1
View File
@@ -26,11 +26,23 @@ pnpm dev
```bash
ACCESS_MANAGE_API_BASE_URL=http://localhost:3500/api
ROLE_USER_SESSION_COOKIE=role_user_session
ROLE_USER_SESSION_COOKIE=role_user_session_local
APP_ENV=local
APP_ENV_LABEL=本地环境
PORT=3210
HOSTNAME=0.0.0.0
```
`ACCESS_MANAGE_API_BASE_URL` 指向 `access-manage` 服务端 API 根路径。员工登录会调用后端 `POST /api/auth/employee/login`,登录成功后只把 JWT 写入 Next.js 服务端 HttpOnly Cookie,不写入 `localStorage`
环境标识按 `APP_ENV` 区分:
- `local`: 本地环境,默认使用 `.env.example` 复制出的 `.env.local`
- `develop`: 测试环境,参考 `.env.develop.example``.env.test.example` 保留为兼容入口。
- `production`: 生产环境,参考 `.env.production.example`
`APP_ENV_LABEL` 可覆盖页面右上角显示文案;未配置时会按 `APP_ENV` 自动显示“本地环境”“测试环境”或“生产环境”。Cookie 只在 `APP_ENV=production` 时设置 `Secure`,避免测试环境 HTTP 访问时登录后被浏览器丢弃 Cookie。
## 可用命令
```bash
@@ -38,6 +50,10 @@ pnpm dev
pnpm typecheck
pnpm lint
pnpm build
pnpm build:develop
pnpm build:prod
pnpm start:develop
pnpm start:prod
```
## 当前实现范围
@@ -83,8 +99,12 @@ pnpm build
## 文档
- `.agents/README.md`: 本项目 Agent skill 索引和使用入口。
- `.agents/skills/chinese-commit-message/SKILL.md`: 中文提交信息规范。
- `.agents/skills/header-comment-sync/SKILL.md`: 中文文件头和注释同步规范。
- `docs/C_EMPLOYEE_APP_REQUIREMENTS.md`: C 端员工工作台需求文档。
- `docs/FULLSTACK_BACKEND_GAP_ANALYSIS.md`: C 端员工工作台三端缺口与改动范围分析。
- `AGENTS.md`: Codex/Agent 入口规则,转到 `RTK.md` 并登记本地 skill。
- `RTK.md`: 本项目 Codex/Agent 协作规则。
## 关联项目
+6 -1
View File
@@ -3,9 +3,14 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev -p 3210",
"dev": "APP_ENV=local APP_ENV_LABEL=本地环境 ACCESS_MANAGE_API_BASE_URL=http://localhost:3500/api ROLE_USER_SESSION_COOKIE=role_user_session_local next dev -p 3210",
"build": "next build",
"build:develop": "APP_ENV=develop APP_ENV_LABEL=测试环境 ACCESS_MANAGE_API_BASE_URL=http://127.0.0.1:3501/api ROLE_USER_SESSION_COOKIE=role_user_session_develop next build",
"build:test": "pnpm build:develop",
"build:prod": "APP_ENV=production APP_ENV_LABEL=生产环境 ACCESS_MANAGE_API_BASE_URL=http://127.0.0.1:3500/api ROLE_USER_SESSION_COOKIE=role_user_session next build",
"start": "next start",
"start:develop": "APP_ENV=develop APP_ENV_LABEL=测试环境 ACCESS_MANAGE_API_BASE_URL=http://127.0.0.1:3501/api ROLE_USER_SESSION_COOKIE=role_user_session_develop next start -p 3211",
"start:prod": "APP_ENV=production APP_ENV_LABEL=生产环境 ACCESS_MANAGE_API_BASE_URL=http://127.0.0.1:3500/api ROLE_USER_SESSION_COOKIE=role_user_session next start -p 3210",
"lint": "eslint",
"typecheck": "tsc --noEmit --incremental false"
},
+16
View File
@@ -1029,6 +1029,22 @@ textarea:focus-visible {
color: var(--accent-ink);
}
.environment-badge {
background: rgba(20, 20, 24, 0.86);
border-radius: 999px;
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.16);
color: #ffffff;
font-size: 12px;
font-weight: 750;
letter-spacing: 0;
line-height: 1;
padding: 8px 10px;
position: fixed;
right: 12px;
top: 12px;
z-index: 60;
}
.skeleton {
animation: pulse 1.2s ease-in-out infinite;
background: linear-gradient(90deg, #ececef, #fafafa, #ececef);
+13 -2
View File
@@ -1,5 +1,7 @@
import type { Metadata, Viewport } from "next";
import { connection } from "next/server";
import { getAppEnvLabel } from "@/lib/environment";
import "./globals.css";
export const metadata: Metadata = {
@@ -24,10 +26,19 @@ export const viewport: Viewport = {
themeColor: "#ffffff"
};
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
export default async function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
await connection();
const environmentLabel = getAppEnvLabel();
return (
<html lang="zh-CN">
<body>{children}</body>
<body>
<div className="environment-badge" aria-label={`当前环境:${environmentLabel}`}>
{environmentLabel}
</div>
{children}
</body>
</html>
);
}
+35
View File
@@ -0,0 +1,35 @@
import "server-only";
export type AppEnv = "local" | "develop" | "production";
export function getAppEnv(): AppEnv {
const appEnv = process.env.APP_ENV;
if (appEnv === "production") {
return "production";
}
if (appEnv === "develop" || appEnv === "test") {
return "develop";
}
if (appEnv === "local") {
return "local";
}
return process.env.NODE_ENV === "production" ? "production" : "local";
}
export function getAppEnvLabel() {
if (process.env.APP_ENV_LABEL) {
return process.env.APP_ENV_LABEL;
}
const labels: Record<AppEnv, string> = {
local: "本地环境",
develop: "测试环境",
production: "生产环境"
};
return labels[getAppEnv()];
}
+3 -1
View File
@@ -2,6 +2,8 @@ import "server-only";
import { cookies } from "next/headers";
import { getAppEnv } from "@/lib/environment";
const DEFAULT_COOKIE_NAME = "role_user_session";
export function getSessionCookieName() {
@@ -17,7 +19,7 @@ export async function setSessionToken(token: string) {
cookieStore.set(getSessionCookieName(), token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
secure: getAppEnv() === "production",
sameSite: "lax",
path: "/",
maxAge: 60 * 60 * 8