迁移 Claude/Codex/Cursor 开发环境与追溯资产

Co-Authored-By: OpenAI Codex <codex@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Neo
2026-05-02 03:11:39 +08:00
parent d269ee6401
commit 81e41730ec
33 changed files with 3734 additions and 34 deletions

45
.cursor/hooks.json Normal file
View File

@@ -0,0 +1,45 @@
{
"version": 1,
"hooks": {
"preToolUse": [
{
"command": ".venv\\Scripts\\python.exe .cursor\\hooks\\ai_env_guard.py preToolUse",
"matcher": "Read|Glob|Edit|Write|ApplyPatch",
"timeout": 5,
"failClosed": true
}
],
"beforeReadFile": [
{
"command": ".venv\\Scripts\\python.exe .cursor\\hooks\\ai_env_guard.py beforeReadFile",
"matcher": "_archived",
"timeout": 5,
"failClosed": true
}
],
"beforeMCPExecution": [
{
"command": ".venv\\Scripts\\python.exe .cursor\\hooks\\ai_env_guard.py beforeMCPExecution",
"matcher": "pg-etl|pg-app",
"timeout": 5,
"failClosed": false
}
],
"beforeShellExecution": [
{
"command": ".venv\\Scripts\\python.exe .cursor\\hooks\\ai_env_guard.py beforeShellExecution",
"matcher": "git\\s+(reset|checkout)|--no-verify|psql|mcp-postgres|PG_DSN|APP_DB_DSN",
"timeout": 5,
"failClosed": false
}
],
"postToolUse": [
{
"command": ".venv\\Scripts\\python.exe .cursor\\hooks\\ai_env_guard.py postToolUse",
"matcher": "ApplyPatch|Edit|Write",
"timeout": 5,
"failClosed": false
}
]
}
}

View File

