diff --git a/docs/superpowers/plans/2026-07-07-enterprise-architecture-normalization.md b/docs/superpowers/plans/2026-07-07-enterprise-architecture-normalization.md new file mode 100644 index 0000000..dbe714d --- /dev/null +++ b/docs/superpowers/plans/2026-07-07-enterprise-architecture-normalization.md @@ -0,0 +1,1003 @@ +# 企业级架构规范化实施计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 消除所有架构违规 + 补齐大仓工程基建 + 升级 TypeScript/CI 配置,使项目达到企业级规范标准 + +**Architecture:** 严格分阶段(P0→P1→P2),每阶段独立 git 提交,失败可回滚。P0 修复 32 个无权限 Action + 5 个超长文件;P1 补齐文档 + 提交钩子 + tech-debt;P2 开启 noUncheckedIndexedAccess + CI 加 unit test + 可观测性补充 + +**Tech Stack:** TypeScript 5 / Next.js 16 / Drizzle / ESLint 9 / husky / commitlint / vitest / @next/bundle-analyzer + +**Spec:** [docs/superpowers/specs/2026-07-07-enterprise-architecture-normalization-design.md](../specs/2026-07-07-enterprise-architecture-normalization-design.md) + +--- + +## 阶段 1: P0 安全合规 + +### Task 1: arch:scan 添加 @public 豁免标记机制 + +**Files:** +- Modify: `scripts/arch-scan/schema.ts` (symbols 表加 is_public 列) +- Modify: `scripts/arch-scan/scanner.ts` (识别 JSDoc @public) +- Modify: `scripts/arch-scan/query.ts` (violations 过滤 is_public) +- Test: `scripts/arch-scan/scanner.test.ts` + +- [ ] **Step 1: 给 symbols 表加 is_public 列** + +修改 `scripts/arch-scan/schema.ts`,在 symbols 表定义中加: +```ts +is_public: integer("is_public", { mode: "boolean" }).notNull().default(false), +``` +删除 `scripts/arch-scan/arch.db` 让 schema 重建。 + +- [ ] **Step 2: scanner.ts 识别 JSDoc @public 标记** + +修改 `scripts/arch-scan/scanner.ts` 的符号扫描逻辑。对每个 async function 导出,检查其上方的 JSDoc 注释是否包含 `@public` 标记。若是,设 `is_public: true`。 + +参考实现(ts-morph API): +```ts +const jsDocs = symbolNode.getJsDocs(); +const isPublic = jsDocs.some(doc => doc.getTags().some(tag => tag.getTagName() === "public")); +``` + +- [ ] **Step 3: query.ts violations 过滤 is_public** + +修改 `scripts/arch-scan/query.ts` 的 `printViolations` 函数,Server Actions without requirePermission 查询加 `AND is_public = 0`。 + +- [ ] **Step 4: 运行测试验证** + +```bash +npm run test:arch-scan +``` +Expected: 所有测试 PASS(25/25)。若 schema 变更导致测试失败,更新测试 fixture。 + +- [ ] **Step 5: 重新扫描验证** + +```bash +npm run arch:scan +npm run arch:query -- violations +``` +Expected: violations 输出仍是 32 个(因为还没给 Action 加 @public)。 + +- [ ] **Step 6: Commit** + +```bash +git add scripts/arch-scan/ +git commit -m "feat(arch-scan): add @public tag exemption mechanism for server actions" +``` + +--- + +### Task 2: 给 12 个豁免 Action 加 @public 标记 + +**Files:** +- Modify: `src/modules/auth/actions.ts` +- Modify: `src/modules/invitation-codes/actions.ts` +- Modify: `src/modules/onboarding/actions.ts` +- Modify: `src/modules/settings/actions-security.ts` +- Modify: `src/modules/rbac/actions.ts` +- Modify: `src/shared/lib/audit-logger.ts` +- Modify: `src/shared/lib/change-logger.ts` +- Modify: `src/shared/lib/login-logger.ts` + +- [ ] **Step 1: auth/actions.ts 加 @public** + +对 `registerAction`、`checkEmailAvailabilityAction`、`preflightTwoFactorAction` 三个函数,在 JSDoc 中加 `@public` 标记。示例: +```ts +/** + * 注册新用户(登录前,无需权限校验) + * @public + */ +export async function registerAction(...): Promise> { ... } +``` + +- [ ] **Step 2: invitation-codes/actions.ts 加 @public** + +对 `validateInvitationCodeAction` 加 `@public`(注册时校验邀请码)。 + +- [ ] **Step 3: onboarding/actions.ts 加 @public** + +对 `getOnboardingStatusAction`、`completeOnboardingAction` 加 `@public`(引导阶段,已登录但未完成引导)。 + +- [ ] **Step 4: settings/actions-security.ts 加 @public** + +对 `preflightTwoFactorAction`、`verifyTwoFactorForLogin` 加 `@public`(登录中 2FA 流程)。 + +- [ ] **Step 5: rbac/actions.ts 加 @public** + +对 `isAdminRole` 加 `@public`(内部辅助函数,非对外 Action)。 + +- [ ] **Step 6: shared/lib 三个 logger 加 @public** + +对 `audit-logger.ts` 的 `logAudit`、`change-logger.ts` 的 `logDataChange`、`login-logger.ts` 的 `logLoginEvent` 加 `@public`(基础设施,被 Action 内部调用,非对外 Action)。 + +- [ ] **Step 7: 重新扫描验证豁免生效** + +```bash +npm run arch:scan +npm run arch:query -- violations +``` +Expected: Server Actions without requirePermission 从 32 降至 20(豁免 12 个)。 + +- [ ] **Step 8: 验证 tsc + lint** + +```bash +npx tsc --noEmit +npm run lint +``` +Expected: 0 errors。 + +- [ ] **Step 9: Commit** + +```bash +git add src/modules/ src/shared/lib/ +git commit -m "fix(permissions): mark 12 public/infrastructure actions with @public tag" +``` + +--- + +### Task 3: 修复 ai 模块 6 个 Action 权限 + +**Files:** +- Modify: `src/modules/ai/actions.ts` +- Possibly modify: `src/shared/types/permissions.ts` (若需新增权限点) + +- [ ] **Step 1: 读取 ai/actions.ts 理解 6 个 Action 签名** + +```bash +# 用 Read 工具读取 src/modules/ai/actions.ts +``` +确认每个 Action 的参数和现有权限校验方式。 + +- [ ] **Step 2: 确认所需权限点存在** + +检查 `src/shared/types/permissions.ts` 是否有:QUESTION_READ、HOMEWORK_GRADE、LESSON_PLAN_CREATE、QUESTION_CREATE、DIAGNOSTIC_READ、ERROR_BOOK_READ。若缺失,新增并注册到 5 处(permissions.ts → ROLE_PERMISSIONS_SEED → permission-bitmap.ts → permission-catalog.ts → i18n/rbac.json)。 + +- [ ] **Step 3: 给 6 个 Action 加 requirePermission** + +```ts +import { requirePermission } from "@/shared/lib/auth-guard"; +import { Permissions } from "@/shared/types/permissions"; + +export async function suggestSimilarQuestionsAction(...) { + const ctx = await requirePermission(Permissions.QUESTION_READ); + // ... 原 logic 使用 ctx.userId +} +``` +6 个映射:suggestSimilarQuestionsAction→QUESTION_READ、suggestGradingAction→HOMEWORK_GRADE、generateLessonContentAction→LESSON_PLAN_CREATE、generateQuestionVariantAction→QUESTION_CREATE、analyzeWeaknessAction→DIAGNOSTIC_READ、explainErrorAction→ERROR_BOOK_READ。 + +- [ ] **Step 4: 验证 tsc + lint** + +```bash +npx tsc --noEmit +npm run lint +``` +Expected: 0 errors。 + +- [ ] **Step 5: Commit** + +```bash +git add src/modules/ai/ src/shared/types/ +git commit -m "fix(ai): add requirePermission to 6 ai server actions" +``` + +--- + +### Task 4: 修复 parent 模块 6 个 Action 权限 + +**Files:** +- Modify: `src/modules/parent/actions.ts` +- Possibly modify: `src/shared/types/permissions.ts` + +- [ ] **Step 1: 读取 parent/actions.ts** + +确认 6 个 Action 的现有实现。parent 模块的特殊性:需要同时校验 parentId + studentId 防跨家庭信息泄露。 + +- [ ] **Step 2: 确认 PARENT_DASHBOARD_VIEW 权限点存在** + +若不存在,新增 `PARENT_DASHBOARD_VIEW` 到 permissions.ts + 5 处注册。 + +- [ ] **Step 3: 给 6 个 Action 加 requirePermission + parentId/studentId 校验** + +```ts +export async function getChildrenAction() { + const ctx = await requirePermission(Permissions.PARENT_DASHBOARD_VIEW); + // ctx.userId 即 parentId + return getChildren(ctx.userId); +} + +export async function getChildDashboardDataAction(studentId: string) { + const ctx = await requirePermission(Permissions.PARENT_DASHBOARD_VIEW); + await verifyParentChildRelationAction(ctx.userId, studentId); // 防越权 + return getChildDashboardData(studentId); +} +``` +6 个:getChildrenAction、getChildBasicInfoAction、getChildDashboardDataAction、getParentDashboardDataAction、getChildNameListAction、verifyParentChildRelationAction。 + +- [ ] **Step 4: 验证 tsc + lint + arch:scan** + +```bash +npx tsc --noEmit +npm run lint +npm run arch:scan +npm run arch:query -- violations +``` +Expected: tsc/lint 0 errors;violations 中 parent 模块的 6 个消失。 + +- [ ] **Step 5: Commit** + +```bash +git add src/modules/parent/ src/shared/types/ +git commit -m "fix(parent): add requirePermission + parent-child relation check to 6 actions" +``` + +--- + +### Task 5: 修复 settings 模块 5 个 Action 权限 + +**Files:** +- Modify: `src/modules/settings/actions.ts` (4 个) +- Modify: `src/modules/settings/actions-service.ts` (1 个: updateProfileAction) + +- [ ] **Step 1: 确认权限点** + +确认 SYSTEM_SETTINGS_READ、SYSTEM_SETTINGS_MANAGE、USER_PROFILE_UPDATE 存在。USER_PROFILE_UPDATE 若不存在,用 `getAuthContext()` 替代(自身资料更新,任何登录用户均可)。 + +- [ ] **Step 2: 给 5 个 Action 加权限校验** + +actions.ts 中 4 个:getAiProviderSummaries→SYSTEM_SETTINGS_READ、upsertAiProviderAction→SYSTEM_SETTINGS_MANAGE、testAiProviderAction→SYSTEM_SETTINGS_MANAGE、deleteAiProviderAction→SYSTEM_SETTINGS_MANAGE、canConfigurePublicAiProvider→SYSTEM_SETTINGS_READ。 + +actions-service.ts 中 updateProfileAction:用 `getAuthContext()`(任何登录用户可改自己资料)。 + +- [ ] **Step 3: 验证** + +```bash +npx tsc --noEmit && npm run lint +``` + +- [ ] **Step 4: Commit** + +```bash +git add src/modules/settings/ +git commit -m "fix(settings): add requirePermission to 5 settings server actions" +``` + +--- + +### Task 6: 修复剩余 3 个 Action 权限 + +**Files:** +- Modify: `src/modules/leave-requests/actions.ts` (listMyLeaveRequests) +- Modify: `src/modules/lesson-preparation/actions.ts` (duplicateLessonPlanFormAction) + +- [ ] **Step 1: leave-requests listMyLeaveRequests** + +用 `getAuthContext()`(查自己的请假记录,任何登录用户可查自己)。 + +- [ ] **Step 2: lesson-preparation duplicateLessonPlanFormAction** + +加 `requirePermission(Permissions.LESSON_PLAN_CREATE)`。 + +- [ ] **Step 3: 验证全部 P0 权限修复完成** + +```bash +npm run arch:scan +npm run arch:query -- violations +``` +Expected: Server Actions without requirePermission = 0。 + +- [ ] **Step 4: tsc + lint** + +```bash +npx tsc --noEmit && npm run lint +``` + +- [ ] **Step 5: Commit P0 权限修复完成** + +```bash +git add src/modules/ +git commit -m "fix(permissions): complete requirePermission for remaining 3 actions" +``` + +--- + +### Task 7: 拆分 shared/db/schema.ts (2245 行 → 按域拆分) + +**Files:** +- Create: `src/shared/db/schema/users.ts` +- Create: `src/shared/db/schema/academic.ts` +- Create: `src/shared/db/schema/exams.ts` +- Create: `src/shared/db/schema/grades.ts` +- Create: `src/shared/db/schema/attendance.ts` +- Create: `src/shared/db/schema/messaging.ts` +- Create: `src/shared/db/schema/notifications.ts` +- Create: `src/shared/db/schema/audit.ts` +- Create: `src/shared/db/schema/rbac.ts` +- Create: `src/shared/db/schema/files.ts` +- Create: `src/shared/db/schema/scheduling.ts` +- Create: `src/shared/db/schema/misc.ts` +- Modify: `src/shared/db/schema.ts` (改为 barrel re-export) + +- [ ] **Step 1: 读取 schema.ts 按表分组** + +读取 `src/shared/db/schema.ts`,按域将 54 张表分组到上述 12 个文件。 + +- [ ] **Step 2: 创建 schema/ 目录,按域拆分** + +每个 `schema/.ts` 文件: +```ts +import { mysqlTable, varchar, ... } from "drizzle-orm/mysql-core"; +// 该域的表定义 +export const users = mysqlTable("users", { ... }); +``` + +- [ ] **Step 3: schema.ts 改为 barrel** + +```ts +// src/shared/db/schema.ts +export * from "./schema/users"; +export * from "./schema/academic"; +export * from "./schema/exams"; +// ... 12 个 re-export +``` + +- [ ] **Step 4: 验证所有 import 仍正常** + +```bash +npx tsc --noEmit +``` +Expected: 0 errors(因为 barrel re-export 保持 import 路径不变)。 + +- [ ] **Step 5: 验证文件行数** + +```powershell +Get-Content src/shared/db/schema.ts | Measure-Object -Line +# Expected: < 50 行(barrel) +Get-ChildItem src/shared/db/schema/*.ts | ForEach-Object { "$($_.Name): $((Get-Content $_.FullName | Measure-Object -Line).Lines)" } +# Expected: 每个文件 < 500 行 +``` + +- [ ] **Step 6: Commit** + +```bash +git add src/shared/db/ +git commit -m "refactor(db): split schema.ts (2245 lines) into 12 domain files" +``` + +--- + +### Task 8: 拆分 shared/lib/cache/invalidation-map.ts (1195 行) + +**Files:** +- Create: `src/shared/lib/cache/invalidation/.ts` (按模块拆分) +- Modify: `src/shared/lib/cache/invalidation-map.ts` (改为 barrel + registerInvalidationMaps) + +- [ ] **Step 1: 读取 invalidation-map.ts 按模块分组** + +- [ ] **Step 2: 按模块拆分到 invalidation/ 目录** + +每个模块的 invalidation map 移到独立文件,导出该模块的 invalidation 配置对象。 + +- [ ] **Step 3: invalidation-map.ts 改为 barrel + 聚合注册** + +```ts +import { examsInvalidation } from "./invalidation/exams"; +// ... +export function registerInvalidationMaps() { + register(examsInvalidation); + // ... +} +``` + +- [ ] **Step 4: 验证** + +```bash +npx tsc --noEmit && npm run lint +``` + +- [ ] **Step 5: Commit** + +```bash +git add src/shared/lib/cache/ +git commit -m "refactor(cache): split invalidation-map.ts (1195 lines) by module" +``` + +--- + +### Task 9: 拆分 messaging/actions.ts (973 行) + +**Files:** +- Create: `src/modules/messaging/actions-conversations.ts` +- Create: `src/modules/messaging/actions-messages.ts` +- Create: `src/modules/messaging/actions-starred.ts` +- Create: `src/modules/messaging/actions-read.ts` +- Modify: `src/modules/messaging/actions.ts` (改为 barrel) + +- [ ] **Step 1: 读取 actions.ts 按职责分组** + +- [ ] **Step 2: 按职责拆分到 4 个文件** + +每个文件顶部 `"use server"`,按职责归类 Action。 + +- [ ] **Step 3: actions.ts 改为 barrel** + +```ts +export * from "./actions-conversations"; +export * from "./actions-messages"; +export * from "./actions-starred"; +export * from "./actions-read"; +``` + +- [ ] **Step 4: 验证** + +```bash +npx tsc --noEmit && npm run lint && npm run arch:scan +npm run arch:query -- violations +``` +Expected: messaging/actions.ts 不再在超长文件列表。 + +- [ ] **Step 5: Commit** + +```bash +git add src/modules/messaging/ +git commit -m "refactor(messaging): split actions.ts (973 lines) by responsibility" +``` + +--- + +### Task 10: 拆分 textbooks/data-access.ts (907 行) + +**Files:** +- Create: `src/modules/textbooks/data-access-chapters.ts` +- Create: `src/modules/textbooks/data-access-knowledge-points.ts` +- Modify: `src/modules/textbooks/data-access.ts` (改为 barrel + 核心) + +- [ ] **Step 1: 拆分章节 CRUD 到 data-access-chapters.ts** + +- [ ] **Step 2: 拆分知识点 CRUD 到 data-access-knowledge-points.ts** + +- [ ] **Step 3: data-access.ts 保留核心 + barrel re-export** + +- [ ] **Step 4: 验证** + +```bash +npx tsc --noEmit && npm run lint && npm run arch:scan +npm run arch:query -- violations +``` +Expected: textbooks/data-access.ts 不再在超长文件列表。 + +- [ ] **Step 5: Commit** + +```bash +git add src/modules/textbooks/ +git commit -m "refactor(textbooks): split data-access.ts (907 lines) by entity" +``` + +--- + +### Task 11: 拆分 questions/data-access.ts (828 行) + +**Files:** +- Create: `src/modules/questions/data-access-search.ts` +- Modify: `src/modules/questions/data-access.ts` (保留核心 CRUD + barrel) + +- [ ] **Step 1: 拆分 FULLTEXT 检索到 data-access-search.ts** + +- [ ] **Step 2: data-access.ts 保留核心 CRUD + re-export** + +- [ ] **Step 3: 验证 P0 全部完成** + +```bash +npm run arch:scan +npm run arch:query -- violations +``` +Expected: Long files = 0;Server Actions without requirePermission = 0。 + +```bash +npx tsc --noEmit && npm run lint +``` +Expected: 0 errors。 + +- [ ] **Step 4: 更新 known-issues.md 工作经验日志** + +在 `docs/troubleshooting/known-issues.md` 工作经验日志区追加 P0 完成记录。 + +- [ ] **Step 5: Commit P0 阶段完成** + +```bash +git add src/modules/questions/ docs/troubleshooting/ +git commit -m "refactor(questions): split data-access.ts and complete P0 security phase" +git push +``` + +--- + +## 阶段 2: P1 大仓工程基建 + +### Task 12: 创建大仓文档 + +**Files:** +- Create: `LICENSE` +- Create: `CHANGELOG.md` +- Create: `CONTRIBUTING.md` +- Create: `SECURITY.md` +- Create: `.env.example` + +- [ ] **Step 1: 创建 LICENSE(专有协议)** + +``` +Copyright (c) 2026 EazyGame. 保留所有权利。 + +未经版权所有者书面许可,不得以任何形式复制、修改、合并、发布、分发、再许可或销售本软件的任何部分。 +``` + +- [ ] **Step 2: 创建 CHANGELOG.md(Keep-a-changelog 格式)** + +```markdown +# 更新日志 + +本项目遵循 [Keep a Changelog](https://keepachangelog.com/) 格式,版本号遵循 [Semantic Versioning](https://semver.org/)。 + +## [1.0.0] - 2026-07-07 + +### Added +- arch.db 架构元数据库 + 扫描器(ts-morph + SQLite) +- 35 个模块 README.md(含 mermaid 架构图与流程图) +- 004 架构设计文档 V3(14 章节 + 13 mermaid 图) +- known-issues.md 索引式速查手册 + +### Changed +- 004 从"影响地图"重构为"架构设计文档"(458→912 行) +- project_rules.md 引入 arch.db + AI 工作强制流程 +- coding-standards.md 更新令牌分层 + cacheFn + 5 层状态模型 +``` + +- [ ] **Step 3: 创建 CONTRIBUTING.md** + +包含:分支策略(main 保护)、Conventional Commits 规范、提交前检查(lint+tsc+arch:scan)、模块 README 维护规则、代码审查清单。 + +- [ ] **Step 4: 创建 SECURITY.md** + +包含:漏洞报告流程、安全联系人、响应 SLA、不公开披露策略。 + +- [ ] **Step 5: 创建 .env.example** + +读取 `src/env.mjs` 提取所有环境变量键,加注释: +``` +# 数据库 +DATABASE_URL="mysql://user:pass@localhost:3306/next_edu" + +# NextAuth +NEXTAUTH_SECRET="your-secret-here" +NEXTAUTH_URL="http://localhost:3000" + +# AI Provider(可选) +OPENAI_API_KEY="" +ANTHROPIC_API_KEY="" + +# Redis(可选) +REDIS_URL="" + +# 文件存储 +UPLOAD_DIR="./uploads" + +# ... 其余从 env.mjs 提取 +``` + +- [ ] **Step 6: Commit** + +```bash +git add LICENSE CHANGELOG.md CONTRIBUTING.md SECURITY.md .env.example +git commit -m "docs: add enterprise baseline docs (LICENSE/CHANGELOG/CONTRIBUTING/SECURITY/env.example)" +``` + +--- + +### Task 13: 安装 husky + lint-staged + commitlint + +**Files:** +- Modify: `package.json` (dependencies + scripts) +- Create: `.husky/pre-commit` +- Create: `.husky/commit-msg` +- Create: `commitlint.config.js` +- Create: `lint-staged.config.js` + +- [ ] **Step 1: 安装依赖** + +```bash +npm install -D husky lint-staged @commitlint/cli @commitlint/config-conventional +``` + +- [ ] **Step 2: 初始化 husky** + +```bash +npx husky init +``` +这会创建 `.husky/` 目录并生成 `.husky/pre-commit` 示例。 + +- [ ] **Step 3: 配置 pre-commit 钩子** + +`.husky/pre-commit`: +```bash +npx lint-staged +``` + +- [ ] **Step 4: 配置 commit-msg 钩子** + +`.husky/commit-msg`: +```bash +npx commitlint --edit "$1" +``` + +- [ ] **Step 5: 创建 commitlint.config.js** + +```js +module.exports = { + extends: ["@commitlint/config-conventional"], + rules: { + "scope-enum": [2, "always", [ + // 35 模块名 + "exams", "homework", "questions", "textbooks", "grades", + "lesson-preparation", "classes", "school", "scheduling", + "attendance", "course-plans", "users", "messaging", + "notifications", "parent", "audit", "rbac", "auth", + "onboarding", "ai", "dashboard", "diagnostic", + "adaptive-practice", "error-book", "elective", + "proctoring", "leave-requests", "invitation-codes", + "files", "settings", "layout", "student", "search", + "announcements", "standards", + // 架构层 + "arch-scan", "db", "cache", "ui", "deps", "config", + // 文档 + "docs", "architecture", "troubleshooting", + ]], + }, +}; +``` + +- [ ] **Step 6: 创建 lint-staged.config.js** + +```js +module.exports = { + "*.{ts,tsx}": ["eslint --fix", "prettier --write"], + "*.{js,mjs,cjs}": ["prettier --write"], + "*.md": ["prettier --write"], + "*.{json,yml,yaml}": ["prettier --write"], +}; +``` + +- [ ] **Step 7: package.json 加 prepare 脚本** + +```json +"scripts": { + "prepare": "husky" +} +``` + +- [ ] **Step 8: 验证钩子工作** + +```bash +# 测试 commitlint 拒绝非规范提交 +git commit -m "bad commit message" --allow-empty +# Expected: FAIL (commitlint 拒绝) + +# 测试规范提交 +git commit -m "test(husky): verify commitlint works" --allow-empty +# Expected: PASS +``` + +- [ ] **Step 9: Commit** + +```bash +git add .husky/ commitlint.config.js lint-staged.config.js package.json package-lock.json +git commit -m "chore(deps): add husky + lint-staged + commitlint for commit convention enforcement" +``` + +--- + +### Task 14: 填充 tech-debt.md + +**Files:** +- Modify: `docs/architecture/roadmap/tech-debt.md` + +- [ ] **Step 1: 整理待解决技术债** + +从 known-issues 工作经验日志 + 本次诊断补充,整理至少 6 条待解决技术债: +1. scripts 目录混放治理(.sh/.ps1/.js/.mjs/.ts 统一) +2. API 文档自动化(OpenAPI 生成) +3. proctoring 模块的 exams 依赖未被 arch.db 捕获(scanner 改进) +4. FULLTEXT 索引迁移工具化 +5. client-error 上报机制完善(Task 12-14) +6. React Flow 移除后的历史包袱清理 +7. noUncheckedIndexedAccess 历史债务(本次 P2 解决) +8. coverage 阈值渐进提升(本次设 60% 起步) + +- [ ] **Step 2: 写入 tech-debt.md** + +每条含:描述、影响、建议方案、优先级(P0/P1/P2)。 + +- [ ] **Step 3: Commit P1 阶段完成** + +```bash +git add docs/architecture/roadmap/tech-debt.md +git commit -m "docs(roadmap): populate tech-debt.md with 8 pending items" +git push +``` + +--- + +## 阶段 3: P2 配置升级 + +### Task 15: 开启 noUncheckedIndexedAccess + 全量修复 + +**Files:** +- Modify: `tsconfig.json` +- Modify: 多个源文件(修复类型错误) + +- [ ] **Step 1: tsconfig.json 开启 noUncheckedIndexedAccess** + +```json +"noUncheckedIndexedAccess": true, +``` + +- [ ] **Step 2: 运行 tsc 收集所有错误** + +```bash +npx tsc --noEmit 2>&1 | Select-String "error TS" +``` +记录所有错误位置。 + +- [ ] **Step 3: 批量修复类型错误** + +按模块批量修复: +- 数组索引 `arr[0]` → `arr[0]!`(确认存在)或 `arr.at(0) ?? defaultValue`(可能不存在) +- 对象索引 `obj[key]` → 显式判空 `if (key in obj) { ... }` +- `split`/`match` 结果 → `?? []` 兜底 +- `Record` 访问 → `T | undefined` 处理 + +- [ ] **Step 4: 验证 tsc 0 错误** + +```bash +npx tsc --noEmit +``` +Expected: 0 errors。 + +- [ ] **Step 5: 验证 lint + arch:scan** + +```bash +npm run lint && npm run arch:scan +``` + +- [ ] **Step 6: Commit** + +```bash +git add tsconfig.json src/ +git commit -m "refactor(ts): enable noUncheckedIndexedAccess and fix all type errors" +``` + +--- + +### Task 16: CI 加 unit test + coverage + +**Files:** +- Modify: `.gitea/workflows/ci.yml` +- Modify: `vitest.unit.config.ts` +- Modify: `package.json` (加 engines + packageManager) + +- [ ] **Step 1: ci.yml 在 Lint 后、Typecheck 前加 Unit test** + +```yaml + - name: Lint + run: npm run lint + + - name: Unit tests + run: npm run test:unit + + - name: Typecheck + run: npm run typecheck +``` + +- [ ] **Step 2: vitest.unit.config.ts 加 coverage 配置** + +```ts +test: { + name: "unit", + environment: "jsdom", + include: ["src/**/*.test.{ts,tsx}"], + clearMocks: true, + restoreMocks: true, + mockReset: true, + coverage: { + provider: "v8", + reporter: ["text", "json-summary"], + lines: 60, + functions: 60, + branches: 60, + statements: 60, + }, +}, +``` + +- [ ] **Step 3: package.json 加 engines + packageManager** + +```json +"engines": { + "node": ">=22" +}, +"packageManager": "npm@10.9.0", +``` + +- [ ] **Step 4: 验证 unit test 通过** + +```bash +npm run test:unit +``` +Expected: 全部 PASS。 + +- [ ] **Step 5: Commit** + +```bash +git add .gitea/workflows/ci.yml vitest.unit.config.ts package.json +git commit -m "ci: add unit test step and coverage threshold to CI" +``` + +--- + +### Task 17: /api/health 端点 + Dockerfile HEALTHCHECK + +**Files:** +- Create: `src/app/api/health/route.ts` +- Modify: Dockerfile(在 .next/standalone/Dockerfile) + +- [ ] **Step 1: 创建 /api/health 路由** + +```ts +// src/app/api/health/route.ts +import { NextResponse } from "next/server"; + +export async function GET(): Promise { + return NextResponse.json({ + status: "ok", + uptime: process.uptime(), + timestamp: new Date().toISOString(), + version: process.env.npm_package_version ?? "unknown", + }); +} +``` + +- [ ] **Step 2: 读取并修改 Dockerfile** + +读取 `.next/standalone/Dockerfile`(若不存在,读取项目根 Dockerfile)。加: +```dockerfile +HEALTHCHECK --interval=30s --timeout=3s --retries=3 \ + CMD wget -qO- http://localhost:3000/api/health || exit 1 +``` + +- [ ] **Step 3: 验证** + +```bash +npx tsc --noEmit && npm run lint +``` + +- [ ] **Step 4: Commit** + +```bash +git add src/app/api/health/ Dockerfile +git commit -m "feat(observability): add /api/health endpoint and Docker HEALTHCHECK" +``` + +--- + +### Task 18: bundle-analyzer + +**Files:** +- Modify: `package.json` (devDeps + scripts) +- Modify: `next.config.ts`(若存在)或 `next.config.mjs` + +- [ ] **Step 1: 安装 @next/bundle-analyzer** + +```bash +npm install -D @next/bundle-analyzer +``` + +- [ ] **Step 2: 配置 next.config** + +读取现有 next.config 文件,用 `@next/bundle-analyzer` 包装: +```ts +import bundleAnalyzer from "@next/bundle-analyzer"; + +const withBundleAnalyzer = bundleAnalyzer({ + enabled: process.env.ANALYZE === "true", +}); + +export default withBundleAnalyzer(nextConfig); +``` + +- [ ] **Step 3: package.json 加 analyze 脚本** + +```json +"analyze": "ANALYZE=true next build" +``` + +- [ ] **Step 4: 验证** + +```bash +npm run lint +``` + +- [ ] **Step 5: Commit** + +```bash +git add next.config.* package.json package-lock.json +git commit -m "feat(perf): add @next/bundle-analyzer for bundle visualization" +``` + +--- + +### Task 19: API 文档 + +**Files:** +- Create: `docs/api/README.md` + +- [ ] **Step 1: 创建 docs/api/README.md** + +列出 17 个 API 路由,每个含:方法、路径、用途、请求 schema、响应 schema、权限要求。从 `withApiErrorHandler` 与 `ActionState` 推导统一格式。 + +- [ ] **Step 2: Commit P2 阶段完成** + +```bash +git add docs/api/ +git commit -m "docs(api): add API documentation for 17 routes" +git push +``` + +--- + +### Task 20: 最终验收 + 经验沉淀 + +- [ ] **Step 1: 全量验证** + +```bash +npm run arch:scan +npm run arch:query -- violations +npx tsc --noEmit +npm run lint +npm run test:unit +``` +Expected: +- violations: Long files = 0, Server Actions without requirePermission = 0 +- tsc: 0 errors +- lint: 0 errors +- test:unit: 全部 PASS + +- [ ] **Step 2: 更新 known-issues.md 工作经验日志** + +追加本次规范化治理的经验记录。 + +- [ ] **Step 3: 更新 004(若架构意图变化)** + +若 P0 阶段的文件拆分改变了架构描述,更新 `docs/architecture/004_architecture_impact_map.md`。 + +- [ ] **Step 4: 最终 commit + push** + +```bash +git add docs/ +git commit -m "docs: final validation and experience logging for architecture normalization" +git push +``` + +--- + +## 验收标准 + +| 阶段 | 验收项 | 验证命令 | +|------|--------|---------| +| P0 | 0 个超长文件 + 0 个无权限 Action | `npm run arch:query -- violations` | +| P0 | tsc + lint 0 错误 | `npx tsc --noEmit && npm run lint` | +| P1 | 5 个文档存在 | `ls LICENSE CHANGELOG.md CONTRIBUTING.md SECURITY.md .env.example` | +| P1 | husky 钩子工作 | `git commit -m "bad"` 被拒绝 | +| P1 | tech-debt.md 含 6+ 条 | `Get-Content docs/architecture/roadmap/tech-debt.md \| Measure-Object -Line` | +| P2 | noUncheckedIndexedAccess=true | `cat tsconfig.json \| Select-String noUncheckedIndexedAccess` | +| P2 | CI 跑 unit test | `Select-String "Unit tests" .gitea/workflows/ci.yml` | +| P2 | /api/health 可用 | `curl http://localhost:3000/api/health` | +| P2 | bundle-analyzer 可用 | `npm run analyze` |