@@ -0,0 +1,121 @@
from __future__ import annotations
import json
import re
import sys
from pathlib import Path
ARCHIVED_RE = re.compile(r"(^|[/\\])_archived([/\\]|$)", re.IGNORECASE)
PROD_MCP_RE = re.compile(r"\b(pg-etl|pg-app)\b", re.IGNORECASE)
TEST_MCP_RE = re.compile(r"\b(pg-etl-test|pg-app-test)\b", re.IGNORECASE)
WRITE_SQL_RE = re.compile(
r"\b(insert|update|delete|truncate|drop|alter|create|grant|revoke|merge|copy|call|vacuum|reindex)\b",
re.IGNORECASE,
)
def load_input() -> dict:
try:
return json.load(sys.stdin)
except Exception:
return {}
def target_text(data: dict) -> str:
chunks = [json.dumps(data, ensure_ascii=False)]
for key in ("command", "file_path", "path"):
value = data.get(key)
if isinstance(value, str):
chunks.append(value)
tool_input = data.get("tool_input")
if isinstance(tool_input, dict):
chunks.append(json.dumps(tool_input, ensure_ascii=False))
return "\n".join(chunks)
def allow() -> None:
print(json.dumps({"permission": "allow"}, ensure_ascii=False))
def before_shell(data: dict) -> None:
text = target_text(data)
dangerous = [
r"git\s+reset\s+--hard",
r"git\s+checkout\s+--",
r"--no-verify",
r"\bPG_DSN\b",
r"\bAPP_DB_DSN\b",
r"\bpsql\b",
]
if any(re.search(pattern, text, re.IGNORECASE) for pattern in dangerous):
print(json.dumps({
"permission": "ask",
"user_message": "命令命中 NeoZQYY 高风险规则:请确认是否需要执行。",
"agent_message": "执行前说明风险、目标库/文件、回滚方式;生产库默认不执行。",
}, ensure_ascii=False))
return
allow()
def guard_archived(data: dict) -> None:
text = target_text(data)
if ARCHIVED_RE.search(text):
print(json.dumps({
"permission": "deny",
"user_message": "已阻断:`_archived/` 是废弃归档目录,禁止读取、搜索或作为实现参考。",
"agent_message": "改用当前版本文件;如确需考古,请让用户明确授权并说明目的。",
}, ensure_ascii=False))
return
allow()
def before_mcp(data: dict) -> None:
text = target_text(data)
if not PROD_MCP_RE.search(text) or TEST_MCP_RE.search(text):
allow()
return
if WRITE_SQL_RE.search(text):
print(json.dumps({
"permission": "deny",
"user_message": "已阻断:生产库 MCP 写入/DDL 类操作默认禁止。请改用测试库验证,或让用户给出单次明确授权。",
"agent_message": "生产库仅允许人工确认后的只读排查DDL/写入需走单独变更流程。",
}, ensure_ascii=False))
return
print(json.dumps({
"permission": "ask",
"user_message": "检测到生产库 MCP 只读调用。请确认本次查询目的、SQL 是否只读、是否会暴露敏感数据。",
"agent_message": "说明查询目的、目标库、SQL 摘要和风险后等待用户确认。",
}, ensure_ascii=False))
def post_tool_use(data: dict) -> None:
text = target_text(data).replace("\\", "/")
hints = []
if "/_archived/" in text or text.endswith("/_archived"):
hints.append("检测到 `_archived/` 路径:该目录内容已废弃,禁止作为实现参考。")
if "apps/demo-miniprogram/" in text:
hints.append("检测到 demo-miniprogram这是 MOCK 标杆目录,修改前需确认目的。")
if re.search(r"(db/|docs/database/|migrations/|schemas/)", text):
hints.append("检测到数据库相关改动:如影响 schema需同步 `docs/database/` 并提供 3 条验证 SQL。")
if re.search(r"(apps/backend/|apps/etl/|app/ai/|prompts/)", text):
hints.append("检测到后端/ETL/AI 逻辑改动:完成后需运行相关测试并写审计记录。")
if hints:
print(json.dumps({"additional_context": "\n".join(f"[NeoZQYY] {hint}" for hint in hints)}, ensure_ascii=False))
def main() -> None:
mode = sys.argv[1] if len(sys.argv) > 1 else ""
data = load_input()
if mode in {"preToolUse", "beforeReadFile"}:
guard_archived(data)
elif mode == "beforeMCPExecution":
before_mcp(data)
elif mode == "beforeShellExecution":
before_shell(data)
elif mode == "postToolUse":
post_tool_use(data)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,12 @@
---
description: admin-web 规则React/Vite/AntD、AI 管理套件与前端验证。
globs: apps/admin-web/**
alwaysApply: false
---
# admin-web 规则
- `admin-web` 是开发/运维后台,不是租户后台。
- 遵循 React + Vite + Ant Design 现有页面和 API 封装风格。
- AI 管理相关改动先查 2026-04-21、2026-04-30 审计记录。
- 前端逻辑改动后优先运行 `pnpm test` / `pnpm lint`,无法运行需说明原因。

View File

@@ -0,0 +1,11 @@
---
description: 历史追溯规则:优先使用精简索引、审计记录和当前代码。
globs: docs/**
alwaysApply: false
---
# 历史追溯规则
- 日常追溯先查 `docs/ai-env-history/README.md`、`docs/claude-history/`、`docs/audit/changes/`。
- 历史摘要只解释来龙去脉,编码前仍以当前文件、当前 diff、当前测试为准。
- 原始 JSONL 可用于追查细节但不要把密钥、DSN、token 原文写入仓库文档。

View File

@@ -0,0 +1,14 @@
---
description: FastAPI 后端规则响应包装、认证、AI 集成、RLS 与测试库。
globs: apps/backend/**
alwaysApply: false
---
# 后端规则
- 2xx 响应经 `ResponseWrapperMiddleware` 包装为 `{ "code": 0, "data": <payload> }`。
- 后端内部使用 snake_caseJSON 输出通过 `CamelModel` 转 camelCase。
- admin、miniapp、tenant-admin 三类 JWT aud 不可混用。
- 访问 ETL FDW/RLS 视图前必须设置 `app.current_site_id`。
- AI 集成涉及 DashScope、熔断、限流、预算、缓存和运行日志改动后必须查审计历史。
- 后端验证默认在 `apps/backend` 下运行,使用测试库,禁止连正式库。

View File

@@ -0,0 +1,13 @@
---
description: 数据库规则schema 变更、RLS 双 schema、文档同步和验证 SQL。
globs: db/**,docs/database/**
alwaysApply: false
---
# 数据库规则
- 任何 PostgreSQL schema/迁移/DDL/ORM 结构变更必须同步 `docs/database/`。
- 新建 DWS/DWD RLS 视图必须同时创建原 schema 和 `app` schema 视图。
- 回滚需逆序 DROP/ALTER并提供至少 3 条验证 SQL。
- 默认使用 `TEST_DB_DSN` / `TEST_APP_DB_DSN`,禁止连正式库。
- DDL 基线变更后运行 `tools/db/gen_consolidated_ddl.py` 并同步权威 DDL。

View File

@@ -0,0 +1,11 @@
---
description: demo-miniprogram 保护规则:假数据标杆,不删除不迁移到 _DEL。
globs: apps/demo-miniprogram/**
alwaysApply: false
---
# demo-miniprogram 保护
- 本目录是假数据 MOCK 版小程序,用于页面样式和展示格式标杆校对。
- 禁止删除、移入 `_DEL/` 或改造成真实 API 驱动。
- 只有在用户明确要求校正 demo 标杆时才修改。

View File

@@ -0,0 +1,14 @@
---
description: 飞球 ETL 规则DWD-DOC 优先、金额口径、DWS 优先、禁止归档目录。
globs: apps/etl/connectors/feiqiu/**
alwaysApply: false
---
# ETL 飞球规则
- 金额、支付、消费链路、字段语义优先参考 `apps/etl/connectors/feiqiu/docs/reports/DWD-DOC/`。
- `consume_money` 禁止直接用于计算,使用 `items_sum` 拆分字段。
- 助教费用必须区分 `assistant_pd_money` 和 `assistant_cx_money`。
- 正向结算使用 `settle_type IN (1, 3)`,禁止随意读取 ODS 做业务计算。
- DWS/DWD 汇总默认保持幂等,禁止 `TRUNCATE`。
- 所有 `_archived/` 目录禁止读取或参考。

View File

@@ -0,0 +1,12 @@
---
description: 微信小程序规则生产小程序、Donut/TDesign、demo 标杆对齐。
globs: apps/miniprogram/**
alwaysApply: false
---
# 小程序规则
- `apps/miniprogram` 是生产小程序,数据来自后端 API。
- UI 样式和展示格式需要参考 `apps/demo-miniprogram` 的 MOCK 标杆。
- 改动关键交互、鉴权、API 字段或页面跳转后必须说明验证路径。
- 涉及微信开发者工具时优先使用 `weixin-devtools-mcp` 或记录手工验证步骤。

View File

@@ -0,0 +1,18 @@
---
description: NeoZQYY 核心工作规范中文、调研、验证、审计、dirty tree 保护。
alwaysApply: true
---
# NeoZQYY 核心规范
- 始终使用中文交流、解释、审计和文档命令、API 字段、变量名保持原文。
- 以 `AGENTS.md` 为权威规则;历史 `CLAUDE.md` 已并入 `AGENTS.md`,需考古时查 git 历史或 `docs/ai-env-history/`。
- 逻辑改动前先做需求审问和前置调研;用户明确跳过时除外。
- 逻辑改动后运行相关验证,输出 diff 摘要和未覆盖风险。
- 不回滚用户已有改动,不使用破坏性 git 命令,除非用户明确要求。
- 审计记录统一写入 `docs/audit/changes/``docs/audit/audit_dashboard.md` 只由脚本生成。
- 历史追溯优先查 `docs/ai-env-history/`、`docs/claude-history/`、`docs/audit/`,再查原始对话。
- 用户偏好模型为 GPT 5.5 与 Claude 4.7;模型选择由 Cursor UI/会话设置控制,规则只保留偏好。
- CLI / Shell 中文处理必须优先确保 UTF-8Python 用 `encoding="utf-8"` / `PYTHONUTF8=1`CSV 给 Excel 用 `utf-8-sig`PowerShell/Node 避免依赖系统默认 ANSI 编码。
- 遇到中文乱码时,不要把乱码输出当作事实;先调整编码重跑,或明确说明终端编码异常并转述可确认的信息。
- Shell 路径和参数含中文、空格或特殊字符时必须正确加引号;复杂中文输出优先用脚本或结构化 API避免手写脆弱转义。

View File

@@ -0,0 +1,110 @@
---
name: audit
description: /audit — 变更审计。从 Claude Code 命令迁移为 Cursor project skill用户要求执行 audit、/audit 或相关流程时使用。
disable-model-invocation: true
---
# /audit — 变更审计
回顾本次会话中你所做的所有文件变更,结合自动预扫描结果,执行审计落盘。
## 执行步骤
### 第 1 步运行预扫描脚本Python零 token
运行:
```bash
python scripts/audit/prescan.py
```
该脚本自动完成:
- 从 git status 获取所有变更文件
- 分类高风险文件 + 生成 risk_tags
- 合规检查:代码→文档映射、迁移 SQL 检测、DDL 基线检查
读取输出的 JSON。如果 `audit_required: false`,告知用户"无需审计"并结束。
**备选**:如果 git status 包含大量非本次会话的历史变更,可以用 `--files` 参数只传入本次会话的文件:
```bash
python scripts/audit/prescan.py --files "file1.py,file2.sql,..."
```
文件列表从你的对话记忆(本次会话的 Edit/Write 工具调用)中提取。
### 第 2 步:补充语义上下文
预扫描脚本能告诉你"哪些文件变了、是否高风险、文档是否缺失",但它不知道**为什么改**。
从对话记忆中补充:
- 每个变更文件的修改原因(用户的需求是什么)
- 改动的技术思路和设计决策
- 与其他模块的关联影响
将预扫描 JSON + 语义上下文合并,作为第 3 步的输入。
### 第 3 步:委托子代理写审计记录
用 Agent 工具启动子代理,传入:
1. 预扫描 JSON 结果(完整)
2. 每个变更的原因和内容概要(你补充的语义上下文)
子代理的任务指令:
> 在 `docs/audit/changes/` 目录下创建审计记录文件,文件名格式 `<YYYY-MM-DD>__<英文短标识>.md`。
>
> 使用以下格式:
>
> ```markdown
> # 变更审计记录:<中文标题>
>
> | 字段 | 值 |
> |------|-----|
> | 日期 | YYYY-MM-DD HH:MM:SS |
>
> ## 操作摘要
> <1-3 段,说清楚做了什么、为什么做>
>
> ## 变更文件
> 按新增/修改/删除分组,每个文件一行,简要说明改动内容。
>
> ## 改动注解
> 对每个变更文件写注解:
> - 高风险文件ETL 任务/后端路由/数据库迁移/金额相关):写详细注解(变更类型、原因、思路、结果)
> - 普通文件:一行简要说明
> - 删除的文件:只记录删除原因
>
> ## 数据库变更(如有)
> 列出新建/修改/删除的表、字段、约束、索引。标注迁移执行状态。
>
> ## 风险与回滚
> - 风险点(标注高/中/低)
> - 回滚要点
>
> ## 验证
> - 至少 1 条可执行的验证方式(测试命令 / SQL / 联调步骤)
>
> ## 合规检查
> - 列出文档同步状态(已同步 / 待补齐 / 不适用)
> ```
>
> 当前北京时间通过 `python -c "from datetime import datetime, timezone, timedelta; print(datetime.now(timezone(timedelta(hours=8))).strftime('%Y-%m-%d %H:%M:%S'))"` 获取。
>
> 审计记录语言使用简体中文。
>
> 完成后运行 `python scripts/audit/gen_audit_dashboard.py` 刷新审计一览表。
>
> 最终只返回done / files_written / next_step。
### 第 4 步:补齐缺失的文档同步
根据预扫描 JSON 中 `code_without_docs` 列出的不合规项,逐项补齐:
- 读取对应代码文件当前内容
- 更新对应文档
如果补齐工作量大(>3 个文档),委托子代理处理。
### 第 5 步:向用户报告
简短回执:
- 审计记录文件路径
- 合规检查结果(全部通过 / N 项已补齐 / N 项待用户处理)
- 下一步建议(如 "commit when ready"

View File

@@ -0,0 +1,69 @@
---
name: db-docs
description: /db-docs — 数据库文档同步。从 Claude Code 命令迁移为 Cursor project skill用户要求执行 db-docs、/db-docs 或相关流程时使用。
disable-model-invocation: true
---
# /db-docs — 数据库文档同步
当 PostgreSQL schema/表结构发生变化时,将变更以审计友好的方式落盘到 `docs/database/`
## 触发条件
- 迁移脚本/DDL 修改(新增/删除/改表、字段、类型、默认值、非空、约束、索引、外键)
- 手工执行了 DDL
## 执行步骤
### 第 1 步:识别结构性变化
从本次会话的改动中,列出新增/修改/删除的对象:
- schema / table / column / index / constraint / foreign key
- 明确变更前后差异before/after
### 第 2 步:更新表结构文档
对每张受影响的表,更新 `docs/database/` 下对应的文档:
- 如果文档已存在:更新字段列表、约束、索引等
- 如果文档不存在:基于以下模板创建
模板:
```markdown
# <schema>.<table_name>
## 概述
<表的用途说明>
## 字段
| 字段名 | 类型 | 可空 | 默认值 | 说明 |
|--------|------|------|--------|------|
| ... | ... | ... | ... | ... |
## 约束与索引
- PRIMARY KEY: ...
- UNIQUE: ...
- INDEX: ...
## 关联
- 上游:<数据来源>
- 下游:<被哪些模块/表消费>
```
特别注意金额类字段:标注精度、币种、舍入规则。
### 第 3 步:回滚与验证
写入审计友好的回滚和验证信息:
- DDL 回滚路径(必要时提供反向迁移 SQL
- 至少 3 条验证 SQL含约束/索引/关键字段检查)
### 第 4 步DDL 基线检查
检查 `docs/database/ddl/` 下的基线文件是否需要合并更新。如需要,更新基线。
### 第 5 步:输出摘要
- 更新/创建了哪些文档
- 迁移脚本执行状态(已执行 / 待执行)
- DDL 基线状态(已合并 / 待合并)

View File

@@ -0,0 +1,61 @@
---
name: doc-sync
description: /doc-sync — 逻辑改动后文档同步。从 Claude Code 命令迁移为 Cursor project skill用户要求执行 doc-sync、/doc-sync 或相关流程时使用。
disable-model-invocation: true
---
# /doc-sync — 逻辑改动后文档同步
检查本次会话中的逻辑改动是否需要同步更新文档,并执行同步。
## 触发条件
修改了以下任一类内容时应执行:
- 业务规则/计算口径/资金处理(精度、舍入、阈值)
- ETL/SQL 清洗聚合映射逻辑
- API 行为(返回结构、错误码、鉴权/权限)
- 小程序关键交互流程
- 数据库表结构
## 执行步骤
### 第 1 步:分类
判断本次会话的改动是否属于"逻辑改动"。如果只是纯格式化/拼写修正/注释调整,告知用户"无逻辑改动,无需文档同步"并结束。
### 第 2 步:逐项评估需要更新的文档
根据变更涉及的模块,评估以下文档是否需要更新:
**各级 README.md**(只更新与本次变更相关的):
- `README.md`(根目录):项目总览、快速开始、环境变量、架构概述
- `apps/backend/README.md`:后端 API 路由、配置、运行方式
- `apps/etl/connectors/feiqiu/README.md`ETL 任务清单、开发约定
- `apps/miniprogram/README.md`:小程序页面结构
- `apps/admin-web/README.md`:管理后台功能说明
- `apps/tenant-admin/README.md`:租户管理后台功能说明
- `packages/shared/README.md`:共享包说明
- `db/README.md`Schema 约定、迁移规范
规则:如果"对读者理解系统行为有帮助"就应更新。若某个 README 尚不存在但变更涉及该模块,应创建。
### 第 3 步:执行更新
对每个需要更新的文档:
1. 读取当前内容
2. 根据本次变更更新相关段落
3. 写入更新后的内容
如果更新工作量大(>3 个文档),委托子代理处理。
### 第 4 步:联动检查
- 如果涉及 DB schema 变化:提醒用户执行 `/db-docs`
- 如果涉及 API 变化:检查 `apps/backend/docs/API-REFERENCE.md` 是否已更新
### 第 5 步:输出摘要
- Changed改了哪些文档
- Why原始原因 + 直接原因
- Risk风险点与回归范围
- Verify建议的验证步骤

View File

@@ -0,0 +1,71 @@
---
name: pre-change
description: /pre-change — 逻辑改动前置调研。从 Claude Code 命令迁移为 Cursor project skill用户要求执行 pre-change、/pre-change 或相关流程时使用。
disable-model-invocation: true
---
# /pre-change — 逻辑改动前置调研
对即将修改的模块进行全面调研,输出上下文摘要供用户确认后再动手。
## 适用场景
任何逻辑改动ETL/业务规则/API/数据模型/前端交互),写代码前执行。
## 执行步骤
### 第 1 步:识别改动范围
从用户需求中提取:
- 要修改的模块和文件
- 涉及的数据表/API/页面
- 预期的行为变化
### 第 2 步:委托 Explore 子代理调研
启动 Explore 子代理thoroughness: very thorough调研以下内容
1. **目标模块文件**:读取要修改的文件及其直接依赖
2. **历史审计**:搜索 `docs/audit/changes/` 中相关模块的历史变更记录
3. **相关文档**README、PRD`docs/prd/`、BD 手册(`docs/database/`、API 参考
4. **调用关系**:要修改文件的调用方和被调用方
5. **数据流向**:上游(数据从哪来)→ 当前模块 → 下游(数据到哪去)
6. **影响范围**:哪些模块/页面/任务可能受影响
### 第 3 步:输出「改动前上下文摘要」
格式:
```
## 改动前上下文摘要
### 模块职责
<模块做什么,在系统中的角色>
### 历史变更
<近期审计记录中的相关改动,特别是踩坑记录>
### 数据流向
上游: <数据来源>
当前: <本模块处理>
下游: <消费方>
### 影响范围
- <受影响的模块/页面/任务列表>
### 风险点
- <可能的副作用、边界条件、兼容性问题>
### 建议方案
<基于调研结果的实施建议>
```
### 第 4 步:等待用户确认
输出摘要后,等待用户确认或调整方向,确认后再进入编码实施。
## 例外(无需执行此流程)
- 纯格式调整、注释/文档纯文字修改
- 用户明确说"直接改/跳过调研"
- 新建文件且不涉及已有逻辑

View File

@@ -0,0 +1,69 @@
---
name: spec-close
description: /spec-close — Spec 收尾通用流程。从 Claude Code 命令迁移为 Cursor project skill用户要求执行 spec-close、/spec-close 或相关流程时使用。
disable-model-invocation: true
---
# /spec-close — Spec 收尾通用流程
当一个功能 spec 开发完成时,执行此收尾检查清单确保质量闭环。
## 执行步骤
### 步骤 1最终测试检查点必选
- 运行 Monorepo 属性测试:`cd /c/NeoZQYY && pytest tests/ -v`
- 运行模块单元测试:`cd <模块路径> && pytest tests/ -v`
- 确保所有测试通过,有问题询问用户
### 步骤 2前后端联调验证涉及 API + 前端时必选)
- 启动后端服务,使用测试库验证各端点完整请求-响应链路
- 验证 JSON 响应结构与 Schema 定义一致camelCase 序列化)
- 验证权限校验和数据隔离(`SET LOCAL app.current_site_id`)在真实请求中生效
- 前端联调验证:确认前端页面能正确调用 API 并渲染数据
- 验证空数据/降级场景下前端不崩溃
### 步骤 3数据库变更审计与 DDL 合并(涉及 DB 改动时必选)
- 审计本次实现中对数据库的所有改动新建表、新增字段、新增索引、FDW 映射变更等)
- **必须通过 pg MCP 工具实际执行迁移 SQL**(禁止仅标记完成而不执行)
- 执行后用查询验证表/字段/索引已正确创建
- RLS 视图双 schema后端查询 `app.v_*` 视图,新建 DWS RLS 视图时必须同时在原 schema 和 `app` schema 下创建
- 合并到主 DDL 基线文件ETL → `docs/database/ddl/etl_feiqiu__<schema>.sql`,业务 → `docs/database/ddl/zqyy_app__<schema>.sql`
- 编写回滚脚本(逆序 DROP/ALTER
### 步骤 4BD 手册更新(涉及 DB 改动时必选)
- 业务库 → `docs/database/BD_manual_*.md`
- ETL 库 → `apps/etl/connectors/feiqiu/docs/database/<层级>/main/BD_manual_*.md`
- FDW → `docs/database/BD_manual_fdw*.md`
- 每份手册必须包含:字段明细、约束与索引、验证 SQL≥3 条)、兼容性影响、回滚策略
### 步骤 5项目文档同步更新按涉及范围裁剪
根据改动类型选择需要更新的文档:
| 文档 | 更新条件 |
|------|----------|
| 模块 README | 模块内部结构变更时 |
| `apps/backend/docs/API-REFERENCE.md` | 新增/修改后端路由时 |
| `docs/contracts/openapi/backend-api.json` | 新增/修改 API 端点时 |
| `docs/DOCUMENTATION-MAP.md` | 新增任何文档条目时 |
### 步骤 6变更审计收口涉及高风险路径时必选
执行 `/audit` 命令完成审计流程。
### 步骤 7服务清理启动了运行时服务时必选
- 关闭浏览器实例、停止后端和前端服务、清理资源
## 按 Spec 类型裁剪
| 类型 | 必选步骤 |
|------|---------|
| ETL 类ODS/DWD/DWS | 1, 3, 4, 5, 6 |
| 后端 API 类 | 1, 2, 5, 6 |
| 全栈类(前后端 + DB | 1, 2, 3, 4, 5, 6 |
| 重构类 | 1, 5, 6 |

46
.cursorignore Normal file
View File

@@ -0,0 +1,46 @@
# Cursor 索引/上下文屏蔽列表
# 目的:减少每次对话注入到 system prompt 的无关文件,降低 token 开销
# 不影响本地文件浏览和手动 Read
# ===== AI 历史会话归档(数百个 .md 文件,正常工作不需要 AI 看到) =====
docs/ai-env-history/
docs/claude-history/
# ===== 审计变更记录(按需手动 Read不需要默认索引 =====
docs/audit/changes/
docs/audit/audit_dashboard.md
# ===== 临时产物与归档 =====
tmp/
_DEL/
.Deleted/
export/
reports/
scripts/logs/
.playwright-mcp/
# ===== 大型二进制/构建产物 =====
node_modules/
.venv/
venv/
dist/
build/
*.egg-info/
htmlcov/
.hypothesis/
.pytest_cache/
# ===== 锁文件(很大且不需要 AI 阅读) =====
pnpm-lock.yaml
package-lock.json
uv.lock
# ===== 小程序打包产物 =====
apps/*.zip
apps/miniprogram/.font_patch_tmp/
# ===== IDE/系统杂项 =====
.idea/
.vscode/
*.lnk
*.swp

6
.gitignore vendored
View File

@@ -86,6 +86,12 @@ infra/**/*.secret
# ===== Claude Code 本地配置 =====
.claude/settings.local.json
# ===== AI 历史会话归档(个人本地,不入库) =====
docs/ai-env-history/sessions/
docs/ai-env-history/conversation_index.csv
docs/ai-env-history/file_impact_index.csv
docs/claude-history/
# ===== Windows 杂项 =====
*.lnk
.Deleted/

53
.ignore Normal file
View File

@@ -0,0 +1,53 @@
# ripgrep / fd / 终端搜索屏蔽列表
# 目的:避免全仓 grep 扫描历史归档、缓存、构建产物和大锁文件。
# AI 历史与审计归档
docs/ai-env-history/
docs/claude-history/
docs/audit/changes/
docs/audit/audit_dashboard.md
# 临时产物与运行日志
tmp/
_DEL/
.Deleted/
export/
reports/
logs/
scripts/logs/
.playwright-mcp/
*.log
*.jsonl
# 依赖、虚拟环境、缓存和构建产物
node_modules/
.venv/
venv/
ENV/
env/
dist/
build/
.vite/
*.egg-info/
htmlcov/
.coverage
.hypothesis/
.pytest_cache/
pytest-cache-files-*/
# 大锁文件
pnpm-lock.yaml
package-lock.json
uv.lock
# 小程序打包产物
apps/*.zip
apps/miniprogram/.font_patch_tmp/
# IDE / 系统杂项
.idea/
.vscode/
*.lnk
*.swp
*.swo
*~

View File

@@ -1,55 +1,48 @@
{
"mcpServers": {
"pg-etl": {
"command": "uvx",
"args": ["postgres-mcp", "--access-mode=unrestricted"],
"env": {
"DATABASE_URI": "postgresql://local-Python:Neo-local-1991125@100.64.0.4:5432/etl_feiqiu"
},
"command": "powershell.exe",
"args": ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "C:\\Project\\NeoZQYY\\tools\\codex\\mcp-postgres.ps1", "PG_DSN"],
"disabled": true
},
"pg-etl-test": {
"command": "uvx",
"args": ["postgres-mcp", "--access-mode=unrestricted"],
"env": {
"DATABASE_URI": "postgresql://local-Python:Neo-local-1991125@100.64.0.4:5432/test_etl_feiqiu"
},
"command": "powershell.exe",
"args": ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "C:\\Project\\NeoZQYY\\tools\\codex\\mcp-postgres.ps1", "TEST_DB_DSN"],
"disabled": false
},
"pg-app": {
"command": "uvx",
"args": ["postgres-mcp", "--access-mode=unrestricted"],
"env": {
"DATABASE_URI": "postgresql://local-Python:Neo-local-1991125@100.64.0.4:5432/zqyy_app"
},
"command": "powershell.exe",
"args": ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "C:\\Project\\NeoZQYY\\tools\\codex\\mcp-postgres.ps1", "APP_DB_DSN"],
"disabled": true
},
"pg-app-test": {
"command": "uvx",
"args": ["postgres-mcp", "--access-mode=unrestricted"],
"env": {
"DATABASE_URI": "postgresql://local-Python:Neo-local-1991125@100.64.0.4:5432/test_zqyy_app"
},
"command": "powershell.exe",
"args": ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "C:\\Project\\NeoZQYY\\tools\\codex\\mcp-postgres.ps1", "TEST_APP_DB_DSN"],
"disabled": false
},
"weixin-devtools-mcp": {
"command": "cmd",
"args": ["/c", "npx", "-y", "weixin-devtools-mcp", "--tools-profile=full", "--ws-endpoint=ws://127.0.0.1:9420"],
"command": "C:\\nvm4w\\nodejs\\npx.cmd",
"args": ["-y", "weixin-devtools-mcp", "--tools-profile=full", "--ws-endpoint=ws://127.0.0.1:9420"],
"env": {
"WECHAT_DEVTOOLS_CLI": "C:\\dev\\WechatDevtools\\cli.bat",
"WECHAT_DEVTOOLS_PROJECT": "C:\\Project\\NeoZQYY\\apps\\miniprogram"
"PATH": "C:\\nvm4w\\nodejs;C:\\Windows\\System32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0",
"WECHAT_DEVTOOLS_CLI": "C:\\dev\\wechat-devtools-cli.bat",
"WECHAT_DEVTOOLS_PROJECT": "C:\\Project\\NeoZQYY\\apps\\miniprogram\\miniprogram"
},
"disabled": false
},
"playwright": {
"command": "cmd",
"args": ["/c", "npx", "@playwright/mcp@latest"],
"command": "C:\\nvm4w\\nodejs\\npx.cmd",
"args": ["@playwright/mcp@latest"],
"env": {
"PATH": "C:\\nvm4w\\nodejs;C:\\Windows\\System32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0"
},
"disabled": false
},
"openapi": {
"command": "uv",
"command": "C:\\Dev\\miniconda3\\Scripts\\uv.exe",
"args": [
"tool", "run",
"--python", "3.12",
"--from", "awslabs.openapi-mcp-server@latest",
"--with", "fastmcp>=2.14.0,<3.0.0",
"awslabs.openapi-mcp-server.exe",

130
AGENTS.md Normal file
View File

@@ -0,0 +1,130 @@
# AGENTS.md
NeoZQYY Monorepo 顶层规则。子模块详细规范见各自 `apps/*/AGENTS.md``docs/database/``apps/etl/connectors/feiqiu/AGENTS.md`
## 语言(强制)
始终中文对话、解释、代码注释、commit message中文描述 + 英文 Co-Authored-By 签名、PR、审计、文档、错误日志。
保持原文:变量/函数/类名、第三方 API 字段名、CLI 命令、技术术语。
禁止英文段落或英文标题。
## CLI / Shell 中文处理
- Python `encoding="utf-8"` / `PYTHONUTF8=1`CSV 给 Excel 用 `utf-8-sig`PowerShell/Node 不依赖系统 ANSI。
- 终端中文乱码时,不当作事实;先调编码重跑,或说明"终端编码异常,结果需复核"。
- Shell 路径含中文/空格/特殊字符必须加引号;复杂中文输出走脚本或结构化 API。
## 项目概览
NeoZQYY = 台球门店全栈数据平台。多门店隔离(`site_id` + RLS中文领域语言CNY金额 `numeric(2)`
| 目录 | 用途 |
|------|------|
| `apps/etl/connectors/feiqiu/` | 飞球 ETLAPI → ODS → DWD → DWS |
| `apps/backend/` | FastAPIJWT 双认证 + WebSocket + AI |
| `apps/miniprogram/` | 微信小程序 C 端Donut + TDesign |
| `apps/admin-web/` | 系统管理后台(开发/运维视角,操作 ETL 库) |
| `apps/tenant-admin/` | 租户管理后台(门店管理员视角,操作业务库) |
| `apps/demo-miniprogram/` | MOCK 标杆小程序(样式校对,禁改) |
| `apps/mcp-server/` | MCP ServerPostgreSQL 只读) |
| `packages/shared/` | 跨项目共享包 |
| `db/` | DDL`schemas/`+ 迁移 + FDW |
| `tools/` | 通用工具 |
| `scripts/ops/` | 日常运维脚本 |
技术栈Python 3.10+ uv workspace、React+Vite+AntD、PostgreSQL 四库(`etl_feiqiu`/`test_etl_feiqiu`/`zqyy_app`/`test_zqyy_app`DSN `PG_DSN`/`APP_DB_DSN`)。配置分层 `.env` < `.env.local` < env < CLI。
## 数据库
- 六层 Schema`meta``ods``dwd``core``dws``app`RLS 视图)
- 跨库:`zqyy_app` 通过 FDW 只读映射 `etl_feiqiu.app`
- RLS`site_id` + `app.current_site_id` 会话变量
- **RLS 双 Schema 踩坑**DWS/DWD 表的 RLS 视图必须同时建在 `dws``app` schema后端走 `app.v_*`。只建 `dws` 会让后端查询失败。回滚需逆序 DROP。
## 飞球数据规范(速查)
- `consume_money` 禁止直接计算 → 用 `items_sum` 拆分字段
- 取数优先级DWS > DWD > 禁止 ODS
- 参考优先级DWD-DOC > DWS 权威规范 > BD 手册 > ETL 任务文档 > DDL 注释
- `_archived/` 目录禁止读取或参考
- 完整规则见 `apps/etl/connectors/feiqiu/AGENTS.md`
## 文件归属
| 类型 | 位置 |
|------|------|
| 模块内文档/测试/脚本 | 模块内 `docs/``tests/``scripts/` |
| 跨模块文档 | 根 `docs/` |
| Monorepo 守护测试 | 根 `tests/` |
| 日常运维脚本 | `scripts/ops/` |
| 通用工具 | `tools/`(按类型分子目录) |
| 审计记录 | 根 `docs/audit/changes/<YYYY-MM-DD>__<slug>.md`(禁止写子模块) |
| 数据库文档 | 根 `docs/database/` |
| 归档/待删 | `_DEL/`(保持原路径,定期清理) |
`docs/audit/audit_dashboard.md``scripts/audit/gen_audit_dashboard.py` 生成,勿手动编辑。
## 编码前需求审问(强制)
新建功能/接口、重构、多模块联动、需求模糊时:
1. 不立即动手,先提问循环(每轮 3-5 个问题)
2. 必问:用户角色、核心操作、数据写入/展示/来源、错误/成功反馈、认证权限、存储(哪库/新表)、终端适配、边界(并发/幂等/超时)
3. 输出「需求确认摘要」,用户确认后实施
例外:用户说"直接改/跳过审问"、Bug 有明确复现步骤、纯格式调整、已有 spec
## 逻辑改动前置调研(强制)
任何逻辑改动ETL/业务规则/API/数据模型/前端交互):
1. 委托 Explore 子代理调研:目标模块、`docs/audit/changes/` 历史、调用方/被调用方、数据流向、影响范围
2. 输出「改动前上下文摘要」,用户确认后实施
流程:需求审问 → 确认 → 前置调研 → 确认 → 编码
例外:纯格式/注释/文档修改、用户说"直接改/跳过调研"、新建独立文件
## 改动后验证(强制)
1. 运行相关测试(单元/集成/lint不能运行需说明原因
2. 输出 diff 摘要(文件清单 + 每个改动要点)
3. 列出未覆盖风险点(未测路径、副作用、需人工验证场景)
例外:纯格式/文档/注释、用户说"跳过验证"
## 数据库 Schema 变更
修改 PostgreSQL schema迁移/DDL/表定义/ORM必须同步 `docs/database/`变更说明、兼容性影响、回滚策略、≥3 条校验 SQL。
## 测试环境
1. `load_dotenv` 加根 `.env`;必需变量缺失立即报错,禁止静默回退空串
2. cwd 与正式一致ETL → `apps/etl/connectors/feiqiu/`、后端 → `apps/backend/`
3. 配置走 `AppConfig.load()`,不得为测试构造简化配置
4. 用测试库(`TEST_DB_DSN`),禁止连正式库
5. 属性测试分组执行(`-k` 筛选禁止一次性全跑hypothesis 默认 `max_examples=100`
例外:用户指定简化环境、纯单元测试用 FakeDB/FakeAPI、`--dry-run` CLI 验证
## 脚本规范
- 复杂操作写 Python 脚本,避免复杂 shell
- 一次性运维 → `scripts/ops/`;模块专属 → 模块内 `scripts/`
- `scripts/ops/` 不在 uv workspace 内,导入 ETL 纯函数用 `importlib.util` + stub参考 `scripts/ops/backfill_finance_area_daily.py`
## 子代理
- 委托:批量读 ≥3 文件、大范围搜索、不熟悉模块探索、多步 shell
- 主流程直接处理:单文件、单命令、小范围精确搜索
## 审计
任何逻辑改动必须可追溯、可验证、可回滚。
完成一轮后用 `/audit`
1. `python scripts/audit/prescan.py` 预扫描(自动识别+分类+合规,零 token
2. 补充语义上下文(从对话提取每变更原因)
3. 委托子代理写 `docs/audit/changes/`
4. 补齐文档同步
5. `python scripts/audit/gen_audit_dashboard.py` 刷新一览表
`prescan.py --files` 参数git status 含大量历史未提交时,只传本次会话文件列表。

52
apps/backend/AGENTS.md Normal file
View File

@@ -0,0 +1,52 @@
# AGENTS.md - Backend (FastAPI)
进入本目录时自动加载。
## 架构模式
### 全局响应包装
`ResponseWrapperMiddleware` 把所有 2xx 响应包为 `{ "code": 0, "data": <payload> }`
非 2xx 响应保持原样。前端统一通过 `response.data` 解包。
### 序列化
`CamelModel` 基类snake_case -> camelCase 自动转换(小程序 API 用)。
后端代码始终用 snake_caseJSON 输出自动转驼峰。
### JWT 双认证
| 认证方式 | 用途 | 表 | JWT aud |
|---------|------|-----|---------|
| 用户名+密码 | admin-web 登录 | `auth.admin_users` | `admin` |
| 微信 code | 小程序登录 | `auth.users` | `miniapp` |
| 用户名+密码 | tenant-admin 登录 | `auth.tenant_admins` | `tenant-admin` |
待审核用户有 limited token仅可访问审核状态接口
### AI 集成
8 个千问应用通过 DashScope SDK
chat / finance / clue / analysis / tactics / note / customer / consolidate
特性:熔断(连续失败自动断路)、限流(每分钟/每日)、预算追踪、对话缓存。
### 后台服务lifespan
- `TaskQueue`:按 site_id 消费FIFO 队列
- `Scheduler`:读 `meta.scheduled_tasks` 自动入队
- 4 个触发器:日结/月结/工资/关系指数
### 数据库访问
- 业务库通过 `APP_DB_DSN` 直连 `zqyy_app`
- ETL 数据通过 FDW 映射的 `app.v_*` RLS 视图访问
- 查询前必须 `SET LOCAL app.current_site_id = :site_id`
## 测试
```bash
cd apps/backend && pytest tests/ -v
```
使用测试库(`TEST_APP_DB_DSN`),禁止连正式库。

View File

@@ -0,0 +1,18 @@
# AGENTS.md - demo-miniprogram
**禁止删除或移入 `_DEL/`。**
本目录是假数据 MOCK 版小程序,使用硬编码数据驱动所有页面,不连接后端 API。
## 用途
- 页面样式和展示格式的**标杆校对**:开发 `apps/miniprogram/` 时,以本目录的 UI 效果为参考基准
- 快速预览各页面在不同数据状态下的渲染效果,无需启动后端服务
## 与 miniprogram 的关系
| | `apps/miniprogram/` | `apps/demo-miniprogram/` |
|--|---------------------|--------------------------|
| 数据来源 | 后端 API | 硬编码假数据 |
| 用途 | 生产代码 | 样式参考 / UI 校对 |
| 可独立运行 | 需后端 | 可独立运行 |

View File

@@ -0,0 +1,81 @@
# AGENTS.md - ETL Feiqiu Connector
进入本目录时自动加载。包含 DWD 和 DWS 层的强制业务规则。
## DWD-DOC 标杆文档(权威数据源)
`docs/reports/DWD-DOC/` 是业务模型与财务数据的权威标杆文档。所有涉及金额口径、支付渠道、消费链路、账务公式、字段语义的开发工作,必须以此目录为第一参考源。
### 文档清单
| 文件 | 内容 | 关键规则 |
|------|------|----------|
| `01-business-panorama.md` | 消费链路 + 优惠机制 + 消费场景 | settle_type 枚举、助教费用拆分、团购券三层价格 |
| `02-accounting-panorama.md` | 支付渠道 + 对账公式 + consume_money 口径 | 支付渠道恒等式、F2 三期公式 |
| `03-financial-panorama.md` | 收入构成 + 储值卡资金流 + 对账矩阵 | 平台结算互斥关系 |
| `04-dimension-panorama.md` | 维度表与主数据全景 | SCD2 维度取值规则 |
| `05-f2-balance-audit.md` | F2 收支平衡公式专项 | 三期公式 + 139 笔失败根因 |
| `06-calibration-checklist.md` | 校准清单 + 验证 SQL | 全部验证公式集中 |
| `consume/consume-money-caliber.md` | consume_money 口径变化时间线 | 三种口径(A/B/C)定义与切换时间点 |
### DWD 强制规则12 条)
1. **consume_money 禁止直接用于计算**:存在三种历史口径(A/B/C)混合DWS 层及下游统一使用 `items_sum = table_charge_money + goods_money + assistant_pd_money + assistant_cx_money + electricity_money`
2. **助教费用必须拆分**:使用 `assistant_pd_money`(陪打)和 `assistant_cx_money`(超休),禁止使用 `service_fee` / `ASSISTANT_BASE` / `ASSISTANT_BONUS``service_fee` 仅在平台结算表中表示"平台服务费",语义不同)
3. **支付渠道恒等式**`balance_amount = recharge_card_amount + gift_card_amount`100% 成立),三者不可重复计算
4. **settle_type 过滤**:正向交易取 `IN (1, 3)`,本表无 `is_delete` 字段
5. **电费未启用**`electricity_money` 全为 0`gross_amount` 不含电费是正确的
6. **折扣互斥**`discount_manual`(大客户优惠)与 `discount_other` 互斥,两者之和 = `adjust_amount`
7. **现金流互斥**`cash_inflow_total``platform_settlement_amount``groupbuy_pay_amount` 互斥
8. **废单判断**:使用 `dwd_assistant_service_log_ex.is_trash``dwd_assistant_trash_event` 已废弃
9. **储值卡字段命名**DWS 层使用 `balance_pay`/`recharge_card_pay`/`gift_card_pay`;财务日报用 `recharge_card_consume`
10. **会员字段断档DQ-6**`settlement_head.member_phone/member_name` 自 2025-12 起全为 NULL -> 通过 `member_id` LEFT JOIN `dwd.dim_member``scd2_is_current=1`
11. **会员卡字段断档DQ-7**`settlement_head.member_card_type_name` 自 2025-07-21 起全为 NULL -> 通过 `member_id` LEFT JOIN `dwd.dim_member_card_account``scd2_is_current=1`)。通用规则:结算单上所有会员冗余字段均不可靠
12. **支付方式拆分DQ-8**`dwd_settlement_head_ex.cash_amount`/`online_amount` 不可靠。正确来源是 `dwd_payment` 表:`payment_method=2` 现金,`payment_method=4` 扫码。通过 `relate_type=2` + `relate_id` 关联结算单
## DWS 层权威规范
> DWD 12 条在 DWS 层同样生效。冲突时以 DWD-DOC 为准。
### 幂等更新策略
- 汇总表默认 delete-before-insert按日期范围 + site_id 先删后插)
- 库存表使用 upsert`ON CONFLICT DO UPDATE`
- 禁止 TRUNCATE
### 课程类型与定价
- 课程类型通过 `cfg_skill_type` 映射(`skill_id` -> `course_type_code`BASE/BONUS/ROOM禁止硬编码
- 定价通过 `cfg_assistant_level_price` 按 SCD2 生效期 as-of join禁止硬编码价格
- 包厢课统一 138 元/小时(`dws.salary.room_course_price`
### 绩效档位与工资
- 绩效档位通过 `cfg_performance_tier` 按有效业绩小时数匹配 `[min_hours, max_hours)` 区间
- 新入职折算:入职日期在当月 1 日后按日均 × 30 定档;> 25 日最高 T2
- 奖金通过 `cfg_bonus_rules`SPRINT 不累计取最高档TOP_RANK 按排名1000/600/400 元)
- 排名使用 `calculate_rank_with_ties()`,相同业绩并列
### 会员与散客
- 散客:`member_id <= 0`,不计入会员统计(但计入助教业绩)
- 客户分层高价值90 天 >= 3 次且 >= 1000 元)-> 中等 -> 低活跃 -> 流失
- 会员信息一律通过 ID 关联维度表
### 时间窗口与调度
- 滚动窗口标准集7/10/15/30/60/90 天
- 月度任务宽限期:月初前 5 天可处理上月数据
- 工资计算周期:月初前 5 天运行
### 指数参数
- 所有权重和阈值通过 `cfg_index_parameters``index_type`WBI/NCI/RS/OS/MS/ML/SPI加载禁止硬编码
### 台桌分类
- `cfg_area_category` 仅精确匹配 + 兜底BILLIARD/SNOOKER/OTHER。`BILLIARD_VIP` 已废弃
### 参考优先级
DWD-DOC > DWS 权威规范 > BD 手册 > ETL 任务文档 > 业务规则文档 > DDL 注释

60
db/AGENTS.md Normal file
View File

@@ -0,0 +1,60 @@
# AGENTS.md - Database (DDL / Migrations / Seeds)
进入本目录时自动加载。
## Schema 变更规则
修改任何影响 PostgreSQL schema 的内容(迁移脚本/DDL/表定义)时,必须同步更新 `docs/database/`
1. **变更说明**:新增/修改/删除的表、字段、约束、索引
2. **兼容性**:对 ETL、后端 API、小程序字段映射的影响
3. **回滚策略**如何撤销DDL 回滚 / 数据回填)
4. **验证步骤**:至少 3 条校验 SQL
## RLS 视图双 Schema 规则
新建 DWS/DWD 表的 RLS 视图必须同时在原 schema`dws`)和 `app` schema 创建:
```sql
-- 1. 原 schema
CREATE VIEW dws.v_xxx AS SELECT ... WHERE site_id = current_setting('app.current_site_id')::int;
-- 2. app schema后端通过此路径访问
CREATE VIEW app.v_xxx AS SELECT ... WHERE site_id = current_setting('app.current_site_id')::int;
```
回滚需逆序 DROP 两个 schema 的视图。只在原 schema 创建会导致后端查询失败。
## 目录结构
```text
db/
├── etl_feiqiu/
│ ├── schemas/ # 权威 DDL — 六层完整定义meta/ods/dwd/core/dws/app
│ ├── migrations/ # 未来增量迁移v1 已全部归档)
│ ├── ods/ # ODS 补充脚本
│ └── scripts/ # 测试数据库脚本
├── zqyy_app/
│ ├── schemas/ # 权威 DDL — 三层完整定义public/auth/biz
│ ├── migrations/ # 未来增量迁移v1 已全部归档)
│ └── scripts/ # 测试数据库脚本
├── fdw/ # FDW 跨库只读映射(正向 + 反向 + 测试环境)
└── _archived/ # 归档v1 迁移 39 个、旧基线)
```
v1 阶段种子数据已合并进 `schemas/` 对应 DDL 文件末尾,不再单独维护。
## DDL 刷新
修改 schema 后,重新生成完整 DDL
```bash
PYTHONUTF8=1 python tools/db/gen_consolidated_ddl.py
```
输出到 `docs/database/ddl/`,然后复制到 `db/*/schemas/` 保持同步。
## 测试规范
- 数据库操作使用测试库(`TEST_DB_DSN` / `TEST_APP_DB_DSN`),禁止连正式库
- 迁移脚本在测试库执行后需验证表结构

View File

@@ -0,0 +1,60 @@
# 变更审计记录Codex 深度迁移与 Claude 历史摘要归档
| 字段 | 值 |
|------|-----|
| 日期 | 2026-04-29 03:57:55 |
## 操作摘要
本轮将 Claude Code 的项目规则、用户习惯、skills、agents、rules 和 NeoZQYY 项目历史会话迁移到 Codex 可读取的结构。迁移目标是让 Codex 尊重用户此前的使用习惯,并在后续修改代码时能追溯“哪次 Claude 会话改了什么、影响了什么”。
迁移没有把 Claude 原始对话全文注入 Prompt而是生成脱敏摘要、会话索引和文件反向索引避免过时信息和敏感信息污染日常上下文。
## 变更文件
### 新增
- `AGENTS.md`:根项目 Codex 工作规范。
- `apps/backend/AGENTS.md`:后端模块 Codex 规则。
- `apps/etl/connectors/feiqiu/AGENTS.md`ETL 飞球模块 Codex 规则。
- `apps/demo-miniprogram/AGENTS.md`demo 小程序模块保护规则。
- `db/AGENTS.md`:数据库目录 Codex 规则。
- `tools/codex/mcp-postgres.ps1`Codex PostgreSQL MCP 启动脚本,按变量名读取 `.env` / `.env.local` / 当前环境。
- `tools/codex/migrate_claude_assets.py`Claude 资产迁移脚本。
- `docs/codex_migration.md`Codex 迁移状态说明。
- `docs/claude-history/`Claude 会话摘要归档、会话索引和文件反向索引。
## 改动注解
- `tools/codex/migrate_claude_assets.py`:实现可重复迁移流程,转换 Claude skills、agents、rules生成全局 Codex 习惯文件,并对 Claude JSONL 历史做脱敏摘要和索引。
- `tools/codex/mcp-postgres.ps1`:避免把数据库 DSN 明文写入 Codex 配置,支持 `-ValidateOnly` 做环境巡检。
- `docs/claude-history/session_index.csv`:记录 94 个 Claude 会话的时间范围、影响范围、风险标签和摘要文件。
- `docs/claude-history/file_index.csv`:记录 246 个被编辑文件与 Claude 会话的关联,支持按文件追溯。
- `docs/codex_migration.md`:同步迁移结果、验证方式和追溯入口。
- `db/AGENTS.md`:高风险目录规则文件,仅迁移原 Claude DB 指令,没有修改 DDL 或迁移 SQL。
## 数据库变更
无数据库 schema 变更。未新增迁移 SQL未修改 DDL。
## 风险与回滚
-Claude 历史摘要为脚本自动提取,可能遗漏自然语言中描述的影响。回溯关键问题时仍需查看原始 JSONL、git diff、审计记录和测试结果。
-Claude skill 中部分内容包含 Claude Code 专属命令或 agent 概念,已迁移为 Codex skill/reference但使用时需要按 Codex 工具等价替换。
-Codex hooks 未默认启用,原 Claude hooks 的强校验不是一比一恢复;当前由 AGENTS 规则和审计流程承接。
- 回滚:删除新增的 `AGENTS.md` / `docs/claude-history/` / `docs/codex_migration.md` / `tools/codex/*`,并恢复 `C:\Users\Administrator\.codex` 下自动生成前的备份。
## 验证
- `python scripts/audit/prescan.py --files "<本次迁移文件列表>"`:审计预扫描通过,未发现缺失文档同步。
- `python -m py_compile tools/codex/migrate_claude_assets.py`:迁移脚本语法通过。
- `powershell.exe -NoProfile -ExecutionPolicy Bypass -File tools\codex\mcp-postgres.ps1 TEST_DB_DSN -ValidateOnly`PostgreSQL MCP 启动配置检查通过。
- Codex skill 校验12/12 个迁移后的 skill 通过 `quick_validate.py`
- 历史归档校验:生成 94 个会话摘要、94 条会话索引、246 个文件反向索引。
- 敏感信息扫描:生成的历史摘要、迁移说明和 Codex skills 未发现常见 DSN/API key/密码形态。
## 合规检查
- 文档同步:已同步到 `docs/codex_migration.md``docs/claude-history/README.md`
- 数据库文档:不适用,本轮无 schema 变更。
- 审计记录:已落盘。

View File

@@ -0,0 +1,47 @@
# 变更审计记录Cursor AI 开发环境迁移
| 字段 | 值 |
|------|-----|
| 日期 | 2026-05-02 00:05:37 |
## 操作摘要
本轮将 Claude Code 与 Codex 中已经沉淀的 AI 开发习惯迁移到 Cursor 原生结构。迁移目标是让 Cursor 继承中文沟通、前置调研、验证、审计、子代理审查、MCP 测试库优先和历史追溯习惯,同时避免把长规则和原始对话全文堆入常规上下文。
## 变更文件
### 新增/修改
- `tools/cursor/migrate_ai_environment.py`:可重复迁移脚本。
- `.cursor/rules/`Cursor 项目级短规则。
- `.cursor/skills/`:审计、前置调研、文档同步和 spec 收尾流程技能。
- `.cursor/hooks.json``.cursor/hooks/ai_env_guard.py`:轻量提醒/保护 hooks。
- `docs/cursor_migration.md`Cursor 迁移说明。
- `docs/ai-env-history/`Claude/Codex/Cursor 原始对话精简索引和逐会话摘要。
- `AGENTS.md` / `CLAUDE.md`:补充 CLI / Shell 中文编码处理规则,要求 UTF-8、乱码复核、中文路径正确加引号。
- `C:\Users\Administrator\.cursor\skills`:用户级技能迁移。
- `C:\Users\Administrator\.cursor\agents`:用户级子代理迁移。
## 数据库变更
无数据库 schema 变更。未执行 DDL未修改迁移 SQL。
## 风险与回滚
-Cursor hooks 与 Claude hooks 事件字段不完全一致,本轮只启用轻量提醒和 ask不启用强阻断。
- 中:历史摘要从原始 JSONL 自动分析,仍可能遗漏自然语言里的隐含决策;关键判断需回看原文和当前代码。
- 低:用户级 Cursor 资产覆盖前已备份,可从 manifest 恢复。
-Windows 终端中文输出可能受代码页影响;已在根规则和 Cursor 核心规则中要求 UTF-8、乱码不采信、必要时重跑。
## 验证
- 运行 `python tools/cursor/migrate_ai_environment.py --check` 校验生成结构。
- 运行 `python scripts/audit/prescan.py --files "<本轮迁移文件>"` 做审计预扫描。
- 解析 `.mcp.json``.cursor/hooks.json`、skill/subagent frontmatter。
- 运行 `.venv\Scripts\python.exe scripts\audit\prescan.py --files "AGENTS.md,CLAUDE.md,.cursor/rules/neozqyy-core.mdc,tools/cursor/migrate_ai_environment.py"` 校验中文规则增量。
## 合规检查
- 文档同步:已新增 `docs/cursor_migration.md``docs/ai-env-history/README.md`
- 数据库文档:不适用,本轮无 schema 变更。
- 审计记录:已落盘。

108
docs/codex_migration.md Normal file
View File

@@ -0,0 +1,108 @@
# Codex 迁移配置说明
本仓库已从 Claude Code 的项目配置迁移到 Codex 可识别的配置结构。
## 已迁移内容
| Claude Code | Codex | 状态 |
|-------------|-------|------|
| 根目录 `CLAUDE.md` | 根目录 `AGENTS.md` | 已迁移 |
| 子目录 `CLAUDE.md` | 子目录 `AGENTS.md` | 已迁移 |
| `.mcp.json` | `C:\Users\Administrator\.codex\config.toml` | 已迁移 |
| PostgreSQL MCP 明文 DSN | `tools/codex/mcp-postgres.ps1` 读取 `.env` / `.env.local` | 已改为间接读取 |
| `.claude/settings.local.json` 中的项目环境变量 | `shell_environment_policy.set` | 已迁移核心变量 |
## Codex 全局配置
Codex 当前读取:
```text
C:\Users\Administrator\.codex\config.toml
```
已配置的 MCP server
- `pg-etl`ETL 正式库,默认禁用
- `pg-etl-test`ETL 测试库,默认启用
- `pg-app`:业务正式库,默认禁用
- `pg-app-test`:业务测试库,默认启用
- `weixin-devtools-mcp`:微信开发者工具 MCP
- `playwright`Playwright MCP
- `openapi`:后端 OpenAPI MCP
已配置的 shell 环境变量:
- `NEOZQYY_ROOT=C:\Project\NeoZQYY`
- `VIRTUAL_ENV=C:\Project\NeoZQYY\.venv`
未覆盖全局 `PATH`,避免影响 Windows、Node.js、uv 等系统命令解析。
数据库 MCP 的 DSN 不直接写入 Codex 全局配置,启动时由 `tools/codex/mcp-postgres.ps1` 按以下优先级解析:
```text
.env < .env.local < 当前进程环境变量
```
## 验证命令
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File tools\codex\mcp-postgres.ps1 TEST_DB_DSN -ValidateOnly
powershell.exe -NoProfile -ExecutionPolicy Bypass -File tools\codex\mcp-postgres.ps1 TEST_APP_DB_DSN -ValidateOnly
```
TOML 静态校验:
```powershell
@'
import pathlib, tomllib
path = pathlib.Path.home().joinpath(".codex/config.toml")
data = tomllib.loads(path.read_text(encoding="utf-8-sig"))
print(sorted(data.get("mcp_servers", {}).keys()))
'@ | .venv\Scripts\python.exe -
```
## 尚未一比一迁移的内容
Claude Code 的 `.claude/commands/*.md``.claude/hooks/*.py` 不完全等价于 Codex 当前稳定能力:
- `/audit` 等自定义命令已经沉淀在根目录 `AGENTS.md` 的审计流程中,可直接要求 Codex “执行审计流程”。
- Claude 的 `PreToolUse` / `PostToolUse` 钩子在 Codex 上仍属于实验能力,且 Windows 兼容性需要按 Codex 版本再验证;本次未默认启用,避免影响日常编辑。
- 如需恢复钩子式强校验,优先迁移 `Stop` 类审计/验证提醒,再评估文件读写前置拦截。
## 用户级 Claude 资产迁移状态
以下原始内容仍保留在 `C:\Users\Administrator\.claude` 作为备份和源材料,同时已经完成 Codex 侧迁移:
- `skills/`10 个 Claude skill 已迁移到 `C:\Users\Administrator\.codex\skills`
- `agents/`8 个 Claude agent 已迁移为 Codex skill `claude-agent-roles`
- `rules/``python``typescript``web``zh` 四组规则已迁移为 Codex skill `claude-rules-reference`,并提炼进 `C:\Users\Administrator\.codex\AGENTS.md`
- `projects/C--Project-NeoZQYY/`NeoZQYY 项目历史会话 94 个 JSONL 文件已摘要归档到 `docs/claude-history/`
- `history.jsonl``plans/``file-history/` 等 Claude 本地历史与辅助状态。
Codex 不会自动学习 Claude Code 的原始历史对话,也不会自动加载 `C:\Users\Administrator\.claude\skills`。当前 Codex 可稳定读取的是:
- 仓库内 `AGENTS.md`
- `C:\Users\Administrator\.codex\config.toml`
- `C:\Users\Administrator\.codex\skills`
- 当前会话上下文与 Codex 自己生成的 memory
- `docs/claude-history/` 中的历史摘要索引
历史会话没有全文注入 Prompt只保留脱敏摘要和索引。需要追溯时先查索引再按需打开对应摘要只有必要时才读取原始 JSONL。
## 使用提醒
修改 `C:\Users\Administrator\.codex\config.toml` 后,需要重启 Codex 会话或重新打开项目MCP server 才会被重新发现。
## 本次深度迁移结果
- 用户全局习惯已写入 `C:\Users\Administrator\.codex\AGENTS.md`
- Claude skills 已迁移 10 个到 `C:\Users\Administrator\.codex\skills`agent-introspection-debugging, claude-api, code-tour, codebase-onboarding, repo-scan, rules-distill, search-first, security-review, strategic-compact, tdd-workflow。
- Claude agents 已迁移为 Codex skill `claude-agent-roles`,包含 8 个角色参考。
- Claude rules 已迁移为 Codex skill `claude-rules-reference`,包含 28 个规则文件。
- NeoZQYY Claude 会话历史已摘要归档到 `docs/claude-history/`94 个会话246 个被编辑文件索引。
### 追溯入口
- 会话索引:`docs/claude-history/session_index.csv`
- 文件索引:`docs/claude-history/file_index.csv`
- 会话摘要:`docs/claude-history/sessions/`

View File

@@ -0,0 +1,207 @@
# Codex 迁移阶段状态报告
| 字段 | 内容 |
|------|------|
| 生成时间 | 2026-04-29 04:01:26 |
| 范围 | 从 Claude Code 迁移至 Codex 的环境、配置、使用习惯、skills、agents、rules 与历史追溯资产 |
| 当前结论 | 已进入“深度迁移完成,等待 Codex 重启生效与后续验收”的阶段 |
## 一、按时间线看当前进行到哪一步
| 时间 | 阶段 | 状态 | 说明 |
|------|------|------|------|
| 2026-04-20 至 2026-04-22 | Claude Code 主要业务开发期 | 已归档 | Claude 历史显示这段时间有大量跨后端、admin-web、小程序、数据库、文档和脚本的修改。 |
| 2026-04-28 | Claude Code 尾声会话 | 已归档 | 最新 Claude 项目会话 `67f30f8e-2db7-467f-82e0-5395f9ed855f` 未检测到文件写入,偏向过渡/查询类会话。 |
| 2026-04-29 03:30 左右 | Codex 基础迁移 | 已完成 | 已迁移根/子目录 `AGENTS.md`、MCP 配置、PostgreSQL MCP 启动脚本、环境变量。 |
| 2026-04-29 03:54 左右 | Codex 深度迁移 | 已完成 | 已迁移 Claude skills、agents、rules、用户全局习惯并生成 Claude 历史摘要归档。 |
| 2026-04-29 03:57 左右 | 审计固化 | 已完成 | 已生成审计记录并刷新审计面板。 |
| 当前 | 验收与生效阶段 | 进行中 | 需要重启 Codex 或重新打开项目让新的全局规则、skills 和 MCP 配置完整加载。 |
## 二、已经完成的迁移
### 1. 项目级规则
- 根目录 `CLAUDE.md` 已迁移为 `AGENTS.md`
- 子模块规则已补齐:
- `apps/backend/AGENTS.md`
- `apps/etl/connectors/feiqiu/AGENTS.md`
- `apps/demo-miniprogram/AGENTS.md`
- `db/AGENTS.md`
### 2. Codex 全局配置
- `C:\Users\Administrator\.codex\config.toml` 已配置 NeoZQYY 项目信任、MCP server 和 shell 环境变量。
- PostgreSQL MCP 已改为通过 `tools/codex/mcp-postgres.ps1` 间接读取 `.env` / `.env.local` / 当前环境变量,避免把 DSN 明文写入 Codex 配置。
- 已配置 MCP
- `pg-etl`:正式 ETL 库,默认禁用
- `pg-etl-test`:测试 ETL 库,默认启用
- `pg-app`:正式业务库,默认禁用
- `pg-app-test`:测试业务库,默认启用
- `weixin-devtools-mcp`
- `playwright`
- `openapi`
### 3. 用户习惯、skills、agents、rules
- 用户全局习惯已写入 `C:\Users\Administrator\.codex\AGENTS.md`
- Claude skills 已迁移 10 个到 `C:\Users\Administrator\.codex\skills`
- Claude agents 已迁移为 Codex skill`claude-agent-roles`,包含 8 个角色参考。
- Claude rules/steering/pre-prompt 已迁移为 Codex skill`claude-rules-reference`
- Codex 当前可发现的用户级 skill 数量12 个。
### 4. Claude 对话历史追溯
- 已生成 `docs/claude-history/`
- 已归档 Claude 项目会话摘要94 个。
- 已建立文件反向索引246 个被编辑文件。
- 追溯入口:
- `docs/claude-history/session_index.csv`
- `docs/claude-history/file_index.csv`
- `docs/claude-history/sessions/`
### 5. 审计与文档
- 迁移说明:`docs/codex_migration.md`
- 审计记录:`docs/audit/changes/2026-04-29__codex_migration_and_claude_history_archive.md`
- 审计面板:`docs/audit/audit_dashboard.md`
- 可重复迁移脚本:`tools/codex/migrate_claude_assets.py`
## 三、已经完成的验证
| 验证项 | 结果 |
|--------|------|
| Codex skill 校验 | 12/12 通过 |
| Claude 会话摘要数量 | 94 个 |
| 文件反向索引数量 | 246 个唯一文件 |
| PostgreSQL MCP 测试库启动配置 | `TEST_DB_DSN -ValidateOnly` 通过 |
| PowerShell MCP 脚本语法 | 通过 |
| Python 迁移脚本语法 | 通过 |
| 迁移产物敏感信息扫描 | 未发现常见 DSN/API key/密码形态 |
| 审计预扫描 | 已执行,需审计,已补审计记录 |
## 四、还没有解决或需要注意的遗留问题
### 1. Codex 需要重启生效
`config.toml``C:\Users\Administrator\.codex\AGENTS.md` 和新迁移的 skills 需要重启 Codex 或重新打开项目后才能稳定加载。
### 2. MCP 只完成静态与启动配置验证
当前已验证 TOML、PostgreSQL MCP 启动脚本和环境变量解析,但还没有在重启后的 Codex 会话里逐个调用 MCP 工具确认可用性。尤其需要后续确认:
- PostgreSQL 测试库 MCP 是否能列 schema / 执行只读 SQL。
- WeChat DevTools MCP 是否能连接当前微信开发者工具。
- Playwright MCP 是否能正常打开本地页面。
- OpenAPI MCP 是否能在后端服务启动后读取接口。
### 3. Claude hooks 没有一比一启用
Claude Code 的 `PreToolUse``PostToolUse``Stop` hooks 没有在 Codex 里默认启用。当前替代方式是:
- 把规则写入 `AGENTS.md`
- 把审计流程写入项目规范。
- 保留迁移说明中关于 hooks 的差异。
遗留风险是:原来由 hooks 自动拦截的行为,现在主要依赖 Codex 遵循规则和人工/脚本验证。
### 4. Claude 历史是摘要,不是全文记忆
历史归档是脱敏摘要和索引,不是原始对话全文导入。它能回答“哪个会话可能改了哪个文件、用了哪些命令、有什么 SQL/风险线索”,但不能保证完整还原每个自然语言决策。
关键问题追溯时仍需组合使用:
- `docs/claude-history/file_index.csv`
- 对应 `sessions/<session_id>.md`
- 原始 Claude JSONL
- git diff / git blame
- `docs/audit/changes/`
- 当前代码与测试结果
### 5. 部分 Claude skill 带有 Claude 专属语境
`claude-api`、部分 agent/skill 文案仍可能包含 Claude Code 专属术语。它们已经可被 Codex 发现,但实际使用时需要按 Codex 工具能力做等价替换。
### 6. 工作区仍有大量既有未提交业务改动
本轮迁移没有处理此前已有的业务代码改动。当前迁移相关文件仍未提交,工作区还存在其他历史未提交内容。后续提交时需要隔离本次迁移文件,避免混入业务改动。
## 五、当前仓库内本轮新增/修改文件
### 迁移配置与规则
- `AGENTS.md`
- `apps/backend/AGENTS.md`
- `apps/demo-miniprogram/AGENTS.md`
- `apps/etl/connectors/feiqiu/AGENTS.md`
- `db/AGENTS.md`
### 工具脚本
- `tools/codex/mcp-postgres.ps1`
- `tools/codex/migrate_claude_assets.py`
### 迁移与历史文档
- `docs/codex_migration.md`
- `docs/claude-history/`
- `docs/codex_migration_status_report_2026-04-29.md`
### 审计
- `docs/audit/changes/2026-04-29__codex_migration_and_claude_history_archive.md`
- `docs/audit/audit_dashboard.md`
## 六、未来工作安排建议
### 第一阶段:生效验收
1. 重启 Codex 或重新打开 NeoZQYY 项目。
2. 确认 Codex 能看到新增 skills
- `claude-agent-roles`
- `claude-rules-reference`
- `tdd-workflow`
- `security-review`
- `search-first`
3. 逐个验证 MCP
- `pg-etl-test`
- `pg-app-test`
- `weixin-devtools-mcp`
- `playwright`
- `openapi`
### 第二阶段:迁移产物固化
1. 单独提交 Codex 迁移相关文件。
2. 不混入既有业务代码改动。
3. 提交信息使用中文描述,并按项目规范保留 Co-Authored-By 签名行。
### 第三阶段:历史追溯增强
1. 按业务模块补充历史摘要标签,例如 AI、财务看板、小程序聊天、触发器、数据库迁移。
2. 对 2026-04-20 至 2026-04-22 的高影响会话做人工二次摘要。
3. 将关键“设计决策”从历史摘要中提炼到 `docs/audit/` 或对应模块文档。
### 第四阶段:恢复强校验能力
1. 评估 Codex hooks 当前版本在 Windows 上的稳定性。
2. 优先恢复 Stop 类检查:
- 是否需要审计
- 是否需要测试验证
- 是否涉及数据库文档同步
3. 再评估 PreToolUse / PostToolUse 类拦截。
### 第五阶段:继续业务开发前的固定流程
后续任何逻辑改动前,建议固定执行:
1.`docs/claude-history/file_index.csv`,确认目标文件历史会话。
2.`docs/audit/changes/`,确认最近审计记录。
3. 读取当前文件和调用链。
4. 输出改动前上下文摘要。
5. 实施后运行测试、输出 diff 摘要和风险清单。
## 七、阶段性结论
当前不是“刚开始迁移”,也不是“已经可以无感继续业务开发”。准确位置是:
> Codex 深度迁移已经完成,历史追溯体系已经建立;下一步是重启 Codex 让配置生效,并做 MCP/skills 的实际运行验收。验收完成后,再把迁移文件单独提交,之后才能稳定进入新的业务开发阶段。

108
docs/cursor_migration.md Normal file
View File

@@ -0,0 +1,108 @@
# Cursor AI 开发环境迁移说明
## 状态
本轮把 NeoZQYY 的 AI 开发环境从 Claude Code / Codex 迁移到 Cursor 原生资产:
- 项目规则:`.cursor/rules/`
- 项目流程技能:`.cursor/skills/`
- 用户技能:`C:\Users\Administrator\.cursor\skills`
- 用户子代理:`C:\Users\Administrator\.cursor\agents`
- 轻量 hooks`.cursor/hooks.json``.cursor/hooks/`
- 历史索引:`docs/ai-env-history/`
## 去重原则
1. 当前仓库 `AGENTS.md` 是项目权威规则。
2. `CLAUDE.md` 保留为历史兼容来源。
3. `.cursor/rules` 只放短规则和入口,不复制长文。
4. skills 使用“短入口 + reference”避免每次会话加载过多旧内容。
5. 原始对话逐一分析为摘要和索引,不把原始 JSONL 全文写入仓库。
## 模型偏好
用户主要使用 GPT 5.5 与 Claude 4.7。Cursor 的实际模型选择由当前会话或 UI 控制;本迁移只把偏好写入规则和说明,不强写不可控的内部模型字段。
## 内置流程与规则边界
Cursor、GPT 5.5、Claude 4.7 都具备调研、审计、验证、子代理和工具调用能力,但它们不知道 NeoZQYY 的项目事实、历史踩坑、数据库口径、RLS 双 schema、demo 标杆保护和审计路径。因此仍需要项目规则和技能作为触发器与约束。
规则不重复实现模型能力,只固化三类内容:
- 项目事实目录职责、数据库、ETL、后端、前端、小程序约定。
- 用户习惯:中文、先调研、再改动、再验证、再审计。
- 风险边界:生产库禁用、敏感信息不扩散、危险 git/SQL/归档目录保护。
## 历史入口
- 新索引:`docs/ai-env-history/README.md`
- Claude 摘要:`docs/claude-history/README.md`
- 审计一览:`docs/audit/audit_dashboard.md`
- Codex 迁移说明:`docs/codex_migration.md`
## 用户级资产触发策略
### 保留自然触发
- `neozqyy-cursor-migration`NeoZQYY 迁移和历史追溯入口。
- `strategic-compact`:长会话上下文压缩建议。
- `claude-api`:仅在 Anthropic / Claude API 场景触发。
### 改为显式触发
- `claude-agent-roles`:旧 Claude agents 参考;日常使用 Cursor subagents。
- `claude-rules-reference`:旧 Claude rules 档案;当前权威规则是 `AGENTS.md``.cursor/rules/`
- `tdd-workflow`:详细 TDD 参考;日常入口是 `tdd-guide` subagent。
- `security-review`:详细安全 checklist日常入口是 `security-reviewer` subagent。
- `search-first`通用搜索优先流程NeoZQYY 内优先使用 `.cursor/skills/pre-change`
- `rules-distill``repo-scan``codebase-onboarding``code-tour``agent-introspection-debugging`:低频或重型工具,按需显式使用。
保留全部 8 个用户级 subagents`planner``architect``code-reviewer``security-reviewer``database-reviewer``python-reviewer``tdd-guide``refactor-cleaner`
## Hooks 强度判断
当前 hooks 保持轻量提醒和 ask不默认强阻断。建议
- `_archived/` 读取/搜索:已升级为 `failClosed` 强阻断。依据:项目规则明确禁止参考归档目录,误用概率高。
- 危险 git 命令:保持 ask不建议 `failClosed`。依据:极少数维护场景可能需要,必须由用户明确确认。
- 生产库 MCP / 生产 DSN只读调用走 ask写入/DDL 类调用 deny。依据测试库足够覆盖日常验证生产库写操作风险高。
- demo-miniprogram保持提醒不建议强阻断。依据它是标杆目录偶尔仍可能需要按用户明确要求修改。
- DB schema 改动提醒:保持提醒。依据:是否需要文档同步要结合实际变更判断。
## MCP 策略
延续既有用途:
- `pg-etl-test``pg-app-test`:测试库,默认可用。
- `pg-etl``pg-app`:生产库,默认禁用或 ask不自动执行。
- `weixin-devtools-mcp`:小程序调试和截图验证。
- `playwright` / `cursor-ide-browser`Web 前端验证、截图、交互检查。
- `openapi`:后端 API 合同查询。
## VSCode Insiders 迁移
- 用户数据目录:`C:\Users\Administrator\AppData\Roaming\Code - Insiders\User`
- 扩展清单:`C:\Users\Administrator\.vscode-insiders\extensions\extensions.json`
- Cursor 扩展对照表:`docs/ai-env-history/vscode_insiders_extensions.csv`
- 补装脚本:`tools/cursor/install_vscode_insiders_extensions.ps1`
- 旧环境 disabled 状态已记录Cursor 没有稳定 CLI 持久禁用接口,补装后需要在 Cursor UI 中按清单确认禁用状态。
## 主题时间线
- 主题时间线:`docs/ai-env-history/topic_timeline.md`
- 主题 CSV`docs/ai-env-history/topic_timeline.csv`
## 验证
```powershell
.venv\Scripts\python.exe tools\cursor\migrate_ai_environment.py --check
.venv\Scripts\python.exe scripts\audit\prescan.py --files "tools/cursor/migrate_ai_environment.py,docs/cursor_migration.md"
```
## 回滚
迁移脚本会把覆盖前的 Cursor 用户资产和项目 `.cursor` 资产备份到:
`C:\Users\Administrator\.cursor\backups\neozqyy-cursor-migration\<timestamp>`
如需回滚,按 manifest 中的源/目标路径恢复即可。

View File

@@ -0,0 +1,68 @@
param(
[Parameter(Mandatory = $true)]
[string]$DsnVariable,
[switch]$ValidateOnly
)
$ErrorActionPreference = "Stop"
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$repoRoot = Resolve-Path (Join-Path $scriptDir "..\..")
$fileVars = @{}
foreach ($envFile in @(".env", ".env.local")) {
$envPath = Join-Path $repoRoot $envFile
if (-not (Test-Path $envPath)) {
continue
}
foreach ($line in Get-Content $envPath) {
$trimmed = $line.Trim()
if ($trimmed.Length -eq 0 -or $trimmed.StartsWith("#")) {
continue
}
$index = $trimmed.IndexOf("=")
if ($index -lt 1) {
continue
}
$key = $trimmed.Substring(0, $index).Trim()
$rawValue = $trimmed.Substring($index + 1).Trim()
if (($rawValue.StartsWith('"') -and $rawValue.EndsWith('"')) -or ($rawValue.StartsWith("'") -and $rawValue.EndsWith("'"))) {
$rawValue = $rawValue.Substring(1, $rawValue.Length - 2)
}
$fileVars[$key] = $rawValue
}
}
$dsn = [Environment]::GetEnvironmentVariable($DsnVariable, "Process")
if ([string]::IsNullOrWhiteSpace($dsn) -and $fileVars.ContainsKey($DsnVariable)) {
$dsn = $fileVars[$DsnVariable]
}
if ([string]::IsNullOrWhiteSpace($dsn)) {
Write-Error "缺少环境变量:$DsnVariable"
exit 2
}
$uvx = [Environment]::GetEnvironmentVariable("UVX_EXE", "Process")
if ([string]::IsNullOrWhiteSpace($uvx)) {
$uvx = "C:\Dev\miniconda3\Scripts\uvx.exe"
}
if (-not (Test-Path $uvx)) {
Write-Error "未找到 uvx$uvx"
exit 2
}
if ($ValidateOnly) {
Write-Output "MCP PostgreSQL 启动配置检查通过:$DsnVariable"
exit 0
}
$env:DATABASE_URI = $dsn
& $uvx --python "3.12" "postgres-mcp" "--access-mode=unrestricted"
exit $LASTEXITCODE

View File

@@ -0,0 +1,647 @@
from __future__ import annotations
import csv
import datetime as dt
import json
import os
import re
import shutil
from collections import Counter, defaultdict
from pathlib import Path
from typing import Any
REPO_ROOT = Path(__file__).resolve().parents[2]
HOME = Path.home()
CLAUDE_HOME = HOME / ".claude"
CODEX_HOME = HOME / ".codex"
CODEX_SKILLS = CODEX_HOME / "skills"
HISTORY_ROOT = REPO_ROOT / "docs" / "claude-history"
SECRET_PATTERNS = [
re.compile(r"sk-[A-Za-z0-9_-]{12,}"),
re.compile(r"sk-proj-[A-Za-z0-9_-]{12,}"),
re.compile(r"(?i)(password|passwd|pwd|token|secret|api[_-]?key)\s*[:=]\s*['\"]?[^'\"\s,;]+"),
re.compile(r"postgresql://[^\s'\"`]+"),
re.compile(r"mysql://[^\s'\"`]+"),
re.compile(r"mongodb(?:\+srv)?://[^\s'\"`]+"),
]
def redact_preserve(text: str) -> str:
value = text.replace("sk-proj-xxxxx", "[示例密钥已脱敏]")
for pattern in SECRET_PATTERNS:
value = pattern.sub("[已脱敏]", value)
return value
def redact(text: str, limit: int | None = None) -> str:
value = redact_preserve(text)
value = re.sub(r"\s+", " ", value).strip()
if limit is not None and len(value) > limit:
return value[: limit - 1].rstrip() + ""
return value
def backup(path: Path) -> None:
if not path.exists():
return
stamp = dt.datetime.now().strftime("%Y%m%d-%H%M%S")
backup_root = CODEX_HOME / "backups" / "claude-migration"
backup_root.mkdir(parents=True, exist_ok=True)
safe_name = re.sub(r"[^A-Za-z0-9_.-]+", "_", str(path).replace(":", ""))
target = backup_root / f"{safe_name}.backup-{stamp}"
if path.is_dir():
shutil.copytree(path, target)
else:
shutil.copy2(path, target)
def parse_frontmatter(text: str) -> tuple[dict[str, str], str]:
if not text.startswith("---"):
return {}, text
end = text.find("\n---", 3)
if end == -1:
return {}, text
raw = text[3:end].strip()
body = text[end + len("\n---") :].lstrip("\n")
meta: dict[str, str] = {}
for line in raw.splitlines():
if ":" not in line:
continue
key, value = line.split(":", 1)
meta[key.strip()] = value.strip().strip('"').strip("'")
return meta, body
def title_case_slug(name: str) -> str:
return " ".join(part.capitalize() for part in name.replace("_", "-").split("-"))
def write_text(path: Path, text: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
def copytree_contents(src: Path, dst: Path) -> None:
if dst.exists():
shutil.rmtree(dst)
dst.mkdir(parents=True, exist_ok=True)
for item in src.iterdir():
target = dst / item.name
if item.is_dir():
shutil.copytree(item, target)
else:
shutil.copy2(item, target)
def migrate_skills() -> list[str]:
src_root = CLAUDE_HOME / "skills"
migrated: list[str] = []
if not src_root.exists():
return migrated
for src in sorted(p for p in src_root.iterdir() if p.is_dir()):
skill_md = src / "SKILL.md"
if not skill_md.exists():
continue
dst = CODEX_SKILLS / src.name
if dst.exists():
backup(dst)
copytree_contents(src, dst)
original = skill_md.read_text(encoding="utf-8", errors="replace")
meta, body = parse_frontmatter(original)
name = re.sub(r"[^a-z0-9-]", "-", meta.get("name", src.name).lower()).strip("-") or src.name
description = meta.get("description") or f"从 Claude Code 迁移的 {src.name} 工作流。"
body = redact_preserve(body)
new_text = (
"---\n"
f"name: {name}\n"
f"description: {description} 从 Claude Code 迁移;当用户提到 ${name}{src.name}、原 Claude skill或需要该工作流时使用。\n"
"---\n\n"
f"> 迁移说明:本 skill 从 `C:\\Users\\Administrator\\.claude\\skills\\{src.name}` 转换而来。"
"如内容包含 Claude Code 专属命令,请按 Codex 当前工具等价替换。\n\n"
+ body
)
write_text(dst / "SKILL.md", new_text)
agents_dir = dst / "agents"
agents_dir.mkdir(exist_ok=True)
short = redact(description, 120).replace('"', "'")
openai_yaml = (
f'display_name: "{title_case_slug(name)}"\n'
f'short_description: "{short}"\n'
f'default_prompt: "使用 {name} 处理当前任务,遵循从 Claude Code 迁移来的工作流。"\n'
)
write_text(agents_dir / "openai.yaml", openai_yaml)
migrated.append(name)
return migrated
def migrate_agents() -> list[str]:
src_root = CLAUDE_HOME / "agents"
if not src_root.exists():
return []
skill_dir = CODEX_SKILLS / "claude-agent-roles"
if skill_dir.exists():
backup(skill_dir)
shutil.rmtree(skill_dir)
refs = skill_dir / "references"
refs.mkdir(parents=True, exist_ok=True)
rows: list[tuple[str, str]] = []
for src in sorted(src_root.glob("*.md")):
text = src.read_text(encoding="utf-8", errors="replace")
meta, body = parse_frontmatter(text)
name = meta.get("name", src.stem)
description = meta.get("description", "")
rows.append((name, description))
write_text(refs / f"{src.stem}.md", redact_preserve(body))
table = "\n".join(
f"| `{name}` | {redact(description, 160)} | `references/{name}.md` |" for name, description in rows
)
skill_md = f"""---
name: claude-agent-roles
description: 从 Claude Code 迁移的自定义 agent 角色参考。Use when 用户提到 planner、architect、code-reviewer、security-reviewer、database-reviewer、python-reviewer、tdd-guide、refactor-cleaner或要求沿用 Claude Code agent/角色/多视角审查习惯时使用。
---
# Claude Agent Roles
本 skill 保存原 Claude Code 自定义 agent 的角色提示词。Codex 当前不能一比一注册这些 Claude agent使用时读取对应 reference把它当作角色视角、检查清单或审查框架。
## 角色映射
| 角色 | 用途 | 参考文件 |
|------|------|----------|
{table}
## 使用规则
1. 用户明确点名某个角色时,读取对应 `references/*.md`。
2. 复杂功能、架构调整、重大重构时优先参考 `planner` 与 `architect`。
3. 代码修改后优先参考 `code-reviewer`;涉及认证、权限、数据库、密钥、用户输入时叠加 `security-reviewer` 或 `database-reviewer`。
4. Bug 修复和新功能需要测试设计时参考 `tdd-guide`。
5. 不要声称已经启动 Claude agent用“按迁移角色检查/规划”描述即可。
"""
write_text(skill_dir / "SKILL.md", skill_md)
write_text(
skill_dir / "agents" / "openai.yaml",
'display_name: "Claude Agent Roles"\n'
'short_description: "迁移自 Claude Code 的 planner、architect、reviewer 等角色参考。"\n'
'default_prompt: "按迁移自 Claude Code 的角色习惯,对当前任务进行规划、审查或安全检查。"\n',
)
return [name for name, _ in rows]
def migrate_rules() -> list[str]:
src_root = CLAUDE_HOME / "rules"
if not src_root.exists():
return []
skill_dir = CODEX_SKILLS / "claude-rules-reference"
if skill_dir.exists():
backup(skill_dir)
shutil.rmtree(skill_dir)
refs = skill_dir / "references"
refs.mkdir(parents=True, exist_ok=True)
copied: list[str] = []
for src in sorted(src_root.rglob("*.md")):
rel = src.relative_to(src_root)
target = refs / rel
write_text(target, redact_preserve(src.read_text(encoding="utf-8", errors="replace")))
copied.append(rel.as_posix())
list_text = "\n".join(f"- `references/{path}`" for path in copied)
skill_md = f"""---
name: claude-rules-reference
description: 从 Claude Code 迁移的个人工程规则、中文工作流、Python/TypeScript/Web 编码规范、安全、测试、审查和性能偏好。Use when 需要沿用用户之前的 Claude Code 使用习惯、steering/rules/pre-prompt或处理代码风格、测试、安全、评审、Web 设计质量要求时使用。
---
# Claude Rules Reference
本 skill 保存原 `C:\\Users\\Administrator\\.claude\\rules`。优先使用当前仓库 `AGENTS.md`,当用户要求沿用旧习惯、或任务涉及代码风格/测试/安全/审查/Web 体验时,再读取相关 reference。
## 可用参考
{list_text}
## 读取建议
- 中文通用习惯:读取 `references/zh/README.md` 及同目录相关主题。
- Python读取 `references/python/*.md`。
- TypeScript/前端:读取 `references/typescript/*.md` 与 `references/web/*.md`。
- 安全、测试、代码审查:按主题读取对应文件,不要一次性加载全部。
"""
write_text(skill_dir / "SKILL.md", skill_md)
write_text(
skill_dir / "agents" / "openai.yaml",
'display_name: "Claude Rules Reference"\n'
'short_description: "迁移自 Claude Code 的个人规则、steering 和工程偏好。"\n'
'default_prompt: "沿用用户从 Claude Code 迁移来的工程规则和审查习惯处理当前任务。"\n',
)
return copied
def migrate_global_agents() -> None:
target = CODEX_HOME / "AGENTS.md"
backup(target)
text = """# 用户全局习惯(由 Claude Code 迁移)
## 语言与沟通
- 默认使用简体中文回复、解释、状态更新和审计记录。
- 技术术语、命令、API 字段、变量名保持原文。
- 先读上下文再动手;不确定时提出关键问题,但对低风险配置/文档迁移可直接执行。
- 回复要高信号、少套话;给出实际结果、验证状态和剩余风险。
## 工作方式
- 尊重既有代码风格和项目约定,优先复用现有模式。
- 每一处改动都应能追溯到用户请求;不要顺手做无关重构。
- 小步实施,保持可验证、可回滚。
- 复杂功能、重构、多模块改动前先做规划和影响分析。
- Bug 修复和新功能优先考虑测试驱动:先确认复现或 RED再实现再验证 GREEN。
- 修改代码后要说明改了哪些文件、为什么改、怎么验证、哪些风险未覆盖。
## 审查与安全偏好
- 代码修改后进行代码审查视角检查。
- 涉及认证、授权、数据库、文件系统、用户输入、外部 API、密钥、支付/财务时,必须叠加安全审查。
- 禁止硬编码密钥、令牌、密码和生产 DSN日志和文档中避免暴露敏感信息。
- 数据库查询优先参数化Schema 变更必须同步文档和回滚/验证步骤。
## 角色与 skill 迁移
- 原 Claude Code agents 已迁移为 Codex skill`claude-agent-roles`。
- 原 Claude Code rules 已迁移为 Codex skill`claude-rules-reference`。
- 原 Claude Code skills 已迁移到 `C:\\Users\\Administrator\\.codex\\skills`。
- 当用户提到旧角色或旧 skill 时,优先读取对应 Codex skill/reference而不是重新发明流程。
## 历史追溯
- Claude Code 历史摘要归档在仓库 `docs/claude-history/`。
- 需要追踪“哪次对话改了什么、影响什么”时,先查 `session_index.csv` 和 `file_index.csv`,再读对应 `sessions/*.md`。
- 历史摘要是追溯材料不是当前事实来源真正编码前仍需读取当前文件、git diff、审计记录和测试结果。
"""
write_text(target, text)
def safe_rel(path: str) -> str:
value = path.replace("\\", "/")
normalized = value.lower()
markers = [
"c:/project/neozqyy/",
"/c/project/neozqyy/",
"c:/neozqyy/",
"/c/neozqyy/",
]
for marker in markers:
if normalized.startswith(marker):
return value[len(marker) :]
return value
def classify_area(path: str) -> str:
normalized = safe_rel(path)
if normalized.startswith("apps/backend/"):
return "后端"
if normalized.startswith("apps/etl/"):
return "ETL"
if normalized.startswith("apps/miniprogram/"):
return "小程序"
if normalized.startswith("apps/admin-web/"):
return "admin-web"
if normalized.startswith("apps/tenant-admin/"):
return "tenant-admin"
if normalized.startswith("db/") or normalized.endswith(".sql"):
return "数据库"
if normalized.startswith("docs/"):
return "文档"
if normalized.startswith("scripts/") or normalized.startswith("tools/"):
return "脚本/工具"
return "其他"
def extract_text_from_content(content: Any) -> str:
if isinstance(content, str):
return content
if isinstance(content, list):
parts: list[str] = []
for item in content:
if isinstance(item, dict) and item.get("type") == "text":
parts.append(str(item.get("text", "")))
return "\n".join(parts)
return ""
def first_sql_summary(sql: str) -> str:
clean = redact(sql, 240)
command = re.match(r"\s*(select|insert|update|delete|create|alter|drop|with|explain|truncate)\b", sql, re.I)
verb = command.group(1).upper() if command else "SQL"
tables = sorted(set(re.findall(r"\b(?:from|join|into|update|table|view)\s+([a-zA-Z_][\w.]*)(?:\s|$)", sql, re.I)))
if tables:
return f"{verb}: {', '.join(tables[:8])}"
return f"{verb}: {clean}"
def summarize_session(path: Path) -> dict[str, Any]:
session_id = path.stem
timestamps: list[str] = []
branches: set[str] = set()
cwds: set[str] = set()
user_prompts: list[str] = []
assistant_notes: list[str] = []
touched: Counter[str] = Counter()
read_files: Counter[str] = Counter()
commands: Counter[str] = Counter()
sql_ops: Counter[str] = Counter()
tools: Counter[str] = Counter()
mcp_tools: Counter[str] = Counter()
agents: Counter[str] = Counter()
risk_flags: set[str] = set()
line_count = 0
with path.open("r", encoding="utf-8", errors="replace") as f:
for line in f:
line_count += 1
try:
obj = json.loads(line)
except json.JSONDecodeError:
continue
ts = obj.get("timestamp")
if isinstance(ts, str):
timestamps.append(ts)
branch = obj.get("gitBranch")
if isinstance(branch, str) and branch:
branches.add(branch)
cwd = obj.get("cwd")
if isinstance(cwd, str) and cwd:
cwds.add(cwd)
msg = obj.get("message")
if not isinstance(msg, dict):
continue
role = msg.get("role")
content = msg.get("content")
if role == "user":
text = extract_text_from_content(content)
if text and not obj.get("isMeta") and not obj.get("isCompactSummary"):
user_prompts.append(redact(text, 260))
elif role == "assistant":
text = extract_text_from_content(content)
if text:
assistant_notes.append(redact(text, 220))
if isinstance(content, list):
for item in content:
if not isinstance(item, dict) or item.get("type") != "tool_use":
continue
name = str(item.get("name", "unknown"))
tools[name] += 1
if name.startswith("mcp__"):
mcp_tools[name] += 1
inp = item.get("input") if isinstance(item.get("input"), dict) else {}
file_path = inp.get("file_path")
if isinstance(file_path, str):
rel = safe_rel(file_path)
if name in {"Edit", "Write", "MultiEdit"}:
touched[rel] += 1
elif name == "Read":
read_files[rel] += 1
path_value = inp.get("path")
if isinstance(path_value, str) and name in {"Write", "Edit", "mcp__weixin-devtools-mcp__screenshot"}:
touched[safe_rel(path_value)] += 1
command = inp.get("command")
if isinstance(command, str):
cmd = redact(command, 180)
commands[cmd] += 1
lowered = command.lower()
if "git reset --hard" in lowered or "git clean" in lowered:
risk_flags.add("包含高风险 git 清理命令")
if "drop table" in lowered or "truncate" in lowered:
risk_flags.add("包含高风险数据库命令")
if ".env" in lowered:
risk_flags.add("命令涉及环境文件")
sql = inp.get("sql")
if isinstance(sql, str):
summary = first_sql_summary(sql)
sql_ops[summary] += 1
lowered_sql = sql.lower()
if re.search(r"\b(drop|truncate|delete)\b", lowered_sql):
risk_flags.add("包含删除/回滚类 SQL")
subagent = inp.get("subagent_type") or inp.get("description")
if isinstance(subagent, str) and name == "Agent":
agents[redact(subagent, 120)] += 1
areas = Counter(classify_area(p) for p in touched)
first_ts = min(timestamps) if timestamps else ""
last_ts = max(timestamps) if timestamps else ""
return {
"session_id": session_id,
"source_path": str(path),
"bytes": path.stat().st_size,
"lines": line_count,
"first_ts": first_ts,
"last_ts": last_ts,
"branches": sorted(branches),
"cwds": sorted(cwds),
"user_prompts": user_prompts[:12],
"assistant_notes": assistant_notes[:8],
"touched_files": touched.most_common(),
"read_files": read_files.most_common(30),
"commands": commands.most_common(40),
"sql_ops": sql_ops.most_common(30),
"tools": tools.most_common(30),
"mcp_tools": mcp_tools.most_common(20),
"agents": agents.most_common(20),
"areas": areas.most_common(),
"risk_flags": sorted(risk_flags),
}
def session_markdown(summary: dict[str, Any]) -> str:
touched = summary["touched_files"]
areas = ", ".join(f"{area}({count})" for area, count in summary["areas"]) or "未识别"
goals = "\n".join(f"- {p}" for p in summary["user_prompts"]) or "- 未提取到用户目标"
files = "\n".join(f"- `{path}`{count} 次写入/编辑" for path, count in touched[:80]) or "- 未检测到写入/编辑工具"
commands = "\n".join(f"- `{cmd}`{count}" for cmd, count in summary["commands"][:30]) or "- 未检测到 Bash 命令"
sql_ops = "\n".join(f"- {op}{count}" for op, count in summary["sql_ops"][:30]) or "- 未检测到 SQL 工具调用"
tools = "\n".join(f"- `{tool}`{count}" for tool, count in summary["tools"][:20]) or "- 无"
agents = "\n".join(f"- {agent}{count}" for agent, count in summary["agents"]) or "- 未检测到 Claude Agent 调用"
risks = "\n".join(f"- {flag}" for flag in summary["risk_flags"]) or "- 未从工具调用中检测到显式高风险信号"
notes = "\n".join(f"- {note}" for note in summary["assistant_notes"][:8]) or "- 未提取"
return f"""# Claude 会话摘要:{summary['session_id']}
| 字段 | 值 |
|------|----|
| 时间范围 | {summary['first_ts']} -> {summary['last_ts']} |
| 原始记录 | `{summary['source_path']}` |
| 大小 | {summary['bytes']} bytes / {summary['lines']} lines |
| 分支 | {', '.join(summary['branches']) or '未记录'} |
| 目录 | {', '.join(summary['cwds']) or '未记录'} |
| 影响范围 | {areas} |
## 用户目标摘录(已脱敏)
{goals}
## 可能修改的文件
{files}
## 运行过的命令(已脱敏)
{commands}
## 数据库/SQL 操作摘要
{sql_ops}
## 工具调用概览
{tools}
## Agent/子任务线索
{agents}
## 助手过程摘要摘录(已脱敏)
{notes}
## 风险与追溯提示
{risks}
> 本摘要由脚本从 Claude JSONL 工具调用和消息元数据中生成,不替代 `git diff`、审计记录、测试结果和当前代码事实。需要深挖时再读取原始 JSONL并继续做脱敏处理。
"""
def migrate_history() -> dict[str, int]:
source = CLAUDE_HOME / "projects" / "C--Project-NeoZQYY"
if not source.exists():
return {"sessions": 0, "files": 0}
if HISTORY_ROOT.exists():
backup(HISTORY_ROOT)
shutil.rmtree(HISTORY_ROOT)
sessions_dir = HISTORY_ROOT / "sessions"
sessions_dir.mkdir(parents=True, exist_ok=True)
summaries = [summarize_session(path) for path in sorted(source.glob("*.jsonl"), key=lambda p: p.stat().st_mtime)]
summaries.sort(key=lambda item: item["last_ts"] or item["first_ts"])
file_index: dict[str, list[dict[str, Any]]] = defaultdict(list)
with (HISTORY_ROOT / "session_index.csv").open("w", newline="", encoding="utf-8-sig") as f:
writer = csv.writer(f)
writer.writerow(["session_id", "first_ts", "last_ts", "bytes", "lines", "branches", "areas", "touched_count", "risk_flags", "summary_file", "source_path"])
for summary in summaries:
summary_file = sessions_dir / f"{summary['session_id']}.md"
write_text(summary_file, session_markdown(summary))
areas = "; ".join(f"{area}:{count}" for area, count in summary["areas"])
writer.writerow([
summary["session_id"],
summary["first_ts"],
summary["last_ts"],
summary["bytes"],
summary["lines"],
"; ".join(summary["branches"]),
areas,
len(summary["touched_files"]),
"; ".join(summary["risk_flags"]),
str(summary_file.relative_to(REPO_ROOT)),
summary["source_path"],
])
for path, count in summary["touched_files"]:
file_index[path].append({"session_id": summary["session_id"], "count": count, "last_ts": summary["last_ts"]})
with (HISTORY_ROOT / "file_index.csv").open("w", newline="", encoding="utf-8-sig") as f:
writer = csv.writer(f)
writer.writerow(["file_path", "session_id", "last_ts", "edit_count", "session_summary"])
for file_path in sorted(file_index):
for row in sorted(file_index[file_path], key=lambda item: item["last_ts"]):
writer.writerow([
file_path,
row["session_id"],
row["last_ts"],
row["count"],
f"docs/claude-history/sessions/{row['session_id']}.md",
])
recent_lines = "\n".join(
f"- `{s['session_id']}`{s['first_ts']} -> {s['last_ts']},修改 {len(s['touched_files'])} 个文件,范围 {', '.join(a for a, _ in s['areas']) or '未识别'}"
for s in summaries[-20:]
)
readme = f"""# Claude Code 历史摘要归档
本目录由 `tools/codex/migrate_claude_assets.py` 从 `C:\\Users\\Administrator\\.claude\\projects\\C--Project-NeoZQYY` 同名项目历史生成,用于迁移到 Codex 后的追本溯源。
## 文件说明
- `session_index.csv`:会话级索引,按 session 记录时间范围、影响范围、风险标签、摘要文件。
- `file_index.csv`:文件反向索引,回答“哪个会话改过这个文件”。
- `sessions/*.md`:每个 Claude JSONL 会话的脱敏摘要。
## 最近 20 个会话
{recent_lines}
## 使用方式
1. 查某个文件历史:在 `file_index.csv` 搜索文件路径。
2. 查某次会话影响:打开对应 `sessions/<session_id>.md`。
3. 需要完整细节时,再回到原始 JSONL读取前注意脱敏。
## 注意
摘要基于工具调用和消息元数据自动生成,不能替代当前代码、审计文档和测试结果。编码前仍需读取当前文件和 `git diff`。
"""
write_text(HISTORY_ROOT / "README.md", readme)
return {"sessions": len(summaries), "files": len(file_index)}
def update_migration_doc(result: dict[str, Any]) -> None:
doc = REPO_ROOT / "docs" / "codex_migration.md"
existing = doc.read_text(encoding="utf-8", errors="replace") if doc.exists() else "# Codex 迁移配置说明\n"
marker = "## 本次深度迁移结果"
existing = existing.split(marker)[0].rstrip()
section = f"""
{marker}
- 用户全局习惯已写入 `C:\\Users\\Administrator\\.codex\\AGENTS.md`。
- Claude skills 已迁移 {len(result['skills'])} 个到 `C:\\Users\\Administrator\\.codex\\skills`{', '.join(result['skills'])}
- Claude agents 已迁移为 Codex skill `claude-agent-roles`,包含 {len(result['agents'])} 个角色参考。
- Claude rules 已迁移为 Codex skill `claude-rules-reference`,包含 {len(result['rules'])} 个规则文件。
- NeoZQYY Claude 会话历史已摘要归档到 `docs/claude-history/`{result['history']['sessions']} 个会话,{result['history']['files']} 个被编辑文件索引。
### 追溯入口
- 会话索引:`docs/claude-history/session_index.csv`
- 文件索引:`docs/claude-history/file_index.csv`
- 会话摘要:`docs/claude-history/sessions/`
"""
write_text(doc, existing + section)
def main() -> None:
CODEX_HOME.mkdir(exist_ok=True)
CODEX_SKILLS.mkdir(exist_ok=True)
result = {
"skills": migrate_skills(),
"agents": migrate_agents(),
"rules": migrate_rules(),
"history": migrate_history(),
}
migrate_global_agents()
update_migration_doc(result)
print(json.dumps(result, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,36 @@
# 从 VSCode Insiders 迁移到 Cursor 的扩展补装脚本
# 自动生成;安装旧 VSCode Insiders 中已安装但 Cursor 当前缺失的扩展。
# 旧环境 disabled 状态见 docs/ai-env-history/vscode_insiders_extensions.csvCursor 无稳定 CLI 持久禁用接口,需在 UI 中按清单确认。
$ErrorActionPreference = 'Continue'
$cursor = 'cursor'
$timeoutSec = 90
$extensions = @(
"leizongmin.node-module-intellisense"
"meezilla.json"
"ms-dotnettools.csdevkit"
"ms-dotnettools.csharp"
"ms-python.vscode-pylance"
"ms-vscode-remote.remote-ssh"
"ms-vscode-remote.remote-ssh-edit"
"ms-vscode.remote-explorer"
)
foreach ($ext in $extensions) {
Write-Host "Installing $ext ..."
$p = Start-Process -FilePath $cursor -ArgumentList @('--install-extension', $ext) -NoNewWindow -PassThru -Wait:$false
if (-not $p.WaitForExit($timeoutSec * 1000)) {
Write-Warning "Timeout installing $ext; killing process and continuing."
try { Stop-Process -Id $p.Id -Force } catch {}
continue
}
Write-Host "Finished $ext exit=$($p.ExitCode)"
}
# 以下扩展在 VSCode Insiders 同步状态中为 disabled安装后建议在 Cursor UI 中禁用:
# disabled-before: leizongmin.node-module-intellisense
# disabled-before: ms-dotnettools.csdevkit
# disabled-before: ms-dotnettools.csharp
# disabled-before: ms-python.vscode-pylance
# disabled-before: ms-vscode-remote.remote-ssh
# disabled-before: ms-vscode-remote.remote-ssh-edit
# disabled-before: ms-vscode.remote-explorer

File diff suppressed because it is too large Load Diff