From 67837df8d04427d44753f524a731cd64705f0db8 Mon Sep 17 00:00:00 2001 From: smart Date: Sat, 8 Aug 2026 11:00:36 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0TRA=E7=BC=96=E7=A8=8B?= =?UTF-8?q?=E5=9F=B9=E8=AE=AD=E8=AF=BE=E7=A8=8B=E6=9D=90=E6=96=99=E3=80=81?= =?UTF-8?q?=E8=AF=BE=E4=BB=B6=E4=B8=8EAI=E6=8A=80=E8=83=BD=E4=BA=A4?= =?UTF-8?q?=E4=BB=98=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增全套考试卷、评估卷、参考答案与考试培训包模板 - 修正slidev培训课件:调整模块标题、链路图缩放,替换工单示例为通用业务对象 - 修正课堂红线表述为证据红线,将小组作业调整为个人作业要求 - 新增权限、状态流转、审计日志、看板指标四类可复用业务规则块 - 整理AI工程技能交付包,迁移旧资产至客户专属交付包并统一术语 - 新增考试输出模板与TRAE使用记录文档 --- CI失败日志-真实Maven环境错误.md | 29 + TRAE 培训大纲V1.0.md | 98 ++-- .../ai-engineering-skills-delivery-utf8.zip | Bin 59302 -> 0 bytes .../ai-engineering-skills-delivery.zip | Bin 51012 -> 0 bytes .../ai-engineering-skills-delivery/INSTALL.md | 67 --- .../ai-engineering-skills-delivery/README.md | 51 -- .../JeecgBoot工单模块开发审查.skill.md | 44 -- .../design-complex-business-module/SKILL.md | 73 --- .../agents/openai.yaml | 7 - .../assets/RBAC权限矩阵.template.md | 19 - .../assets/RBAC权限矩阵生成.prompt.md | 27 - .../assets/审计日志SOP.template.md | 41 -- .../assets/审计日志检查清单.md | 12 - .../assets/异常态检查清单.md | 12 - .../assets/异常态清单.template.md | 13 - .../assets/异常态补齐.prompt.md | 31 - .../assets/权限上线前检查清单.md | 11 - .../assets/权限控制方案.template.md | 11 - .../assets/权限控制方案生成.prompt.md | 26 - .../assets/状态流转检查清单.md | 11 - .../assets/状态流转表.template.md | 21 - .../assets/状态流转设计.prompt.md | 33 -- .../assets/看板指标口径检查清单.md | 13 - .../assets/看板指标字典.template.md | 12 - .../assets/看板指标设计.prompt.md | 35 -- .../assets/看板接口验收.prompt.md | 29 - .../assets/看板接口验收清单.md | 11 - .../assets/看板接口验收记录.template.md | 10 - .../generate-project-constraints/SKILL.md | 114 ---- .../agents/openai.yaml | 7 - .../assets/项目约束提取表.template.md | 43 -- .../assets/项目约束生成.prompt.md | 61 -- .../assets/项目约束验收清单.md | 44 -- .../skills/review-business-delivery/SKILL.md | 89 --- .../agents/openai.yaml | 7 - .../assets/AIReview报告模板.md | 28 - .../assets/业务交付验收清单.md | 59 -- .../assets/审计日志检查清单.md | 12 - .../assets/异常态检查清单.md | 12 - .../assets/权限上线前检查清单.md | 11 - .../assets/状态流转检查清单.md | 11 - .../assets/看板指标口径检查清单.md | 13 - .../assets/看板接口验收清单.md | 11 - .../assets/阶段Review.prompt.md | 32 - .../assets/阶段Review报告.template.md | 27 - .../skills/track-review-fix-loop/SKILL.md | 94 --- .../track-review-fix-loop/agents/openai.yaml | 7 - .../assets/Review修复闭环检查清单.md | 12 - .../assets/修复记录.template.md | 7 - .../assets/修复记录生成.prompt.md | 24 - .../assets/审计日志缺失反例.md | 31 - .../assets/权限遗漏反例.md | 32 - .../assets/看板指标口径不清反例.md | 30 - .../assets/非法状态流转反例.md | 30 - deliverables/客户AI工程Skill交付包.zip | Bin 50745 -> 76388 bytes deliverables/客户AI工程Skill交付包/README.md | 57 +- .../公务用车取消派车案例.md | 48 ++ .../JeecgBoot工单模块开发审查.skill.md | 44 -- .../mcp-assets/MCP工具权限与验收清单.md | 33 ++ .../mcp-assets/MCP能力契约.template.md | 34 ++ .../mcp-assets/README.md | 29 + .../assets/RBAC权限矩阵.template.md | 26 +- .../assets/RBAC权限矩阵生成.prompt.md | 8 +- .../assets/审计日志SOP.template.md | 10 +- .../assets/审计日志检查清单.md | 2 +- .../assets/异常态检查清单.md | 2 +- .../assets/异常态清单.template.md | 4 +- .../assets/异常态补齐.prompt.md | 6 +- .../assets/权限上线前检查清单.md | 4 +- .../assets/权限控制方案.template.md | 16 +- .../assets/权限控制方案生成.prompt.md | 4 +- .../assets/状态流转表.template.md | 4 +- .../assets/状态流转设计.prompt.md | 4 +- .../assets/看板指标字典.template.md | 18 +- .../assets/看板指标设计.prompt.md | 10 +- .../assets/看板接口验收.prompt.md | 4 +- .../assets/看板接口验收清单.md | 2 +- .../assets/看板接口验收记录.template.md | 4 +- .../assets/项目约束生成.prompt.md | 2 +- .../skills/project-knowledge-builder/SKILL.md | 0 .../skills/project-knowledge-builder/SKILL_zh.md | 133 +++++ .../project-knowledge-builder/agents/openai.yaml | 0 .../references/compatibility.md | 0 .../references/compatibility_zh.md | 64 ++ .../references/output-contract.md | 0 .../references/output-contract_zh.md | 135 +++++ .../scripts/project_inventory.py | 0 .../assets/审计日志检查清单.md | 2 +- .../assets/异常态检查清单.md | 2 +- .../assets/权限上线前检查清单.md | 4 +- .../assets/看板接口验收清单.md | 2 +- .../assets/阶段Review.prompt.md | 4 +- .../assets/阶段Review报告.template.md | 2 +- .../review-business-delivery/assets/验收清单.md | 80 +-- .../assets/修复记录生成.prompt.md | 2 +- .../track-review-fix-loop/assets/审计日志缺失反例.md | 6 +- .../track-review-fix-loop/assets/权限遗漏反例.md | 8 +- .../assets/看板指标口径不清反例.md | 8 +- .../track-review-fix-loop/assets/非法状态流转反例.md | 6 +- .../客户AI工程Skill交付包/交付清单.md | 42 ++ .../客户AI工程Skill交付包/安装说明.md | 65 +- ...TRAE编程培训考试评估卷-实操与项目实战.docx | Bin 0 -> 36350 bytes ...培训考试评估卷-实操与项目实战(修订版).docx | Bin 0 -> 37520 bytes ...考试评估卷-实操与项目实战(阅卷参考答案).docx | Bin 0 -> 42837 bytes ...E编程培训考试题-30分钟线上版-客观题版.docx | Bin 0 -> 44396 bytes exams/exam-kit/README.md | 34 ++ .../exam-output/01-project-understanding.md | 21 + .../exam-output/02-change-and-test.md | 23 + .../exam-kit/exam-output/03-pr-and-assets.md | 31 + .../exam-kit/exam-output/trae-usage-record.md | 5 + exams/exam-kit/reference-answer.md | 168 ++++++ exams/exam-kit/review-diff.patch | 37 ++ exams/exam-kit/source-version.txt | 9 + exams/scripts/build_reference_answer_docx.py | 264 +++++++++ exams/scripts/revise_exam_docx.py | 197 +++++++ ...培训考试评估卷-实操与项目实战(修订版).docx | Bin 0 -> 162 bytes slidev/pages/day1.md | 517 ---------------- slidev/pages/day1/m1-trae-positioning.md | 2 +- slidev/pages/day1/m3-workspace-structure.md | 2 +- slidev/pages/day1/m4-call-chain.md | 4 +- slidev/pages/day1/m6-code-report.md | 2 +- slidev/pages/day2.md | 553 ------------------ slidev/pages/day2/m1-context-engineering.md | 16 +- slidev/pages/day2/m10-summary.md | 23 +- slidev/pages/day2/m2-four-references.md | 2 +- slidev/pages/day2/m3-hallucination.md | 2 +- slidev/pages/day2/m5-security-rules.md | 6 +- slidev/pages/day2/m8-asset-library.md | 2 +- slidev/pages/day2/m9-rules-check.md | 2 +- slidev/pages/day3/m1-rules-evolution.md | 2 +- slidev/pages/day3/m2-token-cue-agent.md | 2 +- slidev/pages/day3/m3-commands-skills-mcp.md | 4 +- .../day3/m4-agent-teams-product-shape.md | 4 +- slidev/pages/day3/m5-browser-docs-index.md | 2 +- slidev/pages/day3/m6-plan-review-summary.md | 18 +- slidev/pages/day4/m1-rbac-permission-model.md | 34 +- slidev/pages/day4/m2-permission-control.md | 25 + slidev/pages/day4/m3-status-transition.md | 28 + slidev/pages/day4/m4-audit-log.md | 28 + slidev/pages/day4/m5-exception-defensive.md | 30 + slidev/pages/day4/m6-dashboard-metrics.md | 28 + slidev/pages/day4/m7-review-delivery.md | 157 ++++- slidev/pages/day5/m1-project-workshop.md | 132 +++++ .../pages/day5/m10-agent-skill-composition.md | 163 ++++++ .../pages/day5/m11-asset-library-delivery.md | 285 +++++++++ slidev/pages/day5/m12-mcp-asset-governance.md | 152 +++++ .../day5/m13-historical-code-governance.md | 210 +++++++ slidev/pages/day5/m2-project-knowledge.md | 125 ++++ slidev/pages/day5/m3-task-delegation.md | 112 ++++ slidev/pages/day5/m4-refactor-safety.md | 126 ++++ slidev/pages/day5/m5-testing-safety-net.md | 137 +++++ slidev/pages/day5/m6-asset-skill-writing.md | 131 +++++ slidev/pages/day5/m7-delivery-review.md | 115 ++++ slidev/pages/day5/m8-custom-agent-asset.md | 138 +++++ slidev/pages/day5/m9-agent-boundaries.md | 121 ++++ slidev/pages/day6/m1-team-flow.md | 124 ++++ slidev/pages/day6/m10-ai-trends.md | 236 ++++++++ .../pages/day6/m11-six-day-retrospective.md | 221 +++++++ slidev/pages/day6/m2-pr-template-ai-record.md | 162 +++++ slidev/pages/day6/m3-ai-code-review.md | 111 ++++ slidev/pages/day6/m4-ci-failure-loop.md | 284 +++++++++ slidev/pages/day6/m5-asset-governance.md | 149 +++++ slidev/pages/day6/m6-onboarding-rollout.md | 207 +++++++ .../pages/day6/m7-maintainability-review.md | 179 ++++++ .../pages/day6/m8-asset-change-validation.md | 195 ++++++ slidev/pages/day6/m8-final-demo.md | 83 +++ slidev/slides.md | 112 ++++ ...工单复杂业务开发:权限、审计、异常态与看板.md | 543 +++++++++++++++++ 项目实操-影响分析提示词与TRAE返回结果.md | 133 +++++ 169 files changed, 6618 insertions(+), 2887 deletions(-) create mode 100644 CI失败日志-真实Maven环境错误.md delete mode 100644 deliverables/ai-engineering-skills-delivery-utf8.zip delete mode 100644 deliverables/ai-engineering-skills-delivery.zip delete mode 100644 deliverables/ai-engineering-skills-delivery/INSTALL.md delete mode 100644 deliverables/ai-engineering-skills-delivery/README.md delete mode 100644 deliverables/ai-engineering-skills-delivery/examples/jeecgboot-work-order/JeecgBoot工单模块开发审查.skill.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/SKILL.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/agents/openai.yaml delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/RBAC权限矩阵.template.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/RBAC权限矩阵生成.prompt.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/审计日志SOP.template.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/审计日志检查清单.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/异常态检查清单.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/异常态清单.template.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/异常态补齐.prompt.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/权限上线前检查清单.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/权限控制方案.template.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/权限控制方案生成.prompt.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/状态流转检查清单.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/状态流转表.template.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/状态流转设计.prompt.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板指标口径检查清单.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板指标字典.template.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板指标设计.prompt.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板接口验收.prompt.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板接口验收清单.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板接口验收记录.template.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/SKILL.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/agents/openai.yaml delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/assets/项目约束提取表.template.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/assets/项目约束生成.prompt.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/assets/项目约束验收清单.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/SKILL.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/agents/openai.yaml delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/AIReview报告模板.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/业务交付验收清单.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/审计日志检查清单.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/异常态检查清单.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/权限上线前检查清单.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/状态流转检查清单.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/看板指标口径检查清单.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/看板接口验收清单.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/阶段Review.prompt.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/阶段Review报告.template.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/SKILL.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/agents/openai.yaml delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/Review修复闭环检查清单.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/修复记录.template.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/修复记录生成.prompt.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/审计日志缺失反例.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/权限遗漏反例.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/看板指标口径不清反例.md delete mode 100644 deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/非法状态流转反例.md create mode 100644 deliverables/客户AI工程Skill交付包/examples/jeecgboot-official-vehicle/公务用车取消派车案例.md delete mode 100644 deliverables/客户AI工程Skill交付包/examples/jeecgboot-work-order/JeecgBoot工单模块开发审查.skill.md create mode 100644 deliverables/客户AI工程Skill交付包/mcp-assets/MCP工具权限与验收清单.md create mode 100644 deliverables/客户AI工程Skill交付包/mcp-assets/MCP能力契约.template.md create mode 100644 deliverables/客户AI工程Skill交付包/mcp-assets/README.md rename deliverables/{ai-engineering-skills-delivery => 客户AI工程Skill交付包}/skills/project-knowledge-builder/SKILL.md (100%) create mode 100644 deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/SKILL_zh.md rename deliverables/{ai-engineering-skills-delivery => 客户AI工程Skill交付包}/skills/project-knowledge-builder/agents/openai.yaml (100%) rename deliverables/{ai-engineering-skills-delivery => 客户AI工程Skill交付包}/skills/project-knowledge-builder/references/compatibility.md (100%) create mode 100644 deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/references/compatibility_zh.md rename deliverables/{ai-engineering-skills-delivery => 客户AI工程Skill交付包}/skills/project-knowledge-builder/references/output-contract.md (100%) create mode 100644 deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/references/output-contract_zh.md rename deliverables/{ai-engineering-skills-delivery => 客户AI工程Skill交付包}/skills/project-knowledge-builder/scripts/project_inventory.py (100%) create mode 100644 deliverables/客户AI工程Skill交付包/交付清单.md create mode 100644 exams/TRAE编程培训考试评估卷-实操与项目实战.docx create mode 100644 exams/TRAE编程培训考试评估卷-实操与项目实战(修订版).docx create mode 100644 exams/TRAE编程培训考试评估卷-实操与项目实战(阅卷参考答案).docx create mode 100644 exams/TRAE编程培训考试题-30分钟线上版-客观题版.docx create mode 100644 exams/exam-kit/README.md create mode 100644 exams/exam-kit/exam-output/01-project-understanding.md create mode 100644 exams/exam-kit/exam-output/02-change-and-test.md create mode 100644 exams/exam-kit/exam-output/03-pr-and-assets.md create mode 100644 exams/exam-kit/exam-output/trae-usage-record.md create mode 100644 exams/exam-kit/reference-answer.md create mode 100644 exams/exam-kit/review-diff.patch create mode 100644 exams/exam-kit/source-version.txt create mode 100644 exams/scripts/build_reference_answer_docx.py create mode 100644 exams/scripts/revise_exam_docx.py create mode 100644 exams/~$AE编程培训考试评估卷-实操与项目实战(修订版).docx delete mode 100644 slidev/pages/day1.md delete mode 100644 slidev/pages/day2.md create mode 100644 slidev/pages/day5/m1-project-workshop.md create mode 100644 slidev/pages/day5/m10-agent-skill-composition.md create mode 100644 slidev/pages/day5/m11-asset-library-delivery.md create mode 100644 slidev/pages/day5/m12-mcp-asset-governance.md create mode 100644 slidev/pages/day5/m13-historical-code-governance.md create mode 100644 slidev/pages/day5/m2-project-knowledge.md create mode 100644 slidev/pages/day5/m3-task-delegation.md create mode 100644 slidev/pages/day5/m4-refactor-safety.md create mode 100644 slidev/pages/day5/m5-testing-safety-net.md create mode 100644 slidev/pages/day5/m6-asset-skill-writing.md create mode 100644 slidev/pages/day5/m7-delivery-review.md create mode 100644 slidev/pages/day5/m8-custom-agent-asset.md create mode 100644 slidev/pages/day5/m9-agent-boundaries.md create mode 100644 slidev/pages/day6/m1-team-flow.md create mode 100644 slidev/pages/day6/m10-ai-trends.md create mode 100644 slidev/pages/day6/m11-six-day-retrospective.md create mode 100644 slidev/pages/day6/m2-pr-template-ai-record.md create mode 100644 slidev/pages/day6/m3-ai-code-review.md create mode 100644 slidev/pages/day6/m4-ci-failure-loop.md create mode 100644 slidev/pages/day6/m5-asset-governance.md create mode 100644 slidev/pages/day6/m6-onboarding-rollout.md create mode 100644 slidev/pages/day6/m7-maintainability-review.md create mode 100644 slidev/pages/day6/m8-asset-change-validation.md create mode 100644 slidev/pages/day6/m8-final-demo.md create mode 100644 项目实操-影响分析提示词与TRAE返回结果.md diff --git a/CI失败日志-真实Maven环境错误.md b/CI失败日志-真实Maven环境错误.md new file mode 100644 index 0000000..a4aff9f --- /dev/null +++ b/CI失败日志-真实Maven环境错误.md @@ -0,0 +1,29 @@ +# CI 失败日志: Maven 环境错误 + +## 使用边界 + +这是一段实际保留的 Maven 构建失败记录,用于第六天的 CI 日志分析演练。 + +- 直接错误是本地 Maven 仓库文件无法写入。 +- 它是环境/文件权限问题,不是公务用车模块的业务代码或编译错误。 +- 不将它写入 `training/require-cancel-reason` 训练分支,因此该分支仍保持两处业务改动的真实 diff。 + +## 原始记录 + +```text +[WARNING] Failed to write tracking file '/Users/smart/.m2/repository/org/bouncycastle/bcpkix-jdk18on/resolver-status.properties' +java.io.FileNotFoundException: /Users/smart/.m2/repository/org/bouncycastle/bcpkix-jdk18on/resolver-status.properties (Operation not permitted) + at java.io.RandomAccessFile.open0 (Native Method) + at java.io.RandomAccessFile.open (RandomAccessFile.java:344) + at java.io.RandomAccessFile.(RandomAccessFile.java:259) + at org.eclipse.aether.internal.impl.DefaultTrackingFileManager.update(DefaultTrackingFileManager.java:84) + at org.eclipse.aether.internal.impl.DefaultUpdateCheckManager.write(DefaultUpdateCheckManager.java:529) + at org.eclipse.aether.internal.impl.DefaultMetadataResolver.resolveMetadata(DefaultMetadataResolver.java:180) + +[ERROR] java.io.FileNotFoundException: /Users/smart/.m2/repository/org/bouncycastle/bcpkix-jdk18on/resolver-status.properties (Operation not permitted) +[ERROR] BUILD FAILURE +``` + +## 来源说明 + +记录来源:`JeecgBoot-training` 仓库历史训练分支 `training/no-cross-order-ticket-reassignment` 中的 `training/ci-build-failure.log`。该历史分支仅作为环境错误材料来源,不作为第五、六天的业务案例。 diff --git a/TRAE 培训大纲V1.0.md b/TRAE 培训大纲V1.0.md index df1f010..53262ee 100644 --- a/TRAE 培训大纲V1.0.md +++ b/TRAE 培训大纲V1.0.md @@ -145,61 +145,62 @@ -### 第 5 天:历史代码重构、测试覆盖与团队资产库建设 +### 第 5 天:可沉淀的 AI 工程资产:Rule、任务上下文契约、Skill、Agent 与 MCP **目标:** -掌握用 TRAE 安全治理技术债、补齐测试覆盖,并将课程中的规则、Prompt、模板、案例和流程沉淀为可维护的企业资产库。 +以当前 JeecgBoot 的公务用车模块为真实素材,识别一次状态变更中的项目事实、反例与重复判断;重点拆解如何将这些信息分别沉淀为 Rule、任务上下文契约、检查单、示例和 Skill。案例只提供证据来源,不作为可复制的通用答案。 |内容|实操|产出| |---|---|---| -|历史代码问题识别|分析长函数、重复判断、隐式规则、类型缺失、分支过深|代码坏味道清单| -|重构方案生成|生成策略模式、composable 拆分、类型抽取方案|重构方案| -|重构风险审查|检查行为一致性、影响范围、回滚方案、测试缺口|重构风险清单| -|重构实操|对预置历史模块进行分层拆分和类型治理|重构 Diff| -|TypeScript 类型治理|从接口响应、状态枚举、表单模型中抽取类型|TypeScript Interface| -|测试用例生成|覆盖权限、状态流转、异常输入、边界值|单测用例清单| -|Vitest 单测补齐|修正无效断言和脆弱测试|单测脚本与覆盖率报告| -|团队资产库分层设计|将课程产物分为规则、Prompt、模板、案例、SOP、指标|资产库目录结构| -|`.trae/rules.md` 生命周期|设计规则新增、评审、废弃、版本管理机制|规则维护机制| -|Prompt 模板库建设|标准化读代码、拆需求、写代码、重构、测试、Review、CI 修复 Prompt|Prompt 模板库| -|案例库沉淀|提取优秀案例、问题案例、幻觉案例、CI 失败案例|案例库| -|资产评审机制|明确谁维护、多久评审、如何合入、如何淘汰|资产治理 SOP| -|新人和项目推广机制|设计新人学习路径、项目接入流程、检查机制|TRAE 推广流程| +|案例引子:识别资产原料|阅读取消派车的状态、入口、权限和写入顺序,区分项目事实与可迁移判断|事实与反例清单| +|资产边界与选型|根据变化频率、误用风险、决策复杂度和证据来源,区分稳定约束、运行时事实与可沉淀资产|资产选型表| +|Rule 设计契约|编写证据等级、适用场景、必须项、禁止项、来源与验收方式完整的 Rule|可执行 Rule 草案| +|任务上下文契约|拆分固定约束与运行时事实,定义结构化输出、证据缺口与停止边界,并用代表性输入验证|影响分析任务契约与测试样例| +|Skill 包拆解|理解 SKILL.md、发现配置、Prompt、检查单、模板、参考资料和脚本各自职责|Skill 包结构图| +|Skill 组合与停止条件|用 Rule、Prompt、检查单和模板编排事实提取、方案比较、验证设计与结论输出|Skill 工作流与输出契约| +|资产验证与演进|用完整输入、缺失证据、范围冲突和跨项目输入测试资产边界,明确可复用与必须重查的内容|资产测试矩阵与演进记录| +|自定义 Agent 角色契约|拆解角色名称、用途说明、参数提示、模型、工具和默认指令,区分稳定配置与运行时事实|自定义 Agent 设计卡| +|Agent 权限与停止边界|按职责配置最小工具集,明确证据不足、范围越界和验证缺失时的停止条件|Agent 权限矩阵与停止条件| +|Agent 与 Skill 组合|明确 Agent、Skill、Rule、Prompt、检查单和运行时事实的权威位置,设计结构化交接结果|Agent-Skill 组合图与交接契约| +|历史模块职责识别与方案比选|基于公务用车派车 Service 的现有校验、写入与事务边界,识别重构候选并比较最小方案|重构候选与方案比选表| +|重构风险审查|围绕取消派车的输入保护、状态保护、双对象写入、权限与审计入口识别不可改变行为|重构风险清单| +|跨端类型契约治理|核验审批结果与申请状态在 Java 常量、TypeScript 模型和表单配置中的表示差异|类型契约与核验项| +|测试基线与定向补齐|区分当前 JUnit/Mockito 与 Jest 已证明的范围,补齐派车状态、副作用、请求载荷和类型契约测试|测试缺口矩阵与定向用例清单| +|真实 Skill 包与资产库|拆解现有 Skill 包的目录、Prompt、模板、检查单与反例,用测试夹具验证资产边界|资产索引与验收夹具| +|MCP 能力资产|围绕外部工具的能力说明、输入输出、权限、审计、故障降级与验证夹具编写 MCP 契约|MCP 能力契约与权限验收清单| +|派车重构目标结构|在不改变事务边界、拒绝顺序、接口字段和状态写入语义的前提下,设计派车入口的候选职责拆分|候选职责结构图| +|无损重构行为基线|将申请、车辆、司机的拒绝路径与派车成功后的车辆预占整理为可回归的行为基线|无损重构验收表| +|命令与展示模型分离|基于派车 VO、实体和前端模型区分用户可提交字段与系统生成字段|命令与展示类型表| +|类型契约核验用例|将审批结果大小写与申请状态表示差异转化为可执行的接受、拒绝和载荷检查|类型契约用例集| +|取消派车测试矩阵|覆盖空白原因、任务不存在、非法状态、正常取消与车辆缺失等真实分支|取消派车测试矩阵| +|测试层级与断言边界|区分 Controller 委派、Service 状态副作用与 API 契约各自应验证的内容|测试分层检查表| - - -### 第 6 天:企业研发效能、PR 闭环、CI 门禁与落地计划 +### 第 6 天:工程协作与质量闭环:PR、Git、CI、证据链与资产验证 **目标:** -建立 AI 辅助研发效能管理方法,能够基于 PR、CI、缺陷、需求流转数据分析团队产出率、质量和吞吐,并跑通开发、Review、CI、验收、资产沉淀、管理复盘的完整闭环。 +将 AI 参与的分析、实现和资产使用放入可审查、可验证、可追溯的工程交付链路,掌握 PR 说明、Git 合并控制、CI 失败分析、变更证据链、资产版本兼容性和可演进性审查的方法。AI Code Review 方法沿用第 4 天的既有流程,不再重复讲解。 |内容|实操|产出| |---|---|---| -|研发效能指标体系|定义产出、质量、吞吐、周期、AI 使用、资产复用指标|指标字典| -|产出效率分析|分析 PR 数、有效提交、需求完成数、人均交付量|产出分析模板| -|需求吞吐分析|分析需求进入数、完成数、积压数、吞吐率|需求吞吐看板| -|交付周期分析|分析 Lead Time、Cycle Time、Review 等待时间、CI 修复时间|周期分析模板| -|质量指标分析|分析缺陷数、返工率、测试覆盖率、CI 失败率|质量分析模板| -|AI 使用指标|设计 AI 采纳率、AI 修改率、Prompt 复用率、AI Review 命中率|AI 使用指标表| -|团队资产指标|设计规则更新次数、模板复用次数、案例沉淀数、资产过期率|资产运营指标| -|TRAE 辅助效能分析|将 PR、CI、缺陷样例数据输入 TRAE,生成分析报告|效能分析报告| -|TRAE Code Review 方法|从安全、类型、性能、可维护性、测试缺口审查代码|AI Code Review 清单| -|PR 模板与审查标准|编写需求说明、实现说明、测试结果、风险点、AI 使用记录|PR 模板| -|CI 质量门禁设计|配置 lint、typecheck、unit test、coverage|CI/CD YAML| -|TRAE 分析 CI 失败日志|将失败日志输入 TRAE,定位原因并生成修复方案|CI 失败修复记录| -|标准 PR 闭环演练|完成代码整理、Review、测试、CI 修复和验收|可运行 PR| -|Prompt 模板资产库沉淀|汇总读代码、拆需求、写代码、重构、测试、Review Prompt|Prompt 模板资产库| -|ROI 测算方法|计算 AI 采纳率、PR 周期缩短、缺陷率变化、节省人日|ROI 测算报告| +|AI 改动的工程闭环|建立从影响分析、变更实现到 PR、CI 和技术结论的交付链路|工程质量检查视图| +|标准 PR 说明与 AI 使用记录|基于 diff、需求、测试结果和风险点生成可审查的 PR 描述|PR 模板与样例| +|AI 变更 Git 协作|明确分支范围、提交粒度、冲突处理、合并前置条件和回退方式|Git 协作检查单| +|CI 失败日志分析|结合日志、构建配置、相关 diff 和项目规则,区分源码、依赖与环境问题|CI 失败分析记录| +|CI 质量门禁|理解 lint、typecheck、unit test、coverage 与人工检查各自覆盖的风险|CI 质量门禁清单| +|共享资产的版本与兼容性|识别会改变 Agent 行为的资产 diff,说明版本边界、不兼容点、迁移与回退条件|资产兼容性说明| +|变更证据链与可追溯说明|将需求、diff、测试、CI、日志和人工观察连接到可复核的技术结论|技术说明与证据地图| +|AI 改动的可演进性审查|审查修改入口、规则权威位置、依赖隔离和回归定位能力,降低后续变化成本|可演进性审查清单| +|资产变更的工程验证|审查 Rule、任务上下文契约、Skill、Agent 与 MCP 契约的行为变化、引用完整性、兼容性和测试夹具|资产变更审查清单| ### AI Native 技能清单 @@ -223,8 +224,8 @@ |重构阶段|无损重构|重构 Diff、风险清单| |Review 阶段|AI Code Review|Review 问题清单| |CI 阶段|CI 失败分析|CI 失败原因与修复方案| -|管理阶段|研发效能分析|效能分析报告| -|管理阶段|ROI 测算|ROI 测算报告| +|工程协作阶段|PR 与变更说明|可审查的变更记录| +|工程协作阶段|CI 失败分析|失败原因与验证方案| @@ -272,19 +273,25 @@ - \[ \] 可运行的工单/效能看板业务代码 -- \[ \] 核心历史模块的重构 Diff 记录 +- \[ \] 项目知识库 README 与新人上手导览 -- \[ \] 高质量 TypeScript 接口定义文件 +- \[ \] AI 任务委派模板 -- \[ \] 达到标准覆盖率的单元测试脚本与报告 +- \[ \] 核心历史模块的坏味道清单、重构风险清单与最小重构方案 -- \[ \] 跑通的 CI/CD 质量门禁 YAML 配置 +- \[ \] 测试安全网清单、单元测试脚本与覆盖率记录 -- \[ \] 团队级 AI Code Review 标准清单 +- \[ \] Rule / Prompt / Command / Skill 设计卡与候选资产清单 -- \[ \] 精选沉淀的 Prompt 模板资产库 +- \[ \] 标准 PR 模板与 AI 使用记录表 -- \[ \] ROI 效能提升测算报告及落地计划 +- \[ \] 团队级 AI Code Review 标准清单与 Review 修复闭环记录 + +- \[ \] CI 失败分析记录与 CI/CD 质量门禁方案 + +- \[ \] 团队资产库目录、入库标准、版本维护与淘汰机制 + +- \[ \] 交付证据包与复用边界说明 --- @@ -306,13 +313,10 @@ 8. **工程闭环力:** 能够将 AI Review 融入个人提交习惯,并掌握借助 CI 日志让 AI 自动修复代码的流水线能力。 -9. **管理量化力:** 技术负责人能建立明确的 TRAE 落地方案,掌握代码产出比、交付周期等 ROI 测算方式。 +9. **工程协作力:** 学员能够把 AI 参与的变更说明、验证证据和可复用资产纳入可审查、可追溯的研发交付链路。 ## 咨询费用 6 \* 12000 = 720000 元 付款方式:合同签订后付款50%,培训过半后付款30%,培训结束付款20%。 - - - diff --git a/deliverables/ai-engineering-skills-delivery-utf8.zip b/deliverables/ai-engineering-skills-delivery-utf8.zip deleted file mode 100644 index 648057c7175653b1bb0658d16cd609209c4993f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59302 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 g@!w;V4q_Mdx=wA!H1dQOBCUeA?LgKMk9QEg6W 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 U=mi3z2mGL50|7bp-2L@`0H@W}1^@s6 diff --git a/deliverables/ai-engineering-skills-delivery.zip b/deliverables/ai-engineering-skills-delivery.zip deleted file mode 100644 index da381263edb1a86d94254b3b1964adcf2f6550ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51012 zcmeFZQ*77=8B9`@kn74g?Gd@axAWc1z=rfByLg4*(uO$AsFz(#XWpz`)kT(ums5%*5Q> zj#}Tq+{Dqq)|pmO1sWjnM8GuR<8${389?UaFk}GmkKcbijQp<;6P1!z;FXY|vC#k5 zW8D=;Y%ztA*Cv$h{gEaSRB)^=X4y$$51F_KG9W;?sThEhU=I zB0}`Rfd8p0y^*2%gk~F<94(+)5F$9^{<3a2!Pa0DO2=f?K9;gWX&eutV;0Jz*T0XV zm-5PJ#Suv5B-$PhZ0y6i66?$Be~A=Nee46^4;URcw;mx7cJ#^0XmFwNaU{4`<0XJw zrw0QoLB-N1IO_(Nec6S8iU>T0sLzCe=C!@%tikN8(fJIB*V8S(AN(`mZ!r)`C>eu54;PmNpQZZXdN*q*DpMgYa#HM)#|9NrXoPnYsRof0x# zGQ{t=d2ob!xBe2IEs@}pS+73*-|q3l?eFyn(Ol&p&I_kp{SDGx1EcyI$WOIg(v!j|5t9yyZjSKQt1^V%`s}(7I=D z0@ZR0X4^zsGx|6`g)EHYaAX0d+ZBapC)wh!$a&uQT{`<_;#H<1jI5*2gi7BK{0M__ z*Qbfh_T0%+0s5KCH>8}p%kyGvZqs{BY(~f5FhYs1Be&D@wtO7K z$eKMUrDXN&$Ce9zq8)EQ4Tm5dJ13L4)#Gup^z^}}AQ3n-_(aZ?pd4P*ye$lZzWblV z-9YMY#6T^D^5dqVL66nY^B=#UaB6g}5ZwyVjaAFxtaVcm@7x2JR5?^LZ9k(R2S_YP zD53yIZ?5*dcsOI;Dz__hcR@Q0x^yfE~ruY&>GlvK3qg_tpvmO@UJ+D+{ z#3J9-yj!Xk1}e$dNvU62;abMr=hCqgIUf}5?q@K*-|A@2PUQV@?E#6evV^Erw9o2at<87O zJ~E5p*n5ZTktM^G2BDs@LWNgUz+Nl=1}7s925wg#Cfg1K05Agz@E=IR;a?bs?5{~e zPLNkXQt+P~k@-P88ULgnelskKBq<8Avl0(yuF@NJ&$rMB2k01g z<4tYVr%tX1gR`wGI}lL_OFJI4FF)gml-Q=~+}kvBipfUyZ&&D@g>Lu1j4>a2q#T?O z0JW80xgetbToA z1ew2$NdW}3iYPg5qh$}yuYdfu9MaqHD$WSg&Cb7syxyaWrbp&poQyzJ$ccYpI0#@f zU$vNb=HyfWAIW2*TURCvGQXE>Q)#FFn?hXuJ&&I9qq(Ia0=~Gv^GjR-Xa`S*jj7h^nu%+9XIe-dbVS1 zXSQd3o%`{{#UI5B<5yQNzMn#2s~GvfF~TN>zAb&liNgu?*id&SIZV282IBo$yMpER z*+mJq@t_sHrt&a45!-4V#Os2mvuZQd{UEv{{6XCUuXpTzmM&=YfgpQ#v^5v)XQ1oQ z*3}cLpvqUCZz-sPpOBjxF3YGUM%%8Qd?Mhuf424U?`?StrP^~qxLrOXs0M%s_VAv+^(39RYTEKQSzI~ATx-kHzX> zs_{$nB|L1^*fjF#VRb8c;Of6bYtVHyjI&)9X@L&m8X-DkCt13}NqwRV(pX9hahm{r z!qowOdJOrr{vK$d+`gTCk!?t(%OqdR8*?eSK+l+%y~I#X!Pzi8vC^uEvS>*U9$7!G zqqHDbi-dS!f1NLL>i~0v#1YZ}9J!Vj{bOll=@~=(N0EaE<^qR~vAT4&(i<2p!p`T> z_i$~1GahL2+7q zVrRsy4ow|t&fL`Z-S!8y%GHpCap;%;*jTt-A@cE7A7wrPTf?{yeE3@%_1=>wTWl_a9vTW9CJPVJxzH4YKPgarG z*W9L4wb=Xf7YQ~2P~&6*0|0b_|APeoTz35@NYLQBj)k?kf!&|#uRlMP>#wQxf1he< zV4!EDYh`6m?PO(ZMr~!QZ(#eUSN*xvGy3mdCHCunzK{F&8uItTDjrX814tnIp9Jlm(D7ogbYI>awXFt`c|O!lN&zI1+H7x3?ch_H`@Ld zIv{VmR~%%If6#2~q)Lbg;b;k)tvqvutFAgW59UbL;{MDJXPjZpYN}LH1SPo64R|8G z+twaw<(Y^j$P|B)UORZ&p)=FqY-=Yx@F7PuXT&o;La_Xj9P^wqi`%i9hQLRM8pEhV z>bTiCd#ZA)<(&}%1w1}t!W~EXw1k=6;X$4P@oJoDE_zZfEY^uw0n8^)*R_sQ5dw>E zM-o_7LMy7L}*1n$s-wU%NFy7QQc2FT}Gnat4D zO2WV|DbakSyg0m|fWH$V+d$LO&HFhrprr4*02gK%duz2sS!4?wfa;z*jn%CDqoFt;>N3h6XiSO!d*o z%b?41e!j`+Y;{uYS0F8o)tQ6RF(9-sP1;0auqQRH1JGyBM&~FimC9lwtSPOmmgkDd ze;mCJ;m23%FV%Fu47};evUui0zDYN4lj0x|$Gm%4ey!yh4=DR{-Ud^s6FB05exAyjBS8NB0%1iYr_BxPhADoHQPs2oW+XDw5r{Q2n*GST1LIsgO!Kmhq2nUMVTTl|No z`_uRR=Tsv2pFUIHz|O?Tl3LH|7h8R&)^)J^aFTX*)D~9y4(0~`g)1fgt}Atn3@q*c zoHzfR%#{D;o2{%3EOkt1oOLYB|JCJ6MsMfL>-Zd**16Sy@<$!qsl_uaHGe2@J36T{X=Cyx;p5r98Uok=o%*czbi z`KpB6=wwclt0Lu~gvN?_WMZuPSTK&1Tf{WVAxmShWw2oZZtN+CT)M*w?d-LTjk`Ee zarY!rwN0(BuC2p-xZ-|qyTk6F$(c*N9`ig zCjbDmkI%oaGD!Yi(UKPz{nJW>g|d1ivLL#9g_f-9Hz$`J%|KF1#2jFOpbGgJO$K>$ zhX)nKww4=`Bp3l2Ov0>wK^8FqLL?+0Ri51mx|)`s-8YJg4~$T>rvEh6Dp;)uSd?bO}Z>O zNFFRGK|Fqi2o7#SKxiCnC_y|9p&)bcnVn9VJ+lsu*AVR0⪙?(7+U?-^?db$3ysa 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 CZ0+m- diff --git a/deliverables/ai-engineering-skills-delivery/INSTALL.md b/deliverables/ai-engineering-skills-delivery/INSTALL.md deleted file mode 100644 index 8afc3d5..0000000 --- a/deliverables/ai-engineering-skills-delivery/INSTALL.md +++ /dev/null @@ -1,67 +0,0 @@ -# 安装说明 - -## 方式一:安装到 TRAE Skills 目录 - -把 `skills/` 下的 5 个 Skill 文件夹复制到本机 Skills 目录: - -```text -~/.trae/skills/ -``` - -安装后目录应类似: - -```text -~/.trae/skills/ -├── project-knowledge-builder/ -├── generate-project-constraints/ -├── design-complex-business-module/ -├── review-business-delivery/ -└── track-review-fix-loop/ -``` - -每个 Skill 文件夹必须保留完整结构: - -```text -skill-name/ -├── SKILL.md -├── agents/ -│ └── openai.yaml -└── assets/ -``` - -## 调用方式 - -在支持 Skill 的 AI 编程工具中,可以这样调用: - -```text -Use $generate-project-constraints 为当前项目生成 AI 开发约束。 -Use $project-knowledge-builder 为当前项目生成 docs/project-knowledge 项目知识库。 -Use $design-complex-business-module 设计这个业务模块的权限、状态、审计、异常态和看板。 -Use $review-business-delivery Review 这批阶段交付物。 -Use $track-review-fix-loop 根据 Review 报告生成修复闭环记录。 -``` - -## 方式二:作为团队资产库使用 - -如果当前 AI 工具暂不支持原生 Skill 机制,可以把本包作为团队资产库使用: - -```text -ai-engineering-assets/ -├── skills/ -├── rules/ -├── prompts/ -├── templates/ -├── checklists/ -└── examples/ -``` - -此时 `SKILL.md` 作为标准工作流说明,`assets/` 作为可复制模板、Prompt、检查清单和反例库。 - -`examples/` 目录只用于学习参考,不要复制到通用 Skills 安装目录。换项目复用时,应先用 `project-knowledge-builder` 建立项目知识库,再用 `generate-project-constraints` 重新扫描目标项目,最后使用其他 Skill 生成该项目自己的权限、状态、审计、Review 和修复闭环资产。 - -## 维护建议 - -- 每次项目规则变化后,重新运行 `generate-project-constraints`。 -- 每次复杂业务模块交付后,把高频问题沉淀到 `review-business-delivery` 或 `track-review-fix-loop` 的 assets。 -- 不要把某个项目的专用规则直接复制成通用规则。 -- 对无法从源码或配置证明的内容,统一标记为待确认。 diff --git a/deliverables/ai-engineering-skills-delivery/README.md b/deliverables/ai-engineering-skills-delivery/README.md deleted file mode 100644 index 3cae1b7..0000000 --- a/deliverables/ai-engineering-skills-delivery/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# AI Engineering Skills Delivery - -本交付包提供 5 个可复用 AI 工程 Skill,覆盖真实项目中从项目知识库、项目约束生成、复杂业务设计、交付 Review 到修复闭环的完整流程。 - -这些 Skill 不是单次 Prompt,而是可安装、可复制、可随项目持续维护的团队资产。 - -`skills/` 目录中的内容是通用资产,不能绑定某个训练项目或客户项目;具体案例只放在 `examples/` 目录,作为参考材料,不作为通用 Skill 安装项。 - -## 交付内容 - -```text -客户AI工程Skill交付包/ -├── README.md -├── INSTALL.md -├── skills/ -│ ├── project-knowledge-builder/ -│ ├── generate-project-constraints/ -│ ├── design-complex-business-module/ -│ ├── review-business-delivery/ -│ └── track-review-fix-loop/ -└── examples/ - └── jeecgboot-work-order/ -``` - -## Skill 列表 - -| Skill | 用途 | 典型产物 | -| --- | --- | --- | -| `project-knowledge-builder` | 深度整理代码仓库,生成给人和 AI 复用的项目知识库 | 项目知识库 README、上下文图、架构/运行/前后端/数据/权限文档、RAG 索引建议、生成索引 | -| `generate-project-constraints` | 从项目源码、配置、CI、文档中提取项目约束 | rules、约束提取表、验收清单 | -| `design-complex-business-module` | 设计复杂业务模块的权限、状态、审计、异常态和看板 | RBAC 矩阵、权限控制方案、状态流转表、审计 SOP、异常态清单、看板指标字典、看板接口验收记录 | -| `review-business-delivery` | 审查复杂业务交付物或 PR 的业务风险 | AI Review 报告、阶段 Review 报告、权限/状态/审计/异常态/看板检查清单、业务交付验收清单 | -| `track-review-fix-loop` | 跟踪 Review 问题修复、验证和资产沉淀 | 修复记录、未闭环清单、资产更新建议、权限/状态/审计/指标反例库 | - -## 推荐使用顺序 - -1. 使用 `project-knowledge-builder` 生成项目知识库,让团队和 AI 先理解项目。 -2. 使用 `generate-project-constraints` 为客户项目生成项目规则。 -3. 使用 `design-complex-business-module` 生成复杂业务设计资产。 -4. 使用 `review-business-delivery` 审查设计、代码或阶段交付物。 -5. 使用 `track-review-fix-loop` 跟踪修复、验证结果和资产沉淀。 - -## 适用项目 - -适用于后台管理系统、低代码平台二开、CRM、工单、审批、运营系统、数据看板等企业业务项目。 - -这些 Skill 不要求项目必须有 `CLAUDE.md`、`AGENTS.md` 或 `README.md`。如果项目没有显式说明文档,Skill 会从配置、源码模式、测试和 CI 中提取约束,并把无法证明的内容标记为待确认。 - -## 示例说明 - -`examples/jeecgboot-work-order/` 提供 JeecgBoot 工单案例参考,帮助理解这些 Skill 在真实后台项目中的使用方式。该示例不是通用 Skill 安装项。 diff --git a/deliverables/ai-engineering-skills-delivery/examples/jeecgboot-work-order/JeecgBoot工单模块开发审查.skill.md b/deliverables/ai-engineering-skills-delivery/examples/jeecgboot-work-order/JeecgBoot工单模块开发审查.skill.md deleted file mode 100644 index 4bc5f74..0000000 --- a/deliverables/ai-engineering-skills-delivery/examples/jeecgboot-work-order/JeecgBoot工单模块开发审查.skill.md +++ /dev/null @@ -1,44 +0,0 @@ -# JeecgBoot 工单模块开发审查 Skill - -## 触发场景 - -涉及工单列表、详情、评论、状态、指派、看板、权限字符、数据权限、操作日志的需求拆解、代码生成或代码修改。 - -## 输入材料 - -- PR diff 或相关文件。 -- 工单需求说明。 -- 数据模型与接口契约。 -- 当前 rules。 -- 前端页面文件。 -- API 模块文件。 -- 后端 Controller / Service。 -- 权限矩阵。 -- 状态流转表。 -- 审计日志 SOP。 - -## 执行步骤 - -1. 识别需求范围和非目标范围。 -2. 检查字段、接口契约和页面状态是否完整。 -3. 审查 JeecgBoot 生成代码的字段、查询、分页、字典和异常态。 -4. 检查新增、编辑、删除、指派、状态变更的前端按钮与后端权限。 -5. 检查操作日志、数据权限和测试缺口。 -6. 检查看板指标口径和接口验收记录。 -7. 输出风险、不确定点和验收建议。 - -## 输出格式 - -- 需求范围确认。 -- 接口契约问题表。 -- 代码生成 Review 清单。 -- 权限风险表。 -- 状态流转风险表。 -- 审计日志风险表。 -- 测试缺口清单。 -- 不确定点清单。 - -## 验收标准 - -每个关键结论必须标注文件、函数、注解、配置或调用链依据。每条建议必须有验收方式。 - diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/SKILL.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/SKILL.md deleted file mode 100644 index 36337fa..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/SKILL.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -name: design-complex-business-module -description: Use when designing a complex enterprise business module that needs RBAC permissions, frontend/backend permission controls, state transitions, illegal transition handling, audit logging, exception states, dashboard metrics, interface contracts, or acceptance-ready design assets. ---- - -# Design Complex Business Module - -## Core Principle - -Design the control surfaces before designing code. For complex business modules, produce permission, state, audit, exception, dashboard, and acceptance assets before implementation planning. - -Do not jump from a requirement to CRUD code. Every business rule must have an owner, a source, and a verification method. - -## Inputs - -Collect what exists, then mark gaps explicitly: - -- Business requirement or user stories. -- Target project paths and project constraints. -- Existing roles, permissions, menus, routes, API layer, backend controllers/services, data model, and audit/logging patterns. -- Known non-goals and delivery deadline. -- Existing rules, checklists, or project knowledge docs. - -If project evidence is missing, label outputs as `建议` or `待确认`, not `事实`. - -## Workflow - -1. Define scope: module name, actors, key workflows, non-goals, and acceptance boundary. -2. Build the RBAC matrix: roles x features, with `允许`, `拒绝`, or `条件允许`. -3. Translate RBAC into a permission control plan: page/menu, button, backend API, and data scope. -4. Design the state machine: states, actions, allowed roles, preconditions, post-actions, and terminal states. -5. List illegal transitions and rejection behavior. -6. Design audit logging: actions, write timing, actor, target, old/new values, source/request id, masking, and failure behavior. -7. Complete exception states: permission denied, missing data, invalid state, duplicate submit, validation failure, concurrency, empty dashboard, empty audit log. -8. Define dashboard metrics: meaning, formula, dimensions, filters, data source, permission, empty state, and manual verification. -9. Produce interface and acceptance notes only after the above assets are consistent. - -## Output Contract - -Generate these assets unless the user narrows scope: - -```text -RBAC权限矩阵 -权限控制方案 -状态流转表 -非法流转清单 -审计日志SOP -异常态清单 -看板指标字典 -看板接口验收记录 -不确定点清单 -``` - -Use the templates in `assets/` when creating files. - -## Quality Gates - -- Every sensitive action has frontend visibility control and backend enforcement. -- Data permissions are not replaced by button hiding. -- Every state transition has a reverse or rejection story, or is explicitly terminal. -- Every illegal transition has expected system response and validation method. -- Every successful critical operation has an audit record; failed operations do not write success audit. -- Every dashboard metric has a formula or filter logic that can be manually checked. -- Every output distinguishes `事实`, `推断`, `建议`, and `待确认`. - -## Common Mistakes - -- Do not design UI first and add permissions later. -- Do not use "admin can do everything" as a complete RBAC design. -- Do not put all status rules into prose; use a state table. -- Do not write "record audit log" without naming fields and write timing. -- Do not define dashboard cards without metric formulas and data scope. -- Do not turn project-specific permission strings into facts without source evidence. diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/agents/openai.yaml b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/agents/openai.yaml deleted file mode 100644 index 1d0695e..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/agents/openai.yaml +++ /dev/null @@ -1,7 +0,0 @@ -interface: - display_name: "复杂业务模块设计" - short_description: "生成权限状态审计异常态看板等复杂业务设计资产" - default_prompt: "Use $design-complex-business-module to design permissions, state transitions, audit logs, exception states, and dashboard metrics for this business module." - -policy: - allow_implicit_invocation: true diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/RBAC权限矩阵.template.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/RBAC权限矩阵.template.md deleted file mode 100644 index 438030f..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/RBAC权限矩阵.template.md +++ /dev/null @@ -1,19 +0,0 @@ -# <模块名称> RBAC 权限矩阵 - -## 使用说明 - -将列名中的 `<角色A>`、`<角色B>`、`<角色C>` 替换为目标项目真实角色。权限字符只能写成建议,除非源码、菜单配置或后端注解已经证明。 - -| 功能点 | <管理员角色> | <业务负责人角色> | <处理人角色> | <审核人角色> | <只读角色> | 权限字符建议 | 数据范围 | 来源证据 | 备注 | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| 查看列表 | 允许 | 条件允许 | 条件允许 | 条件允许 | 条件允许 | `::list` | 按授权范围 | | | -| 查看详情 | 允许 | 条件允许 | 条件允许 | 条件允许 | 条件允许 | `::query` | 仅可见数据 | | | -| 新建记录 | 允许 | 允许 | 条件允许 | 条件允许 | 拒绝 | `::add` | 通常无数据范围 | | | -| 编辑记录 | 允许 | 条件允许 | 条件允许 | 拒绝 | 拒绝 | `::edit` | 创建人/负责人/授权范围 | | 需结合状态 | -| 分配/转交 | 允许 | 条件允许 | 拒绝 | 拒绝 | 拒绝 | `::assign` | 管理范围 | | 高风险操作 | -| 状态流转 | 允许 | 条件允许 | 条件允许 | 条件允许 | 拒绝 | `::transition` | 当前数据范围 | | 需结合状态表 | -| 关闭/归档 | 允许 | 条件允许 | 拒绝 | 条件允许 | 拒绝 | `::close` | 当前数据范围 | | 终态动作 | -| 删除记录 | 条件允许 | 拒绝 | 拒绝 | 拒绝 | 拒绝 | `::delete` | 管理范围 | | 高危操作 | -| 查看审计日志 | 允许 | 条件允许 | 条件允许 | 条件允许 | 拒绝 | `::audit` | 当前数据范围 | | | -| 查看统计看板 | 允许 | 条件允许 | 条件允许 | 条件允许 | 条件允许 | `::dashboard` | 按角色过滤 | | | -| 导出数据 | 条件允许 | 条件允许 | 拒绝 | 拒绝 | 拒绝 | `::export` | 管理范围或授权范围 | | 需脱敏 | diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/RBAC权限矩阵生成.prompt.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/RBAC权限矩阵生成.prompt.md deleted file mode 100644 index 0ff18f8..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/RBAC权限矩阵生成.prompt.md +++ /dev/null @@ -1,27 +0,0 @@ -# RBAC 权限矩阵生成 Prompt - -```text -请基于目标业务模块设计 RBAC 权限矩阵。 - -输入材料: -- 模块名称:<填写> -- 业务对象:<填写,例如订单、客户、申请、任务> -- 需求说明或用户故事:<粘贴> -- 角色列表:<粘贴目标项目真实角色> -- 功能点:<粘贴页面、按钮、接口、数据动作> -- 项目依据:<粘贴权限、路由、菜单、接口、后端校验相关路径> - -输出要求: -1. 先列事实依据:权限、菜单、动态路由、接口、后端校验相关路径。 -2. 输出 RBAC 权限矩阵:角色 x 功能点,允许、拒绝、条件允许。 -3. 输出权限字符建议,并标注哪些是事实、哪些是建议。 -4. 输出页面级、按钮级、接口级、数据范围四层控制。 -5. 输出不确定点:哪些权限必须业务方确认。 -6. 输出验收方式:如何证明权限确实生效。 - -约束: -- 不要直接改代码。 -- 不要把前端按钮隐藏当作安全边界。 -- 所有结论必须区分事实、推断、建议。 -- 不要把示例模块、示例角色或示例权限字符当成目标项目事实。 -``` diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/审计日志SOP.template.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/审计日志SOP.template.md deleted file mode 100644 index bb815dc..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/审计日志SOP.template.md +++ /dev/null @@ -1,41 +0,0 @@ -# <模块名称> 审计日志 SOP - -## 记录原则 - -所有会改变目标业务对象关键状态、负责人/处理人、可见性、金额、结论或对外结果的动作都必须记录审计日志。 - -## 审计字段 - -| 字段 | 含义 | 示例 | -| --- | --- | --- | -| target_type | 业务对象类型 | order / customer / request | -| target_id | 业务对象 ID | 100001 | -| actor_id | 操作人 ID | 1001 | -| actor_name | 操作人名称 | zhangsan | -| action | 操作类型 | ASSIGN / APPROVE / CLOSE | -| field_name | 变更字段 | status / owner_id / amount | -| old_value | 旧值 | lisi | -| new_value | 新值 | wangwu | -| source | 来源 | web | -| request_id | 请求 ID | req-001 | -| created_at | 操作时间 | 2026-07-01 10:00:00 | - -## 必须记录的动作 - -| 动作 | 写入时机 | oldValue | newValue | 验收 | -| --- | --- | --- | --- | --- | -| 创建记录 | 创建成功后 | 空 | 业务摘要 | 审计列表可见 | -| 编辑字段 | 保存成功后 | 修改前字段 | 修改后字段 | 字段前后值正确 | -| 分配/转交 | 分配成功后 | 原处理人 | 新处理人 | 审计记录处理人变化 | -| 状态流转 | 状态变更成功后 | 原状态 | 新状态 | 审计记录状态变化 | -| 结论/审核 | 结论保存成功后 | 原结论 | 新结论 | 审计记录结论变化 | -| 评论/备注新增 | 保存成功后 | 空 | 摘要或脱敏内容 | 审计记录评论动作 | -| 关闭/归档 | 操作成功后 | 原状态 | 目标终态 | 审计记录关闭或归档时间 | - -## 验收方式 - -1. 执行关键操作后能在审计列表查询到记录。 -2. oldValue/newValue 和实际变更一致。 -3. 敏感字段已脱敏。 -4. 操作失败不写成功审计。 -5. 多步写入失败时审计和业务数据保持一致。 diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/审计日志检查清单.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/审计日志检查清单.md deleted file mode 100644 index 1eebc8f..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/审计日志检查清单.md +++ /dev/null @@ -1,12 +0,0 @@ -# 审计日志检查清单 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 创建记录 | 创建业务记录后能查询审计记录 | | -| 字段变更 | 编辑关键字段记录 oldValue/newValue | | -| 分配记录 | 分配或转交记录原处理人和新处理人 | | -| 状态记录 | 状态流转记录原状态和新状态 | | -| 操作人 | actor_id 和 actor_name 可追溯 | | -| 来源 | source 或 request_id 可追溯 | | -| 脱敏 | 敏感字段不明文进入审计 | | -| 失败处理 | 操作失败不写成功审计 | | diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/异常态检查清单.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/异常态检查清单.md deleted file mode 100644 index d53299c..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/异常态检查清单.md +++ /dev/null @@ -1,12 +0,0 @@ -# 异常态检查清单 - -| 异常态 | 必须验证 | 结果 | -| --- | --- | --- | -| 空列表 | 页面显示空状态,不报错 | | -| 详情不存在 | 提示记录不存在,页面不崩溃 | | -| 权限不足 | 前端提示或隐藏,后端拒绝 | | -| 非法状态 | 返回非法流转,状态不变 | | -| 重复提交 | 只成功一次,只写一条有效审计 | | -| 参数缺失 | 返回参数错误,业务数据不变 | | -| 并发修改 | 拒绝覆盖或提示刷新 | | -| 看板无数据 | 图表显示空态,不显示假数据 | | diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/异常态清单.template.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/异常态清单.template.md deleted file mode 100644 index f591b2e..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/异常态清单.template.md +++ /dev/null @@ -1,13 +0,0 @@ -# <模块名称> 异常态清单 - -| 场景 | 触发条件 | 前端表现 | 后端响应 | 验收 | 来源证据 | -| --- | --- | --- | --- | --- | --- | -| 权限不足 | 无权限操作敏感功能 | 按钮不可见或提示无权限 | 403 或业务拒绝 | 不完成操作 | | -| 记录不存在 | 传入错误 ID | 显示不存在 | 404 或业务错误 | 页面不崩溃 | | -| 非法状态流转 | `<状态A> -> <状态C>` 不在状态表 | 提示非法流转 | 状态不变 | 数据状态不变 | | -| 重复提交 | 连续点击提交或重复请求 | 按钮 loading/禁用 | 只处理一次 | 只生成一条有效审计 | | -| 参数缺失 | 必填原因、金额、范围或结论缺失 | 表单校验提示 | 参数错误 | 不改变业务数据 | | -| 并发修改 | 数据版本过期 | 提示刷新 | 拒绝覆盖或合并冲突 | 旧数据不覆盖新数据 | | -| 统计无数据 | 无匹配记录 | 空状态 | 返回 0 或空集合 | 页面不显示假数据 | | -| 审计日志为空 | 无审计记录 | 空状态 | 返回空集合 | 页面不报错 | | -| 下游服务失败 | 外部接口超时或失败 | 错误提示且可重试 | 失败响应可追踪 | 不写成功审计 | | diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/异常态补齐.prompt.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/异常态补齐.prompt.md deleted file mode 100644 index 3dca924..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/异常态补齐.prompt.md +++ /dev/null @@ -1,31 +0,0 @@ -# 异常态补齐 Prompt - -```text -请基于目标业务模块,列出状态流转、分配/转交、删除/作废、查询详情、审计日志、统计看板相关的异常态和防御性逻辑。 - -输入材料: -- 模块名称:<填写> -- 业务对象:<填写> -- 角色与权限:<粘贴> -- 状态流转表:<粘贴> -- 接口契约或页面说明:<粘贴> - -输出表格: -场景 | 触发条件 | 前端表现 | 后端响应 | 用户提示 | 测试点 | 验收 - -必须覆盖: -- 权限不足 -- 记录不存在 -- 非法状态流转 -- 重复提交 -- 参数缺失 -- 并发修改 -- 统计无数据 -- 审计日志为空 -- 下游服务失败 - -约束: -- 先列异常态,不要直接改代码。 -- 给出每个异常态的验收步骤。 -- 标注事实、推断、建议和不确定点。 -``` diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/权限上线前检查清单.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/权限上线前检查清单.md deleted file mode 100644 index e7bf421..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/权限上线前检查清单.md +++ /dev/null @@ -1,11 +0,0 @@ -# 权限上线前检查清单 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 菜单权限 | 目标模块菜单只对授权角色可见 | | -| 按钮权限 | 新增、编辑、删除、分配、状态按钮按权限显示 | | -| 接口权限 | 无权限账号直接请求敏感接口失败 | | -| 数据权限 | 普通用户只能看到授权范围内数据 | | -| 权限字符 | 前端按钮、菜单配置、后端接口命名一致 | | -| 审计 | 权限敏感操作成功后有审计记录 | | -| 反例验证 | 无权限账号不能绕过前端执行删除/分配 | | diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/权限控制方案.template.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/权限控制方案.template.md deleted file mode 100644 index adbf43f..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/权限控制方案.template.md +++ /dev/null @@ -1,11 +0,0 @@ -# <模块名称> 权限控制方案 - -| 操作 | 前端入口 | 权限字符 | 后端接口 | 后端校验 | 数据权限 | 验收 | -| --- | --- | --- | --- | --- | --- | --- | -| 新增记录 | 新增按钮 | `::add` | `` | 权限注解或统一鉴权 | 通常无数据范围 | 无权限账号看不到按钮且直接请求失败 | -| 编辑记录 | 编辑按钮 | `::edit` | `` | 权限校验 + 状态校验 | 创建人/负责人/授权范围 | 越权账号返回 403 或业务拒绝 | -| 分配/转交 | 分配按钮 | `::assign` | `` | 权限校验 | 管理范围或当前授权范围 | 未授权角色无法分配给他人 | -| 状态流转 | 状态按钮 | `::transition` | `` | 权限校验 + 状态机校验 | 当前授权范围 | 非法流转失败且状态不变 | -| 删除/作废 | 删除或作废按钮 | `::delete` | `` | 权限校验 + 风险校验 | 管理范围 | 非授权角色返回 403 或业务拒绝 | -| 查看审计 | 审计页签 | `::audit` | `` | 权限校验 | 当前授权范围 | 无权限不可见且接口拒绝 | -| 统计看板 | 看板菜单 | `::dashboard` | `` | 权限校验 | 数据范围过滤 | 不同角色返回授权范围内统计 | diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/权限控制方案生成.prompt.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/权限控制方案生成.prompt.md deleted file mode 100644 index d568350..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/权限控制方案生成.prompt.md +++ /dev/null @@ -1,26 +0,0 @@ -# 权限控制方案生成 Prompt - -```text -请基于目标业务模块 RBAC 权限矩阵,生成权限控制方案。 - -输入材料: -- 模块名称:<填写> -- 业务对象:<填写> -- RBAC 权限矩阵:<粘贴> -- 接口契约表:<粘贴> -- 当前项目权限相关路径:<粘贴> -- 当前 rules:<粘贴> - -输出要求: -1. 权限控制方案表,列为:操作、前端入口、权限字符、后端接口、后端校验、数据权限、验收。 -2. 影响范围:前端页面、API 文件、后端 Controller、Service、菜单配置、数据权限。 -3. 缺失风险:按钮隐藏、接口未校验、数据范围不清、权限字符不一致。 -4. 无权限访问验收步骤。 -5. 不确定点和需要人工确认的权限边界。 - -约束: -- 不要直接改代码。 -- 后端接口权限必须单独说明。 -- 每个敏感操作必须有验收方式。 -- 权限字符、接口路径和角色名称必须来自输入材料;没有依据时标注为建议。 -``` diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/状态流转检查清单.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/状态流转检查清单.md deleted file mode 100644 index 759c879..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/状态流转检查清单.md +++ /dev/null @@ -1,11 +0,0 @@ -# 状态流转检查清单 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 状态枚举 | 所有状态有统一枚举或字典 | | -| 合法流转 | 状态流转表中的合法路径可执行 | | -| 非法流转 | 非法路径被拒绝且状态不变 | | -| 角色限制 | 不同角色只能执行被授权动作 | | -| 前置条件 | 驳回原因、关闭条件、处理人、审批意见等校验完整 | | -| 审计日志 | 每次成功流转写审计 | | -| 重复提交 | 重复点击不会产生重复有效流转 | | diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/状态流转表.template.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/状态流转表.template.md deleted file mode 100644 index 8a737e6..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/状态流转表.template.md +++ /dev/null @@ -1,21 +0,0 @@ -# <模块名称> 状态流转表 - -| 当前状态 | 动作 | 目标状态 | 允许角色 | 前置条件 | 后置动作 | 验收 | 来源证据 | -| --- | --- | --- | --- | --- | --- | --- | --- | -| 草稿/新建 | 提交 | 待处理/待审核 | 创建人/提交人 | 必填字段完整 | 写审计日志 | 状态变为目标状态 | | -| 待处理/待审核 | 分配/领取 | 处理中 | 管理员/负责人/处理人 | 处理人存在且有权限 | 写审计日志 | 处理人变化且状态正确 | | -| 处理中 | 提交结果 | 待确认/待复核 | 处理人/负责人 | 处理说明或结果不为空 | 写审计日志 | 状态变为待确认 | | -| 待确认/待复核 | 通过 | 已完成/已关闭 | 审核人/负责人 | 审核结论通过 | 写审计日志和完成时间 | 状态进入终态 | | -| 待确认/待复核 | 驳回 | 已驳回/待修改 | 审核人/负责人 | 驳回原因不为空 | 写审计日志 | 状态变为驳回态 | | -| 已驳回/待修改 | 重新提交 | 待处理/待审核 | 创建人/处理人 | 修改说明不为空 | 写审计日志 | 回到处理链路 | | -| 已完成/已关闭 | 重新打开 | 草稿/新建/处理中 | 管理员/负责人 | 重开原因不为空 | 写审计日志 | 状态按规则回退 | | - -## 非法流转清单 - -| 当前状态 | 尝试动作 | 问题 | 系统响应 | 验收 | -| --- | --- | --- | --- | --- | -| 已完成/已关闭 | 直接处理 | 终态不能直接进入处理中 | 返回非法流转 | 状态不变 | -| 草稿/新建 | 直接通过 | 未提交或未处理不能通过 | 返回非法流转 | 状态不变 | -| 待确认/待复核 | 重复提交结果 | 重复动作不应再次生效 | 返回非法流转或幂等结果 | 只保留原状态 | -| 处理中 | 直接通过 | 未进入确认/复核节点 | 返回非法流转 | 状态不变 | -| 已驳回/待修改 | 直接关闭 | 驳回后不能绕过修改或确认 | 返回非法流转 | 状态不变 | diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/状态流转设计.prompt.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/状态流转设计.prompt.md deleted file mode 100644 index 683b298..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/状态流转设计.prompt.md +++ /dev/null @@ -1,33 +0,0 @@ -# 状态流转设计 Prompt - -```text -请为目标业务模块设计状态流转规则。 - -输入材料: -- 模块名称:<填写> -- 业务对象:<填写> -- 角色列表:<粘贴> -- 当前需求或已有状态枚举:<粘贴> -- 关键动作:<粘贴> - -可参考但必须按项目替换的状态类型: -- 草稿/新建 -- 待处理/待审核 -- 处理中 -- 待确认/待复核 -- 已完成/已关闭 -- 已驳回/待修改 - -输出要求: -1. 状态流转表:当前状态、动作、目标状态、允许角色、前置条件、后置动作、验收。 -2. 非法流转清单:当前状态、尝试动作、问题、系统响应、验收。 -3. 接口设计建议。 -4. 测试点清单。 -5. 不确定点。 - -约束: -- 先出状态表,不要直接写 if else。 -- 终态、越权、缺少说明、重复提交必须覆盖。 -- 每条规则必须有验收方式。 -- 没有需求依据的状态或动作必须标注为建议或待确认。 -``` diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板指标口径检查清单.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板指标口径检查清单.md deleted file mode 100644 index b732c20..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板指标口径检查清单.md +++ /dev/null @@ -1,13 +0,0 @@ -# 看板指标口径检查清单 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 指标含义 | 每个指标有业务解释 | | -| 计算口径 | 每个指标有公式或筛选条件 | | -| 时间范围 | 明确按创建时间、关闭时间还是更新时间统计 | | -| 数据范围 | 不同角色的数据范围清楚 | | -| 空数据 | 空数据展示为 0 或空态,不误导 | | -| 分组一致性 | 分组总和能和总数对齐 | | -| 权限 | 无看板权限不能访问接口 | | -| 验收证据 | 至少有一组样例数据手工校验 | | - diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板指标字典.template.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板指标字典.template.md deleted file mode 100644 index 980755b..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板指标字典.template.md +++ /dev/null @@ -1,12 +0,0 @@ -# <模块名称> 看板指标字典 - -| 指标 | 含义 | 口径 | 数据来源 | 展示方式 | 权限 | 验收 | -| --- | --- | --- | --- | --- | --- | --- | -| 记录总数 | 当前筛选范围内业务记录数量 | count(target_id) | 主业务表 | 指标卡 | 看板权限 | 与列表同条件数量一致 | -| 未完成数量 | 状态不在终态集合内的数量 | status not in `` | 主业务表 | 指标卡 | 看板权限 | 过滤状态验证 | -| 平均完成周期 | 从开始时间到完成时间的平均耗时 | avg(completed_at - started_at) | 主业务表 | 指标卡/趋势 | 看板权限 | 抽样计算一致 | -| 按状态分布 | 各状态记录数量 | group by status | 主业务表/状态字典 | 饼图/柱状图 | 看板权限 | 分组总和等于总数 | -| 按类型分布 | 各业务类型数量 | group by type | 主业务表/字典 | 柱状图 | 看板权限 | 分组总和等于总数 | -| 趋势指标 | 按日期统计新增、完成或失败数量 | group by date_field | 主业务表 | 折线图 | 看板权限 | 日期范围正确 | -| 逾期数量 | 超过 SLA 且未完成的数量 | due_at < now and status not in `` | 主业务表 | 指标卡 | 看板权限 | SLA 口径明确 | -| 人员处理负载 | 按负责人/处理人统计未完成数量 | group by owner_id / assignee_id | 主业务表 | 排行榜 | 管理员/负责人 | 数据范围正确 | diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板指标设计.prompt.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板指标设计.prompt.md deleted file mode 100644 index 80815d9..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板指标设计.prompt.md +++ /dev/null @@ -1,35 +0,0 @@ -# 看板指标设计 Prompt - -```text -请为目标业务模块设计业务看板指标。 - -输入材料: -- 模块名称:<填写> -- 业务对象:<填写> -- 角色与数据范围:<粘贴> -- 状态枚举与时间字段:<粘贴> -- 列表筛选条件:<粘贴> - -指标方向可参考: -- 记录总数 -- 未完成数量 -- 平均完成周期 -- 按状态分布 -- 按类型分布 -- 趋势指标 -- 逾期数量 -- 人员处理负载 - -输出要求: -1. 指标字典:指标名、含义、口径、维度、过滤条件、数据来源、展示方式、权限。 -2. 看板统计接口契约建议。 -3. 空态、错误态、无权限态。 -4. 验收方式。 -5. 不确定点。 - -约束: -- 每个指标必须可计算。 -- 不要只写页面 UI。 -- 指标口径不清必须写入不确定点。 -- 不要把示例指标名直接当成目标项目事实。 -``` diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板接口验收.prompt.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板接口验收.prompt.md deleted file mode 100644 index 4ba40fc..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板接口验收.prompt.md +++ /dev/null @@ -1,29 +0,0 @@ -# 看板接口验收 Prompt - -```text -请为目标业务模块看板统计接口生成验收记录。 - -输入材料: -- 模块名称:<填写> -- 业务对象:<填写> -- 看板指标字典:<粘贴> -- 看板接口契约:<粘贴> -- 权限矩阵:<粘贴> - -输出表格: -验收项 | 账号/条件 | 请求参数 | 预期 | 实际 | 证据 | 结论 - -必须覆盖: -- 有数据 -- 空数据 -- 无权限 -- 过滤条件 -- 指标口径 -- 时间范围 -- 数据范围 - -约束: -- 不要只说“接口正常”。 -- 每个指标至少有一个验收动作。 -- 每个验收动作都要说明账号/角色、请求参数、预期、实际和证据。 -``` diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板接口验收清单.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板接口验收清单.md deleted file mode 100644 index d1f43b4..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板接口验收清单.md +++ /dev/null @@ -1,11 +0,0 @@ -# 看板接口验收清单 - -| 验收项 | 请求条件 | 通过标准 | 结果 | -| --- | --- | --- | --- | -| 默认查询 | 管理员或授权角色默认条件 | 返回完整指标 | | -| 时间过滤 | 指定 start/end | 只统计时间范围内数据 | | -| 类型过滤 | 指定业务类型 | 只统计对应类型 | | -| 空数据 | 无匹配条件 | 返回 0 或空集合 | | -| 无权限 | 未授权账号 | 返回 403 或业务拒绝 | | -| 数据范围 | 普通角色 | 只返回授权范围 | | -| 指标一致性 | 固定样例数据 | 与手工计算一致 | | diff --git a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板接口验收记录.template.md b/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板接口验收记录.template.md deleted file mode 100644 index 91cb3e0..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/design-complex-business-module/assets/看板接口验收记录.template.md +++ /dev/null @@ -1,10 +0,0 @@ -# <模块名称> 看板接口验收记录 - -| 验收项 | 账号/条件 | 请求参数 | 预期 | 实际 | 证据 | 结论 | -| --- | --- | --- | --- | --- | --- | --- | -| 记录总数 | 管理员或授权角色 | 默认筛选 | 返回总数 | | | | -| 空数据 | 授权角色 + 无匹配条件 | 不存在的日期范围或筛选条件 | 返回空集合或 0 | | | | -| 无权限 | 未授权角色 | 默认筛选 | 返回 403 或业务拒绝 | | | | -| 时间过滤 | 授权角色 | start/end | 只统计范围内数据 | | | | -| 数据范围 | 普通业务角色 | 默认筛选 | 只返回本人或授权范围内数据 | | | | -| 指标口径 | 授权角色 | 固定样例数据 | 与手工计算一致 | | | | diff --git a/deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/SKILL.md b/deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/SKILL.md deleted file mode 100644 index 27f43b1..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/SKILL.md +++ /dev/null @@ -1,114 +0,0 @@ ---- -name: generate-project-constraints -description: Use when a user needs to extract project-specific AI coding constraints, team rules, repository rules, .trae rules, Cursor rules, CLAUDE.md/AGENTS.md content, or reusable rules/checklists from an existing software project, including projects without README or explicit instruction files. ---- - -# Generate Project Constraints - -## Core Principle - -Generate constraints from evidence, not preference. Treat instruction files as optional inputs: if they exist, read them first; if not, use code-only mode and infer cautiously from config, source patterns, tests, CI, and runtime behavior. - -Every output rule must be labeled as `硬约束`, `观察性约束`, or `待确认约束`, and every non-obvious rule must cite source paths. - -## Evidence Order - -Read available sources in this order: - -1. Explicit instructions: `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `.trae/rules`, `.cursor/rules`, `.cursorrules`, `README*`, `CONTRIBUTING*`, team docs. -2. Build/runtime manifests: `package.json`, lockfiles, `pom.xml`, `build.gradle`, `.env*`, `application*.yml`, Docker files, compose files. -3. Quality gates: ESLint, Prettier, Stylelint, Checkstyle, Spotless, PMD, Jest/Vitest/JUnit/Playwright configs, CI workflows. -4. Source patterns: API layer, response wrappers, auth/permission flow, routing, state management, controller/service/repository or mapper layers, error handling, audit/logging. -5. Existing examples: representative modules that already implement the pattern requested by the user. - -If a source category is absent, record it in `缺失证据`, then continue. Never block only because `README.md`, `CLAUDE.md`, or `AGENTS.md` is missing. - -## Constraint Levels - -| Level | Use When | Wording | -| --- | --- | --- | -| `硬约束` | Directly proven by config, build files, CI, runtime settings, or explicit team docs | `必须` / `禁止` | -| `观察性约束` | Repeated pattern appears in existing code but no written rule exists | `默认采用` / `优先复用` | -| `待确认约束` | Needed for delivery but not provable from the repo | `需团队确认` | - -Do not upgrade an observed pattern into a hard rule unless a source explicitly requires it. - -## Workflow - -1. Define scope: target repo path, output path, desired rule categories, and whether the user wants installable Skill assets or plain rules. -2. Inventory sources with fast shell commands such as `rg --files`, `find`, `sed`, and focused `rg` searches. -3. Extract facts into an evidence table: source path, observed fact, affected area, confidence, and candidate rule. -4. Normalize candidate rules into the output contract below. -5. Validate each rule against at least one source path or mark it `待确认约束`. -6. Write rules and checklists. Prefer small files such as `rules/backend.md`, `rules/frontend.md`, `rules/security.md`, `rules/testing.md`, `rules/delivery.md`. -7. Run verification: placeholder scan, broken-path spot checks, and a final pass that every hard/observed rule has evidence. - -## Output Contract - -When the user asks to generate constraints, produce these artifacts unless they request a lighter output: - -```text -ai-dev-assets/ -├── rules/ -│ ├── global.md -│ ├── frontend.md -│ ├── backend.md -│ ├── security.md -│ ├── testing.md -│ └── delivery.md -├── templates/ -│ └── 项目约束提取表.template.md -└── checklists/ - └── 项目约束验收清单.md -``` - -Each rule file should use this structure: - -```md -## 规则名称 - -- 级别:硬约束 | 观察性约束 | 待确认约束 -- 适用场景: -- 必须: -- 禁止: -- 来源证据: -- 验收方式: -- 迁移说明: -``` - -Use `assets/项目约束提取表.template.md` and `assets/项目约束验收清单.md` as copyable starting points when helpful. - -## Code-Only Mode - -Use this when no explicit instruction files exist. - -Required behavior: - -- State that explicit project docs were not found. -- Build constraints from manifests, configs, CI, tests, and repeated source patterns. -- Label all pattern-derived rules as `观察性约束`. -- Create a `待确认约束` section for business delivery rules that the repo cannot prove, such as review gates, branch policy, audit requirements, test minimums, and release process. - -Good wording: - -```md -本项目未发现 `AGENTS.md`、`CLAUDE.md`、`.trae/rules` 或 `README.md` 中的明确 AI 协作规则。以下约束来自配置文件和源码模式;未能由仓库证明的内容已标记为待确认。 -``` - -## Quality Gates - -Before claiming completion: - -- List generated files. -- Scan generated files for unfinished placeholders, dummy names, and empty headings. -- Check hard and observed constraints cite source paths. -- Confirm missing evidence is not silently converted into rules. -- Report which sources were read and which rule categories remain `待确认`. - -## Common Mistakes - -- Do not require `CLAUDE.md`, `AGENTS.md`, or `README.md` to exist. -- Do not turn general best practices into project rules without evidence. -- Do not merge business constraints, technical constraints, and delivery constraints into one vague list. -- Do not omit verification steps; rules are useful only if a learner or team can check them. -- Do not copy one project's specific constraints into unrelated projects. diff --git a/deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/agents/openai.yaml b/deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/agents/openai.yaml deleted file mode 100644 index 43f4885..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/agents/openai.yaml +++ /dev/null @@ -1,7 +0,0 @@ -interface: - display_name: "项目约束生成" - short_description: "从项目证据提取可执行规则、模板和验收清单" - default_prompt: "Use $generate-project-constraints to extract project-specific AI coding constraints, rules, and checklists from this repository." - -policy: - allow_implicit_invocation: true diff --git a/deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/assets/项目约束提取表.template.md b/deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/assets/项目约束提取表.template.md deleted file mode 100644 index 2fc3a9f..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/assets/项目约束提取表.template.md +++ /dev/null @@ -1,43 +0,0 @@ -# 项目约束提取表 - -## 项目信息 - -| 字段 | 内容 | -| --- | --- | -| 项目名称 | | -| 项目路径 | | -| 提取日期 | | -| 提取人 | | -| 目标输出 | rules / prompts / checklists / Skill | - -## 证据扫描记录 - -| 证据类型 | 文件或目录 | 是否存在 | 关键发现 | 备注 | -| --- | --- | --- | --- | --- | -| 显式规则 | AGENTS.md / CLAUDE.md / .trae/rules / .cursor/rules | | | | -| 项目说明 | README / CONTRIBUTING / docs | | | | -| 后端构建 | pom.xml / build.gradle / settings.gradle | | | | -| 前端构建 | package.json / lockfile / vite.config / tsconfig | | | | -| 运行配置 | .env / application.yml / Dockerfile / compose | | | | -| 质量工具 | ESLint / Prettier / Checkstyle / CI / test config | | | | -| 源码模式 | API / Controller / Service / Router / Store | | | | -| 权限与安全 | auth / permission / role / route guard / interceptor | | | | -| 数据与迁移 | schema / migration / mapper / repository | | | | - -## 候选约束 - -| 编号 | 分类 | 约束内容 | 级别 | 来源证据 | 影响范围 | 验收方式 | 迁移说明 | -| --- | --- | --- | --- | --- | --- | --- | --- | -| C-001 | global / frontend / backend / security / testing / delivery | | 硬约束 / 观察性约束 / 待确认约束 | | | | | - -## 缺失证据 - -| 缺失项 | 影响 | 处理方式 | -| --- | --- | --- | -| | | 标记为待确认 / 询问团队 / 暂不生成规则 | - -## 待团队确认 - -| 问题 | 为什么需要确认 | 建议默认值 | 决策人 | -| --- | --- | --- | --- | -| 是否强制单测 | 仓库未发现 CI 或测试要求时无法证明 | 新增核心逻辑需补测试或说明豁免 | 技术负责人 | diff --git a/deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/assets/项目约束生成.prompt.md b/deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/assets/项目约束生成.prompt.md deleted file mode 100644 index 7c25980..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/assets/项目约束生成.prompt.md +++ /dev/null @@ -1,61 +0,0 @@ -# 项目约束生成 Prompt - -请为下面项目生成可复用的 AI 开发约束资产。 - -## 项目输入 - -- 项目路径: -- 输出路径: -- 目标项目类型: -- 是否需要生成 rules: -- 是否需要生成 checklist: -- 是否需要生成 Skill: - -## 执行要求 - -1. 先扫描项目中是否存在显式规则文件,例如 `AGENTS.md`、`CLAUDE.md`、`.trae/rules`、`.cursor/rules`、`README.md`、`CONTRIBUTING.md`。 -2. 如果不存在显式规则文件,不要停止,进入 code-only 模式。 -3. 从构建配置、运行配置、源码模式、测试配置、CI 配置中提取约束。 -4. 每条约束必须标注为: - - `硬约束`:由配置、CI、构建或显式规则直接证明。 - - `观察性约束`:由多个源码样例归纳,但没有显式规则。 - - `待确认约束`:项目无法证明,但交付前必须团队确认。 -5. 每条硬约束和观察性约束必须标注来源路径。 -6. 不要把通用最佳实践当成项目事实。 -7. 不要把某个项目的专用规则复制到另一个项目。 - -## 输出格式 - -请生成: - -```text -rules/global.md -rules/frontend.md -rules/backend.md -rules/security.md -rules/testing.md -rules/delivery.md -templates/项目约束提取表.template.md -checklists/项目约束验收清单.md -``` - -每条规则使用: - -```md -## 规则名称 - -- 级别: -- 适用场景: -- 必须: -- 禁止: -- 来源证据: -- 验收方式: -- 迁移说明: -``` - -最后输出: - -- 已读来源。 -- 缺失证据。 -- 待确认问题。 -- 不能迁移到其他项目的项目特有约束。 diff --git a/deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/assets/项目约束验收清单.md b/deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/assets/项目约束验收清单.md deleted file mode 100644 index 78e13c6..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/assets/项目约束验收清单.md +++ /dev/null @@ -1,44 +0,0 @@ -# 项目约束验收清单 - -## 基础完整性 - -- [ ] 已列出目标项目路径和输出路径。 -- [ ] 已说明是否发现显式规则文件。 -- [ ] 已记录缺失证据,没有因为缺失 README / CLAUDE.md / AGENTS.md 而停止。 -- [ ] 已区分 `硬约束`、`观察性约束`、`待确认约束`。 -- [ ] 每条硬约束都有明确来源路径。 -- [ ] 每条观察性约束至少有一个代表性源码样例。 -- [ ] 每条待确认约束都写明需要谁确认。 - -## 规则质量 - -- [ ] 规则包含适用场景。 -- [ ] 规则包含必须做什么。 -- [ ] 规则包含禁止做什么,或说明没有禁止项。 -- [ ] 规则包含验收方式。 -- [ ] 规则包含迁移说明,说明换项目时能否复用。 -- [ ] 规则没有把通用最佳实践冒充为项目事实。 -- [ ] 规则没有把某个单一代码样例冒充为全项目硬约束。 - -## 覆盖范围 - -- [ ] global 约束覆盖项目理解、证据引用、事实/推断区分。 -- [ ] frontend 约束覆盖包管理器、框架、路径别名、API 层、路由和状态管理。 -- [ ] backend 约束覆盖语言版本、框架、分层、响应结构、异常和数据访问。 -- [ ] security 约束覆盖认证、权限、敏感信息、数据范围和接口防护。 -- [ ] testing 约束覆盖已有测试工具、运行命令和缺失测试时的处理。 -- [ ] delivery 约束覆盖计划、Review、验收记录、发布或交付要求。 - -## 可复用性 - -- [ ] 某个项目特有规则没有写进通用规则。 -- [ ] 每条规则都标注了“可直接复用 / 需重新扫描 / 不可迁移”。 -- [ ] 换项目时必须重新扫描的内容已经列出。 -- [ ] 可以从本清单反推出生成步骤。 - -## 交付检查 - -- [ ] 生成文件中没有未完成占位符、示例假名或空标题。 -- [ ] Markdown 链接或路径经过抽样检查。 -- [ ] 规则文件标题不为空。 -- [ ] 交付说明列出已读来源、未读来源和仍需确认的问题。 diff --git a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/SKILL.md b/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/SKILL.md deleted file mode 100644 index 916473a..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/SKILL.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -name: review-business-delivery -description: Use when reviewing complex business delivery artifacts, AI-generated plans, PR diffs, or module designs for permission gaps, illegal state transitions, missing audit logs, weak exception handling, unclear dashboard metrics, missing tests, or incomplete acceptance evidence. ---- - -# Review Business Delivery - -## Core Principle - -Review behavior and risk before wording. Findings must be evidence-based, severity-ranked, and tied to a verification action. - -The review output should help the team decide whether to accept, conditionally accept, or reject the delivery. - -## Inputs - -Use any available inputs: - -- Requirement scope and non-goals. -- RBAC matrix and permission control plan. -- State transition table and illegal transition list. -- Audit SOP and exception-state list. -- Dashboard metric dictionary and API acceptance record. -- Code diff, API contract, test result, Browser verification record, or PR description. -- Project rules and constraints. - -If source evidence is missing, create an `证据缺口` section instead of guessing. - -## Review Flow - -1. Confirm delivery scope: what is being reviewed and what is outside scope. -2. Check source evidence: paths, rules, design tables, test results, and verification records. -3. Review permission risk: page/menu, button, backend API, and data scope. -4. Review state risk: legal transitions, illegal transitions, terminal states, duplicate submits, and role restrictions. -5. Review audit risk: action coverage, field completeness, masking, failure behavior, and consistency with business writes. -6. Review exception risk: empty, missing, invalid, forbidden, duplicate, concurrent, and downstream failure states. -7. Review dashboard risk: formula, filter, dimension, data source, permission, empty state, and manual calculation. -8. Review tests and acceptance: commands, roles, sample data, browser evidence, and unresolved risks. -9. Output findings sorted by severity. - -## Severity - -| Level | Meaning | -| --- | --- | -| P0 | Data loss, privilege bypass, irreversible wrong state, or release-blocking failure | -| P1 | Security, permission, audit, or state-machine defect with realistic business impact | -| P2 | Missing acceptance evidence, unclear metric, weak exception handling, or test gap | -| P3 | Maintainability, naming, documentation, or minor consistency issue | - -## Output Contract - -```md -## Review 结论 - -通过 / 有条件通过 / 不通过 - -## 问题清单 - -| 等级 | 模块 | 问题 | 证据 | 风险 | 修复建议 | 验收方式 | -| --- | --- | --- | --- | --- | --- | --- | - -## 证据缺口 - -| 缺口 | 影响 | 需要谁补充 | -| --- | --- | --- | - -## 待确认 - -| 问题 | 决策人 | 建议默认值 | -| --- | --- | --- | -``` - -Use the templates and checklists in `assets/` when writing files. - -## Quality Gates - -- Findings lead the response; summaries are secondary. -- Every P0/P1 finding cites a source path, table row, endpoint, role, state, metric, or verification record. -- Every finding has a concrete fix direction and acceptance method. -- Review does not accept frontend-only security controls. -- Missing tests or missing evidence are called out explicitly. -- Unclear business rules are marked `待确认`, not invented. - -## Common Mistakes - -- Do not write "整体不错" before listing risks. -- Do not merge all issues into one generic recommendation. -- Do not downgrade permission bypass, illegal state transitions, or missing audit for critical actions. -- Do not claim an issue is fixed without verification evidence. -- Do not review only code style when the requested delivery is business behavior. diff --git a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/agents/openai.yaml b/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/agents/openai.yaml deleted file mode 100644 index f2dc7c9..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/agents/openai.yaml +++ /dev/null @@ -1,7 +0,0 @@ -interface: - display_name: "业务交付 Review" - short_description: "审查权限状态审计异常态看板测试与验收风险" - default_prompt: "Use $review-business-delivery to review complex business delivery artifacts for permission, state, audit, exception, and dashboard risks." - -policy: - allow_implicit_invocation: true diff --git a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/AIReview报告模板.md b/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/AIReview报告模板.md deleted file mode 100644 index 0fe0982..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/AIReview报告模板.md +++ /dev/null @@ -1,28 +0,0 @@ -# AI Review 报告模板 - -## Review 输入 - -- PR diff: -- 需求说明: -- rules: -- 测试结果: -- 历史问题: - -## 结论 - -通过 / 有条件通过 / 不通过 - -## 问题清单 - -| 等级 | 文件/模块 | 问题 | 触发规则 | 风险 | 建议 | 测试/验收 | -| --- | --- | --- | --- | --- | --- | --- | - -## 不确定点 - -| 不确定点 | 需要谁确认 | 验证方式 | -| --- | --- | --- | - -## 是否建议合入 - -说明理由。 - diff --git a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/业务交付验收清单.md b/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/业务交付验收清单.md deleted file mode 100644 index cea4742..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/业务交付验收清单.md +++ /dev/null @@ -1,59 +0,0 @@ -# 业务交付验收清单 - -本清单用于判断复杂业务模块是否达到可验收状态,适用于后台管理、审批流、运营系统、CRM、低代码二开和数据看板等项目。 - -## 项目理解 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 能说明模块边界 | 说明模块负责什么、不负责什么 | | -| 能定位关键目录 | 能指出前端页面、API 层、后端入口、服务层和数据层位置 | | -| 能区分事实和推断 | 对没有源码依据的业务规则标注为推断或待确认 | | -| 能记录不确定点 | 不确定内容没有被包装成确定事实 | | - -## 需求与接口 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 用户故事完整 | 覆盖主要角色、关键动作和限制条件 | | -| 非目标范围明确 | 明确本次不做的能力、数据迁移、生产部署或外部集成 | | -| 接口契约完整 | 覆盖列表、详情、新增、编辑、分配、状态、审计和看板 | | -| 失败场景明确 | 每个关键接口至少有权限、参数、状态或数据不存在失败场景 | | -| 验收方式明确 | 每个关键需求都有可执行检查方法 | | - -## 权限与状态 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 权限矩阵完整 | 覆盖页面级、按钮级、接口级和数据范围 | | -| 权限字符合理 | 命名统一,并标注为建议或源码事实 | | -| 后端权限未缺失 | 没有用前端按钮隐藏替代后端接口控制 | | -| 状态流转表完整 | 覆盖初始态、处理中、确认/审核态、终态和驳回态 | | -| 非法流转明确 | 列出关键非法流转、拒绝原因和验收方式 | | - -## 审计、异常态与看板 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 审计操作覆盖关键动作 | 创建、编辑、分配、状态流转、关闭/归档等关键动作被覆盖 | | -| 审计字段可追溯 | 包含 actor、action、targetId、oldValue、newValue、source | | -| 敏感信息处理 | oldValue/newValue 有脱敏说明 | | -| 异常态可测试 | 权限不足、重复提交、非法状态、数据不存在、并发修改均覆盖 | | -| 看板指标可计算 | 每个指标有口径、维度、过滤条件和数据来源建议 | | - -## Review 与交付 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| Review 有证据 | 问题能回到路径、表格、接口、角色或业务规则 | | -| 风险按严重程度排序 | 高风险问题优先展示 | | -| 建议可行动 | 每条建议有修复方向和验收方式 | | -| 交付物齐全 | 设计表、Review 报告、验证记录和未闭环事项完整 | | -| 能沉淀资产 | 高频问题能转成规则、检查清单、Prompt、反例或 Skill | | - -## 判定原则 - -- 优先看证据,不优先看文字是否漂亮。 -- 优先看权限、状态、审计、异常态,不只看 CRUD。 -- 优先看不确定点是否被诚实标出。 -- 优先看结论能否被角色账号、接口请求、样例数据或浏览器操作验证。 diff --git a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/审计日志检查清单.md b/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/审计日志检查清单.md deleted file mode 100644 index 1eebc8f..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/审计日志检查清单.md +++ /dev/null @@ -1,12 +0,0 @@ -# 审计日志检查清单 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 创建记录 | 创建业务记录后能查询审计记录 | | -| 字段变更 | 编辑关键字段记录 oldValue/newValue | | -| 分配记录 | 分配或转交记录原处理人和新处理人 | | -| 状态记录 | 状态流转记录原状态和新状态 | | -| 操作人 | actor_id 和 actor_name 可追溯 | | -| 来源 | source 或 request_id 可追溯 | | -| 脱敏 | 敏感字段不明文进入审计 | | -| 失败处理 | 操作失败不写成功审计 | | diff --git a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/异常态检查清单.md b/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/异常态检查清单.md deleted file mode 100644 index d53299c..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/异常态检查清单.md +++ /dev/null @@ -1,12 +0,0 @@ -# 异常态检查清单 - -| 异常态 | 必须验证 | 结果 | -| --- | --- | --- | -| 空列表 | 页面显示空状态,不报错 | | -| 详情不存在 | 提示记录不存在,页面不崩溃 | | -| 权限不足 | 前端提示或隐藏,后端拒绝 | | -| 非法状态 | 返回非法流转,状态不变 | | -| 重复提交 | 只成功一次,只写一条有效审计 | | -| 参数缺失 | 返回参数错误,业务数据不变 | | -| 并发修改 | 拒绝覆盖或提示刷新 | | -| 看板无数据 | 图表显示空态,不显示假数据 | | diff --git a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/权限上线前检查清单.md b/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/权限上线前检查清单.md deleted file mode 100644 index e7bf421..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/权限上线前检查清单.md +++ /dev/null @@ -1,11 +0,0 @@ -# 权限上线前检查清单 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 菜单权限 | 目标模块菜单只对授权角色可见 | | -| 按钮权限 | 新增、编辑、删除、分配、状态按钮按权限显示 | | -| 接口权限 | 无权限账号直接请求敏感接口失败 | | -| 数据权限 | 普通用户只能看到授权范围内数据 | | -| 权限字符 | 前端按钮、菜单配置、后端接口命名一致 | | -| 审计 | 权限敏感操作成功后有审计记录 | | -| 反例验证 | 无权限账号不能绕过前端执行删除/分配 | | diff --git a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/状态流转检查清单.md b/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/状态流转检查清单.md deleted file mode 100644 index 759c879..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/状态流转检查清单.md +++ /dev/null @@ -1,11 +0,0 @@ -# 状态流转检查清单 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 状态枚举 | 所有状态有统一枚举或字典 | | -| 合法流转 | 状态流转表中的合法路径可执行 | | -| 非法流转 | 非法路径被拒绝且状态不变 | | -| 角色限制 | 不同角色只能执行被授权动作 | | -| 前置条件 | 驳回原因、关闭条件、处理人、审批意见等校验完整 | | -| 审计日志 | 每次成功流转写审计 | | -| 重复提交 | 重复点击不会产生重复有效流转 | | diff --git a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/看板指标口径检查清单.md b/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/看板指标口径检查清单.md deleted file mode 100644 index b732c20..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/看板指标口径检查清单.md +++ /dev/null @@ -1,13 +0,0 @@ -# 看板指标口径检查清单 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 指标含义 | 每个指标有业务解释 | | -| 计算口径 | 每个指标有公式或筛选条件 | | -| 时间范围 | 明确按创建时间、关闭时间还是更新时间统计 | | -| 数据范围 | 不同角色的数据范围清楚 | | -| 空数据 | 空数据展示为 0 或空态,不误导 | | -| 分组一致性 | 分组总和能和总数对齐 | | -| 权限 | 无看板权限不能访问接口 | | -| 验收证据 | 至少有一组样例数据手工校验 | | - diff --git a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/看板接口验收清单.md b/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/看板接口验收清单.md deleted file mode 100644 index d1f43b4..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/看板接口验收清单.md +++ /dev/null @@ -1,11 +0,0 @@ -# 看板接口验收清单 - -| 验收项 | 请求条件 | 通过标准 | 结果 | -| --- | --- | --- | --- | -| 默认查询 | 管理员或授权角色默认条件 | 返回完整指标 | | -| 时间过滤 | 指定 start/end | 只统计时间范围内数据 | | -| 类型过滤 | 指定业务类型 | 只统计对应类型 | | -| 空数据 | 无匹配条件 | 返回 0 或空集合 | | -| 无权限 | 未授权账号 | 返回 403 或业务拒绝 | | -| 数据范围 | 普通角色 | 只返回授权范围 | | -| 指标一致性 | 固定样例数据 | 与手工计算一致 | | diff --git a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/阶段Review.prompt.md b/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/阶段Review.prompt.md deleted file mode 100644 index 57a6da6..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/阶段Review.prompt.md +++ /dev/null @@ -1,32 +0,0 @@ -# 阶段 Review Prompt - -```text -请对目标业务模块阶段交付物做 Review。 - -输入材料: -- 模块名称:<填写> -- 业务对象:<填写> -- RBAC 权限矩阵 -- 权限控制方案 -- 状态流转表 -- 非法流转清单 -- 审计日志 SOP -- 异常态清单 -- 看板指标字典 -- 看板接口验收记录 -- 测试结果或验证记录 - -输出表格: -等级 | 模块 | 问题 | 触发规则 | 风险 | 修复建议 | 验收 - -要求: -1. 问题按 P0/P1/P2/P3 分级。 -2. 每个问题必须说明触发了哪条规则。 -3. 每条建议必须有验收方式。 -4. 输出不确定点和需要人工确认的内容。 - -约束: -- 不要只说整体不错。 -- 不要直接改代码。 -- 不要把示例模块、示例角色或示例接口当成目标项目事实。 -``` diff --git a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/阶段Review报告.template.md b/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/阶段Review报告.template.md deleted file mode 100644 index 5a56f75..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/阶段Review报告.template.md +++ /dev/null @@ -1,27 +0,0 @@ -# 阶段 Review 报告 - -## Review 输入 - -- RBAC 权限矩阵: -- 权限控制方案: -- 状态流转表: -- 非法流转清单: -- 审计日志 SOP: -- 异常态清单: -- 看板指标字典: -- 看板接口验收记录: - -## 问题清单 - -| 等级 | 模块 | 问题 | 触发规则 | 风险 | 修复建议 | 验收 | -| --- | --- | --- | --- | --- | --- | --- | -| P1 | 权限 | 敏感操作只做前端按钮隐藏,后端缺少权限校验 | 前端按钮不是安全边界 | 可绕过前端直接调用接口完成高风险操作 | 后端补权限校验和数据范围校验 | 无权限账号直接请求返回 403 或业务拒绝 | - -## 不确定点 - -| 不确定点 | 影响 | 需要谁确认 | -| --- | --- | --- | - -## 结论 - -通过 / 有条件通过 / 不通过 diff --git a/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/SKILL.md b/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/SKILL.md deleted file mode 100644 index 67b0c6c..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/SKILL.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -name: track-review-fix-loop -description: Use when turning review findings into a fix-tracking record, deciding whether findings are valid, mapping fixes to files, verifying results, documenting unresolved items, or deciding which issues should become reusable rules, checklists, prompts, examples, or team assets. ---- - -# Track Review Fix Loop - -## Core Principle - -A review issue is not closed by saying "fixed". It closes only when the team records validity, changed artifacts, verification evidence, unresolved risk, and whether the lesson should become a reusable asset. - -## Inputs - -- Review report with numbered findings. -- Requirement or acceptance criteria. -- Code diff, design document, generated tables, or changed files. -- Test results, Browser verification evidence, API response samples, or manual validation notes. -- Existing team rules, checklists, examples, and asset index. - -If validation evidence is missing, mark the item as `未闭环`. - -## Status Model - -Use one of these statuses for each review item: - -| Status | Meaning | -| --- | --- | -| `成立-已修复` | Finding is valid, changed artifacts are listed, and verification evidence exists | -| `成立-暂不修复` | Finding is valid but deferred with reason, owner, and follow-up date | -| `不成立` | Finding is invalid with evidence | -| `需确认` | Finding depends on business or technical decision not available in current evidence | -| `未闭环` | Fix is claimed but evidence is missing | - -## Workflow - -1. Normalize review findings: ensure each issue has an ID, severity, module, risk, and suggested verification. -2. Decide validity: `成立`, `不成立`, or `需确认`; cite evidence. -3. Map valid fixes to changed files, tables, prompts, rules, or templates. -4. Verify each fix using commands, API checks, browser steps, role-based tests, or manual calculation. -5. Identify regressions: related permissions, states, audit actions, dashboard metrics, and exception states to re-check. -6. Decide asset sedimentation: rule, checklist, prompt, Command, Skill, example, or no asset. -7. Output unresolved items with owner and next step. - -## Output Contract - -```md -## 修复闭环结论 - -完成 / 部分完成 / 未完成 - -## 修复记录 - -| Review 编号 | 等级 | 问题 | 状态 | 修改文件/产物 | 验证证据 | 剩余风险 | 资产沉淀 | -| --- | --- | --- | --- | --- | --- | --- | --- | - -## 未闭环事项 - -| 编号 | 原因 | 负责人 | 下一步 | 截止时间 | -| --- | --- | --- | --- | --- | - -## 资产更新建议 - -| 来源问题 | 资产类型 | 建议文件 | 更新内容 | -| --- | --- | --- | --- | -``` - -Use `assets/修复记录.template.md` and `assets/Review修复闭环检查清单.md` when creating files. - -## Asset Decision Guide - -| If the issue is... | Asset | -| --- | --- | -| Repeated project convention violation | rule | -| Repeated review checklist miss | checklist | -| Reusable question or generation pattern | prompt | -| One-step repeatable agent action | command | -| Multi-step judgment workflow | skill | -| Good or bad teaching sample | example | - -## Quality Gates - -- Every fixed issue has verification evidence. -- Every deferred issue has owner and next step. -- Every invalid issue has source evidence, not opinion. -- P0/P1 issues cannot be marked complete without direct verification. -- Asset updates are specific file suggestions, not vague "沉淀经验". - -## Common Mistakes - -- Do not close issues with "已修复" without evidence. -- Do not erase invalid findings; keep why they are invalid. -- Do not mark business decisions as technical fixes. -- Do not create a Skill for a one-off checklist item. -- Do not ignore regressions around permissions, state transitions, audit logs, and dashboard metrics. diff --git a/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/agents/openai.yaml b/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/agents/openai.yaml deleted file mode 100644 index 500b19a..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/agents/openai.yaml +++ /dev/null @@ -1,7 +0,0 @@ -interface: - display_name: "Review 修复闭环" - short_description: "跟踪Review问题成立判断修复验证未闭环与资产沉淀" - default_prompt: "Use $track-review-fix-loop to turn review findings into verified fixes, unresolved decisions, and reusable team assets." - -policy: - allow_implicit_invocation: true diff --git a/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/Review修复闭环检查清单.md b/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/Review修复闭环检查清单.md deleted file mode 100644 index 39df455..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/Review修复闭环检查清单.md +++ /dev/null @@ -1,12 +0,0 @@ -# Review 修复闭环检查清单 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 问题编号 | 每个 Review 问题有编号 | | -| 成立判断 | 每个问题说明是否成立 | | -| 修改说明 | 成立问题说明修改文件和内容 | | -| 验证结果 | 每个修复有验证证据 | | -| 未修复说明 | 暂不修复的问题有原因和后续计划 | | -| 资产沉淀 | 有复用价值的问题已进入检查清单或反例库 | | -| 回归检查 | 修复后重新检查相关权限/状态/审计/看板 | | - diff --git a/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/修复记录.template.md b/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/修复记录.template.md deleted file mode 100644 index b98ac20..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/修复记录.template.md +++ /dev/null @@ -1,7 +0,0 @@ -# Review 修复记录 - -| Review 编号 | 问题 | 是否成立 | 修改文件 | 修改说明 | 验证结果 | 资产沉淀 | -| --- | --- | --- | --- | --- | --- | --- | -| R01 | 敏感操作接口缺少后端权限校验 | 是 | `` | 增加权限校验和数据范围校验 | 无权限请求被拒绝 | 权限检查清单 | -| R02 | 终态记录可被直接改回处理中 | 是 | `` | 补非法流转校验 | terminal -> processing 失败 | 状态流转检查清单 | -| R03 | 看板关闭周期口径不清 | 是 | 指标字典/看板接口 | 明确 create_time 到 close_time | 指标说明已更新 | 看板指标口径清单 | diff --git a/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/修复记录生成.prompt.md b/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/修复记录生成.prompt.md deleted file mode 100644 index 24fcfe7..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/修复记录生成.prompt.md +++ /dev/null @@ -1,24 +0,0 @@ -# 修复记录生成 Prompt - -```text -请根据阶段 Review 报告生成修复记录。 - -输入材料: -- Review 报告:<粘贴> -- 已完成修改说明:<粘贴> -- 验证结果:<粘贴> - -输出表格: -Review 编号 | 问题 | 是否成立 | 修改文件 | 修改说明 | 验证结果 | 资产沉淀 - -要求: -1. 如果问题不成立,说明理由和依据。 -2. 如果问题成立,说明修改了哪些文件。 -3. 每条修复必须有验证结果。 -4. 判断是否需要沉淀为资产,例如权限检查清单、状态流转检查清单、审计日志检查清单。 - -约束: -- 不要模糊写“已修复”。 -- 没有验证结果的修复不能算完成。 -``` - diff --git a/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/审计日志缺失反例.md b/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/审计日志缺失反例.md deleted file mode 100644 index f3982b1..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/审计日志缺失反例.md +++ /dev/null @@ -1,31 +0,0 @@ -# 审计日志缺失反例 - -## 场景 - -业务记录分配处理人成功,但没有记录审计日志。 - -## 问题表现 - -| 操作 | 结果 | -| --- | --- | -| 张三将记录分配给李四 | 处理人变更成功 | -| 查询审计日志 | 没有处理人变更记录 | - -## 风险 - -无法追溯处理人变化,出现延误或争议时无法定位责任。 - -## 修复要求 - -1. 分配成功后写审计日志。 -2. 记录原处理人、新处理人、操作人、时间、来源。 -3. 分配失败不写成功审计。 -4. 多步写入需要事务一致性。 - -## 验收 - -| 操作 | 预期 | -| --- | --- | -| 分配成功 | 审计列表出现 ASSIGN 或同等业务动作记录 | -| 分配失败 | 不出现成功审计 | -| 查询详情 | 审计记录可在详情页查看 | diff --git a/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/权限遗漏反例.md b/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/权限遗漏反例.md deleted file mode 100644 index d1406a1..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/权限遗漏反例.md +++ /dev/null @@ -1,32 +0,0 @@ -# 权限遗漏反例 - -## 场景 - -敏感删除或作废按钮在前端按权限隐藏,但后端接口没有权限校验。 - -## 问题代码表现 - -| 层级 | 表现 | -| --- | --- | -| 前端 | 普通业务账号看不到删除按钮 | -| 后端 | 直接请求删除或作废接口仍可成功 | -| 审计 | 删除记录可能缺失 | - -## 风险 - -攻击者或误操作用户可以绕过前端直接调用接口删除或作废业务数据。 - -## 修复要求 - -1. 后端删除接口增加权限校验。 -2. 删除前校验数据范围。 -3. 删除成功写审计日志。 -4. 无权限账号直接请求接口返回 403 或业务拒绝。 - -## 验收 - -| 账号 | 操作 | 预期 | -| --- | --- | --- | -| 管理员或授权角色 | 删除允许删除的数据 | 成功并写审计 | -| 普通业务角色 | 直接请求删除接口 | 失败 | -| 只读角色 | 前端访问删除按钮 | 不可见 | diff --git a/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/看板指标口径不清反例.md b/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/看板指标口径不清反例.md deleted file mode 100644 index d636fcc..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/看板指标口径不清反例.md +++ /dev/null @@ -1,30 +0,0 @@ -# 看板指标口径不清反例 - -## 场景 - -看板展示“平均完成周期”,但没有说明从哪个时间到哪个时间。 - -## 问题表现 - -| 指标 | 当前说明 | -| --- | --- | -| 平均完成周期 | 最近记录平均处理时间 | - -## 风险 - -不同人理解不同:可能是创建到完成,也可能是开始处理到完成,导致管理判断失真。 - -## 修复要求 - -1. 明确定义为 `completed_at - created_at`、`closed_at - created_at` 或其他确定口径。 -2. 明确是否排除未完成记录。 -3. 明确时间范围过滤字段。 -4. 用样例数据手工校验。 - -## 验收 - -| 样例 | 预期 | -| --- | --- | -| 记录 A 创建 1 日、完成 3 日 | 周期 2 天 | -| 未完成记录 | 不计入平均完成周期 | -| 过滤 7 月 | 只统计 7 月范围内符合口径的数据 | diff --git a/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/非法状态流转反例.md b/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/非法状态流转反例.md deleted file mode 100644 index 0606610..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/track-review-fix-loop/assets/非法状态流转反例.md +++ /dev/null @@ -1,30 +0,0 @@ -# 非法状态流转反例 - -## 场景 - -已完成或已关闭的业务记录可以直接被改回处理中。 - -## 问题表现 - -| 当前状态 | 动作 | 结果 | -| --- | --- | --- | -| 已关闭 | 开始处理 | 状态变成处理中 | - -## 风险 - -终态记录被重新打开但没有重开原因、没有审计、没有负责人确认,导致统计和责任链失真。 - -## 修复要求 - -1. Service 层集中校验状态流转。 -2. 已完成或已关闭记录只能通过“重新打开”动作回到指定状态。 -3. 重新打开必须填写原因。 -4. 所有成功流转写审计日志。 - -## 验收 - -| 操作 | 预期 | -| --- | --- | -| closed -> processing | 失败,状态不变 | -| closed -> reopen -> new | 成功,必须有原因 | -| 重开成功 | 审计记录 oldValue=closed, newValue=new | diff --git a/deliverables/客户AI工程Skill交付包.zip b/deliverables/客户AI工程Skill交付包.zip index 541ff94085c501d1c41910cd4e984ac6db5aeed1..53e981207396d1c8076de4b7a5d0017d51f696e1 100644 GIT binary patch delta 52108 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 V1#KLzQ?si=q|rh^RNai}{{uWJfqDP{ delta 26869 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 diff --git a/deliverables/客户AI工程Skill交付包/README.md b/deliverables/客户AI工程Skill交付包/README.md index b5ff6e0..925175c 100644 --- a/deliverables/客户AI工程Skill交付包/README.md +++ b/deliverables/客户AI工程Skill交付包/README.md @@ -1,8 +1,8 @@ -# 客户 AI 工程 Skill 交付包 +# 客户 AI 工程资产交付包 -本交付包提供 4 个可复用 AI 工程 Skill,覆盖真实项目中从项目约束生成、复杂业务设计、交付 Review 到修复闭环的完整流程。 +本包是第五、六天课程配套的正式资产交付物,用于把 AI 编程中的项目理解、约束提取、复杂业务设计、交付 Review 和修复闭环沉淀为可维护的团队资产。 -这些 Skill 不是单次 Prompt,而是可安装、可复制、可随项目持续维护的团队资产。 +它不包含客户代码、服务地址、账号、令牌、真实业务数据或未经验证的项目结论。所有业务字段、权限码、接口和状态均以占位符表达,使用时必须从目标项目重新取证。 ## 交付内容 @@ -10,37 +10,46 @@ 客户AI工程Skill交付包/ ├── README.md ├── 安装说明.md +├── 交付清单.md ├── skills/ -│ ├── generate-project-constraints/ -│ ├── design-complex-business-module/ -│ ├── review-business-delivery/ -│ └── track-review-fix-loop/ +│ ├── generate-project-constraints/ # 提取项目规则与约束 +│ ├── project-knowledge-builder/ # 建立源码依据的项目知识库 +│ ├── design-complex-business-module/ # 设计权限、状态、审计、异常态和指标 +│ ├── review-business-delivery/ # 审查业务交付与变更风险 +│ └── track-review-fix-loop/ # 跟踪 Review 修复与验证闭环 +├── mcp-assets/ +│ ├── MCP能力契约.template.md +│ └── MCP工具权限与验收清单.md └── examples/ - └── jeecgboot-work-order/ + └── jeecgboot-official-vehicle/ + └── 公务用车取消派车案例.md ``` ## Skill 列表 -| Skill | 用途 | 典型产物 | -| -------------------------------- | ------------------------------------------------ | --------------------------------------------- | -| `generate-project-constraints` | 从项目源码、配置、CI、文档中提取项目约束 | rules、约束提取表、验收清单 | -| `design-complex-business-module` | 设计复杂业务模块的权限、状态、审计、异常态和看板 | RBAC 矩阵、状态流转表、审计 SOP、看板指标字典 | -| `review-business-delivery` | 审查复杂业务交付物或 PR 的业务风险 | 阶段 Review 报告、风险清单、验收建议 | -| `track-review-fix-loop` | 跟踪 Review 问题修复、验证和资产沉淀 | 修复记录、未闭环清单、资产更新建议 | +| Skill | 解决的问题 | 典型产物 | +|---|---|---| +| `generate-project-constraints` | 从源码、配置、测试和 CI 中提取项目约束 | Rule、约束提取表、验收清单 | +| `project-knowledge-builder` | 建立可追溯、可持续更新的项目知识库 | 项目地图、接口与权限索引、维护指南 | +| `design-complex-business-module` | 设计高风险业务模块的工程边界 | 权限矩阵、状态流转表、审计 SOP、指标字典 | +| `review-business-delivery` | 审查设计、代码或 PR 的业务风险 | Review 报告、风险清单、验收建议 | +| `track-review-fix-loop` | 跟踪问题修复、验证和资产更新 | 修复记录、未闭环清单、资产更新建议 | -## 推荐使用顺序 +## MCP 资产 -1. 使用 `generate-project-constraints` 为项目生成项目规则。 -2. 使用 `design-complex-business-module` 生成复杂业务设计资产。 -3. 使用 `review-business-delivery` 审查设计、代码或阶段交付物。 -4. 使用 `track-review-fix-loop` 跟踪修复、验证结果和资产沉淀。 +`mcp-assets/` 只提供能力契约和验收清单,不预置任何 MCP 服务地址、工具配置或凭据。接入外部能力前,必须明确输入输出、数据范围、权限、副作用、失败降级和验证夹具;写入、删除、发布和外发能力不应作为通用 Agent 的默认权限。 -## 适用项目 +## 公务用车案例 -适用于后台管理系统、低代码平台二开、CRM、工单、审批、运营系统、数据看板等企业业务项目。 +`examples/jeecgboot-official-vehicle/` 是基于现有 JeecgBoot 公务用车模块的单人训练案例。它展示如何把“取消派车时取消原因不能为空”的训练改造,转成源码依据、最小改动边界、验证证据和待确认项。 -这些 Skill 不要求项目必须有 `CLAUDE.md`、`AGENTS.md` 或 `README.md`。如果项目没有显式说明文档,Skill 会从配置、源码模式、测试和 CI 中提取约束,并把无法证明的内容标记为待确认。 +该案例只说明已读取到的项目事实与已执行验证,不代表客户已提出相同业务需求,也不能直接替代目标项目的重新取证。 -## 示例说明 +## 使用边界 -`examples/jeecgboot-work-order/` 提供 JeecgBoot 工单案例参考,帮助理解这些 Skill 在真实后台项目中的使用方式。该示例不是通用 Skill 安装项。 +- Skill 保存可复用的判断流程,不保存某个项目的路径、字段、接口或权限码。 +- Rule、任务上下文契约、检查单、模板、Skill、Agent 和 MCP 职责不同,应保持单一权威位置。 +- 任何没有源码、日志、测试或明确规范支持的结论,都要标记为待确认。 +- 资产变更应检查行为变化、引用完整性、兼容性、迁移与回退,并使用完整、缺失、冲突和越界输入夹具复核。 + +安装与团队使用方式见 [安装说明.md](./安装说明.md),本次正式交付范围见 [交付清单.md](./交付清单.md)。 diff --git a/deliverables/客户AI工程Skill交付包/examples/jeecgboot-official-vehicle/公务用车取消派车案例.md b/deliverables/客户AI工程Skill交付包/examples/jeecgboot-official-vehicle/公务用车取消派车案例.md new file mode 100644 index 0000000..30404a0 --- /dev/null +++ b/deliverables/客户AI工程Skill交付包/examples/jeecgboot-official-vehicle/公务用车取消派车案例.md @@ -0,0 +1,48 @@ +# 公务用车取消派车:影响分析与验证案例 + +## 案例边界 + +本案例来自当前 JeecgBoot 已接入的公务用车模块,用于单人练习“先取证、再限制改动范围、最后如实记录验证”的工作方式。 + +训练改造目标:取消已派车任务时,后端必须拒绝空值和全空白的取消原因。前端表单必填只用于减少误操作,不能替代 Service 入口的业务保护。 + +本案例不代表客户已提出相同需求;其他项目必须重新读取自身源码、配置和测试材料。 + +## 已确认的项目事实 + +| 维度 | 已读取依据 | 结论 | +|---|---|---| +| 前端入口 | `jeecgboot-vue3/src/views/official/vehicle/dispatch/DispatchList.vue`、`DispatchModal.vue`、`dispatch.data.ts` | 仅在有取消权限且派车状态为 `DISPATCHED` 时显示取消操作;弹窗已配置取消原因必填。 | +| 前端 API | `jeecgboot-vue3/src/api/official/vehicle/dispatch.ts` | 页面外仍可直接调用取消接口,因此前端必填不是最终保护。 | +| 后端入口 | `OfficialVehicleDispatchController.java`、`OfficialVehicleDispatchServiceImpl.java` | Controller 负责权限与请求转交;Service 负责状态写入和车辆状态释放。 | +| 取消参数 | `OfficialVehicleCancelDispatchVo.java` | 请求体包含派车 ID 与取消原因,原有参数对象未限制空白文本。 | +| 测试基座 | `OfficialVehicleDispatchControllerTest.java`、模块 `pom.xml` | 模块具备 JUnit 测试基础,原用例未覆盖空白取消原因。 | + +## 可直接复用的任务上下文契约 + +```text +请基于当前打开的项目,且只基于实际读取到的源码,完成一次变更影响分析;不要修改代码,不要猜测项目外逻辑。 + +训练目标:取消已派车任务时,取消原因必须为非空文本;前端必填不能替代后端业务保护。 + +请读取前端页面、前端 API、后端 Controller、Service、参数对象、实体、现有测试、模块构建配置和必要数据库脚本。 + +请输出:已读取源码;已确认事实及文件依据;必须检查的模块;接口、数据、权限、状态、配置和测试影响;明确不改动项;不超过四步的最小改动计划;人工待确认项。 + +要求:事实与推测分开;没有源码或验证证据时标记待确认;不编造测试结果;不直接实施修改。 +``` + +## 实际改动与证据 + +| 项目 | 记录 | +|---|---| +| 最小改动 | 在 `OfficialVehicleDispatchServiceImpl.cancel` 的状态写入前加入取消原因非空校验。 | +| 对应测试 | 新增 Service 定向测试,传入全空白取消原因并断言请求被拒绝。 | +| 已执行验证 | Maven 定向测试已执行成功,定向 Service 测试通过。 | +| 保持不变 | 前端必填、既有取消权限、审计注解、车辆状态释放逻辑和数据库表结构未修改。 | + +## 结论边界 + +下列事项没有被本案例写成“已通过”:实际数据库中非空取消原因的正常取消回归、HTTP 响应映射、浏览器提示、审计记录和车辆状态的集成环境验证。 + +这正是本案例的核心要求:已有证据支持多大的结论,就只形成多大的结论。 diff --git a/deliverables/客户AI工程Skill交付包/examples/jeecgboot-work-order/JeecgBoot工单模块开发审查.skill.md b/deliverables/客户AI工程Skill交付包/examples/jeecgboot-work-order/JeecgBoot工单模块开发审查.skill.md deleted file mode 100644 index 4bc5f74..0000000 --- a/deliverables/客户AI工程Skill交付包/examples/jeecgboot-work-order/JeecgBoot工单模块开发审查.skill.md +++ /dev/null @@ -1,44 +0,0 @@ -# JeecgBoot 工单模块开发审查 Skill - -## 触发场景 - -涉及工单列表、详情、评论、状态、指派、看板、权限字符、数据权限、操作日志的需求拆解、代码生成或代码修改。 - -## 输入材料 - -- PR diff 或相关文件。 -- 工单需求说明。 -- 数据模型与接口契约。 -- 当前 rules。 -- 前端页面文件。 -- API 模块文件。 -- 后端 Controller / Service。 -- 权限矩阵。 -- 状态流转表。 -- 审计日志 SOP。 - -## 执行步骤 - -1. 识别需求范围和非目标范围。 -2. 检查字段、接口契约和页面状态是否完整。 -3. 审查 JeecgBoot 生成代码的字段、查询、分页、字典和异常态。 -4. 检查新增、编辑、删除、指派、状态变更的前端按钮与后端权限。 -5. 检查操作日志、数据权限和测试缺口。 -6. 检查看板指标口径和接口验收记录。 -7. 输出风险、不确定点和验收建议。 - -## 输出格式 - -- 需求范围确认。 -- 接口契约问题表。 -- 代码生成 Review 清单。 -- 权限风险表。 -- 状态流转风险表。 -- 审计日志风险表。 -- 测试缺口清单。 -- 不确定点清单。 - -## 验收标准 - -每个关键结论必须标注文件、函数、注解、配置或调用链依据。每条建议必须有验收方式。 - diff --git a/deliverables/客户AI工程Skill交付包/mcp-assets/MCP工具权限与验收清单.md b/deliverables/客户AI工程Skill交付包/mcp-assets/MCP工具权限与验收清单.md new file mode 100644 index 0000000..45cc916 --- /dev/null +++ b/deliverables/客户AI工程Skill交付包/mcp-assets/MCP工具权限与验收清单.md @@ -0,0 +1,33 @@ +# MCP 工具权限与验收清单 + +## 接入前 + +- [ ] 已确认该能力确实需要 MCP,而非 CLI、Rule、任务上下文契约 或 Skill。 +- [ ] 每个工具都有用途、输入、输出、副作用和错误语义说明。 +- [ ] 已确认数据范围、鉴权、审计和脱敏要求。 +- [ ] 未将密钥、Token、账号、完整个人信息或内部地址写入配置、示例或日志。 +- [ ] 已明确只读、可逆写入和不可逆操作,未根据工具名称猜测性质。 + +## Agent 授权 + +- [ ] 每个自定义 Agent 只绑定完成职责所需的 MCP 工具。 +- [ ] 读取受限信息时,调用身份和数据范围可被 MCP 服务校验。 +- [ ] 写入、删除、发布和外发操作不属于通用 Agent 默认权限。 +- [ ] 有副作用操作具备二次确认、幂等或明确的重复调用处理。 +- [ ] Agent 在 MCP 失败时输出“外部事实未取得”,不编造结果。 + +## 验收夹具 + +- [ ] 合法完整请求:返回结构、来源时间和范围符合契约。 +- [ ] 缺少必填参数:拒绝并指出缺口。 +- [ ] 越权请求:拒绝或触发确认,不泄露超范围数据。 +- [ ] 鉴权失败、限流、超时和服务错误:错误类型可区分。 +- [ ] 返回空结果:与工具故障明确区分。 +- [ ] 敏感字段:按契约脱敏,审计日志不暴露秘密信息。 +- [ ] 重复调用:符合幂等设计或被安全阻止。 + +## 结论 + +- [ ] 可接入:契约、权限、审计、失败处理和验收夹具完整。 +- [ ] 待确认:记录缺失的外部规范或验证材料。 +- [ ] 不接入:说明改用 CLI、Skill 或人工流程的原因。 diff --git a/deliverables/客户AI工程Skill交付包/mcp-assets/MCP能力契约.template.md b/deliverables/客户AI工程Skill交付包/mcp-assets/MCP能力契约.template.md new file mode 100644 index 0000000..71336cb --- /dev/null +++ b/deliverables/客户AI工程Skill交付包/mcp-assets/MCP能力契约.template.md @@ -0,0 +1,34 @@ +# MCP 能力契约 + +## 基本信息 + +| 字段 | 内容 | +|---|---| +| 服务名称 | `<名称>` | +| 接入目的 | `<要解决的外部事实或操作>` | +| 适用场景 | `<何时允许使用>` | +| 不适用场景 | `<何时不得使用>` | +| 来源依据 | `<接口文档、系统规范或已验证记录>` | + +## 工具清单 + +| 工具 | 用途 | 输入契约 | 输出契约 | 副作用 | 权限与确认 | 错误语义 | 审计与脱敏 | +|---|---|---|---|---|---|---|---| +| `` | `<用途>` | `<必填、格式、范围>` | `<字段、时效、空值>` | `只读 / 可逆写入 / 不可逆` | `<调用资格与确认>` | `<鉴权、限流、超时等>` | `<留痕字段与脱敏规则>` | + +## Agent 绑定边界 + +- 允许调用的自定义 Agent:`<名称或角色>`。 +- 不允许调用的 Agent:`<名称或角色>`。 +- Agent 调用前必须具备的运行时事实:`<数据范围、目标对象、确认信息>`。 +- Agent 调用后必须输出:`<来源、时间、结果、失败或待确认>`。 + +## 停止与降级 + +| 情况 | MCP 或 Agent 的行为 | +|---|---| +| 缺少关键输入 | 拒绝调用并列出字段缺口 | +| 鉴权或数据范围不满足 | 拒绝调用,不尝试扩大权限 | +| 限流、超时或服务不可用 | 区分故障类型,标记外部事实未取得 | +| 有副作用但未确认 | 停止并请求确认 | +| 返回敏感信息 | 按规则脱敏,避免写入任务记录或日志 | diff --git a/deliverables/客户AI工程Skill交付包/mcp-assets/README.md b/deliverables/客户AI工程Skill交付包/mcp-assets/README.md new file mode 100644 index 0000000..bc7c86c --- /dev/null +++ b/deliverables/客户AI工程Skill交付包/mcp-assets/README.md @@ -0,0 +1,29 @@ +# MCP 能力资产包 + +本目录用于沉淀可被 Agent 调用的外部能力边界,不保存任何服务地址、访问令牌、账号、内部域名或业务数据。 + +## 何时使用 + +- 需要跨系统读取经授权的事实、日志或文档。 +- 需要调用具有结构化输入输出的外部工具。 +- 需要将浏览器、数据库、内部平台或其他服务能力提供给 Agent。 + +## 不适用场景 + +- 已有稳定命令且不需要交互式工具调用的任务,优先使用 CLI。 +- 只有知识、流程、模板或检查清单的任务,优先沉淀为 Rule、任务上下文契约或 Skill。 +- 未确认数据范围、鉴权方式、审计要求或副作用的外部服务。 + +## 使用顺序 + +1. 使用 `MCP能力契约.template.md` 描述每个拟接入的服务与工具。 +2. 使用 `MCP工具权限与验收清单.md` 审查权限、敏感数据、错误和副作用。 +3. 为完整请求、缺参、越权、故障和脱敏结果准备验证夹具。 +4. 只有契约与验收都满足时,才将对应工具分配给自定义 Agent 或 Skill。 + +## 基本原则 + +- 不根据工具名称推断只读或安全性。 +- 外部工具失败时,结论必须标记为“外部事实未取得”。 +- 写入、删除、发布和外发能力不应作为通用 Agent 的默认权限。 +- 日志、示例和夹具不得包含密钥、Token、账号、完整个人信息或内部地址。 diff --git a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/RBAC权限矩阵.template.md b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/RBAC权限矩阵.template.md index 1885beb..b7bfb1b 100644 --- a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/RBAC权限矩阵.template.md +++ b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/RBAC权限矩阵.template.md @@ -1,16 +1,16 @@ -# 工单 RBAC 权限矩阵 +# 业务对象 RBAC 权限矩阵 -| 功能点 | 管理员 | 工单负责人 | 研发处理人 | 测试验证人 | 只读观察者 | 权限字符建议 | 备注 | +| 功能点 | 管理员 | 业务对象负责人 | 研发处理人 | 测试验证人 | 只读观察者 | 权限字符建议 | 备注 | | --- | --- | --- | --- | --- | --- | --- | --- | -| 查看列表 | 允许 | 条件允许 | 条件允许 | 条件允许 | 条件允许 | work:order:list | 按数据范围 | -| 查看详情 | 允许 | 条件允许 | 条件允许 | 条件允许 | 条件允许 | work:order:query | 仅可见范围 | -| 新建工单 | 允许 | 允许 | 允许 | 允许 | 拒绝 | work:order:add | | -| 编辑工单 | 允许 | 条件允许 | 条件允许 | 拒绝 | 拒绝 | work:order:edit | 状态限制 | -| 指派负责人 | 允许 | 条件允许 | 拒绝 | 拒绝 | 拒绝 | work:order:assign | | -| 状态流转 | 允许 | 条件允许 | 条件允许 | 条件允许 | 拒绝 | work:order:transition | 按状态 | -| 关闭工单 | 允许 | 条件允许 | 拒绝 | 条件允许 | 拒绝 | work:order:close | 测试验证后 | -| 删除工单 | 允许 | 拒绝 | 拒绝 | 拒绝 | 拒绝 | work:order:delete | 高危操作 | -| 查看审计日志 | 允许 | 条件允许 | 条件允许 | 条件允许 | 拒绝 | work:order:audit | | -| 查看看板 | 允许 | 允许 | 条件允许 | 条件允许 | 条件允许 | work:order:dashboard | 按数据范围 | -| 导出数据 | 允许 | 条件允许 | 拒绝 | 拒绝 | 拒绝 | work:order:export | | +| 查看列表 | 允许 | 条件允许 | 条件允许 | 条件允许 | 条件允许 | :list | 按数据范围 | +| 查看详情 | 允许 | 条件允许 | 条件允许 | 条件允许 | 条件允许 | :query | 仅可见范围 | +| 新建业务对象 | 允许 | 允许 | 允许 | 允许 | 拒绝 | :add | | +| 编辑业务对象 | 允许 | 条件允许 | 条件允许 | 拒绝 | 拒绝 | :edit | 状态限制 | +| 指派负责人 | 允许 | 条件允许 | 拒绝 | 拒绝 | 拒绝 | :assign | | +| 状态流转 | 允许 | 条件允许 | 条件允许 | 条件允许 | 拒绝 | :transition | 按状态 | +| 关闭业务对象 | 允许 | 条件允许 | 拒绝 | 条件允许 | 拒绝 | :close | 测试验证后 | +| 删除业务对象 | 允许 | 拒绝 | 拒绝 | 拒绝 | 拒绝 | :delete | 高危操作 | +| 查看审计日志 | 允许 | 条件允许 | 条件允许 | 条件允许 | 拒绝 | :audit | | +| 查看看板 | 允许 | 允许 | 条件允许 | 条件允许 | 条件允许 | :dashboard | 按数据范围 | +| 导出数据 | 允许 | 条件允许 | 拒绝 | 拒绝 | 拒绝 | :export | | diff --git a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/RBAC权限矩阵生成.prompt.md b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/RBAC权限矩阵生成.prompt.md index 84a9100..4856c49 100644 --- a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/RBAC权限矩阵生成.prompt.md +++ b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/RBAC权限矩阵生成.prompt.md @@ -1,11 +1,11 @@ -# RBAC 权限矩阵生成 Prompt +# RBAC 权限矩阵生成 任务上下文契约 ```text -请基于 JeecgBoot 工单模块设计 RBAC 权限矩阵。 +请基于 目标业务模块设计 RBAC 权限矩阵。 输入材料: -- 工单模块需求:<粘贴需求> -- 角色列表:管理员、工单负责人、研发处理人、测试验证人、只读观察者 +- 业务模块需求:<粘贴需求> +- 角色列表:管理员、业务对象负责人、研发处理人、测试验证人、只读观察者 - 功能点:查看列表、查看详情、新建、编辑、指派、状态流转、关闭、删除、查看审计、查看看板、导出 - 项目依据:<粘贴权限、路由、菜单相关路径> diff --git a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/审计日志SOP.template.md b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/审计日志SOP.template.md index 876d692..6f27039 100644 --- a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/审计日志SOP.template.md +++ b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/审计日志SOP.template.md @@ -1,14 +1,14 @@ -# 工单审计日志 SOP +# 业务对象审计日志 SOP ## 记录原则 -所有会改变工单关键业务状态、负责人、可见性或结论的动作都必须记录审计日志。 +所有会改变业务对象关键业务状态、负责人、可见性或结论的动作都必须记录审计日志。 ## 审计字段 | 字段 | 含义 | 示例 | | --- | --- | --- | -| ticket_id | 工单 ID | WO-001 | +| | 业务对象 ID | <业务编号> | | actor_id | 操作人 ID | 1001 | | actor_name | 操作人名称 | zhangsan | | action | 操作类型 | ASSIGN | @@ -23,12 +23,12 @@ | 动作 | 写入时机 | oldValue | newValue | 验收 | | --- | --- | --- | --- | --- | -| 创建工单 | 创建成功后 | 空 | 工单摘要 | 审计列表可见 | +| 创建业务对象 | 创建成功后 | 空 | 业务对象摘要 | 审计列表可见 | | 编辑字段 | 保存成功后 | 修改前字段 | 修改后字段 | 字段前后值正确 | | 指派负责人 | 指派成功后 | 原负责人 | 新负责人 | 审计记录负责人变化 | | 状态流转 | 状态变更成功后 | 原状态 | 新状态 | 审计记录状态变化 | | 评论新增 | 评论保存后 | 空 | 评论摘要 | 审计记录评论动作 | -| 关闭工单 | 关闭成功后 | 原状态 | 已关闭 | 审计记录关闭时间 | +| 关闭业务对象 | 关闭成功后 | 原状态 | 已关闭 | 审计记录关闭时间 | ## 验收方式 diff --git a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/审计日志检查清单.md b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/审计日志检查清单.md index 0e271a4..093e3bd 100644 --- a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/审计日志检查清单.md +++ b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/审计日志检查清单.md @@ -2,7 +2,7 @@ | 检查项 | 通过标准 | 结果 | | --- | --- | --- | -| 创建记录 | 创建工单后能查询审计记录 | | +| 创建记录 | 创建业务对象后能查询审计记录 | | | 字段变更 | 编辑关键字段记录 oldValue/newValue | | | 指派记录 | 指派负责人记录原负责人和新负责人 | | | 状态记录 | 状态流转记录原状态和新状态 | | diff --git a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/异常态检查清单.md b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/异常态检查清单.md index e1b6496..74442ae 100644 --- a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/异常态检查清单.md +++ b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/异常态检查清单.md @@ -3,7 +3,7 @@ | 异常态 | 必须验证 | 结果 | | --- | --- | --- | | 空列表 | 页面显示空状态,不报错 | | -| 详情不存在 | 提示工单不存在,页面不崩溃 | | +| 详情不存在 | 提示业务对象不存在,页面不崩溃 | | | 权限不足 | 前端提示或隐藏,后端拒绝 | | | 非法状态 | 返回非法流转,状态不变 | | | 重复提交 | 只成功一次,只写一条有效审计 | | diff --git a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/异常态清单.template.md b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/异常态清单.template.md index 286eaff..9e4d2fd 100644 --- a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/异常态清单.template.md +++ b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/异常态清单.template.md @@ -1,9 +1,9 @@ -# 工单异常态清单 +# 业务对象异常态清单 | 场景 | 触发条件 | 前端表现 | 后端响应 | 验收 | | --- | --- | --- | --- | --- | | 权限不足 | 无权限操作 | 按钮不可见或提示无权限 | 403 或业务拒绝 | 不完成操作 | -| 工单不存在 | 传入错误 ID | 显示不存在 | 404 或业务错误 | 页面不崩溃 | +| 业务对象不存在 | 传入错误 ID | 显示不存在 | 404 或业务错误 | 页面不崩溃 | | 非法状态流转 | closed -> processing | 提示非法流转 | 状态不变 | 数据状态不变 | | 重复提交 | 连续点击提交 | 按钮 loading/禁用 | 只处理一次 | 只生成一条有效审计 | | 参数缺失 | 驳回缺少原因 | 表单校验提示 | 参数错误 | 不改变状态 | diff --git a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/异常态补齐.prompt.md b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/异常态补齐.prompt.md index 17bd793..cd63f5b 100644 --- a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/异常态补齐.prompt.md +++ b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/异常态补齐.prompt.md @@ -1,14 +1,14 @@ -# 异常态补齐 Prompt +# 异常态补齐 任务上下文契约 ```text -请基于 JeecgBoot 工单模块,列出状态流转、指派、删除、查询详情、审计日志、看板相关的异常态和防御性逻辑。 +请基于 目标业务模块,列出状态流转、指派、删除、查询详情、审计日志、看板相关的异常态和防御性逻辑。 输出表格: 场景 | 触发条件 | 前端表现 | 后端响应 | 用户提示 | 测试点 | 验收 必须覆盖: - 权限不足 -- 工单不存在 +- 业务对象不存在 - 非法状态流转 - 重复提交 - 参数缺失 diff --git a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/权限上线前检查清单.md b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/权限上线前检查清单.md index cacf893..c99b571 100644 --- a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/权限上线前检查清单.md +++ b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/权限上线前检查清单.md @@ -2,10 +2,10 @@ | 检查项 | 通过标准 | 结果 | | --- | --- | --- | -| 菜单权限 | 工单菜单只对授权角色可见 | | +| 菜单权限 | 业务对象菜单只对授权角色可见 | | | 按钮权限 | 新增、编辑、删除、指派、状态按钮按权限显示 | | | 接口权限 | 无权限账号直接请求敏感接口失败 | | -| 数据权限 | 普通用户只能看到授权范围内工单 | | +| 数据权限 | 普通用户只能看到授权范围内业务对象 | | | 权限字符 | 前端按钮、菜单配置、后端接口命名一致 | | | 审计 | 权限敏感操作成功后有审计记录 | | | 反例验证 | 无权限账号不能绕过前端执行删除/指派 | | diff --git a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/权限控制方案.template.md b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/权限控制方案.template.md index d7d0910..158b452 100644 --- a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/权限控制方案.template.md +++ b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/权限控制方案.template.md @@ -1,12 +1,12 @@ -# 工单权限控制方案 +# 业务对象权限控制方案 | 操作 | 前端入口 | 权限字符 | 后端接口 | 后端校验 | 数据权限 | 验收 | | --- | --- | --- | --- | --- | --- | --- | -| 新增工单 | 新增按钮 | work:order:add | /workOrder/add | 权限注解 | 无 | 无权限账号看不到按钮且直接请求失败 | -| 编辑工单 | 编辑按钮 | work:order:edit | /workOrder/edit | 权限注解 + 状态校验 | 负责人/创建人范围 | 越权账号返回 403 或业务拒绝 | -| 指派负责人 | 指派按钮 | work:order:assign | /workOrder/assign | 权限注解 | 管理员/负责人范围 | 普通研发无法指派他人 | -| 状态流转 | 状态按钮 | work:order:transition | /workOrder/transition | 权限注解 + 状态机校验 | 相关工单范围 | 非法流转失败 | -| 删除工单 | 删除按钮 | work:order:delete | /workOrder/delete | 权限注解 | 管理员范围 | 非管理员返回 403 | -| 查看审计 | 审计页签 | work:order:audit | /workOrder/audit/list | 权限注解 | 相关工单范围 | 无权限不可见且接口拒绝 | -| 看板 | 看板菜单 | work:order:dashboard | /workOrder/dashboard | 权限注解 | 数据范围过滤 | 不同角色数据范围不同 | +| 新增业务对象 | 新增按钮 | :add | //add | 权限注解 | 无 | 无权限账号看不到按钮且直接请求失败 | +| 编辑业务对象 | 编辑按钮 | :edit | //edit | 权限注解 + 状态校验 | 负责人/创建人范围 | 越权账号返回 403 或业务拒绝 | +| 指派负责人 | 指派按钮 | :assign | //assign | 权限注解 | 管理员/负责人范围 | 普通研发无法指派他人 | +| 状态流转 | 状态按钮 | :transition | //transition | 权限注解 + 状态机校验 | 相关业务对象范围 | 非法流转失败 | +| 删除业务对象 | 删除按钮 | :delete | //delete | 权限注解 | 管理员范围 | 非管理员返回 403 | +| 查看审计 | 审计页签 | :audit | //audit/list | 权限注解 | 相关业务对象范围 | 无权限不可见且接口拒绝 | +| 看板 | 看板菜单 | :dashboard | //dashboard | 权限注解 | 数据范围过滤 | 不同角色数据范围不同 | diff --git a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/权限控制方案生成.prompt.md b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/权限控制方案生成.prompt.md index 7dcf834..23dc4f7 100644 --- a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/权限控制方案生成.prompt.md +++ b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/权限控制方案生成.prompt.md @@ -1,7 +1,7 @@ -# 权限控制方案生成 Prompt +# 权限控制方案生成 任务上下文契约 ```text -请基于工单模块 RBAC 权限矩阵,生成 JeecgBoot 权限控制方案。 +请基于业务模块 RBAC 权限矩阵,生成 JeecgBoot 权限控制方案。 输入材料: - RBAC 权限矩阵:<粘贴> diff --git a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/状态流转表.template.md b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/状态流转表.template.md index 29456b8..1f5b02b 100644 --- a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/状态流转表.template.md +++ b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/状态流转表.template.md @@ -1,8 +1,8 @@ -# 工单状态流转表 +# 业务对象状态流转表 | 当前状态 | 动作 | 目标状态 | 允许角色 | 前置条件 | 后置动作 | 验收 | | --- | --- | --- | --- | --- | --- | --- | -| 新建 | 指派 | 新建 | 管理员/负责人 | 工单未关闭 | 写审计日志 | 指派人变化且状态不变 | +| 新建 | 指派 | 新建 | 管理员/负责人 | 业务对象未关闭 | 写审计日志 | 指派人变化且状态不变 | | 新建 | 开始处理 | 处理中 | 研发处理人/负责人 | 已指派处理人 | 写审计日志 | 状态变为处理中 | | 处理中 | 提交验证 | 待验证 | 研发处理人/负责人 | 处理说明不为空 | 写审计日志 | 状态变为待验证 | | 待验证 | 验证通过 | 已关闭 | 测试验证人/负责人 | 验证结果通过 | 写审计日志和关闭时间 | 状态变为已关闭 | diff --git a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/状态流转设计.prompt.md b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/状态流转设计.prompt.md index 58b609b..b73a840 100644 --- a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/状态流转设计.prompt.md +++ b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/状态流转设计.prompt.md @@ -1,7 +1,7 @@ -# 状态流转设计 Prompt +# 状态流转设计 任务上下文契约 ```text -请为 JeecgBoot 工单模块设计状态流转规则。 +请为 目标业务模块设计状态流转规则。 状态候选: - 新建 diff --git a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板指标字典.template.md b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板指标字典.template.md index 32da66b..16cf9d3 100644 --- a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板指标字典.template.md +++ b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板指标字典.template.md @@ -1,13 +1,13 @@ -# 工单看板指标字典 +# 业务对象看板指标字典 | 指标 | 含义 | 口径 | 数据来源 | 展示方式 | 权限 | 验收 | | --- | --- | --- | --- | --- | --- | --- | -| 工单总数 | 当前筛选范围内工单数量 | count(ticket_id) | 工单表 | 指标卡 | 看板权限 | 与列表同条件数量一致 | -| 未关闭工单数 | 状态不是已关闭的数量 | status != closed | 工单表 | 指标卡 | 看板权限 | 过滤状态验证 | -| 平均关闭周期 | 从创建到关闭平均耗时 | avg(close_time - create_time) | 工单表 | 指标卡/趋势 | 看板权限 | 抽样计算一致 | -| 按优先级分布 | 高中低优先级数量 | group by priority | 工单表/字典 | 饼图/柱状图 | 看板权限 | 分组总和等于总数 | -| 按类型分布 | 缺陷/需求/支持数量 | group by type | 工单表/字典 | 柱状图 | 看板权限 | 分组总和等于总数 | -| 缺陷趋势 | 按日期统计缺陷工单 | type=bug group by date | 工单表 | 折线图 | 看板权限 | 日期范围正确 | -| 逾期工单数 | 超过 SLA 未关闭数量 | due_time < now and status != closed | 工单表 | 指标卡 | 看板权限 | SLA 口径明确 | -| 人员处理负载 | 按负责人统计未关闭数量 | group by assignee | 工单表 | 排行榜 | 管理员/负责人 | 数据范围正确 | +| 业务对象总数 | 当前筛选范围内业务对象数量 | count() | 业务对象表 | 指标卡 | 看板权限 | 与列表同条件数量一致 | +| 未关闭业务对象数 | 状态不是已关闭的数量 | status != closed | 业务对象表 | 指标卡 | 看板权限 | 过滤状态验证 | +| 平均关闭周期 | 从创建到关闭平均耗时 | avg(close_time - create_time) | 业务对象表 | 指标卡/趋势 | 看板权限 | 抽样计算一致 | +| 按优先级分布 | 高中低优先级数量 | group by priority | 业务对象表/字典 | 饼图/柱状图 | 看板权限 | 分组总和等于总数 | +| 按类型分布 | 缺陷/需求/支持数量 | group by type | 业务对象表/字典 | 柱状图 | 看板权限 | 分组总和等于总数 | +| 缺陷趋势 | 按日期统计缺陷业务对象 | type=bug group by date | 业务对象表 | 折线图 | 看板权限 | 日期范围正确 | +| 逾期业务对象数 | 超过 SLA 未关闭数量 | due_time < now and status != closed | 业务对象表 | 指标卡 | 看板权限 | SLA 口径明确 | +| 人员处理负载 | 按负责人统计未关闭数量 | group by assignee | 业务对象表 | 排行榜 | 管理员/负责人 | 数据范围正确 | diff --git a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板指标设计.prompt.md b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板指标设计.prompt.md index 9851ec7..c513237 100644 --- a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板指标设计.prompt.md +++ b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板指标设计.prompt.md @@ -1,16 +1,16 @@ -# 看板指标设计 Prompt +# 看板指标设计 任务上下文契约 ```text -请为 JeecgBoot 工单模块设计研发效能看板指标。 +请为 目标业务模块设计研发效能看板指标。 指标方向: -- 工单总数 -- 未关闭工单数 +- 业务对象总数 +- 未关闭业务对象数 - 平均关闭周期 - 按优先级分布 - 按类型分布 - 缺陷趋势 -- 逾期工单数 +- 逾期业务对象数 - 人员处理负载 输出要求: diff --git a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板接口验收.prompt.md b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板接口验收.prompt.md index b3f2e75..3abc4f4 100644 --- a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板接口验收.prompt.md +++ b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板接口验收.prompt.md @@ -1,7 +1,7 @@ -# 看板接口验收 Prompt +# 看板接口验收 任务上下文契约 ```text -请为 JeecgBoot 工单看板统计接口生成验收记录。 +请为 目标业务看板统计接口生成验收记录。 输入材料: - 看板指标字典:<粘贴> diff --git a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板接口验收清单.md b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板接口验收清单.md index 95e740a..b21d2d7 100644 --- a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板接口验收清单.md +++ b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板接口验收清单.md @@ -4,7 +4,7 @@ | --- | --- | --- | --- | | 默认查询 | 管理员默认条件 | 返回完整指标 | | | 时间过滤 | 指定 start/end | 只统计时间范围内数据 | | -| 类型过滤 | 指定工单类型 | 只统计对应类型 | | +| 类型过滤 | 指定业务对象类型 | 只统计对应类型 | | | 空数据 | 无匹配条件 | 返回 0 或空集合 | | | 无权限 | 未授权账号 | 返回 403 或业务拒绝 | | | 数据范围 | 普通角色 | 只返回授权范围 | | diff --git a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板接口验收记录.template.md b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板接口验收记录.template.md index 6ddf072..7becf5e 100644 --- a/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板接口验收记录.template.md +++ b/deliverables/客户AI工程Skill交付包/skills/design-complex-business-module/assets/看板接口验收记录.template.md @@ -1,8 +1,8 @@ -# 工单看板接口验收记录 +# 业务对象看板接口验收记录 | 验收项 | 账号/条件 | 请求参数 | 预期 | 实际 | 证据 | 结论 | | --- | --- | --- | --- | --- | --- | --- | -| 工单总数 | 管理员 | 默认筛选 | 返回总数 | | | | +| 业务对象总数 | 管理员 | 默认筛选 | 返回总数 | | | | | 空数据 | 管理员 + 无匹配条件 | 不存在的日期范围 | 返回空集合或 0 | | | | | 无权限 | 只读外角色 | 默认筛选 | 返回 403 或业务拒绝 | | | | | 时间过滤 | 管理员 | start/end | 只统计范围内数据 | | | | diff --git a/deliverables/客户AI工程Skill交付包/skills/generate-project-constraints/assets/项目约束生成.prompt.md b/deliverables/客户AI工程Skill交付包/skills/generate-project-constraints/assets/项目约束生成.prompt.md index 7c25980..0e4001b 100644 --- a/deliverables/客户AI工程Skill交付包/skills/generate-project-constraints/assets/项目约束生成.prompt.md +++ b/deliverables/客户AI工程Skill交付包/skills/generate-project-constraints/assets/项目约束生成.prompt.md @@ -1,4 +1,4 @@ -# 项目约束生成 Prompt +# 项目约束生成 任务上下文契约 请为下面项目生成可复用的 AI 开发约束资产。 diff --git a/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/SKILL.md b/deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/SKILL.md similarity index 100% rename from deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/SKILL.md rename to deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/SKILL.md diff --git a/deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/SKILL_zh.md b/deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/SKILL_zh.md new file mode 100644 index 0000000..d4662c8 --- /dev/null +++ b/deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/SKILL_zh.md @@ -0,0 +1,133 @@ +--- +name: project-knowledge-builder +description: 当用户要求“项目知识库”“看项目”“整理项目”,或需要深度检查软件项目、构建/刷新项目知识库、创建入门文档、架构图、领域词汇表、ADR、API/配置/数据库生成索引或 RAG 友好仓库文档时使用。 +--- + +# 项目知识库构建 + +## 概览 + +构建一个有源码依据的项目知识库,帮助人和 AI 理解、扩展、排障和索引代码库。把知识库视为持续维护的系统资产,而不是 README 改写。 + +## 快速使用 + +用户不需要记住命令、脚本路径或 `$project-knowledge-builder`。 + +最短触发语即可: + +- `项目知识库` + +除非用户另有说明,将当前仓库作为目标,输出到 `docs/project-knowledge/`。由你运行 bundled inventory 脚本并写文档。只有用户明确要求命令行用法时,才展示手工命令。 + +## 语言和受众默认值 + +如果用户使用中文或要求中文,人类阅读的叙述文档默认使用中文:README、上下文、架构、运行、后端、前端、数据、认证/权限、扩展、排障、RAG 指南和 ADR。源码路径、类名、命令、API 路径、配置键、表名、权限码、元数据枚举值保持原样。 + +把新维护者作为主要受众。一个有用的知识库应该让新人无需额外辅导就能回答四个问题: + +1. 这个项目是什么,主要模块在哪里? +2. 如何本地运行,或如何识别当前运行模式? +3. 如何找到某个功能对应的后端接口、前端页面、数据库表和权限码? +4. 新增普通功能时通常需要修改什么,尤其是菜单/按钮权限? + +生成索引不要为了友好而牺牲精确性。生成文件要便于机器刷新;叙述文档要便于人阅读。 + +## 核心规则 + +每个结论都必须能追溯到仓库,或明确标记为推断。优先使用源码路径、命令、生成索引和未解决问题清单,而不是自信散文。 + +## 工作流 + +1. **设定范围。** 确认或默认输出位置为 `docs/project-knowledge/`。如果用户提到 RAG、飞书/Lark、Dify、Notion、入门、架构 Review 或 API 文档,调整输出契约。 +2. **优先读取本地说明。** 检查根文档,以及 `AGENTS.md`、`CLAUDE.md`、`GEMINI.md`、`.codex/`、`.agents/`、包管理文件、构建文件、env 示例、Docker 文件和模块 README。 +3. **生成 inventory。** 对目标仓库运行本 Skill 的 `scripts/project_inventory.py`。用输出作为事实基础,再人工检查关键文件。 +4. **映射上下文。** 识别限界上下文、运行模式、主要模块、路径暗示的 owner、外部系统和数据存储。使用 `CONTEXT.md` 和 `CONTEXT-MAP.md` 风格文档。 +5. **提取生成索引。** 尽可能产出可机器刷新的文档:接口、前端路由/视图、配置键、环境变量、数据库表、权限码、定时任务、队列、扩展点。 +6. **编写人类指南。** 产出入门、架构、本地运行/部署、数据模型、认证/权限、扩展、排障和术语表文档。按用户语言书写;中文用户使用 `已观察来源`、`核心事实`、`操作影响`、`未知与风险` 等标题。 +7. **为真实决策创建 ADR。** 记录当前代码已经体现的重要架构决策。不要发明未来决策,除非标为 proposed。 +8. **添加 RAG 元数据指南。** 包括切块、source-path metadata、过期策略和排除规则。 +9. **验证。** 重新运行 inventory,扫描占位符、坏链接、过期路径和无依据结论。报告已验证内容和仍不确定内容。 + +## 输出契约 + +创建或升级完整知识库时读取 `references/output-contract_zh.md`。 + +最小有用集合: + +```text +docs/project-knowledge/ +├── README.md +├── CONTEXT.md +├── CONTEXT-MAP.md +├── 01-architecture.md +├── 02-runtime.md +├── 03-backend.md +├── 04-frontend.md +├── 05-data-and-migrations.md +├── 06-auth-permissions.md +├── 07-extension-guide.md +├── 08-troubleshooting.md +├── 09-rag-indexing.md +├── adr/ +│ └── 0001-current-runtime-architecture.md +└── generated/ + ├── project-inventory.md + ├── backend-endpoints.md + ├── frontend-routes.md + ├── config-reference.md + ├── database-dictionary.md + └── permission-codes.md +``` + +只有仓库较小或用户要求轻量产物时,才缩小范围。 + +## 来源依据标准 + +- 非显然结论必须内联引用来源路径。 +- 区分 **已观察**、**推断** 和 **未知**。 +- 不要凭记忆总结生成代码、依赖或配置,必须读取。 +- 如果不稳定事实需要外部文档,浏览一手来源并附链接。 +- 保留项目专用约定。如果仓库要求追踪注释、ADR 格式或 issue ID,记录并遵守。 + +## 跨平台兼容 + +当用户询问 macOS、Windows、Linux、CI、可移植性或跨机器安装/运行时,读取 `references/compatibility_zh.md`。bundled inventory 脚本面向 Python 3.9+,只使用标准库。 + +## Inventory 脚本 + +仅在手工刷新或用户询问命令时展示。请在目标仓库运行: + +macOS/Linux: + +```sh +python3 /path/to/skill/scripts/project_inventory.py --root . --out docs/project-knowledge/generated +``` + +Windows PowerShell: + +```powershell +py -3 C:\path\to\skill\scripts\project_inventory.py --root . --out docs\project-knowledge\generated +``` + +常用选项: + +```sh +python3 /path/to/skill/scripts/project_inventory.py --root . --out ./kb-inventory --json +python3 /path/to/skill/scripts/project_inventory.py --root . --out docs/project-knowledge/generated --max-files 20000 +``` + +脚本只是起点,不能替代阅读核心文件。编写叙述文档前,必须检查代表性文件。 + +## 质量门禁 + +最终回复前: + +- 用适合平台的命令列出生成文档,例如 macOS/Linux 使用 `find docs/project-knowledge -type f | sort`,PowerShell 使用 `Get-ChildItem docs/project-knowledge -Recurse -File`。 +- 执行 `rg -n "TODO|TBD|待补|unknown|your/path|foo|bar" docs/project-knowledge`。 +- 编辑后重新运行 inventory 脚本。 +- 检查顶层 README 链接是否可解析。 +- 如果用户是中文用户,扫描叙述文档中是否仍有英文结构标题,例如 `Observed Sources`、`Core Facts`、`Operational Implications`、`Unknowns/Risks`、`Architecture`、`Runtime`、`Backend`、`Frontend`、`Troubleshooting`,除代码、元数据或生成输出外应翻译。 +- 检查新人可用性:README 给出阅读顺序;运行文档说明本地/Docker/云端模式;权限文档说明菜单/按钮权限流;扩展文档说明后端 CRUD、前端页面和权限变更。 +- 使用 `git status --short` 报告变更文件。 + +如果用户要求“深度”或“落地”,不要停在单页概览。必须交付生成索引和人类指南。 diff --git a/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/agents/openai.yaml b/deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/agents/openai.yaml similarity index 100% rename from deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/agents/openai.yaml rename to deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/agents/openai.yaml diff --git a/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/references/compatibility.md b/deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/references/compatibility.md similarity index 100% rename from deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/references/compatibility.md rename to deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/references/compatibility.md diff --git a/deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/references/compatibility_zh.md b/deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/references/compatibility_zh.md new file mode 100644 index 0000000..16e7fc6 --- /dev/null +++ b/deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/references/compatibility_zh.md @@ -0,0 +1,64 @@ +# 跨平台兼容 + +在 macOS、Windows、Linux 或 CI 上运行或安装本 Skill 时使用本参考。 + +## 兼容性摘要 + +| 项目 | macOS | Linux | Windows | +| --- | --- | --- | --- | +| `SKILL.md` 工作流 | 支持 | 支持 | 支持 | +| `project_inventory.py` | Python 3.9+ 支持 | Python 3.9+ 支持 | Python 3.9+ 支持 | +| Python 依赖 | 仅标准库 | 仅标准库 | 仅标准库 | +| 路径处理 | `pathlib` | `pathlib` | `pathlib` | +| shell 依赖 | 脚本无依赖 | 脚本无依赖 | 脚本无依赖 | +| 生成 Markdown 路径 | POSIX 风格相对路径 | POSIX 风格相对路径 | POSIX 风格相对路径 | + +脚本故意使用带 `/` 分隔符的仓库相对路径,使生成 Markdown 在跨平台、GitHub URL 和 RAG metadata 中更稳定。 + +## Python 调用 + +macOS 和 Linux 通常使用: + +```sh +python3 skills/project-knowledge-builder/scripts/project_inventory.py --root . --out docs/project-knowledge/generated +python skills/project-knowledge-builder/scripts/project_inventory.py --root . --out docs/project-knowledge/generated +``` + +Windows PowerShell 通常使用: + +```powershell +py -3 skills\project-knowledge-builder\scripts\project_inventory.py --root . --out docs\project-knowledge\generated +python skills\project-knowledge-builder\scripts\project_inventory.py --root . --out docs\project-knowledge\generated +``` + +CI 应优先使用明确的 Python: + +```sh +python -m py_compile skills/project-knowledge-builder/scripts/project_inventory.py +python skills/project-knowledge-builder/scripts/project_inventory.py --root . --out docs/project-knowledge/generated --json +``` + +## 命令替换 + +| 任务 | macOS/Linux | Windows PowerShell | +| --- | --- | --- | +| 列出文档 | `find docs/project-knowledge -type f \| sort` | `Get-ChildItem docs/project-knowledge -Recurse -File \| Sort-Object FullName` | +| 搜索占位符 | `rg -n "TODO|TBD" docs/project-knowledge` | `rg -n "TODO|TBD" docs/project-knowledge` | +| 查看 git 状态 | `git status --short` | `git status --short` | +| 删除临时输出 | `rm -rf ./kb-inventory` | `Remove-Item .\kb-inventory -Recurse -Force` | + +优先使用 `rg`。如果环境中没有 `rg`,使用该平台可用的搜索工具。 + +## Skill 示例中应避免 + +- 在应支持 Windows 的命令中写死 `/tmp` 路径。 +- 在面向用户的说明中只给 POSIX 命令链,例如 `cmd1 && cmd2`,而不给 PowerShell 等价方式。 +- 假设 shell glob 在 zsh、bash、PowerShell、cmd 中行为一致。 +- 依赖 shebang 可执行行为的 Python 用法;Windows 用户应调用 `py -3 script.py`。 + +## 已知限制 + +- Java endpoint 提取基于正则,故意保持保守。它不会完整解析常量、组合注解或继承的 controller mapping。 +- SQL 表提取查找 `CREATE TABLE`;动态创建的表或其他迁移 DSL 需要人工文档化。 +- 前端路由提取会扫描 route-like 字面量和 view 文件;运行时由 API 生成的路由仍需要后端菜单/权限数据或数据库导出。 +- 超大仓库可能需要调整 `--max-files`,或增加忽略目录。 diff --git a/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/references/output-contract.md b/deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/references/output-contract.md similarity index 100% rename from deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/references/output-contract.md rename to deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/references/output-contract.md diff --git a/deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/references/output-contract_zh.md b/deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/references/output-contract_zh.md new file mode 100644 index 0000000..8bb5cee --- /dev/null +++ b/deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/references/output-contract_zh.md @@ -0,0 +1,135 @@ +# 项目知识库输出契约 + +生成完整代码库知识库时使用本参考。 + +## 语言策略 + +人类阅读的叙述文档应匹配用户语言。如果用户使用中文或要求中文,README、上下文、架构、运行、后端、前端、数据、认证/权限、扩展、排障、RAG 指南和 ADR 都应使用中文。 + +技术锚点保持原样:源码路径、命令、类名、方法名、API 路径、配置键、表名、权限码、JSON key 和元数据枚举值。 + +生成索引可以保持技术化和接近源码。不要为了翻译而破坏可刷新性。 + +## 文档职责 + +| 文件 | 用途 | +| --- | --- | +| `README.md` | 入口、阅读顺序、高价值链接、新鲜度说明。中文输出时标题和使用说明应为中文。 | +| `CONTEXT.md` | 领域语言、业务能力、用户角色、核心名词、不变量。 | +| `CONTEXT-MAP.md` | 限界上下文、模块归属、上下游依赖、运行边界。 | +| `01-architecture.md` | 系统形态、模块图、请求/数据/控制流、扩展点。 | +| `02-runtime.md` | 本地运行、构建、环境变量、Docker、部署模式、进程拓扑。存在时必须识别本地、Docker standalone 和云端模式。 | +| `03-backend.md` | 后端模块、入口点、API 约定、service/mapper/entity 模式。 | +| `04-frontend.md` | 前端启动、路由、状态、API client、组件/页面约定。 | +| `05-data-and-migrations.md` | 数据库、迁移、schema 来源、种子数据、表族。 | +| `06-auth-permissions.md` | 认证、授权、token/session 流、权限码生命周期。仓库存在后端驱动菜单时,必须解释菜单/按钮权限流。 | +| `07-extension-guide.md` | 如何端到端新增功能,并给出有来源依据的示例。适用时必须覆盖后端 CRUD、前端页面和权限/菜单数据变更。 | +| `08-troubleshooting.md` | 面向症状的排障手册,包含命令、文件和可能原因。 | +| `09-rag-indexing.md` | 切块、元数据、排除规则、刷新频率、过期数据策略。 | +| `adr/*.md` | 当前代码已经体现的架构决策。 | +| `generated/*.md` | 可重新生成的数据索引。 | + +## 必要章节 + +每份叙述文档应包含: + +- **已观察来源**:使用过的具体文件或命令。 +- **核心事实**:简洁、有来源依据的陈述。 +- **操作影响**:开发者因这些事实需要采取的不同做法。 +- **未知与风险**:需要运行权限、数据库导出、产品负责人输入或外部文档才能确认的缺口。 + +## 新人可用性门禁 + +认为知识库可交接前,确认新维护者能回答: + +1. 这个产品/项目是什么,哪些目录最重要? +2. 如何启动,或如何识别当前运行模式? +3. 后端接口、前端路由/页面、配置、数据表、权限码在哪里索引? +4. 菜单、按钮和后端权限校验如何连接? +5. 新增普通功能时,通常会改哪些后端、前端、数据库和权限产物? + +如果 README 加叙述文档无法明显回答这些问题,先补充或修改人类阅读指南。不要通过加长生成索引来解决。 + +## 生成索引 + +### `project-inventory.md` + +包含: + +- 仓库根路径 +- 构建/包管理清单 +- 主要语言 +- 主要目录 +- scripts +- Docker/compose 文件 +- env 文件 +- 测试文件 + +### `backend-endpoints.md` + +包含路由方法、路径、类/文件、近似行号、认证注解和备注。不要在 class-level 与 method-level mapping 需要 Java 表达式解析时编造完整路径;展示已观察片段,并标记组合限制。 + +### `frontend-routes.md` + +包含静态路由文件、动态路由加载器、`src/views` 索引、路由守卫文件和外部路由包加载器。 + +### `config-reference.md` + +包含 env 文件、YAML/properties 文件、相关 key、默认值和风险说明。敏感信息必须脱敏。 + +### `database-dictionary.md` + +包含 SQL 文件、迁移文件、表名、表族,以及表是初始化、迁移还是推断得出。不要把巨大 SQL 写进正文;摘要并链接来源。 + +### `permission-codes.md` + +包含后端注解、前端权限 helper/usage、菜单/权限 SQL 命中和认证生命周期说明。 + +## ADR 模板 + +```markdown +# ADR NNNN: 标题 + +Date: YYYY-MM-DD +Status: Observed + +## Context + +说明该决策已经存在的来源事实。 + +## Decision + +当前代码体现的架构决策。 + +## Consequences + +收益、取舍、操作约束和迁移风险。 + +## Sources + +- `path/to/file` +``` + +## RAG 元数据 + +推荐每个 chunk 使用: + +```json +{ + "project": "name", + "snapshot_date": "YYYY-MM-DD", + "source_path": "path", + "doc_type": "architecture|runtime|generated|adr|runbook", + "domain": "backend|frontend|data|auth|deploy", + "freshness": "manual|generated", + "confidence": "observed|inferred|unknown" +} +``` + +## 反模式 + +- 只有长篇说明,没有生成索引。 +- 写与仓库无关的“最佳实践”建议。 +- 把 README 营销文案复制成架构文档。 +- 没有来源路径就声称某接口、表、权限或运行模式存在。 +- 隐藏未知问题,而不是列出来。 diff --git a/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/scripts/project_inventory.py b/deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/scripts/project_inventory.py similarity index 100% rename from deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/scripts/project_inventory.py rename to deliverables/客户AI工程Skill交付包/skills/project-knowledge-builder/scripts/project_inventory.py diff --git a/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/审计日志检查清单.md b/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/审计日志检查清单.md index 0e271a4..093e3bd 100644 --- a/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/审计日志检查清单.md +++ b/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/审计日志检查清单.md @@ -2,7 +2,7 @@ | 检查项 | 通过标准 | 结果 | | --- | --- | --- | -| 创建记录 | 创建工单后能查询审计记录 | | +| 创建记录 | 创建业务对象后能查询审计记录 | | | 字段变更 | 编辑关键字段记录 oldValue/newValue | | | 指派记录 | 指派负责人记录原负责人和新负责人 | | | 状态记录 | 状态流转记录原状态和新状态 | | diff --git a/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/异常态检查清单.md b/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/异常态检查清单.md index e1b6496..74442ae 100644 --- a/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/异常态检查清单.md +++ b/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/异常态检查清单.md @@ -3,7 +3,7 @@ | 异常态 | 必须验证 | 结果 | | --- | --- | --- | | 空列表 | 页面显示空状态,不报错 | | -| 详情不存在 | 提示工单不存在,页面不崩溃 | | +| 详情不存在 | 提示业务对象不存在,页面不崩溃 | | | 权限不足 | 前端提示或隐藏,后端拒绝 | | | 非法状态 | 返回非法流转,状态不变 | | | 重复提交 | 只成功一次,只写一条有效审计 | | diff --git a/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/权限上线前检查清单.md b/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/权限上线前检查清单.md index cacf893..c99b571 100644 --- a/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/权限上线前检查清单.md +++ b/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/权限上线前检查清单.md @@ -2,10 +2,10 @@ | 检查项 | 通过标准 | 结果 | | --- | --- | --- | -| 菜单权限 | 工单菜单只对授权角色可见 | | +| 菜单权限 | 业务对象菜单只对授权角色可见 | | | 按钮权限 | 新增、编辑、删除、指派、状态按钮按权限显示 | | | 接口权限 | 无权限账号直接请求敏感接口失败 | | -| 数据权限 | 普通用户只能看到授权范围内工单 | | +| 数据权限 | 普通用户只能看到授权范围内业务对象 | | | 权限字符 | 前端按钮、菜单配置、后端接口命名一致 | | | 审计 | 权限敏感操作成功后有审计记录 | | | 反例验证 | 无权限账号不能绕过前端执行删除/指派 | | diff --git a/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/看板接口验收清单.md b/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/看板接口验收清单.md index 95e740a..b21d2d7 100644 --- a/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/看板接口验收清单.md +++ b/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/看板接口验收清单.md @@ -4,7 +4,7 @@ | --- | --- | --- | --- | | 默认查询 | 管理员默认条件 | 返回完整指标 | | | 时间过滤 | 指定 start/end | 只统计时间范围内数据 | | -| 类型过滤 | 指定工单类型 | 只统计对应类型 | | +| 类型过滤 | 指定业务对象类型 | 只统计对应类型 | | | 空数据 | 无匹配条件 | 返回 0 或空集合 | | | 无权限 | 未授权账号 | 返回 403 或业务拒绝 | | | 数据范围 | 普通角色 | 只返回授权范围 | | diff --git a/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/阶段Review.prompt.md b/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/阶段Review.prompt.md index 99c3843..db1c747 100644 --- a/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/阶段Review.prompt.md +++ b/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/阶段Review.prompt.md @@ -1,7 +1,7 @@ -# 阶段 Review Prompt +# 阶段 Review 任务上下文契约 ```text -请对 JeecgBoot 工单模块阶段交付物做 Review。 +请对 目标业务模块阶段交付物做 Review。 输入材料: - RBAC 权限矩阵 diff --git a/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/阶段Review报告.template.md b/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/阶段Review报告.template.md index eba0738..b2f6c12 100644 --- a/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/阶段Review报告.template.md +++ b/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/阶段Review报告.template.md @@ -15,7 +15,7 @@ | 等级 | 模块 | 问题 | 触发规则 | 风险 | 修复建议 | 验收 | | --- | --- | --- | --- | --- | --- | --- | -| P1 | 权限 | 删除接口只隐藏按钮,后端缺少权限校验 | 前端按钮不是安全边界 | 可绕过前端删除工单 | 后端补权限校验 | 无权限账号请求返回 403 | +| P1 | 权限 | 删除接口只隐藏按钮,后端缺少权限校验 | 前端按钮不是安全边界 | 可绕过前端删除业务对象 | 后端补权限校验 | 无权限账号请求返回 403 | ## 不确定点 diff --git a/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/验收清单.md b/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/验收清单.md index 230cd33..384e658 100644 --- a/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/验收清单.md +++ b/deliverables/客户AI工程Skill交付包/skills/review-business-delivery/assets/验收清单.md @@ -1,71 +1,35 @@ -# 验收清单 +# 业务交付验收清单 -本清单用于判断学员是否真正掌握 JeecgBoot 工单案例,而不是只复制了 TRAE 输出。建议讲师在小组展示时逐项打分。 +本清单用于审查一个业务模块的设计、代码或 PR 是否具备可交付证据。它不替代目标项目的实际规则、接口规范或测试要求。 ## 项目理解 | 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 能说明 JeecgBoot 前后端目录 | 至少指出 `jeecg-boot`、`jeecgboot-vue3`、`docs/project-knowledge` 的用途 | | -| 能定位后端入口 | 能指出 `JeecgSystemApplication.java` 和后端启动模块 | | -| 能定位前端入口 | 能指出 `src/main.ts`、路由、Pinia 和 API 请求层 | | -| 能区分事实和推断 | 对没有源码依据的业务规则标注为推断或待确认 | | -| 能记录不确定点 | 不确定内容没有被包装成确定事实 | | +|---|---|---| +| 模块边界明确 | 已定位前后端入口、接口、数据模型和必要配置 | | +| 事实与推断分开 | 每个非显然结论能够回到源码、日志、配置或规范 | | +| 待确认项明确 | 无证据内容没有被包装成确定事实 | | -## 需求与接口 +## 改动边界 | 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 用户故事完整 | 覆盖管理员、负责人、研发、测试、只读用户 | | -| 非目标范围明确 | 明确课堂不做完整编码、生产部署、真实数据迁移 | | -| 接口契约完整 | 覆盖列表、详情、新增、编辑、指派、状态、审计、看板 | | -| 失败场景明确 | 每个关键接口至少有权限、参数、状态或数据不存在失败场景 | | -| 验收方式明确 | 每个关键需求都有可执行检查方法 | | +|---|---|---| +| 目标与非目标明确 | 说明本次解决的问题及明确不修改的相邻范围 | | +| 关键风险已识别 | 权限、状态、审计、异常、数据范围和兼容性按需核对 | | +| 最小改动可解释 | 关键规则有权威入口,未引入无关重构 | | -## 权限与状态 +## 验证与交付 | 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 权限矩阵完整 | 覆盖页面级、按钮级、接口级和数据范围 | | -| 权限字符合理 | 命名统一,并标注为建议而非源码事实 | | -| 后端权限未缺失 | 没有用前端按钮隐藏替代后端接口控制 | | -| 状态流转表完整 | 覆盖新建、处理中、待验证、已关闭、已驳回 | | -| 非法流转明确 | 至少列出 5 条非法流转和拒绝原因 | | +|---|---|---| +| 验证覆盖匹配风险 | 正常、异常、权限、状态与边界场景按实际风险设计 | | +| 执行结果如实记录 | 已执行命令、CI、日志或人工观察可复核;未执行项明确列出 | | +| PR 或变更说明完整 | 包含目标、实现范围、验证证据和已知边界 | | +| 资产沉淀有依据 | 仅将可复用且已验证的判断沉淀为 Rule、任务上下文契约、检查单、Skill 或 MCP 契约 | | -## 审计、异常态与看板 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 审计操作覆盖关键动作 | 创建、编辑、指派、状态流转、关闭、评论至少覆盖 5 类 | | -| 审计字段可追溯 | 包含 actor、action、targetId、oldValue、newValue、source | | -| 敏感信息处理 | oldValue/newValue 有脱敏说明 | | -| 异常态可测试 | 权限不足、重复提交、非法状态、数据不存在、并发修改均覆盖 | | -| 看板指标可计算 | 每个指标有口径、维度、过滤条件和数据来源建议 | | - -## Review 与交付 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| Review 有证据 | 问题能回到路径、表格或业务规则 | | -| 风险按严重程度排序 | 高风险问题优先展示 | | -| 建议可行动 | 每条建议有修复方向和验收方式 | | -| 交付物齐全 | [worksheets.md](./worksheets.md) 中最终交付记录完整 | | -| 能沉淀资产 | 能说明哪些内容进入 [assets-index.md](./assets-index.md) 的资产库 | | - -## 评分建议 - -| 等级 | 分数 | 描述 | -| --- | ---: | --- | -| A | 90-100 | 依据充分,设计完整,风险清楚,验收可执行 | -| B | 75-89 | 主链路完整,少量不确定点或验收细节不足 | -| C | 60-74 | 有基本产出,但权限、状态、审计或异常态明显薄弱 | -| D | 0-59 | 泛泛总结项目,缺少源码依据或无法验收 | - -## 讲师判定原则 - -- 优先看证据,不优先看文字是否漂亮。 -- 优先看权限、状态、审计、异常态,不只看 CRUD。 -- 优先看不确定点是否被诚实标出。 -- 优先看能否复用到团队资产库。 -- 对没有依据的自信结论扣分。 +## 判定原则 +- 优先审查证据与边界,不以篇幅或术语数量判断质量。 +- 前端隐藏或必填不能替代后端权限和业务状态保护。 +- 局部测试、CI 通过和源码阅读分别只支撑相应范围的结论。 +- 发生资产变更时,补充行为、引用、兼容性和夹具验证证据。 diff --git a/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/修复记录生成.prompt.md b/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/修复记录生成.prompt.md index 24fcfe7..01d6ad3 100644 --- a/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/修复记录生成.prompt.md +++ b/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/修复记录生成.prompt.md @@ -1,4 +1,4 @@ -# 修复记录生成 Prompt +# 修复记录生成 任务上下文契约 ```text 请根据阶段 Review 报告生成修复记录。 diff --git a/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/审计日志缺失反例.md b/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/审计日志缺失反例.md index 8dba8d6..e0f7166 100644 --- a/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/审计日志缺失反例.md +++ b/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/审计日志缺失反例.md @@ -2,13 +2,13 @@ ## 场景 -工单指派负责人成功,但没有记录审计日志。 +业务对象指派负责人成功,但没有记录审计日志。 ## 问题表现 | 操作 | 结果 | | --- | --- | -| 张三将工单指派给李四 | 工单负责人变更成功 | +| 张三将业务对象指派给李四 | 业务对象负责人变更成功 | | 查询审计日志 | 没有负责人变更记录 | ## 风险 @@ -28,5 +28,5 @@ | --- | --- | | 指派成功 | 审计列表出现 ASSIGN 记录 | | 指派失败 | 不出现成功审计 | -| 查询详情 | 审计记录可在工单详情查看 | +| 查询详情 | 审计记录可在业务对象详情查看 | diff --git a/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/权限遗漏反例.md b/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/权限遗漏反例.md index 0e3cfb7..60b9887 100644 --- a/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/权限遗漏反例.md +++ b/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/权限遗漏反例.md @@ -2,19 +2,19 @@ ## 场景 -工单删除按钮在前端按权限隐藏,但后端删除接口没有权限校验。 +业务对象删除按钮在前端按权限隐藏,但后端删除接口没有权限校验。 ## 问题代码表现 | 层级 | 表现 | | --- | --- | | 前端 | 普通研发账号看不到删除按钮 | -| 后端 | 直接请求 `/workOrder/delete` 仍可删除 | +| 后端 | 直接请求 `//delete` 仍可删除 | | 审计 | 删除记录可能缺失 | ## 风险 -攻击者或误操作用户可以绕过前端直接调用接口删除工单。 +攻击者或误操作用户可以绕过前端直接调用接口删除业务对象。 ## 修复要求 @@ -27,7 +27,7 @@ | 账号 | 操作 | 预期 | | --- | --- | --- | -| 管理员 | 删除允许删除的工单 | 成功并写审计 | +| 管理员 | 删除允许删除的业务对象 | 成功并写审计 | | 普通研发 | 直接请求删除接口 | 失败 | | 只读用户 | 前端访问删除按钮 | 不可见 | diff --git a/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/看板指标口径不清反例.md b/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/看板指标口径不清反例.md index be5ee62..885263a 100644 --- a/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/看板指标口径不清反例.md +++ b/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/看板指标口径不清反例.md @@ -8,7 +8,7 @@ | 指标 | 当前说明 | | --- | --- | -| 平均关闭周期 | 最近工单平均处理时间 | +| 平均关闭周期 | 最近业务对象平均处理时间 | ## 风险 @@ -17,7 +17,7 @@ ## 修复要求 1. 明确定义为 `close_time - create_time` 或其他确定口径。 -2. 明确是否排除未关闭工单。 +2. 明确是否排除未关闭业务对象。 3. 明确时间范围过滤字段。 4. 用样例数据手工校验。 @@ -25,7 +25,7 @@ | 样例 | 预期 | | --- | --- | -| 工单 A 创建 1 日、关闭 3 日 | 周期 2 天 | -| 未关闭工单 | 不计入平均关闭周期 | +| 业务对象 A 创建 1 日、关闭 3 日 | 周期 2 天 | +| 未关闭业务对象 | 不计入平均关闭周期 | | 过滤 7 月 | 只统计 7 月范围内符合口径的数据 | diff --git a/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/非法状态流转反例.md b/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/非法状态流转反例.md index e675156..6830a33 100644 --- a/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/非法状态流转反例.md +++ b/deliverables/客户AI工程Skill交付包/skills/track-review-fix-loop/assets/非法状态流转反例.md @@ -2,7 +2,7 @@ ## 场景 -已关闭工单可以直接被改回处理中。 +已关闭业务对象可以直接被改回处理中。 ## 问题表现 @@ -12,12 +12,12 @@ ## 风险 -已关闭工单被重新打开但没有重开原因、没有审计、没有负责人确认,导致统计和责任链失真。 +已关闭业务对象被重新打开但没有重开原因、没有审计、没有负责人确认,导致统计和责任链失真。 ## 修复要求 1. Service 层集中校验状态流转。 -2. 已关闭工单只能通过“重新打开”动作变为新建。 +2. 已关闭业务对象只能通过“重新打开”动作变为新建。 3. 重新打开必须填写原因。 4. 所有成功流转写审计日志。 diff --git a/deliverables/客户AI工程Skill交付包/交付清单.md b/deliverables/客户AI工程Skill交付包/交付清单.md new file mode 100644 index 0000000..82f46ca --- /dev/null +++ b/deliverables/客户AI工程Skill交付包/交付清单.md @@ -0,0 +1,42 @@ +# 正式交付清单 + +## 版本信息 + +- 交付名称:客户 AI 工程资产交付包 +- 版本:V1.0 +- 整理日期:2026-08-06 +- 正式载体:本目录与同级 `客户AI工程Skill交付包.zip` + +目录与 ZIP 内容一致;对外发送时任选一种载体即可,不应混用历史压缩包或旧目录。 + +## 正式交付范围 + +| 类别 | 内容 | 用途 | +|---|---|---| +| 项目约束 | `generate-project-constraints` | 从目标项目提取可验证的开发约束 | +| 项目知识 | `project-knowledge-builder` | 建立源码依据的项目知识库与索引 | +| 复杂业务设计 | `design-complex-business-module` | 设计权限、状态、审计、异常态和指标 | +| 交付审查 | `review-business-delivery` | 审查设计、代码或 PR 的风险与验证缺口 | +| 修复闭环 | `track-review-fix-loop` | 跟踪 Review 问题、修复、验证与资产更新 | +| MCP 资产 | `mcp-assets/` | 描述外部能力、权限、副作用、失败降级和验收夹具 | +| 实战案例 | `examples/jeecgboot-official-vehicle/` | 基于现有公务用车模块的单人影响分析与验证案例 | + +## 使用边界 + +- 五个 Skill 与 MCP 资产均为通用工程资产;业务对象、路径、接口、权限码、状态和测试命令必须从目标项目重新取得。 +- 公务用车案例只保留已经读取和验证的事实,用于说明方法,不构成任何客户业务要求。 +- MCP 目录不包含服务地址、凭据、内部域名或真实数据。接入时必须先完成能力契约、权限审查和验证夹具。 +- 本包不包含客户源码、训练分支、构建产物、在线考试题或讲师逐字稿;这些材料按其使用场景独立管理。 + +## 交付前核验项 + +- [x] 五个 Skill 目录均包含 `SKILL.md`,并保留各自的 assets、references、scripts 或 agents 文件。 +- [x] `mcp-assets/` 包含能力契约模板、权限与验收清单及使用说明。 +- [x] 扫描确认未携带历史案例、多人协作要求、默认账号或系统元数据文件。 +- [x] 压缩包可完整解压,且内容与本目录一致。 + +## 交付后的首次动作 + +1. 阅读 `README.md` 与 `安装说明.md`,确认目标 AI 编程工具的 Skill 加载方式。 +2. 在目标项目先运行项目约束提取与项目知识构建,再选择复杂业务设计或 Review 类 Skill。 +3. MCP 接入前填写能力契约,未完成权限和夹具验证时不得给 Agent 授权高风险工具。 diff --git a/deliverables/客户AI工程Skill交付包/安装说明.md b/deliverables/客户AI工程Skill交付包/安装说明.md index e931a84..3c0974e 100644 --- a/deliverables/客户AI工程Skill交付包/安装说明.md +++ b/deliverables/客户AI工程Skill交付包/安装说明.md @@ -1,63 +1,56 @@ # 安装说明 -## 方式一:安装到 TRAE Skills 目录 +## 使用前确认 -把 `skills/` 下的 4 个 Skill 文件夹复制到本机 Skills 目录: +本包不携带外部系统地址、账号、令牌或客户数据。请先在目标项目中确认 AI 工具的 Skill 加载方式、项目规则位置和访问权限,再决定安装范围。 -```text -~/.trae/skills/ -``` +## 方式一:作为本地 Skill 目录使用 -安装后目录应类似: +将 `skills/` 下需要启用的完整文件夹复制到当前 AI 编程工具配置的本地 Skill 目录。不同版本和不同工具的目录规则可能不同,应以当前客户端实际配置为准。 -```text -~/.trae/skills/ -├── generate-project-constraints/ -├── design-complex-business-module/ -├── review-business-delivery/ -└── track-review-fix-loop/ -``` - -每个 Skill 文件夹必须保留完整结构: +每个 Skill 必须保留原始目录结构: ```text skill-name/ ├── SKILL.md ├── agents/ │ └── openai.yaml -└── assets/ +├── assets/ # 部分 Skill 提供 +├── references/ # 部分 Skill 提供 +└── scripts/ # 部分 Skill 提供 ``` -## 调用方式 +推荐启用顺序: -在支持 Skill 的 AI 编程工具中,可以这样调用: - -```text -Use $generate-project-constraints 为当前项目生成 AI 开发约束。 -Use $design-complex-business-module 设计这个业务模块的权限、状态、审计、异常态和看板。 -Use $review-business-delivery Review 这批阶段交付物。 -Use $track-review-fix-loop 根据 Review 报告生成修复闭环记录。 -``` +1. `generate-project-constraints`:先获取项目约束。 +2. `project-knowledge-builder`:建立项目地图和可检索知识。 +3. `design-complex-business-module`:设计权限、状态、审计等高风险边界。 +4. `review-business-delivery`:审查设计、代码或 PR。 +5. `track-review-fix-loop`:跟踪修复、验证与资产更新。 ## 方式二:作为团队资产库使用 -如果当前 AI 工具暂不支持原生 Skill 机制,可以把本包作为团队资产库使用: +如果当前工具不支持原生 Skill 发现,可将本包纳入团队资产库。保留目录结构,并让任务上下文契约或 Agent 明确引用对应文件。 ```text ai-engineering-assets/ ├── skills/ -├── rules/ -├── prompts/ -├── templates/ -├── checklists/ +├── mcp-assets/ └── examples/ ``` -此时 `SKILL.md` 作为标准工作流说明,`assets/` 作为可复制模板、Prompt、检查清单和反例库。 +其中 `SKILL.md` 负责输入、工作流、输出契约和停止条件;`assets/` 存放模板、任务上下文契约、检查单和反例;`mcp-assets/` 负责外部能力的契约、权限与验收边界。 -## 维护建议 +## MCP 资产的使用 -- 每次项目规则变化后,重新运行 `generate-project-constraints`。 -- 每次复杂业务模块交付后,把高频问题沉淀到 `review-business-delivery` 或 `track-review-fix-loop` 的 assets。 -- 不要把某个项目的专用规则直接复制成通用规则。 -- 对无法从源码或配置证明的内容,统一标记为待确认。 +1. 使用 `mcp-assets/MCP能力契约.template.md` 描述每项待接入能力。 +2. 使用 `mcp-assets/MCP工具权限与验收清单.md` 审查数据范围、权限、副作用和故障降级。 +3. 为完整请求、缺参、越权、超时和敏感字段准备验证夹具。 +4. 只有契约与验证完整后,才把工具授权给对应 Agent。 + +## 维护要求 + +- 项目规则、框架、接口、权限或测试基座变化后,重新取证并更新相关资产。 +- 不要将示例中的业务对象、路径、字段、接口、状态或验证结论复制到其他项目。 +- 资产变更需要说明行为变化、兼容边界、迁移方式、回退条件和验证证据。 +- 对无法证明的内容保留为待确认,不以经验或模型建议替代事实。 diff --git a/exams/TRAE编程培训考试评估卷-实操与项目实战.docx b/exams/TRAE编程培训考试评估卷-实操与项目实战.docx new file mode 100644 index 0000000000000000000000000000000000000000..c9947dfe4bab491fe11f8a3f2711c3cea99008a7 GIT binary patch literal 36350 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 C$j2)H literal 0 HcmV?d00001 diff --git a/exams/TRAE编程培训考试评估卷-实操与项目实战(修订版).docx b/exams/TRAE编程培训考试评估卷-实操与项目实战(修订版).docx new file mode 100644 index 0000000000000000000000000000000000000000..e027adfd7c9247a73e3abfab96d71a9534616635 GIT binary patch literal 37520 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 literal 0 HcmV?d00001 diff --git a/exams/TRAE编程培训考试评估卷-实操与项目实战(阅卷参考答案).docx b/exams/TRAE编程培训考试评估卷-实操与项目实战(阅卷参考答案).docx new file mode 100644 index 0000000000000000000000000000000000000000..a3f70cbec3cd82f6829086efede9989c2695b90b GIT binary patch literal 42837 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 U;H&qqjtmR10`3L8OaJ=xe^^d_WB>pF literal 0 HcmV?d00001 diff --git a/exams/exam-kit/README.md b/exams/exam-kit/README.md new file mode 100644 index 0000000..599d43b --- /dev/null +++ b/exams/exam-kit/README.md @@ -0,0 +1,34 @@ +# TRAE 编程培训考试包 + +本目录是 B 卷的唯一考试输入。评分以本目录给出的 GitHub 仓库、固定提交、指定 diff 和提交模板为准。 + +## 阅卷参考答案 + +`reference-answer.md` 仅供讲师和阅卷人员使用,不能与学生版 `exam-output/` 一同发放。它给出每题的关键证据、可接受结论、测试口径和 Review 要点;学员不必逐字复述,但结论必须能由固定版本源码或指定 diff 支撑。 + +## 公开源码与 diff + +- GitHub 仓库:`https://github.com/jeecgboot/JeecgBoot.git` +- 固定版本:标签 `v3.9.2`,提交 `7df07a823fd558be857d0208ccae96342539fbc1` +- 指定变更:`review-diff.patch`,仅包含两处文件变更。 + +考试开始前完成源码下载;下载时间不计入作答时间。建议命令: + +```bash +git clone --depth 1 --branch v3.9.2 https://github.com/jeecgboot/JeecgBoot.git +cd JeecgBoot +git rev-parse HEAD +``` + +输出必须为 `7df07a823fd558be857d0208ccae96342539fbc1`。版本不一致时,立即记录为环境问题并联系监考人员,不得基于其他版本作答。 + +## 允许与禁止 + +- 允许使用 TRAE、上述 GitHub 固定版本和本考试包中的材料。 +- 不要求修改业务代码;不得将本次作答建立在未指定版本、历史成品或他人交付物上。 +- 所有项目结论必须能回溯到固定工作区中的文件、符号或指定 diff。 +- 测试只能如实标记为“已执行”或“未执行”;未执行时必须说明原因和计划验证方式。 + +## 提交方式 + +将 `exam-output/` 中的四个模板另存为个人提交目录并补全内容。不要改动文件名。 diff --git a/exams/exam-kit/exam-output/01-project-understanding.md b/exams/exam-kit/exam-output/01-project-understanding.md new file mode 100644 index 0000000..15188c0 --- /dev/null +++ b/exams/exam-kit/exam-output/01-project-understanding.md @@ -0,0 +1,21 @@ +# 项目理解与影响分析 + +## 结论摘要 + + + +## 证据表 + +| 结论 | 文件路径与符号 | 证据说明 | 判断类型 | +|---|---|---|---| +| | | | 事实 / 推断 / 待确认 | + +## 影响与保护边界 + + + +## 待确认项 + +| 待确认问题 | 原因 | 需要的证据或验证方式 | +|---|---|---| +| | | | diff --git a/exams/exam-kit/exam-output/02-change-and-test.md b/exams/exam-kit/exam-output/02-change-and-test.md new file mode 100644 index 0000000..d889677 --- /dev/null +++ b/exams/exam-kit/exam-output/02-change-and-test.md @@ -0,0 +1,23 @@ +# 最小改造与测试安全网 + +## 改造目标与验收标准 + +## 校验位置与顺序 + + + +## 不改动项 + +| 不改动项 | 保持原因 | 代码依据 | +|---|---|---| +| | | | + +## 测试矩阵 + +| 场景 | 预期结果 | 验证入口 | 执行状态 | 命令或未执行原因 | +|---|---|---|---|---| +| 空白或非法状态值 | | | 已执行 / 未执行 | | +| 正常冻结 | | | 已执行 / 未执行 | | +| 正常解冻 | | | 已执行 / 未执行 | | +| 无权限调用 | | | 已执行 / 未执行 | | +| 管理员保护与缓存/用户状态回归 | | | 已执行 / 未执行 | | diff --git a/exams/exam-kit/exam-output/03-pr-and-assets.md b/exams/exam-kit/exam-output/03-pr-and-assets.md new file mode 100644 index 0000000..4188f62 --- /dev/null +++ b/exams/exam-kit/exam-output/03-pr-and-assets.md @@ -0,0 +1,31 @@ +# Review、PR 与资产沉淀 + +## Review 结论 + + + +| 等级或结论 | 证据 | 风险 | 建议的验收方式 | +|---|---|---|---| +| | | | | + +## PR 说明 + +### 背景 + +### 实现 + +### 测试 + +### 风险与回退 + +## 可复用 Prompt + +### 输入 + +### 约束与输出 + +### 验收方式 + +## Rule、Checklist 或 Skill 设计卡 + + diff --git a/exams/exam-kit/exam-output/trae-usage-record.md b/exams/exam-kit/exam-output/trae-usage-record.md new file mode 100644 index 0000000..ac9dd37 --- /dev/null +++ b/exams/exam-kit/exam-output/trae-usage-record.md @@ -0,0 +1,5 @@ +# TRAE 使用记录 + +| Prompt 或操作 | TRAE 建议摘要 | 采纳或否决 | 人工判断点 | 证据或验证状态 | +|---|---|---|---|---| +| | | | | 已执行 / 未执行 | diff --git a/exams/exam-kit/reference-answer.md b/exams/exam-kit/reference-answer.md new file mode 100644 index 0000000..a2f1b71 --- /dev/null +++ b/exams/exam-kit/reference-answer.md @@ -0,0 +1,168 @@ +# TRAE 编程培训考试评估卷 - 阅卷参考答案 + +> 阅卷专用。对应 `jeecgboot/JeecgBoot` 标签 `v3.9.2`、提交 `7df07a823fd558be857d0208ccae96342539fbc1` 及 `review-diff.patch`。学生不需要逐字复述本答案,但所有核心结论都必须有等价的源码依据。 + +## A 卷 + +### 任务 1:Prompt 改写(15 分) + +下列内容是一份达到满分要求的参考 Prompt。允许更换表述,但不能省略范围、约束、交付格式和验收条件。 + +```text +你是负责 JeecgBoot 系统用户模块的后端工程师。请基于 GitHub 仓库 +jeecgboot/JeecgBoot 的 v3.9.2(提交 7df07a823fd558be857d0208ccae96342539fbc1), +审查“批量冻结/解冻用户”的后端链路是否允许非法状态值进入数据层。 + +范围:只分析 +1. SysUserController.frozenBatch; +2. ISysUserService.updateStatus 和 SysUserServiceImpl.updateStatus; +3. CommonConstant 中的用户状态常量; +4. 与本接口直接相关的权限、管理员保护、缓存和测试入口。 + +目标:给出最小改造建议,使 status 只允许 1(解冻)或 2(冻结),并在数据库写入前拒绝 +空白或其他值。不要修改接口路径、权限 system:user:frozenBatch、管理员保护或缓存失效策略。 + +请按以下格式输出: +- 调用链和每个结论的“文件路径 + 方法/符号”; +- 变更位置、伪代码和不改动项; +- 空白/非法状态、冻结、解冻、无权限、管理员保护和缓存回归的测试矩阵; +- 仍需人工确认的问题。事实、推断和待确认项必须分别标识。 + +验收:每个结论均可回溯到上述固定提交;不编造测试结果;信息不足时先提出待确认项。 +``` + +判分关键:背景与目标(3 分)、范围(2 分)、约束(4 分)、输出格式(3 分)、验收和证据要求(3 分)。只写“帮我检查有没有问题”不能得高分。 + +### 任务 2:AI 输出辨析(15 分) + +题干中的 AI 结论“页面已限制状态值,因此 Service 不需要校验;直接调用接口也不会有问题”应判为错误,理由如下: + +1. 前端限制不是服务端的可信边界。调用者可以绕过页面直接发送 `PUT /sys/user/frozenBatch` 请求。 +2. `SysUserController.frozenBatch` 从 JSON 读取 `status` 后逐个调用 `sysUserService.updateStatus(id, status)`;原版 `updateStatus` 会直接执行 `Integer.parseInt(status)` 并更新数据库。 +3. Service 入口是数据写入前的最后业务保护点。正确做法是在 `updateStatus` 内、`Integer.parseInt` 和 `userMapper.update` 之前,仅允许 `CommonConstant.USER_UNFREEZE`(1)和 `CommonConstant.USER_FREEZE`(2)。`null`、空串、空白串、`3` 及其他值均应抛出业务异常。 +4. 不能因此删除接口权限、管理员保护或缓存注解;这些分别处理调用授权、关键账号保护和用户缓存一致性。 + +## B 卷任务 1:项目理解与影响分析(25 分) + +### 满分结论摘要 + +1. 批量冻结/解冻的 HTTP 入口是 `PUT /sys/user/frozenBatch`,由 `SysUserController.frozenBatch` 处理,受 `system:user:frozenBatch` 权限保护。 +2. Controller 从请求体读取 `ids`、先调用 `checkUserAdminRejectDel(ids)`,再读取 `status`,对每个非空 ID 调用 `updateStatus(id, status)`。 +3. `updateStatus` 是最终写入入口:它将状态转换为整数,通过 `userMapper.update` 更新 `SysUser.status`,并用 `@CacheEvict(CacheConstant.SYS_USERS_CACHE, allEntries = true)` 失效用户缓存。 +4. 用户状态常量中,`1` 表示解冻,`2` 表示冻结,`3` 表示注销;本题只允许前两种状态。 +5. 冻结状态会影响登录有效性判断:服务在读取到 `USER_FREEZE` 时返回“该用户已冻结”。 + +### 至少应出现的证据 + +| 结论 | 文件路径与符号 | 证明内容 | 判断类型 | +|---|---|---|---| +| 接口、HTTP 方法和权限 | `.../controller/SysUserController.java`,`frozenBatch` | `@RequiresPermissions("system:user:frozenBatch")` 与 `@RequestMapping(... "/frozenBatch", PUT)` | 事实 | +| Controller 到 Service 调用链 | 同文件 `frozenBatch` | 读取 `ids`、管理员保护、读取 `status`,循环调用 `updateStatus` | 事实 | +| 服务契约 | `.../service/ISysUserService.java`,`updateStatus(String id, String status)` | 状态更新是服务接口的一部分 | 事实 | +| 数据库与缓存副作用 | `.../service/impl/SysUserServiceImpl.java`,`updateStatus` | `@CacheEvict`、`Integer.parseInt(status)`、`userMapper.update` | 事实 | +| 合法状态枚举 | `.../common/constant/CommonConstant.java`,`USER_UNFREEZE`、`USER_FREEZE`、`USER_QUIT` | 取值分别为 1、2、3 | 事实 | +| 管理员保护 | `.../service/impl/SysUserServiceImpl.java`,`checkUserAdminRejectDel` | 目标 ID 中含用户名 `admin` 时抛出业务异常;`frozenBatch` 会调用它 | 事实 | +| 登录影响 | `.../service/impl/SysUserServiceImpl.java`,登录有效性检查中对 `USER_FREEZE` 的分支 | 冻结用户不能正常登录 | 事实 | + +### 影响和保护边界 + +- 非法状态若进入原版 Service,会在 `Integer.parseInt` 处异常,或在可解析但不受业务允许的值(如 `3`)场景写入不符合本题要求的状态;因此应在 Service 入口先拒绝。 +- 校验位于 `userMapper.update` 前;抛出异常时不会完成本次用户状态写入。`@CacheEvict` 默认在方法成功返回后生效,因此异常路径不应把一次失败校验伪装为成功变更。 +- 必须保持:接口路径和 PUT 方法、`system:user:frozenBatch` 权限、`checkUserAdminRejectDel(ids)` 调用、逐个用户更新的方式、缓存失效注解。 +- 可接受的待确认项:批量操作是否要求原子性、前端是否会发送 `null` 或空白状态、缓存是否有跨节点配置。不得把这些未查证项写成事实。 + +## B 卷任务 2:最小改造与测试安全网(25 分) + +### 改造位置和参考实现 + +文件:`jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysUserServiceImpl.java`。 + +方法:`updateStatus(String id, String status)`。 + +位置:保留已有 `@Override` 和 `@CacheEvict`,在 `Integer.parseInt(status)`、`userMapper.update(...)` 之前加入校验: + +```java +if (!String.valueOf(CommonConstant.USER_UNFREEZE).equals(status) + && !String.valueOf(CommonConstant.USER_FREEZE).equals(status)) { + throw new JeecgBootException("账号状态仅支持冻结或解冻"); +} +``` + +该写法同时拒绝 `null`、空串、空白串、`3` 和其他非 `1`/`2` 的值;允许 `"1"` 与 `"2"`。它没有改变原有数据更新、缓存失效、权限或管理员保护路径。 + +### 必须明确的不改动项 + +| 不改动项 | 原因和代码依据 | +|---|---| +| 权限 | `SysUserController.frozenBatch` 的 `@RequiresPermissions("system:user:frozenBatch")` 仍是接口授权边界。 | +| 管理员保护 | Controller 继续先调用 `sysUserService.checkUserAdminRejectDel(ids)`;状态校验不能替代该保护。 | +| 缓存失效 | `SysUserServiceImpl.updateStatus` 的 `@CacheEvict(value = {CacheConstant.SYS_USERS_CACHE}, allEntries = true)` 保留,以保证成功修改后的用户缓存刷新。 | +| 数据写入方式 | 合法状态仍走原有 `userMapper.update(new SysUser().setStatus(...), ...)`。 | + +### 参考测试矩阵 + +| 场景 | 预期结果 | 验证入口 | 执行状态的正确写法 | +|---|---|---|---| +| `null`、空串、空白串、`3` | 抛出 `JeecgBootException`;不调用 mapper,不成功失效缓存 | `SysUserServiceImpl.updateStatus` 单元测试 | 未实际运行时必须写“未执行”和原因;不能凭空写通过 | +| `"1"` 解冻 | 调用 mapper,写入状态 1;成功返回后缓存失效 | Service 单元测试或集成测试 | 记录实际命令和结果 | +| `"2"` 冻结 | 调用 mapper,写入状态 2;成功返回后缓存失效 | Service 单元测试或集成测试 | 记录实际命令和结果 | +| 无权限请求 | 被权限框架拒绝,不能执行状态写入 | `PUT /sys/user/frozenBatch` 的接口/集成测试 | 记录测试账号、HTTP 结果或未执行原因 | +| 目标包含 `admin` | `checkUserAdminRejectDel` 拒绝,不能更新管理员 | 接口或 Controller 集成测试 | 记录结果或未执行原因 | +| 冻结后登录 | 读取冻结用户时走“该用户已冻结”分支 | 登录集成测试 | 可标为待环境验证,不得臆造 | + +`review-diff.patch` 已提供一个针对 `"3"` 的最小单元测试。它覆盖非法数值在访问 mapper 前被拒绝;高质量答案还应补充空白值和两个合法值的测试。项目父 POM 默认配置 `maven-surefire-plugin` 跳过测试,因此学员若未调整本地测试配置,应如实标记“未执行”,并给出启用测试后的计划命令,而不是伪造成功结果。 + +## B 卷任务 3:Review、PR 与资产沉淀(20 分) + +### 参考 Review 结论 + +**结论:可以合入,但建议在合入前补充测试用例。** + +检查范围是 `review-diff.patch` 的两处改动:`SysUserServiceImpl.updateStatus` 和新增的 `SysUserServiceImplTest`。 + +| 级别 | 结论 | 证据 | 建议 | +|---|---|---|---| +| 通过 | 校验位于 `Integer.parseInt` 和 `userMapper.update` 前,阻止非法状态写入 | 服务实现新增条件与业务异常 | 保持 | +| 通过 | 仅允许常量 1/2,`null` 和空白均不匹配,状态 3 也被拒绝 | 两个常量比较均通过 `String.valueOf` | 保持 | +| 通过 | 没有删除缓存失效注解、接口权限或管理员保护 | diff 只改 Service 校验和新增测试 | 保持 | +| 建议 | 新测试仅覆盖 `"3"`,未直接覆盖 `null`、空白、`"1"`、`"2"` 和 mapper 未调用 | 新增测试类只有一个 `assertThrows` | 加参数化负例和合法值的 mapper 交互断言 | +| 残余风险 | Controller 对异常统一返回的语义、批量更新原子性和集群缓存行为不属于本 diff 的直接变更 | 原始 Controller 和框架配置 | 作为后续集成测试范围,不阻断本次最小修复 | + +不能仅写“代码没问题”。若给出“未发现阻断问题”,仍须写清检查范围、证据和残余风险。 + +### 参考 PR 说明 + +**背景**:系统用户批量冻结/解冻接口会将请求中的 `status` 传入 Service。前端选项限制不能替代服务端边界,非法值应在数据写入前被拒绝。 + +**实现**:在 `SysUserServiceImpl.updateStatus` 增加状态白名单,仅允许解冻(1)和冻结(2);非法值抛出 `JeecgBootException`。保留原缓存失效、Controller 权限和管理员保护。 + +**测试**:新增非法状态 `"3"` 的单元测试。合入前建议补充 `null`、空白、`"1"`、`"2"` 及接口权限/管理员保护的测试;每项测试状态必须按实际执行情况填写。 + +**风险与回退**:该变更会拒绝过去可能被错误接受的状态值,属于预期的业务收紧。出现兼容性问题时可回退本次 Service 白名单提交;不能通过放宽为任意整数解决。 + +### 参考可复用 Prompt + +```text +基于指定 Git 提交审查一个状态更新 diff。先输出 Controller -> Service -> Mapper/缓存的调用链, +再判断校验是否发生在所有数据写入和可见副作用之前。只接受代码中已定义的状态常量;列出保留的 +权限、关键账号保护、缓存和审计逻辑。最后按“阻断问题 / 建议 / 残余风险 / 测试矩阵”输出, +每项都给出文件路径和符号。没有执行的测试必须标为未执行,不能推断为通过。 +``` + +验收方式:输出必须能标出校验位置、常量来源、未改动保护和至少一项负例及正例测试;不能凭空声称测试成功。 + +### 参考 Checklist 设计卡 + +| 项目 | 内容 | +|---|---| +| 名称 | 状态变更服务端边界 Checklist | +| 适用场景 | 新增或修改“启用/停用/冻结/解冻/审批状态”等服务端状态写入逻辑时 | +| 输入 | 固定提交、变更 diff、状态常量定义、调用入口 | +| 检查项 | 1. 合法状态是否白名单化;2. 校验是否早于解析、写库和副作用;3. 权限和关键账号保护是否保留;4. 缓存/审计/事件是否明确;5. 正负例和接口授权测试是否如实记录 | +| 输出 | Review 结论、证据表、测试矩阵、残余风险 | +| 边界 | 只辅助检查,不能替代实际权限配置、集成测试和人工业务审批 | +| 验收 | 每项结论都有文件路径和符号;未执行测试有原因与后续验证计划 | + +## AI 使用记录的判分口径 + +不提供统一的“标准 Prompt 列表”。满分记录应至少能够说明:使用 AI 或 TRAE 做了什么、建议是什么、哪些被采纳或否决、人工为什么这样判断、相应证据或测试是否真的执行。工具名称可以不同;缺少 `trae-usage-record.md` 时,本题“AI 使用记录”2 分为 0 分。 diff --git a/exams/exam-kit/review-diff.patch b/exams/exam-kit/review-diff.patch new file mode 100644 index 0000000..ce92a3c --- /dev/null +++ b/exams/exam-kit/review-diff.patch @@ -0,0 +1,37 @@ +diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysUserServiceImpl.java b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysUserServiceImpl.java +index c2fe868..236b4f2 100644 +--- a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysUserServiceImpl.java ++++ b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysUserServiceImpl.java +@@ -1464,6 +1464,10 @@ public class SysUserServiceImpl extends ServiceImpl impl + @Override + @CacheEvict(value={CacheConstant.SYS_USERS_CACHE}, allEntries=true) + public void updateStatus(String id, String status) { ++ if (!String.valueOf(CommonConstant.USER_UNFREEZE).equals(status) ++ && !String.valueOf(CommonConstant.USER_FREEZE).equals(status)) { ++ throw new JeecgBootException("账号状态仅支持冻结或解冻"); ++ } + userMapper.update(new SysUser().setStatus(Integer.parseInt(status)), + new UpdateWrapper().lambda().eq(SysUser::getId,id)); + } +diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/test/java/org/jeecg/modules/system/service/impl/SysUserServiceImplTest.java b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/test/java/org/jeecg/modules/system/service/impl/SysUserServiceImplTest.java +new file mode 100644 +index 0000000..082f21a +--- /dev/null ++++ b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/test/java/org/jeecg/modules/system/service/impl/SysUserServiceImplTest.java +@@ -0,0 +1,16 @@ ++package org.jeecg.modules.system.service.impl; ++ ++import org.jeecg.common.exception.JeecgBootException; ++import org.junit.jupiter.api.Test; ++ ++import static org.junit.jupiter.api.Assertions.assertThrows; ++ ++class SysUserServiceImplTest { ++ ++ @Test ++ void rejectsAStatusOutsideFreezeAndUnfreezeBeforeUpdatingUser() { ++ SysUserServiceImpl service = new SysUserServiceImpl(); ++ ++ assertThrows(JeecgBootException.class, () -> service.updateStatus("user-1", "3")); ++ } ++} diff --git a/exams/exam-kit/source-version.txt b/exams/exam-kit/source-version.txt new file mode 100644 index 0000000..154d244 --- /dev/null +++ b/exams/exam-kit/source-version.txt @@ -0,0 +1,9 @@ +repository=jeecgboot/JeecgBoot +repository_url=https://github.com/jeecgboot/JeecgBoot.git +base_ref=v3.9.2 +base_commit=7df07a823fd558be857d0208ccae96342539fbc1 +controller_path=jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysUserController.java +service_path=jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysUserServiceImpl.java +test_path=jeecg-boot/jeecg-module-system/jeecg-system-biz/src/test/java/org/jeecg/modules/system/service/impl/SysUserServiceImplTest.java +diff_file=review-diff.patch +diff_command=git apply --check review-diff.patch diff --git a/exams/scripts/build_reference_answer_docx.py b/exams/scripts/build_reference_answer_docx.py new file mode 100644 index 0000000..92fb3eb --- /dev/null +++ b/exams/scripts/build_reference_answer_docx.py @@ -0,0 +1,264 @@ +from pathlib import Path + +from docx import Document +from docx.enum.section import WD_SECTION +from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT +from docx.enum.text import WD_ALIGN_PARAGRAPH +from docx.oxml import OxmlElement +from docx.oxml.ns import qn +from docx.shared import Inches, Pt, RGBColor + + +ROOT = Path('/Users/smart/Documents/TRAE培训/TRAE课件/exams') +TARGET = ROOT / 'TRAE编程培训考试评估卷-实操与项目实战(阅卷参考答案).docx' + +FONT = 'Microsoft YaHei' +BODY = 11 +BLUE = '2E74B5' +DARK_BLUE = '1F4D78' +HEADER_FILL = 'E8EEF5' +LIGHT_FILL = 'F4F6F9' +TABLE_WIDTH = 9360 + + +def set_font(run, size=BODY, bold=False, color=None, italic=False): + run.font.name = FONT + run._element.rPr.rFonts.set(qn('w:ascii'), 'Calibri') + run._element.rPr.rFonts.set(qn('w:hAnsi'), 'Calibri') + run._element.rPr.rFonts.set(qn('w:eastAsia'), FONT) + run.font.size = Pt(size) + run.bold = bold + run.italic = italic + if color: + run.font.color.rgb = RGBColor.from_string(color) + + +def shade(cell, color): + tc_pr = cell._tc.get_or_add_tcPr() + shd = OxmlElement('w:shd') + shd.set(qn('w:fill'), color) + tc_pr.append(shd) + + +def set_cell_margins(cell, top=80, start=120, bottom=80, end=120): + tc_pr = cell._tc.get_or_add_tcPr() + tc_mar = tc_pr.first_child_found_in('w:tcMar') + if tc_mar is None: + tc_mar = OxmlElement('w:tcMar') + tc_pr.append(tc_mar) + for side, value in [('top', top), ('start', start), ('bottom', bottom), ('end', end)]: + element = tc_mar.find(qn(f'w:{side}')) + if element is None: + element = OxmlElement(f'w:{side}') + tc_mar.append(element) + element.set(qn('w:w'), str(value)) + element.set(qn('w:type'), 'dxa') + + +def set_table_geometry(table, widths): + table.autofit = False + tbl_pr = table._tbl.tblPr + tbl_w = tbl_pr.first_child_found_in('w:tblW') + tbl_w.set(qn('w:w'), str(sum(widths))) + tbl_w.set(qn('w:type'), 'dxa') + tbl_ind = tbl_pr.first_child_found_in('w:tblInd') + if tbl_ind is None: + tbl_ind = OxmlElement('w:tblInd') + tbl_pr.append(tbl_ind) + tbl_ind.set(qn('w:w'), '120') + tbl_ind.set(qn('w:type'), 'dxa') + grid_cols = table._tbl.tblGrid.gridCol_lst + for grid_col, width in zip(grid_cols, widths): + grid_col.set(qn('w:w'), str(width)) + for row in table.rows: + for cell, width in zip(row.cells, widths): + tc_w = cell._tc.tcPr.tcW + tc_w.set(qn('w:w'), str(width)) + tc_w.set(qn('w:type'), 'dxa') + cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER + set_cell_margins(cell) + + +def add_paragraph(doc, text='', style=None, before=0, after=6, line=1.1, color=None, bold=False, size=BODY): + paragraph = doc.add_paragraph(style=style) + paragraph.paragraph_format.space_before = Pt(before) + paragraph.paragraph_format.space_after = Pt(after) + paragraph.paragraph_format.line_spacing = line + run = paragraph.add_run(text) + set_font(run, size=size, bold=bold, color=color) + return paragraph + + +def add_heading(doc, text, level): + sizes = {1: 16, 2: 13, 3: 12} + colors = {1: BLUE, 2: BLUE, 3: DARK_BLUE} + before = {1: 16, 2: 12, 3: 8} + after = {1: 8, 2: 6, 3: 4} + paragraph = doc.add_paragraph() + paragraph.paragraph_format.space_before = Pt(before[level]) + paragraph.paragraph_format.space_after = Pt(after[level]) + paragraph.paragraph_format.keep_with_next = True + run = paragraph.add_run(text) + set_font(run, size=sizes[level], bold=True, color=colors[level]) + return paragraph + + +def add_table(doc, headers, rows, widths): + table = doc.add_table(rows=1, cols=len(headers)) + table.style = 'Table Grid' + table.rows[0]._tr.get_or_add_trPr().append(OxmlElement('w:tblHeader')) + for cell, text in zip(table.rows[0].cells, headers): + shade(cell, HEADER_FILL) + paragraph = cell.paragraphs[0] + paragraph.paragraph_format.space_after = Pt(0) + run = paragraph.add_run(text) + set_font(run, size=10, bold=True) + for values in rows: + cells = table.add_row().cells + for cell, text in zip(cells, values): + paragraph = cell.paragraphs[0] + paragraph.paragraph_format.space_after = Pt(0) + paragraph.paragraph_format.line_spacing = 1.08 + run = paragraph.add_run(text) + set_font(run, size=9.5) + set_table_geometry(table, widths) + doc.add_paragraph().paragraph_format.space_after = Pt(2) + return table + + +def add_callout(doc, label, text): + table = doc.add_table(rows=1, cols=1) + table.style = 'Table Grid' + shade(table.cell(0, 0), LIGHT_FILL) + paragraph = table.cell(0, 0).paragraphs[0] + paragraph.paragraph_format.space_after = Pt(0) + label_run = paragraph.add_run(f'{label}:') + set_font(label_run, bold=True, color=DARK_BLUE) + text_run = paragraph.add_run(text) + set_font(text_run) + set_table_geometry(table, [TABLE_WIDTH]) + doc.add_paragraph().paragraph_format.space_after = Pt(2) + + +def build_document(): + doc = Document() + section = doc.sections[0] + section.top_margin = Inches(1) + section.bottom_margin = Inches(1) + section.left_margin = Inches(1) + section.right_margin = Inches(1) + section.header_distance = Inches(0.492) + section.footer_distance = Inches(0.492) + + styles = doc.styles + normal = styles['Normal'] + normal.font.name = 'Calibri' + normal._element.rPr.rFonts.set(qn('w:eastAsia'), FONT) + normal.font.size = Pt(BODY) + normal.paragraph_format.space_after = Pt(6) + normal.paragraph_format.line_spacing = 1.1 + + header = section.header.paragraphs[0] + header.alignment = WD_ALIGN_PARAGRAPH.RIGHT + header.paragraph_format.space_after = Pt(0) + set_font(header.add_run('TRAE 编程培训 | 阅卷参考答案'), size=9, color='6B7280') + footer = section.footer.paragraphs[0] + footer.alignment = WD_ALIGN_PARAGRAPH.CENTER + footer.paragraph_format.space_after = Pt(0) + set_font(footer.add_run('阅卷专用 - 不随学生试卷发放'), size=9, color='6B7280') + + title = doc.add_paragraph() + title.paragraph_format.space_before = Pt(16) + title.paragraph_format.space_after = Pt(4) + set_font(title.add_run('TRAE 编程培训考试评估卷'), size=23, bold=True, color='0B2545') + subtitle = doc.add_paragraph() + subtitle.paragraph_format.space_after = Pt(16) + set_font(subtitle.add_run('实操与项目实战 - 阅卷参考答案'), size=14, color='475569') + for label, value in [ + ('适用试卷', '系统用户状态管理综合卷(修订版)'), + ('固定源码', 'GitHub jeecgboot/JeecgBoot,v3.9.2'), + ('固定提交', '7df07a823fd558be857d0208ccae96342539fbc1'), + ('阅卷原则', '考查证据与判断,不要求逐字复述;禁止把未执行测试写成已通过。'), + ]: + paragraph = doc.add_paragraph() + paragraph.paragraph_format.space_after = Pt(2) + set_font(paragraph.add_run(f'{label}:'), bold=True, color='1F4D78') + set_font(paragraph.add_run(value)) + + add_callout(doc, '使用说明', '本答案与 exam-kit/reference-answer.md 内容对应。学生提交可以有不同表述,但必须给出等价的源码证据、边界判断和如实的测试状态。') + + add_heading(doc, 'A 卷参考答案', 1) + add_heading(doc, '任务 1:Prompt 改写', 2) + add_paragraph(doc, '合格 Prompt 必须明确固定提交、分析范围、状态白名单目标、不可改动的保护项、输出结构和验收证据。可按以下六项核查:', after=4) + add_table(doc, ['项目', '参考要求'], [ + ('背景与目标', '在固定版本中审查批量冻结/解冻,保证 status 仅允许 1/2 且在写库前校验。'), + ('范围', 'SysUserController.frozenBatch、ISysUserService.updateStatus、SysUserServiceImpl.updateStatus、状态常量及直接关联的保护。'), + ('约束', '不得改接口路径、system:user:frozenBatch 权限、管理员保护和缓存失效;事实、推断、待确认分开。'), + ('输出', '调用链与证据、最小改造、测试矩阵、待确认项。'), + ('验收', '每项结论写文件路径和符号;未执行测试必须如实标注。'), + ], [1800, 7560]) + + add_heading(doc, '任务 2:AI 输出辨析', 2) + add_callout(doc, '正确结论', '“页面已限制状态值,因此 Service 不需要校验”是错误结论。页面不是可信边界,调用者可直接请求接口;Service.updateStatus 在原版中直接解析 status 并更新数据库。') + add_table(doc, ['应说明的点', '参考答案'], [ + ('正确校验位置', 'SysUserServiceImpl.updateStatus 内,在 Integer.parseInt(status) 与 userMapper.update 之前。'), + ('允许值', 'CommonConstant.USER_UNFREEZE(1)和 USER_FREEZE(2)。'), + ('拒绝值', 'null、空串、空白串、3 及其他非 1/2 值,抛 JeecgBootException。'), + ('保留项', 'Controller 权限、管理员保护、@CacheEvict 和原有数据更新路径。'), + ], [1800, 7560]) + + add_heading(doc, 'B 卷参考答案', 1) + add_heading(doc, '任务 1:项目理解与影响分析', 2) + add_table(doc, ['结论', '文件路径与符号', '证明内容', '类型'], [ + ('接口与权限', 'SysUserController.java / frozenBatch', '@RequiresPermissions("system:user:frozenBatch");PUT /sys/user/frozenBatch。', '事实'), + ('调用链', 'SysUserController.java / frozenBatch', '读取 ids,先做 checkUserAdminRejectDel,再读取 status,逐个调用 updateStatus。', '事实'), + ('服务契约', 'ISysUserService.java / updateStatus', '状态更新是 Service 接口。', '事实'), + ('写库与缓存', 'SysUserServiceImpl.java / updateStatus', '@CacheEvict;Integer.parseInt(status) 后调用 userMapper.update。', '事实'), + ('状态枚举', 'CommonConstant.java / USER_UNFREEZE、USER_FREEZE、USER_QUIT', '取值为 1、2、3;本题只允许 1、2。', '事实'), + ('管理员保护', 'SysUserServiceImpl.java / checkUserAdminRejectDel', '检测目标 ID 中的 admin 用户并抛业务异常。', '事实'), + ('登录影响', 'SysUserServiceImpl.java / 用户登录有效性检查', '状态为 USER_FREEZE 时返回“该用户已冻结”。', '事实'), + ], [1500, 3000, 3600, 1260]) + add_callout(doc, '边界判断', 'Service 白名单必须早于解析和 mapper 写入。异常返回时不完成该次状态更新;缓存失效注解、接口权限和管理员保护都不是此变更的替代项。批量原子性与跨节点缓存可列为待确认,不能直接写成事实。') + + add_heading(doc, '任务 2:最小改造与测试安全网', 2) + add_paragraph(doc, '参考改造:在 SysUserServiceImpl.updateStatus 方法开头增加状态白名单,保持已有 @CacheEvict 和 mapper 写入。', after=3) + code_table = doc.add_table(rows=1, cols=1) + code_table.style = 'Table Grid' + shade(code_table.cell(0, 0), 'F8FAFC') + code = 'if (!String.valueOf(CommonConstant.USER_UNFREEZE).equals(status)\n && !String.valueOf(CommonConstant.USER_FREEZE).equals(status)) {\n throw new JeecgBootException("账号状态仅支持冻结或解冻");\n}' + paragraph = code_table.cell(0, 0).paragraphs[0] + paragraph.paragraph_format.space_after = Pt(0) + set_font(paragraph.add_run(code), size=9.5) + set_table_geometry(code_table, [TABLE_WIDTH]) + doc.add_paragraph().paragraph_format.space_after = Pt(2) + add_table(doc, ['场景', '预期', '验证口径'], [ + ('null、空串、空白串、3', '抛 JeecgBootException;不写库。', '负例单元测试;未跑则写明原因和计划。'), + ('"1" 解冻', '更新状态 1,成功后缓存失效。', 'Service 单元测试或集成测试。'), + ('"2" 冻结', '更新状态 2,成功后缓存失效。', 'Service 单元测试或集成测试。'), + ('无权限调用', '被权限框架拒绝。', '接口/集成测试,记录账号与结果。'), + ('目标含 admin', '管理员保护拒绝更新。', 'Controller/接口测试。'), + ('冻结后登录', '登录有效性检查返回冻结错误。', '可列待环境验证,不得虚报通过。'), + ], [1950, 3660, 3750]) + add_callout(doc, '测试状态', 'review-diff.patch 中的最小测试只覆盖 status 为 "3"。父 POM 默认跳过 surefire 测试;未修改本地测试配置的学员应标注未执行,而不是将测试状态写为已通过。') + + add_heading(doc, '任务 3:Review、PR 与资产沉淀', 2) + add_table(doc, ['结论', '证据', '阅卷判断'], [ + ('可合入,建议补测', '白名单校验位于 parse 和 mapper 写入前;保护项未被删除。', '认可“无阻断问题”,但必须列检查范围和残余风险。'), + ('测试覆盖不足', '新增测试只断言 "3" 抛异常。', '建议补 null、空白、"1"、"2" 与 mapper 交互断言。'), + ('残余风险', '异常返回语义、批量原子性、跨节点缓存并非此 diff 的直接变更。', '可列后续集成验证;不能凭空断言已验证。'), + ], [1950, 4200, 3210]) + add_paragraph(doc, '参考 PR 要点:背景是前端限制不能替代服务端边界;实现是在 Service 白名单化 status;测试写明实际已跑或未跑状态;风险是业务收紧会拒绝此前错误状态,必要时回退本次白名单提交。', after=4) + add_callout(doc, '资产答案', '可复用资产应围绕“状态变更服务端边界”设计:输入固定提交、diff 和状态常量;检查白名单、校验顺序、权限/关键账号保护、缓存/审计、副作用和正负例;输出 Review、证据表、测试矩阵和残余风险。') + + add_heading(doc, 'AI 使用记录的判分口径', 1) + add_paragraph(doc, '不提供统一的逐字 Prompt。可得满分的记录需要写清:使用 AI/TRAE 做了什么、建议摘要、采纳或否决、人工判断和证据/验证状态。工具名称可以不同;缺少 trae-usage-record.md 时,任务 3 的该项 2 分为 0 分。') + + doc.core_properties.title = 'TRAE 编程培训考试评估卷(阅卷参考答案)' + doc.core_properties.subject = '系统用户状态管理综合卷阅卷参考' + doc.core_properties.author = 'TRAE 培训' + doc.save(TARGET) + print(TARGET) + + +if __name__ == '__main__': + build_document() diff --git a/exams/scripts/revise_exam_docx.py b/exams/scripts/revise_exam_docx.py new file mode 100644 index 0000000..5e159be --- /dev/null +++ b/exams/scripts/revise_exam_docx.py @@ -0,0 +1,197 @@ +from copy import deepcopy +from pathlib import Path +from docx import Document +from docx.enum.text import WD_BREAK + + +ROOT = Path('/Users/smart/Documents/TRAE培训/TRAE课件/exams') +SOURCE = ROOT / 'TRAE编程培训考试评估卷-实操与项目实战.docx' +TARGET = ROOT / 'TRAE编程培训考试评估卷-实操与项目实战(修订版).docx' + + +def replace_paragraph(paragraph, text, bold=None, size=None): + first_run = paragraph.runs[0] if paragraph.runs else None + old_bold = first_run.bold if first_run else None + old_size = first_run.font.size if first_run else None + paragraph.clear() + run = paragraph.add_run(text) + run.bold = old_bold if bold is None else bold + if size is not None: + run.font.size = size + elif old_size is not None: + run.font.size = old_size + return paragraph + + +def replace_cell(cell, text, bold=None): + for paragraph in cell.paragraphs[1:]: + paragraph._element.getparent().remove(paragraph._element) + paragraph = cell.paragraphs[0] + first_run = paragraph.runs[0] if paragraph.runs else None + old_bold = first_run.bold if first_run else None + old_size = first_run.font.size if first_run else None + paragraph.clear() + run = paragraph.add_run(text) + run.bold = old_bold if bold is None else bold + if old_size is not None: + run.font.size = old_size + + +def find_paragraph(document, text): + for paragraph in document.paragraphs: + if text in paragraph.text: + return paragraph + raise ValueError(f'Paragraph not found: {text}') + + +def set_table_rows(table, rows): + while len(table.rows) < len(rows): + table.add_row() + if len(table.rows) != len(rows): + raise ValueError('Unexpected table row count') + for row, values in zip(table.rows, rows): + if len(row.cells) != len(values): + raise ValueError('Unexpected table column count') + for cell, value in zip(row.cells, values): + replace_cell(cell, value) + + +def clone_paragraph(document, source_paragraph, text): + paragraph = document.add_paragraph() + placeholder = paragraph._p + element = deepcopy(source_paragraph._element) + placeholder.getparent().replace(placeholder, element) + paragraph._p = paragraph._element = element + replace_paragraph(paragraph, text) + return paragraph + + +def clone_table(document, source_table): + table = document.add_table(rows=1, cols=1) + placeholder = table._tbl + element = deepcopy(source_table._element) + placeholder.getparent().replace(placeholder, element) + table._tbl = table._element = element + return table + + +def insert_after(anchor, element): + anchor._element.addnext(element._element) + + +doc = Document(SOURCE) + +replace_paragraph(find_paragraph(doc, '实操 + 公务用车项目实战综合卷'), '实操 + 系统用户状态管理综合卷') + +# 1. Fixed time: 20 minutes for A, 60 for B, 10 for submission check. +set_table_rows(doc.tables[0], [ + ['项目', '说明'], + ['适用对象', '参加 TRAE 编程培训并完成 JeecgBoot 实战的技术学员。'], + ['考试形式', '允许使用 TRAE,上机完成;提交 Markdown 产物包与 AI 使用记录。'], + ['建议时长', '90 分钟:A 卷 20 分钟,B 卷 60 分钟,提交检查 10 分钟。'], + ['总分', '100 分:小实操 30 分,项目实战 70 分。'], + ['评估目标', '考察学员能否把真实研发问题拆成 TRAE 可执行、团队可验收、后续可复用的工程任务。'], +]) +set_table_rows(doc.tables[2], [ + ['部分', '时间', '分值', '形式', '核心能力'], + ['A. 小实操', '20 分钟', '30 分', '个人完成', 'Prompt 改写、证据判断'], + ['B. 项目实战', '60 分钟', '70 分', '个人完成', '项目理解、变更边界、测试安全网、Review 与交付'], + ['提交检查', '10 分钟', '-', '个人完成', '核对目录、文件名与执行状态'], + ['合计', '90 分钟', '100 分', '上机 + 文档交付', '个人基本功 + 项目落地能力'], +]) + +# 2. Deliverable templates and missing-file treatment. +replace_paragraph(find_paragraph(doc, '建议要求学员提交一个 Markdown 目录包'), '使用 exam-kit/exam-output/ 中的模板提交,目录和文件名不得改动:') +set_table_rows(doc.tables[3], [ + ['文件', '主要内容'], + ['01-project-understanding.md', '结论摘要、证据表、影响与保护边界、待确认项'], + ['02-change-and-test.md', '验收标准、校验位置、不改动项、测试矩阵、执行状态'], + ['03-pr-and-assets.md', 'Review、PR 说明、可复用 Prompt、Rule/Checklist/Skill 设计卡'], + ['trae-usage-record.md', 'Prompt、采纳/否决、人工判断点、证据或验证状态;缺失则任务 3 的如实记录项计 0 分'], +]) + +# Insert the fixed source / tools contract after the deliverable table. +input_heading = clone_paragraph(doc, find_paragraph(doc, '二、最终提交物'), '考试输入与作答约束') +input_intro = clone_paragraph(doc, find_paragraph(doc, '使用 exam-kit/exam-output/'), 'B 卷仅以 exam-kit/ 中的固定版本、指定 diff 和模板为准。') +input_table = clone_table(doc, doc.tables[3]) +set_table_rows(input_table, [ + ['项目', '统一要求'], + ['固定源码', 'GitHub jeecgboot/JeecgBoot;标签 v3.9.2:7df07a823fd558be857d0208ccae96342539fbc1。'], + ['指定模块', 'jeecg-module-system;以 SysUserController.frozenBatch 和 SysUserServiceImpl.updateStatus 为事实入口。'], + ['指定 diff', 'exam-kit/review-diff.patch;仅以该两文件变更为 Review 输入。'], + ['允许材料', 'TRAE、GitHub 固定版本与 exam-kit;版本不一致时记录环境问题并联系监考人员。'], +]) +anchor = doc.tables[3] +insert_after(anchor, input_heading) +insert_after(input_heading, input_intro) +insert_after(input_intro, input_table) + +replace_paragraph(find_paragraph(doc, '本部分由学员个人完成,重点考察 TRAE 基础使用'), '本部分由学员个人完成,重点考察 TRAE 基础使用与 AI 协作基本功。限时 20 分钟。') +replace_cell(doc.tables[5].cell(0, 0), '原始 Prompt\n帮我看一下系统用户批量冻结/解冻功能有没有问题。') +replace_cell(doc.tables[7].cell(0, 0), 'AI 输出片段\n页面已限制状态值,因此 Service 不需要校验;直接调用接口也不会有问题。') +replace_paragraph(find_paragraph(doc, 'B 卷:公务用车项目实战'), 'B 卷:系统用户状态管理项目实战(70 分)') +replace_paragraph(find_paragraph(doc, '本部分使用 JeecgBoot 现有公务用车模块'), '本部分使用 exam-kit 指定版本的 JeecgBoot 系统用户模块,由学员个人完成。限时 60 分钟。') +replace_cell(doc.tables[9].cell(0, 0), '实战任务背景\n案例使用 GitHub 公开的 JeecgBoot v3.9.2 中系统用户模块。训练改造要求:批量冻结/解冻用户时,后端必须拒绝空白或 1、2 之外的状态值;页面限制不能替代 Service 入口的业务保护。本次考试不要求完整开发所有代码,重点考察如何使用 TRAE 完成项目理解、变更边界分析、测试设计、Review 和资产沉淀。') + +# 3. Observable requirements for B tasks. +replace_paragraph(find_paragraph(doc, '使用 TRAE 输出公务用车模块结构说明'), '使用 TRAE 完成 01-project-understanding.md:说明系统用户模块结构、批量冻结/解冻接口、Service 入口、权限、管理员保护、缓存和测试入口。证据表至少含 5 条有效依据,每条必须写文件路径、符号或方法,以及它证明的结论;另说明非法状态值与接口、用户状态写入、缓存失效的影响关系,并区分事实、推断和待确认项。') +replace_paragraph(find_paragraph(doc, '输出最小改造方案和测试安全网'), '完成 02-change-and-test.md:说明应在哪个方法、哪次用户状态写入之前校验非法状态值;列出不改动的接口权限、管理员保护和缓存失效逻辑及依据;为空白或非法状态、正常冻结、正常解冻、无权限调用、管理员保护与缓存/用户状态回归逐项写明预期结果、验证入口、执行状态。已执行项必须给出命令和结果,未执行项必须说明原因和计划验证方式。') +replace_paragraph(find_paragraph(doc, '基于 `main...training/require-cancel-reason`'), '基于 exam-kit/review-diff.patch 的实际两文件 diff,完成 03-pr-and-assets.md:生成 Review 和 PR 说明,再输出一条可复用 Prompt 及一条 Rule、Checklist 或 Skill 设计卡。Review 可以得出“未发现阻断问题”的结论,但必须说明检查范围、证据和残余风险;所有已执行与未执行的验证必须如实标注。') + +# 4. Replace vague scoring with observable, non-overlapping criteria. +set_table_rows(doc.tables[6], [ + ['评分项', '分值'], + ['背景与目标清楚', '3'], + ['范围可控,限定目录、文件、接口或页面', '2'], + ['约束清楚,含项目规则、安全、权限、测试要求及信息不足时先提问', '4'], + ['输出格式明确,如表格、清单、报告或 PR 说明', '3'], + ['验收标准和证据要求明确', '3'], +]) +set_table_rows(doc.tables[10], [ + ['评分项', '分值'], + ['系统用户模块与批量冻结/解冻入口明确', '4'], + ['frozenBatch 接口、Controller 与 Service 调用链明确', '5'], + ['权限、管理员保护、缓存和测试入口明确', '4'], + ['至少 5 条有效证据,每条含路径、符号和证明内容', '7'], + ['影响与保护边界完整,并区分事实、推断和待确认项', '5'], +]) +set_table_rows(doc.tables[11], [ + ['评分项', '分值'], + ['状态校验改造目标和验收标准清楚', '4'], + ['校验方法、位置和副作用前后顺序正确', '5'], + ['不改动项及其依据清楚', '4'], + ['非法状态、冻结、解冻、权限、管理员保护/缓存五类场景完整', '8'], + ['验证命令、执行结果或未执行原因明确', '4'], +]) +set_table_rows(doc.tables[12], [ + ['评分项', '分值'], + ['Review 结论、证据、风险和验收方式完整', '5'], + ['PR 说明包含背景、实现、测试和风险', '5'], + ['可复用 Prompt 的输入、约束、输出和验收清楚', '5'], + ['Rule、Checklist 或 Skill 设计卡的边界和验收清楚', '3'], + ['AI 使用记录完整;不编造测试结果,能说明人工取舍', '2'], +]) +replace_paragraph(find_paragraph(doc, '三、综合评分维度'), '三、阅卷通用原则(不重复计分)') +set_table_rows(doc.tables[13], [ + ['阅卷原则', '优秀表现', '使用方式'], + ['证据意识', '结论能引用文件、函数、接口、日志或数据来源', '仅用于判断对应任务中的证据项,不单独追加扣分'], + ['上下文选择', '知道先全局找路,再文件聚焦,再代码细查', '仅用于判断对应任务中的任务表达与证据项'], + ['任务表达', '背景、目标、范围、约束、输出、验收完整', '仅用于判断 A 卷 Prompt 改写项'], + ['风险意识', '能识别权限、状态、审计、异常态、测试缺口', '仅用于判断 B 卷影响、测试和 Review 项'], + ['人机协作', '知道哪些让 AI 做,哪些必须人工确认', '仅用于判断 AI 使用记录与待确认项'], + ['资产意识', '能沉淀可复用 Prompt、Rule、Checklist、Skill', '仅用于判断任务 3 的资产项'], +]) + +set_table_rows(doc.tables[15], [ + ['环节', '要求'], + ['发卷前检查', '发放 exam-kit;核对 GitHub 仓库、v3.9.2 完整提交和 review-diff.patch 均可读取。'], + ['考试输入', '考试前完成 GitHub 固定版本下载;版本不一致时由监考人员处理,不得由学员自行替换版本。'], + ['过程记录', '必须提交 trae-usage-record.md;缺失时任务 3 的 AI 使用记录项计 0 分。'], + ['阅卷方式', '先核对四个交付文件,再按对应评分表打分;通用原则不重复扣分。'], + ['独立作答', '抽查关键路径、证据来源和人工取舍,确认交付物与作答者理解一致。'], +]) + +doc.core_properties.title = 'TRAE 编程培训考试评估卷(修订版)' +doc.core_properties.subject = '实操与系统用户状态管理综合卷' +doc.save(TARGET) +print(TARGET) diff --git a/exams/~$AE编程培训考试评估卷-实操与项目实战(修订版).docx b/exams/~$AE编程培训考试评估卷-实操与项目实战(修订版).docx new file mode 100644 index 0000000000000000000000000000000000000000..1215360c558324f168a348f977b46d5a160ee437 GIT binary patch literal 162 zcmWgj%}g%JFV0UZQSeVo%S=vH2rW)6VjuuS8GIQs8Il=_81fm4fjEt!gh7G9A4sQx Q#Z!U2P@qgIPz9v`0A -真实项目案例:若依 RuoYi-Vue / RuoYi-Vue3 - - -

- ---- - -# 为什么选若依作为案例? - -
- -
-
-
若依的特点
-
- -- 🏢 足够**真实** — 不是 demo,是完整后台管理系统 -- 📋 足够**典型** — 用户、角色、菜单、权限、日志、字典 -- 🔗 足够**复杂** — 前后端分离、多模块、多层级 - -
-
-
- -
-
-
适合训练的维度
-
- -- 陌生代码库理解 -- 前后端分离结构分析 -- 登录认证链路 -- 动态菜单与路由 -- 角色与权限体系 -- 按钮权限控制 -- 操作日志审计 - -
-
-
- -
- ---- - -# 今天训练四个动作 - -
- -
-
- 🏗️ -
-
看结构
-
目录、模块
分层、技术栈
-
- -
-
- 🔍 -
-
找入口
-
登录、用户信息
菜单、权限入口
-
- -
-
- 🔗 -
-
追链路
-
从登录到路由
到按钮权限
-
- -
-
- ✅ -
-
查依据
-
每个结论
验证文件来源
-
- -
- -
-最终产出:若依权限与菜单模块代码理解报告 -
- ---- - -# 模块 1:TRAE 在真实研发中的定位 - -
- -
-
TRAE 适合辅助
-
- -- 检索代码 -- 解释函数 -- 梳理结构 -- 生成链路初稿 -- 生成文档初稿 - -
-
- -
-
不能直接替代
-
- -- 业务判断 -- 权限决策 -- 安全判断 -- 上线决策 - -
-
- -
- -
-核心区分: -代码事实(代码现在怎么写) vs -业务事实(业务应该怎么规定)
-TRAE 擅长发现代码事实,人工必须判断业务事实 -
- ---- - -# 模块 2:若依核心业务对象 - -
- -```mermaid {scale: 0.85} -graph LR - U[User 用户] -->|绑定| R[Role 角色] - R -->|授权| M[Menu 菜单] - M -->|包含| P[Permission 权限字符] - P -->|控制| A[页面/按钮/接口] - U -->|属于| D[Dept 部门] - R -->|数据权限| D -``` - -
- -
- -
-
页面权限
-能不能看到某个菜单页面 -
- -
-
按钮权限
-能不能操作新增/修改/删除 -
- -
-
数据权限
-能看到哪些部门范围的数据 -
- -
- ---- - -# 模块 3:@workspace 理解全局结构 - -
- -
- -**标准 Prompt:** - -``` -@workspace 请分析若依项目全局结构。 - -维度: -1. 前端目录 2. 后端目录 -3. 登录认证 4. 动态路由 -5. API 封装 6. 权限控制 -7. 操作日志 8. 代码生成 -9. 测试与构建 10. 不确定点 - -每个结论标注依据文件。 -``` - -
-
- -
-⚠️ 不要问:「@workspace 帮我看看若依项目」— 太泛泛,会得到空洞总结 -
- ---- -layout: two-cols -layoutClass: gap-6 ---- - -# 若依前后端结构重点 - -
- -### 前端重点目录 - -| 目录 | 职责 | -|------|------| -| `src/api` | 接口封装 | -| `src/router` | 路由配置 | -| `src/store` | 状态管理 | -| `src/views` | 页面组件 | -| `src/directive` | 权限指令 | -| `src/permission` | 路由守卫 | -| `src/utils/request` | 请求拦截 | - -
- -::right:: - -
- -### 后端重点模块 - -| 模块 | 职责 | -|------|------| -| `ruoyi-admin` | 启动+接口 | -| `ruoyi-framework` | 框架配置 | -| `ruoyi-system` | 业务逻辑 | -| `ruoyi-common` | 公共工具 | - -
- -**分层结构:** Controller → Service → Mapper - -
-
- ---- - -# 模块 4:核心调用链路 - -
- -```mermaid {scale: 0.8} -graph TD - A[用户登录] --> B[获取 Token] - B --> C[获取用户信息 getInfo] - C --> D[获取菜单路由 getRouters] - D --> E[生成动态路由] - E --> F[进入系统页面] - F --> G[权限字符控制按钮] -``` - -
- -
-
登录页面
-
API 调用
-
后端 Controller
-
状态更新
-
- ---- - -# 登录认证链路 - -
- -
- -
1. 用户输入账号密码
-
↓
-
2. Login 页面提交
-
↓
-
3. 调用 login API
-
↓
-
4. 后端认证用户
-
↓
-
5. 返回 Token
-
↓
-
6. 前端保存 Token
-
↓
-
7. Request 拦截器携带 Token
- -
- -
-
-
重点确认
-
- -- Token 存储在哪里? -- Request 拦截器在哪里带 Token? -- Token 失效后如何处理? -- 是否有刷新 Token 机制? - -
-
-
- -
- ---- - -# 动态路由加载链路 - -
- -
-
1. 调用 getRouters
-
↓
-
2. 后端返回菜单树
-
↓
-
3. 前端转换为路由对象
-
↓
-
4. 动态挂载路由 (addRoute)
-
↓
-
5. 渲染侧边栏菜单
-
↓
-
6. 用户进入页面
-
- -
-
-
菜单类型
-
- -| 类型 | 说明 | -|------|------| -| 目录 | M - 包含子菜单 | -| 菜单 | C - 对应页面组件 | -| 按钮 | F - 权限字符 | - -
-
-
- -
- ---- - -# 按钮权限链路 - -
- -```mermaid {scale: 0.85} -graph LR - A[菜单配置权限字符] --> B[角色授权该字符] - B --> C[getInfo 返回 permissions] - C --> D[前端权限指令判断] - D --> E[按钮显示/隐藏] -``` - -
- -
- -
-
⚠️ 关键警告
-
- -前端按钮权限 **只能控制 UI 展示** - -如果用户绕过前端直接请求后端接口,安全由后端接口权限决定 - -
-
- -
-
必须确认
-
- -- Controller 是否有权限注解? -- Service 是否校验数据权限? -- 不能只看前端就下结论 - -
-
- -
- ---- - -# 模块 5:AI 输出可信度判断 - -
- -
-
❌ 不可信回答
-
-「若依已经实现了完整权限控制,用户登录后会自动加载菜单和按钮权限,后端接口也都有权限校验,整体风险较低。」 -
-
-无文件路径 · 无函数名 · 无依据 · 无不确定点 -
-
- -
-
✅ 可信回答
-
-「前端登录成功后调用 getInfo 获取用户信息和权限字符。随后调用 getRouters 获取动态路由菜单。可确认前端存在菜单和按钮权限控制。但仍需检查后端 Controller 权限注解。」 -
-
-有依据 · 有调用关系 · 区分前后端 · 标注不确定点 -
-
- -
- ---- - -# 可信结论四件套 - -
- -
-
📁
-
文件路径
-
前端 api 文件
后端 Controller
-
- -
-
⚙️
-
函数/类/注解
-
getInfo · getRouters
权限注解
-
- -
-
🔗
-
调用关系
-
登录→token→info
→routes→权限
-
- -
-
❓
-
不确定点
-
无法确认的内容
必须标注
-
- -
- -
-没有这四项内容的 AI 输出,不能轻易采纳进入报告 -
- ---- - -# 模块 6:代码理解报告结构 - -
- -| 章节 | 内容要求 | -|------|---------| -| 模块职责 | 具体说明,不能写空话 | -| 核心对象 | User / Role / Menu / Permission / Dept | -| 目录与文件 | 标注和权限主线的关系 | -| 核心调用链路 | 拆分子链路,标注依据 | -| 权限风险 | 风险描述 + 触发条件 + 影响 + 验证 | -| 操作日志风险 | 哪些操作缺失日志 | -| 改造建议 | 文件 + 方法 + 验收标准 | -| 不确定结论 | 必须单独列出 | - -
- -
-风险写法要求:不能写「权限有风险」,必须具体到触发条件 + 影响范围 + 验证方式 -
- ---- -layout: center ---- - -# 第 1 天交付物 - -
- -
-
01
-TRAE 使用边界清单 -
- -
-
02
-若依模块认知图 -
- -
-
03
-若依项目结构图 -
- -
-
04
-权限调用链路图 -
- -
-
05
-TRAE 输出校验清单 -
- -
-
06
-代码理解报告 -
- -
diff --git a/slidev/pages/day1/m1-trae-positioning.md b/slidev/pages/day1/m1-trae-positioning.md index 66000f0..400a754 100644 --- a/slidev/pages/day1/m1-trae-positioning.md +++ b/slidev/pages/day1/m1-trae-positioning.md @@ -91,7 +91,7 @@ AI 会替代一部分重复性、低上下文、低判断含量的工作方式 --- -# 第一天真正要学什么? +# 陌生项目分析的四个动作
diff --git a/slidev/pages/day1/m3-workspace-structure.md b/slidev/pages/day1/m3-workspace-structure.md index ea1b9c8..65bf9ac 100644 --- a/slidev/pages/day1/m3-workspace-structure.md +++ b/slidev/pages/day1/m3-workspace-structure.md @@ -1,4 +1,4 @@ -# 模块 3:不要让 AI 泛泛总结项目 +# 模块 3:项目理解的证据化提问
diff --git a/slidev/pages/day1/m4-call-chain.md b/slidev/pages/day1/m4-call-chain.md index f184689..3acad8a 100644 --- a/slidev/pages/day1/m4-call-chain.md +++ b/slidev/pages/day1/m4-call-chain.md @@ -1,8 +1,8 @@ -# 模块 4:第一天核心链路 +# 模块 4:权限与认证核心链路
-```mermaid {scale: 0.75} +```mermaid {scale: 0.6} graph TD A[用户登录] --> B[获取 token] B --> C[获取用户信息] diff --git a/slidev/pages/day1/m6-code-report.md b/slidev/pages/day1/m6-code-report.md index 700265f..bc8fae4 100644 --- a/slidev/pages/day1/m6-code-report.md +++ b/slidev/pages/day1/m6-code-report.md @@ -122,7 +122,7 @@ User 用户 · Role 角色 · Menu 菜单 · Permission 权限字符 · Dept 部 --- -# 第 1 天最终交付物 +# 项目认知的可审查产物
01 TRAE 使用边界清单
diff --git a/slidev/pages/day2.md b/slidev/pages/day2.md deleted file mode 100644 index 20db772..0000000 --- a/slidev/pages/day2.md +++ /dev/null @@ -1,553 +0,0 @@ ---- -layout: cover -background: https://images.unsplash.com/photo-1558494949-ef010cbdcc31?w=1920&q=80 ---- - -# 第 2 天:TRAE 上下文工程与规则体系 - -
-让 TRAE 按团队规则改代码 — 从代码理解到研发治理 -
- -
-上下文决定 AI 看什么 · 规则决定 AI 怎么做 · 资产化决定团队怎么复用 -
- ---- - -# 第 1 天到第 2 天的跨越 - -
- -
-
第 1 天
-
让 TRAE 看懂若依代码
-
-看结构 · 找入口 · 追链路 · 查依据 -
-
- -
-
第 2 天
-
让 TRAE 按团队规则改代码
-
-组织上下文 · 控制幻觉 · 编写 rules · 沉淀资产 -
-
- -
- -
-核心变化:当团队多人都使用 AI 时,必须有统一上下文、统一 rules、统一验收标准,否则 AI 会带来不一致甚至风险。 -
- ---- - -# 若依场景下的团队规则问题 - -
-
-
若依类后台项目常见风险
-
- -
- -- 🚨 绕过权限,页面直接调接口 -- 🚨 按钮权限当安全边界 -- 🚨 接口缺少权限注解 -- 🚨 操作日志缺失 - -
- -
- -- 🚨 代码生成后直接上线 -- 🚨 敏感信息进入日志 -- 🚨 数据权限范围不清 -- 🚨 AI 修改未经复核 - -
- -
-
-
- -
-今天要把这些隐性开发要求写成规则,让 TRAE 长期遵守 -
- ---- - -# 今天三个关键词 - -
- -
-
- 🎯 -
-
上下文
-
决定 AI 看什么
-
整个项目 vs 单模块 vs 单文件
-
- -
-
- 📏 -
-
规则
-
决定 AI 怎么做
-
不能绕过权限 · 不能遗漏日志
-
- -
-
- 📦 -
-
资产化
-
决定团队怎么复用
-
Prompt · Rules · 模板
-
- -
- ---- - -# 模块 1:上下文工程 — 好任务五要素 - -
- -
-好任务 = 目标 + 范围 + 约束 + 输出格式 + 验收标准 -
- -
- -
- -
-
❌ 坏 Prompt
- -``` -@workspace 帮我检查若依权限有没有问题。 -``` - -
目标不清 · 范围不清 · 约束不清 · 输出不清
-
- -
-
✅ 好 Prompt
- -``` -@workspace 分析若依用户管理页面按钮权限调用链路 - -范围:只分析新增/修改/删除按钮 -约束:不修改代码;标注文件和函数 -输出:表格 -验收:覆盖按钮/权限字符/前端判断/后端接口 -``` - -
- -
- ---- - -# 模块 2:四类上下文工具 - -
- -
-
-@workspace -找路 -
-
全局搜索入口、文件、模块
-
如:找 getInfo、getRouters、权限指令
-
- -
-
-#File -看文件 -
-
分析指定文件间的调用关系
-
如:分析用户 Controller 和 Service
-
- -
-
-#Code -抠细节 -
-
分析单个方法的参数、调用、异常
-
如:分析 handleLogin 函数
-
- -
-
-#Web -查外部 -
-
查询框架文档或最佳实践
-
但若依当前实现必须回到代码验证
-
- -
- ---- - -# 模块 3:幻觉死循环切断 - -
- -
-
常见幻觉表现
-
-
AI 说所有接口都有权限校验
但不给 Controller 依据
-
AI 说操作日志完整
但不给注解或 AOP 依据
-
AI 说按钮权限安全
但只看到前端代码
-
-
- -
- -
-
1️⃣
-
先找证据
-
文件 · 类 · 方法 · 注解
-
- -
-
2️⃣
-
再判断原因
-
区分事实和推测
-
- -
-
3️⃣
-
最后给方案
-
修改 · 影响 · 验证
-
- -
- -
- ---- - -# 证据收集标准 Prompt - -
- -``` -请先不要修改代码。 - -请围绕"用户管理删除按钮权限"收集证据: - -1. 前端按钮在哪个页面 -2. 使用了哪个权限字符 -3. 前端如何判断按钮显示 -4. 后端删除接口在哪里 -5. Controller 是否有权限注解 -6. 是否记录操作日志 -7. 无法确认的内容 -``` - -
- -
-先收集证据,再判断风险,最后才给方案。
不要让 AI 跳过证据直接给结论。 -
- ---- - -# 模块 4:.gitignore 与敏感配置 - -
- -
-
不应提交的文件
-
- -- `.env` 环境变量 -- `application-dev.yml` 真实密码 -- 数据库账号 / Redis 密码 -- `target/` 编译产物 -- `node_modules/` / `dist/` -- 日志文件 -- IDE 本地配置 - -
-
- -
-
敏感配置规则
-
- -- 禁止硬编码数据库密码 -- 禁止硬编码 Redis 密码 -- 禁止硬编码 JWT 密钥 -- 禁止硬编码第三方密钥 -- 不得提交真实配置文件 -- 示例配置必须使用占位符 - -
-
- -
- ---- - -# 模块 5:.trae/rules.md 安全规则 - -
- -
-规则 1:前端按钮权限只能作为交互控制,不能作为安全边界。 -
- -
-规则 2:涉及用户、角色、菜单、部门、岗位等敏感接口时,必须检查后端 Controller 是否存在权限控制。 -
- -
-规则 3:涉及新增、修改、删除、导出等操作时,必须检查是否记录操作日志。 -
- -
-规则 4:禁止在源码中硬编码数据库密码、Redis 密码、JWT 密钥。 -
- -
-规则 5:列表查询必须确认是否需要数据权限过滤。 -
- -
- ---- - -# 模块 6:编码与测试规则 - -
- -
-
编码规则重点
-
- -- 前端接口放 `api` 目录,不在页面写请求 -- 后端遵守 Controller → Service → Mapper 分层 -- 敏感接口关注权限注解 -- 敏感操作关注日志注解 -- 列表查询关注数据权限 -- 固定枚举值使用字典 -- 代码生成结果必须人工检查 - -
-
- -
-
测试规则重点
-
- -- ✅ 登录成功 / 失败 -- ✅ 无权限访问接口返回拒绝 -- ✅ 按钮权限和权限字符匹配 -- ✅ 角色授权变化后权限更新 -- ✅ 数据权限范围正确 -- ✅ 敏感操作有日志记录 -- ✅ 代码生成 CRUD 完整性 - -
-
- -
- ---- - -# 模块 7:前端专项规则 - -
- -
-
用户管理页面规则示例
- -| 规则项 | 要求 | -|--------|------| -| 按钮权限 | 新增/修改/删除/导出必须使用权限控制 | -| 删除操作 | 必须二次确认 | -| 提交失败 | loading 必须恢复 | -| 数据刷新 | 修改后必须刷新列表 | -| 敏感字段 | 不得明文展示或输出日志 | -| 接口调用 | 统一放 api 目录 | -| 动态路由 | 不能随意绕过 | -| 字典字段 | 使用统一字典显示 | - -
-
- ---- - -# 模块 8:规则可执行性检查 - -
- -
-
❌ 坏规则
-
- -- 「注意权限安全」 -- 「代码要规范」 -- 「日志要完整」 -- 「数据权限要合理」 -- 「生成代码要检查」 - -
-
方向没错,但不可执行
-
- -
-
✅ 好规则
-
- -- 涉及用户/角色/菜单/部门/岗位的新增/修改/删除/导出接口 → **必须检查后端权限和操作日志** -- 涉及列表查询 → **必须确认数据权限过滤** -- 代码生成结果上线前 → **必须检查权限、日志、校验、异常和测试** - -
-
有触发场景 · 有必须动作 · 可被检查
-
- -
- ---- - -# 模块 9:技能与规则资产化 - -
- -``` -ai-dev-assets/ -├── prompts/若依/ # 若依相关 Prompt 模板 -│ ├── 结构分析.md -│ ├── 登录权限链路.md -│ ├── 按钮权限检查.md -│ └── 操作日志检查.md -├── rules/若依/ # 若依 rules.md -├── templates/若依/ # 报告模板 -├── checklists/若依/ # 检查清单 -│ ├── 权限检查.md -│ ├── 日志检查.md -│ └── 代码生成Review.md -└── examples/若依/ # 优秀案例 & 反面案例 -``` - -
- -
-这些资产以后可以直接用于公司内部类似后台管理系统 -
- ---- - -# 若依版 rules.md 核心条款 - -
- -
-
1
-
前端按钮权限不是安全边界
-
- -
-
2
-
敏感接口必须检查后端权限
-
- -
-
3
-
敏感操作必须检查操作日志
-
- -
-
4
-
列表查询必须考虑数据权限
-
- -
-
5
-
代码生成结果不得直接上线
-
- -
-
6
-
不确定内容必须标注
-
- -
- ---- -layout: center ---- - -# 第 2 天交付物 - -
- -
01 上下文组织清单
-
02 上下文引用模板
-
03 幻觉控制 Prompt
-
04 .gitignore 配置样例
-
05 若依安全规则
-
06 若依编码测试规则
-
07 若依前端规则
-
08 若依 AI 资产库目录
-
09 规则验收清单
-
10 若依版 rules.md v0.1
- -
- ---- - -# 两天课程总结 - -
- -
-
第 1 天:读懂若依
-
- -- 看结构 — 全局目录与模块职责 -- 找入口 — 登录、权限、菜单入口 -- 追链路 — 完整调用链路分析 -- 查依据 — 验证 AI 输出可信度 - -
-
- -
-
第 2 天:按规则参与若依
-
- -- 组织上下文 — 精准任务五要素 -- 控制幻觉 — 证据→原因→方案 -- 编写 rules — 可执行规则 -- 沉淀资产 — 团队长期复用 - -
-
- -
- -
-
-
最终能力
-
-
读得懂
-
问得准
-
查得实
-
改得稳
-
可复用
-
-
-
diff --git a/slidev/pages/day2/m1-context-engineering.md b/slidev/pages/day2/m1-context-engineering.md index 0fe5c45..f1ba112 100644 --- a/slidev/pages/day2/m1-context-engineering.md +++ b/slidev/pages/day2/m1-context-engineering.md @@ -1,15 +1,15 @@ -# 第 1 天到第 2 天 +# 从项目理解到研发治理
-**第 1 天:** 让 TRAE 看懂若依代码 +**项目理解:** 让 TRAE 看懂若依代码
-**第 2 天:** 让 TRAE 按团队规则改若依代码 +**研发治理:** 让 TRAE 按团队规则改若依代码 -核心变化:从代码理解到研发治理 +从项目事实到可执行规则
@@ -43,7 +43,7 @@ AI 可能在页面里直接新增接口调用;只做前端按钮隐藏;没 --- -# 今天三个关键词 +# 上下文、规则与资产
@@ -72,7 +72,7 @@ AI 可能在页面里直接新增接口调用;只做前端按钮隐藏;没 --- -# 第 2 天训练主线 +# 上下文工程流程
@@ -94,7 +94,7 @@ AI 可能在页面里直接新增接口调用;只做前端按钮隐藏;没
-课堂总产出:围绕若依“用户管理删除权限”完成一次证据审查,并产出一版可落地的 .trae/rules.md v0.1。 +任务产物:围绕若依“用户管理删除权限”完成证据审查,并形成可落地的 .trae/rules.md v0.1。
--- -# 第 2 天交付物 +# 上下文与规则资产
01 上下文组织清单
@@ -91,17 +90,17 @@ layout: center class: text-center --- -# 两天课程总结 +# 从项目事实到规则资产
-**第 1 天:用 TRAE 看懂若依** +**项目理解:用 TRAE 看懂若依** - 看结构 · 找入口 · 追链路 · 查依据
-**第 2 天:让 TRAE 按规则参与若依研发** +**规则资产:让 TRAE 按规则参与若依研发** - 组织上下文 · 控制幻觉 · 编写 rules · 沉淀资产
diff --git a/slidev/pages/day2/m2-four-references.md b/slidev/pages/day2/m2-four-references.md index 1fa0906..0deb94c 100644 --- a/slidev/pages/day2/m2-four-references.md +++ b/slidev/pages/day2/m2-four-references.md @@ -88,7 +88,7 @@
-课堂要求:每组至少让 TRAE 找出 3 个前端文件、2 个后端文件、1 个权限或日志相关依据。 +最低证据:至少定位 3 个前端文件、2 个后端文件,以及 1 条权限或日志相关依据。
diff --git a/slidev/pages/day2/m8-asset-library.md b/slidev/pages/day2/m8-asset-library.md index 4a35d09..c9b8a72 100644 --- a/slidev/pages/day2/m8-asset-library.md +++ b/slidev/pages/day2/m8-asset-library.md @@ -82,7 +82,7 @@ examples 存优秀案例和反面案例。 diff --git a/slidev/pages/day2/m9-rules-check.md b/slidev/pages/day2/m9-rules-check.md index 6ed7540..b48026c 100644 --- a/slidev/pages/day2/m9-rules-check.md +++ b/slidev/pages/day2/m9-rules-check.md @@ -129,7 +129,7 @@ --- diff --git a/slidev/pages/day3/m1-rules-evolution.md b/slidev/pages/day3/m1-rules-evolution.md index e5a413f..bc4b6e3 100644 --- a/slidev/pages/day3/m1-rules-evolution.md +++ b/slidev/pages/day3/m1-rules-evolution.md @@ -115,7 +115,7 @@ AGENTS.md 的价值在于跨工具复用,同一个仓库可以被多个 Agent --- -# 课堂产出:规则迁移清单 +# 规则迁移清单
diff --git a/slidev/pages/day3/m2-token-cue-agent.md b/slidev/pages/day3/m2-token-cue-agent.md index 45ad979..397ee94 100644 --- a/slidev/pages/day3/m2-token-cue-agent.md +++ b/slidev/pages/day3/m2-token-cue-agent.md @@ -137,7 +137,7 @@ Cue 的能力并没有消失,它进入了更大的 Agent 工作流。 --- -# 课堂产出:成本与任务匹配表 +# 成本与任务匹配表
diff --git a/slidev/pages/day3/m3-commands-skills-mcp.md b/slidev/pages/day3/m3-commands-skills-mcp.md index d7f54c5..f72e83c 100644 --- a/slidev/pages/day3/m3-commands-skills-mcp.md +++ b/slidev/pages/day3/m3-commands-skills-mcp.md @@ -206,7 +206,7 @@ assets 里放模板、检查清单、Prompt、反例和案例。
-| 接入方式 | 适合场景 | 课堂判断 | +| 接入方式 | 适合场景 | 选择依据 | |---|---|---| | Command | 固定话术、固定格式 | 降低重复输入 | | CLI | 命令稳定、脚本成熟 | 优先轻量封装 | @@ -223,7 +223,7 @@ MCP 是一种协议形态,CLI 是一种执行方式,Command 是轻量入口 --- -# 课堂产出:能力封装决策表 +# 能力封装决策表
diff --git a/slidev/pages/day3/m4-agent-teams-product-shape.md b/slidev/pages/day3/m4-agent-teams-product-shape.md index 80c1043..dcdac45 100644 --- a/slidev/pages/day3/m4-agent-teams-product-shape.md +++ b/slidev/pages/day3/m4-agent-teams-product-shape.md @@ -58,7 +58,7 @@ Agent Teams 适合有角色分工的复杂交付,比如设计、开发、测 @@ -172,7 +172,7 @@ AI 产品的每次调用都有真实计算成本,企业落地需要分层计 --- -# 课堂产出:Agent 分工表 +# Agent 分工表
diff --git a/slidev/pages/day3/m5-browser-docs-index.md b/slidev/pages/day3/m5-browser-docs-index.md index 578b058..e39dd17 100644 --- a/slidev/pages/day3/m5-browser-docs-index.md +++ b/slidev/pages/day3/m5-browser-docs-index.md @@ -203,7 +203,7 @@ Markdown 很适合 AI 读写。 --- -# 课堂产出:前端验证清单 +# 前端验证清单
diff --git a/slidev/pages/day3/m6-plan-review-summary.md b/slidev/pages/day3/m6-plan-review-summary.md index 9b38e34..a58a37d 100644 --- a/slidev/pages/day3/m6-plan-review-summary.md +++ b/slidev/pages/day3/m6-plan-review-summary.md @@ -127,7 +127,7 @@ Trae CLI 企业版也可以嵌入流水线,形成类似能力。
-| 第 4 天任务 | 为什么不能直接改 | Plan 里必须写清 | +| 复杂业务任务 | 为什么不能直接改 | Plan 里必须写清 | |---|---|---| | RBAC 权限矩阵 | 影响菜单、按钮、接口和数据范围 | 角色、权限字符、接口、验收账号 | | 状态流转 | 容易出现越权流转和脏状态 | 状态枚举、合法路径、非法路径 | @@ -145,7 +145,7 @@ Trae CLI 企业版也可以嵌入流水线,形成类似能力。 --- -# 第 3 天交付物 +# 任务编排资产
01 规则迁移清单
@@ -155,7 +155,7 @@ Trae CLI 企业版也可以嵌入流水线,形成类似能力。
05 前端浏览器验证清单
06 Plan 与 Review 验收清单
07 JeecgBoot 工单链路索引
-
08 第 4 天任务 Plan 草案
+
08 复杂业务任务 Plan 草案
+ +--- + +# 可直接复制到 rules:权限安全 + +
+ +```md +## 敏感操作必须四层校验 +- 级别:硬约束 +- 适用场景:新增、编辑、删除、指派、状态流转、查看审计、查看看板。 +- 必须:同时覆盖页面/菜单权限、按钮权限、后端接口权限、数据范围权限。 +- 禁止:只做页面或按钮控制。 +- 验收方式:授权角色能完成;未授权角色前端不可见且直接请求接口失败;越权数据访问失败。 + +## 权限字符必须有来源或标记为建议 +- 必须:来自现有菜单、权限配置、前端权限指令、后端鉴权注解或团队规范。 +- 禁止:凭空编造权限字符并当成事实。 +- 验收方式:权限字符在前端、菜单配置、后端接口之间一致。 + +## 数据权限不得被查询条件替代 +- 必须:后端统一校验当前用户可访问的数据范围。 +- 禁止:只依赖前端传入 userId、deptId、tenantId 或筛选条件。 +``` + +
+ + diff --git a/slidev/pages/day4/m2-permission-control.md b/slidev/pages/day4/m2-permission-control.md index 2c7b45e..9f74c41 100644 --- a/slidev/pages/day4/m2-permission-control.md +++ b/slidev/pages/day4/m2-permission-control.md @@ -102,3 +102,28 @@ work:order:dashboard 课堂实操时,不要求只看代码。 要用不同账号或模拟请求验证:前端不可见只是第一步,后端拒绝才是强制边界。 --> + +--- + +# rules 要落到这些位置 + +
+ +| rule 条款 | 代码落点 | 验证项 | +|---|---|---| +| 四层校验 | 菜单配置、按钮权限、Controller、Service 查询 | 无权限账号不可见且接口失败 | +| 权限字符有来源 | 菜单表、路由、按钮指令、后端注解 | 同一字符贯穿前后端 | +| 数据权限后端校验 | 列表、详情、导出、看板查询 | 改请求参数不能越权 | +| 敏感字段最小返回 | 列表接口、详情接口、审计展示 | 不暴露不必要 old/new value | + +
+ +
+让 TRAE 改权限前,先把相关 rule 和权限控制方案一起给它。 +
+ + diff --git a/slidev/pages/day4/m3-status-transition.md b/slidev/pages/day4/m3-status-transition.md index b8c7e99..5c8666c 100644 --- a/slidev/pages/day4/m3-status-transition.md +++ b/slidev/pages/day4/m3-status-transition.md @@ -89,3 +89,31 @@ POST /workOrder/transition 指派和状态流转建议有明确接口。 Service 层负责读取工单、校验权限、校验状态、更新状态、记录日志。 --> + +--- + +# 可直接复制到 rules:状态流转 + +
+ +```md +## 复杂业务状态必须先建模 +- 级别:硬约束 +- 适用场景:工单指派、处理、验证、驳回、关闭、重开。 +- 必须:先产出状态流转表,列出当前状态、动作、目标状态、允许角色、前置条件、后置动作和验收方式。 +- 禁止:直接在代码中堆状态判断而没有状态表。 +- 验收方式:每个状态动作都有合法路径和非法路径验证。 + +## 非法状态流转必须拒绝且状态不变 +- 必须:集中校验非法流转;失败时返回明确错误,业务状态不变,不写成功审计。 +- 禁止:仅靠前端禁用按钮防止非法流转。 +- 验收方式:构造非法状态动作,接口失败且数据库状态不变。 +``` + +
+ + diff --git a/slidev/pages/day4/m4-audit-log.md b/slidev/pages/day4/m4-audit-log.md index ad0ef58..69f689d 100644 --- a/slidev/pages/day4/m4-audit-log.md +++ b/slidev/pages/day4/m4-audit-log.md @@ -93,3 +93,31 @@ 状态更新成功但日志失败,或者日志成功但状态失败,都会造成追溯问题。 所以 Service 层要明确事务边界。 --> + +--- + +# 可直接复制到 rules:审计日志 + +
+ +```md +## 关键业务操作必须写审计 +- 级别:硬约束 +- 适用场景:创建、编辑关键字段、指派、状态流转、驳回、关闭、删除、导出、权限变更。 +- 必须:记录 actor、action、target_id、old_value、new_value、source、request_id、created_at 或项目等价字段。 +- 禁止:只记录普通日志而没有业务审计;操作失败后写成功审计。 +- 验收方式:执行关键操作后可查询审计记录,字段与实际变更一致。 + +## 审计和业务写入必须一致 +- 必须:明确审计写入时机、事务关系、失败处理和重试策略。 +- 禁止:业务成功审计丢失,或审计成功但业务失败。 +- 验收方式:模拟业务失败和审计失败,结果符合一致性策略。 +``` + +
+ + diff --git a/slidev/pages/day4/m5-exception-defensive.md b/slidev/pages/day4/m5-exception-defensive.md index 802d5a1..48969b2 100644 --- a/slidev/pages/day4/m5-exception-defensive.md +++ b/slidev/pages/day4/m5-exception-defensive.md @@ -88,3 +88,33 @@ 异常态修复很容易扩散。 课堂里要控制边界:只补工单模块相关前端状态、后端校验、Service 状态保护和审计日志。 --> + +--- + +# 可直接复制到 rules:异常态测试 + +
+ +```md +## 核心逻辑必须有测试或豁免说明 +- 级别:待确认约束 +- 适用场景:权限校验、状态流转、审计、看板指标、异常态修复。 +- 必须:优先补自动化测试;无法测试时说明原因、风险和手工验收方式。 +- 禁止:只说“已验证”但没有命令、样例、截图、接口响应或手工步骤。 + +## 权限必须做正反角色验证 +- 必须:至少验证一个授权角色和一个未授权角色;涉及数据范围时验证越权数据。 +- 禁止:只用管理员账号验证。 + +## 状态流转必须覆盖合法和非法路径 +- 必须:每个关键动作至少覆盖一条合法路径和一条非法路径。 +- 禁止:只测试从初始状态到成功终态的主链路。 +``` + +
+ + diff --git a/slidev/pages/day4/m6-dashboard-metrics.md b/slidev/pages/day4/m6-dashboard-metrics.md index 1903f55..5ba55d8 100644 --- a/slidev/pages/day4/m6-dashboard-metrics.md +++ b/slidev/pages/day4/m6-dashboard-metrics.md @@ -93,3 +93,31 @@ GET /workOrder/dashboard/trend 看板页面不是能打开就算完成。 验收要看权限、口径、空态和数字是否能解释。 --> + +--- + +# 可直接复制到 rules:看板指标 + +
+ +```md +## 看板指标必须手工抽样校验 +- 级别:硬约束 +- 适用场景:统计卡片、趋势图、分布图、排行、效能指标。 +- 必须:每个关键指标有含义、公式、过滤条件、时间字段、数据范围,并用样例数据手工校验。 +- 禁止:只验证接口返回 200 或页面有图表。 +- 来源证据:指标字典、接口契约、样例数据、数据库查询或列表数据。 +- 验收方式:至少一组样例数据的手工计算结果与接口返回一致。 +``` + +
+ +
+看板交付时要同时交付指标字典、接口契约和一组手工核对样例。 +
+ + diff --git a/slidev/pages/day4/m7-review-delivery.md b/slidev/pages/day4/m7-review-delivery.md index 3a09de5..ed8d8c9 100644 --- a/slidev/pages/day4/m7-review-delivery.md +++ b/slidev/pages/day4/m7-review-delivery.md @@ -2,7 +2,7 @@
-第四天改动涉及权限、状态、审计、异常和看板,必须做阶段 Review。 +工单模块改动涉及权限、状态、审计、异常和看板,必须做阶段 Review。
@@ -45,6 +45,61 @@ AI Review 不能只给 diff。 --- +# 复杂业务控制面的三个 Skill + +
+ +| Skill | 什么时候用 | 输入材料 | 输出产物 | +|---|---|---|---| +| design-complex-business-module | 开始写代码前 | 需求、项目规则、现有角色/API/表结构 | RBAC、权限方案、状态表、审计 SOP、异常清单、指标字典 | +| review-business-delivery | 阶段 Review 时 | diff、业务产物、测试结果、验收记录 | 问题清单、证据缺口、待确认项 | +| track-review-fix-loop | 修 Review 后 | Review 报告、修改文件、验证证据 | 修复记录、未闭环事项、资产更新建议 | + +
+ +
+Skill 负责流程和质量门禁;rule 负责长期约束;template/checklist 负责稳定输出。 +
+ + + +--- + +# design-complex-business-module 怎么用 + +
+ +```md +请使用 design-complex-business-module,基于 JeecgBoot 工单模块设计复杂业务控制面。 + +输入材料: +1. 需求范围:工单权限、状态流转、审计、异常态、看板。 +2. 项目规则:security-permission、state-audit、testing、delivery。 +3. 现有证据:角色、菜单权限、接口、数据表、相似模块。 + +输出必须包含: +RBAC 权限矩阵、权限控制方案、状态流转表、非法流转清单、 +审计日志 SOP、异常态清单、看板指标字典、看板接口验收记录。 + +要求: +没有源码或配置证据的内容标记为“建议”或“待确认”。 +``` + +
+ + + +--- + # 阶段 Review 报告
@@ -65,6 +120,36 @@ Review 输出必须结构化。 --- +# review-business-delivery 怎么用 + +
+ +```md +请使用 review-business-delivery 对本次工单模块改动做阶段 Review。 + +输入材料: +- 当天 diff 或修改文件清单 +- RBAC 权限矩阵和权限控制方案 +- 状态流转表和非法流转清单 +- 审计日志 SOP 和异常态清单 +- 看板指标字典和接口验收记录 +- 测试命令、接口响应、浏览器验证或手工验收记录 + +输出格式: +先列问题清单,按 P0/P1/P2/P3 排序; +每个问题必须有证据、风险、修复建议和验收方式; +缺少证据时单独列“证据缺口”,不要猜测。 +``` + +
+ + + +--- + # 修复记录
@@ -84,7 +169,38 @@ Review 不是结束。 --- -# 第 4 天交付物 +# track-review-fix-loop 怎么用 + +
+ +```md +请使用 track-review-fix-loop,把阶段 Review 问题转成修复闭环记录。 + +输入材料: +- 带编号的 Review 报告 +- 修改文件或产物 +- 测试输出、接口响应、浏览器截图或手工验收步骤 +- 现有 rules、checklists、prompts、examples、skills 目录 + +每个问题状态只能是: +成立-已修复 / 成立-暂不修复 / 不成立 / 需确认 / 未闭环 + +输出必须包含: +修复记录、未闭环事项、资产更新建议。 +资产类型从 rule、checklist、prompt、command、skill、example 中选择。 +``` + +
+ + + +--- + +# 复杂业务模块资产
01 工单 RBAC 权限矩阵
@@ -104,12 +220,47 @@ Review 不是结束。 这些产物共同说明:工单模块不只是能运行,还具备权限、状态、审计、异常和指标的工程闭环。 --> +--- + +# 复杂业务资产包目录 + +
+ +```text +ai-dev-assets/ +├── rules/ +│ ├── security-permission.md +│ ├── state-audit.md +│ ├── testing.md +│ └── delivery.md +├── skills/ +│ ├── design-complex-business-module/ +│ ├── review-business-delivery/ +│ └── track-review-fix-loop/ +├── templates/jeecgboot/ +├── checklists/jeecgboot/ +├── prompts/jeecgboot/ +└── examples/jeecgboot/ +``` + +
+ +
+交付给团队的不是聊天记录,而是可安装、可复用、可审查的工程资产包。 +
+ + + --- layout: center class: text-center --- -# 第 4 天总结 +# 复杂业务模块的控制面
让工单模块从“能跑通”升级到“可控制、可审计、可验证、可 Review”。 diff --git a/slidev/pages/day5/m1-project-workshop.md b/slidev/pages/day5/m1-project-workshop.md new file mode 100644 index 0000000..7b1ce12 --- /dev/null +++ b/slidev/pages/day5/m1-project-workshop.md @@ -0,0 +1,132 @@ +# 模块 1:案例引子:从状态变更提取资产原料 + +
+ +“取消原因不能为空”看起来只是一个字段校验,实际却处在派车、车辆、司机和行程状态交汇的位置。 + +这里不讨论如何完成这一项改动,而是用它识别后续可沉淀的约束、提问框架与验证方法。 + +
+ +
+
申请
+
派车
+
车辆 / 司机
+
行程
+
+ + + +--- + +# 公务用车模块中的三条状态线 + +
+ +| 对象 | 正常演进 | 取消或完成后的变化 | +|---|---|---| +| 申请 | `DRAFT -> PROCESSING -> COMPLETED` | 取消回到 `CANCELLED` | +| 派车 | `DISPATCHED -> COMPLETED` | 取消变为 `CANCELLED` | +| 车辆 | `AVAILABLE -> RESERVED -> IN_USE` | 取消或行程完成后回到 `AVAILABLE` | +| 司机 | `AVAILABLE -> ON_TASK` | 行程完成后回到 `AVAILABLE` | + +
+ +
+一个派车动作会改变多个对象;校验位置错了,错误状态就会扩散。 +
+ + + +--- + +# 取消派车的真实调用链 + +
+ +```text +取消按钮(仅 DISPATCHED 状态可见) + -> 取消弹窗(cancelReason 必填) + -> POST /official/vehicle/dispatch/cancel + -> Controller(权限 + 审计) + -> DispatchService.cancel + -> 派车状态 CANCELLED + 车辆状态 AVAILABLE +``` + +
+ +
+
前端:减少误操作
+
权限:限制谁能调用
+
Service:保护业务状态
+
+ + + +--- + +# 把业务语言变成可验证的目标 + +
+ +| 业务表达 | 可验证的技术表达 | +|---|---| +| 取消必须写原因 | `cancelReason` 不能是 `null`、空串或全空白 | +| 不允许无效取消 | 在读取派车记录、更新派车和释放车辆之前拒绝请求 | +| 不能影响正常取消 | 非空原因仍沿用原有状态更新与资源释放逻辑 | +| 不扩大改动 | 不改变权限、审计、页面交互、数据库字段或行程协议 | + +
+ +
+目标越可验证,TRAE 越不容易把一个小需求扩写成跨层重构。 +
+ + + +--- + +# 从案例中提取资产原料 + +
+
+
项目事实
+
状态、入口、权限、写入顺序和测试结构。
+
+
+
重复判断
+
哪些结论下次同类任务仍然需要重新做。
+
+
+
可沉淀资产
+
Rule、Prompt、Skill、检查单和反例各自承接什么。
+
+
+ + diff --git a/slidev/pages/day5/m10-agent-skill-composition.md b/slidev/pages/day5/m10-agent-skill-composition.md new file mode 100644 index 0000000..eb806dc --- /dev/null +++ b/slidev/pages/day5/m10-agent-skill-composition.md @@ -0,0 +1,163 @@ +# 模块 10:自定义 Agent 如何调用 Skill + +
+ +一个成熟的 Agent 不把所有知识写进自己身上。 + +它负责选择合适的工作流、加载必要资产,并将结果交给下一步;Skill 继续负责具体任务的判断顺序和交付格式。 + +
+ +
+ +```text +自定义 Agent(角色 + 工具边界) + -> 选择 Skill(任务工作流) + -> 加载 Rule / 任务上下文契约 / Checklist / Example + -> 输出结构化结论 + -> 交给 Review、PR 或下一项任务 +``` + +
+ + + +--- + +# Agent、Skill 与 MCP 的本质区别 + +
+ +| 对象 | 本质 | 负责什么 | 不负责什么 | +|---|---|---|---| +| Agent | 执行角色与权限边界 | 选择任务、调用 Skill 和工具、控制停止与交接 | 承载全部业务流程与项目事实 | +| Skill | 可复用的任务工作流 | 规定输入、判断步骤、输出与验收 | 决定谁可调用、拥有什么工具权限 | +| MCP | 外部能力接口 | 提供查询、读取、写入等工具能力 | 判断业务规则、决定任务流程 | + +
+ +
+
谁来做:Agent
+
如何做:Skill
+
能调用什么:MCP
+
+ + + +--- + +# 不同信息应该放在哪里? + +
+ +| 信息类型 | 最合适的位置 | 原因 | +|---|---|---| +| “先取证,缺口必须暴露” | Rule / Agent 默认指令 | 多个任务都必须遵守 | +| “状态变更如何审查” | Skill | 需要多步判断与停止条件 | +| “影响分析报告长什么样” | 任务上下文契约 / Template | 下游需要固定结构 | +| “五项不可遗漏检查” | Checklist | 适合逐项核对 | +| “数据库 NOT NULL 不适用的条件” | Example / 反例 | 防止机械套用 | +| 某项目的路径、字段和日志 | 运行时输入 | 换任务或换项目会变化 | + +
+ +
+一份内容只应有一个权威位置;重复复制到 Agent、Skill 和任务上下文契约中,更新时一定会漂移。 +
+ + + +--- + +# 上下文分层:稳定、任务与事实 + +
+ +| 层级 | 内容 | 更新频率 | 典型落点 | +|---|---|---|---| +| 稳定层 | 角色职责、最小授权、通用安全规则 | 低 | 自定义 Agent、Rule | +| 任务层 | 事实提取、方案比较、验证设计流程 | 中 | Skill、任务上下文契约、Checklist | +| 事实层 | 当前需求、源码、diff、日志和测试结果 | 高 | 本次对话或任务输入 | + +
+ +
+
稳定层错了
所有任务都会偏
+
任务层错了
同类任务反复偏
+
事实层缺失
当前结论必须停止
+
+ + + +--- + +# Agent 之间只交接结构化结果 + +
+ +```md +## 已确认事实 +- <证据路径或记录> + +## 交接去向 +- <应调用的 Skill 或应执行的验证> + +## 输入缺口 +- <缺少什么,为什么不能继续> + +## 不可覆盖的边界 +- <权限、范围、状态或发布限制> +``` + +
+ +
+交接的是可复核结论,不是把完整对话、推理过程和未经验证的猜测交给下一个 Agent。 +
+ + + +--- + +# Agent 与 Skill 组合的三种失效方式 + +
+ +| 失效方式 | 表现 | 修正方向 | +|---|---|---| +| Agent 变成万能角色 | 读、改、测、发、审全都默认允许 | 按职责拆分,降低工具权限 | +| Skill 被复制进 Agent | 多份流程说明彼此不一致 | Skill 保持唯一工作流来源 | +| 交接只有自然语言总结 | 下游重新猜事实、范围和验证状态 | 使用固定输出契约 | +| 运行时事实混入资产 | 换项目仍引用旧路径、字段和权限 | 把事实移回每次任务输入 | + +
+ +
+组合的目的不是增加层数,而是让角色、工作流和事实各自保持可维护。 +
+ + diff --git a/slidev/pages/day5/m11-asset-library-delivery.md b/slidev/pages/day5/m11-asset-library-delivery.md new file mode 100644 index 0000000..d1a4412 --- /dev/null +++ b/slidev/pages/day5/m11-asset-library-delivery.md @@ -0,0 +1,285 @@ +# 模块 11:历史代码治理与可交付资产库 + +
+ +资产库不是提示词文件夹。 + +一份可交付资产要让使用者看得出:解决什么重复问题、需要什么输入、如何运行、输出什么、什么情况下不能继续,以及如何验证它仍然有效。 + +
+ +
+
项目约束生成
+
项目知识构建
+
复杂业务设计
+
业务交付 Review
+
Review 修复闭环
+
+ + + +--- + +# 历史代码识别:先定位职责交界 + +
+ +`OfficialVehicleDispatchServiceImpl.assign` 不是已被判定的缺陷;源码能确认的是,它是一个同时协调多个业务对象的入口。 + +| 源码事实 | 涉及对象 | 重构时必须保留的含义 | +|---|---|---| +| 申请已完成、且不能重复派车 | 申请 / 派车 | 拒绝条件仍在任何写入之前 | +| 车辆可用且时间不冲突 | 车辆 / 派车 | 车辆选择不是只看当前状态 | +| 需要司机时司机必填、且时间不冲突 | 司机 / 派车 | `needDriver` 决定额外前置条件 | +| 创建派车任务后预占车辆 | 派车 / 车辆 | 两次写入处于同一个事务中 | + +
+ +
+“职责交界较多”是重构候选,不等同于“现有实现有错误”。 +
+ + + +--- + +# 重构方案先比选,再决定是否拆分 + +
+ +| 方案 | 对当前 `assign` 的处理 | 依据与判断 | +|---|---|---| +| 保留编排入口,提取校验段 | 分出申请、车辆、司机校验与派车创建的内部方法 | 保持事务、写入顺序和接口不变,便于定向测试 | +| 合并车辆与司机冲突校验 | 抽成通用“资源冲突”组件 | 当前只有查询形状相似,数据模型和错误语义是否统一尚无证据 | +| 将业务校验移动到前端 | 依赖页面下拉选项和表单必填 | 前端可被绕过,不能代替 Service 的业务保护 | + +
+ +
+当前源码支持“先做内部拆分的可行性评估”,不支持直接引入跨领域的通用抽象。 +
+ + + +--- + +# 重构风险审查要逐项回到现有行为 + +
+ +以 `cancel` 为例,以下行为来自当前 Service、Controller 和事务注解: + +| 风险点 | 当前行为 | 重构后的验证点 | +|---|---|---| +| 输入保护 | 空白取消原因在查询与写入之前被拒绝 | 无效输入不能读取或修改业务状态 | +| 状态保护 | 只有 `DISPATCHED` 的派车任务可取消 | 非法状态被拒绝,不能释放车辆 | +| 写入耦合 | 派车变为 `CANCELLED` 并记录原因;车辆恢复 `AVAILABLE` | 两类状态同时成功,异常时整体回滚 | +| 接口边界 | Controller 保留取消权限、审计记录与成功语义 | 不因内部拆分丢失权限或审计入口 | + +
+ +
+风险清单不是重构方案的附录,而是判断“能否改”的前提。 +
+ + + +--- + +# TypeScript 类型治理:类型不能遮住契约冲突 + +
+ +当前公务用车模块已经提供了一个需要核验的跨端契约案例: + +| 位置 | 现有定义或行为 | 需要确认的事实 | +|---|---|---| +| 后端常量 | 审批结果仅定义 `pass` / `reject` | Service 只接受这两个小写值 | +| 前端类型 | `ApproveResult` 同时允许大小写字符串和 `boolean` | 放宽联合类型掩盖了实际允许范围 | +| 前端表单 | 审批选项提交 `PASS` / `REJECT` | 请求载荷与后端接收值是否一致 | +| 申请状态模型 | 后端为字符串,前端同时接受字符串与 `number` | 需要明确接口中的唯一表示方式 | + +
+ +
+源码已证明存在定义差异;是否发生运行时错误,仍需以实际请求和接口验证为准。 +
+ + + +--- + +# 现有测试证明了什么,也暴露了什么 + +
+ +| 已有测试证据 | 能支持的结论 | 仍不能支持的结论 | +|---|---|---| +| `OfficialVehicleDispatchServiceImplTest` | 空白取消原因会在依赖访问前被拒绝 | 正常取消是否更新派车和车辆状态 | +| `OfficialVehicleDispatchControllerTest` | 接口委派、成功提示和 Service 异常透传 | Service 内的状态校验、事务与持久化结果 | +| 官方车辆模块的 Maven 测试配置 | 该后端模块可以执行 Surefire 测试 | 所有状态分支均已覆盖 | +| 前端 `jest.config.mjs` | 当前前端已有 Jest 测试基座 | 派车页面或模型已经具备测试 | + +
+ +
+“已有单测通过”只能说明已运行的场景通过,不能替代对未覆盖状态和副作用的判断。 +
+ + + +--- + +# 单测补齐必须服从当前项目的测试基座 + +
+ +远程大纲中的“Vitest 单测补齐”,在当前项目应转换为“按既有测试框架补齐行为证据”。 + +| 测试目标 | 当前适用基座 | 应补的行为证据 | +|---|---|---| +| Service 规则 | JUnit 5 / Mockito | 非 `DISPATCHED` 取消被拒绝且不写入;正常取消同步更新派车与车辆 | +| 派车前置条件 | JUnit 5 / Mockito | 未审批、重复派车、车辆不可用、需司机但缺司机等分支 | +| 前端请求与模型 | Jest / Vue Test Utils | 审批值的真实载荷、派车表单的必填与可见条件 | +| 跨端类型契约 | API 验证或集成测试 | 大小写审批值、状态值表示与服务端拒绝语义 | + +
+ +
+不为引入新工具而另起 Vitest;先让现有 JUnit 和 Jest 覆盖真实业务行为。 +
+ + + +--- + +# 拆开一个真实 Skill 包 + +
+ +```text +track-review-fix-loop/ +├── SKILL.md # 输入、状态模型、流程、输出与质量门禁 +├── agents/openai.yaml # 展示名、简介和默认触发语 +└── assets/ + ├── 修复记录生成.prompt.md # 将原始材料整理为固定结构 + ├── 修复记录.template.md # 下游可消费的结果格式 + ├── Review修复闭环检查清单.md # 固定遗漏点 + └── 多份反例.md # 权限、审计、状态等典型误用 +``` + +
+ +
+核心文档不求长,而是把“输入、判断、输出、停止、验证”写成能被人和 Agent 同时执行的契约。 +
+ + + +--- + +# 资产库需要一个可检索的索引 + +
+ +| 索引字段 | 要回答的问题 | 示例 | +|---|---|---| +| 资产名称与类型 | 这是 Rule、Prompt、Skill 还是 Agent? | 状态变更审查 Agent | +| 适用信号 | 什么任务应该选择它? | 涉及状态写入或资源释放 | +| 必要输入 | 缺少什么就不能运行? | 目标、源码、状态规则、验证记录 | +| 输出契约 | 结果能交给谁使用? | 事实表、方案比较、验证清单 | +| 权威位置 | 应到哪个文件更新? | Skill 的 SKILL.md 或 assets/ | +| 复核触发 | 什么变化会使它失效? | 框架、流程、接口或真实误用变化 | + +
+ +
+索引不记录“有哪些好提示词”,而是帮助使用者快速判断“此刻该用哪一份资产”。 +
+ + + +--- + +# 用测试夹具验证资产,而不是验证文案 + +
+ +| 夹具类型 | 提供的材料 | 应观察的行为 | +|---|---|---| +| 完整事实 | 入口、状态、diff 与测试记录齐全 | 结论可追溯,输出结构完整 | +| 证据缺失 | 缺少关键 Service 或验证记录 | 明确列出缺口并停止结论 | +| 范围冲突 | 同时要求改数据库、权限和页面 | 指出越界,不默认扩大改动 | +| 反例输入 | 已知的权限、审计或状态误用 | 能识别风险并引用检查项 | +| 跨项目输入 | 不同目录、字段、框架和测试基座 | 重新取证,不复制旧项目结论 | + +
+ +
+资产测试关心的是边界是否正确,不要求模型每次生成完全相同的句子。 +
+ + + +--- + +# 资产库还应包含 MCP 能力契约 + +
+案例提供事实;资产保存判断;自定义 Agent 在受控权限下调用这些资产。连接外部系统的 MCP 还需要独立的能力与权限契约。 +
+ +
+
选型
+
Rule
+
Prompt
+
Skill
+
Agent
+
验证
+
+ + diff --git a/slidev/pages/day5/m12-mcp-asset-governance.md b/slidev/pages/day5/m12-mcp-asset-governance.md new file mode 100644 index 0000000..d20d099 --- /dev/null +++ b/slidev/pages/day5/m12-mcp-asset-governance.md @@ -0,0 +1,152 @@ +# 模块 12:MCP 也必须沉淀为能力资产 + +
+ +MCP 不是一条连接地址,也不是把所有外部系统工具都暴露给 Agent。 + +它是“可被 Agent 调用的外部能力”,因此同样需要清楚的输入输出、权限、审计、失败处理和验证边界。 + +
+ +
+
能力说明
+
工具契约
+
权限范围
+
失败降级
+
验收夹具
+
+ + + +--- + +# MCP 资产包应包含什么? + +
+ +```text +mcp-assets/ +├── README.md # 接入边界、适用场景与不适用场景 +├── MCP能力契约.template.md # 服务、工具、输入、输出、错误与审计 +├── MCP工具权限与验收清单.md # 最小授权、敏感数据与副作用验证 +├── MCP故障降级清单.md # 超时、鉴权、限流、不可用时的处理 +└── fixtures/ # 完整、拒绝、失败与脱敏结果样例 +``` + +
+ +
+连接配置只说明“连得上”;资产包还要说明“什么时候可以调用、调用后能相信到什么程度”。 +
+ + + +--- + +# 每个 MCP 工具都需要输入输出契约 + +
+ +| 契约字段 | 必须说明的问题 | +|---|---| +| 工具名称与用途 | 它解决哪一类外部事实或操作? | +| 输入 | 必填字段、格式、数据范围和敏感参数处理方式是什么? | +| 输出 | 返回哪些已确认字段,哪些字段可能为空或过期? | +| 副作用 | 只读、可逆写入,还是不可逆操作? | +| 错误 | 鉴权、限流、超时、无结果和服务错误如何区分? | +| 审计 | 调用主体、目标对象、时间和结果如何留痕并脱敏? | + +
+ +
+工具名叫“查询”不代表一定只读;是否有副作用必须由契约明确,而不是让 Agent 猜测。 +
+ + + +--- + +# Agent 调用 MCP 时,权限要再收一层 + +
+ +| 外部能力类型 | Agent 默认策略 | 必须补充的保护 | +|---|---|---| +| 读取公开或脱敏信息 | 可按职责开放 | 结果范围、来源时间与引用记录 | +| 读取受限业务信息 | 仅在明确授权后调用 | 数据范围、脱敏与审计 | +| 创建草稿或临时资源 | 需要用户确认 | 幂等键、撤销方式和可见结果 | +| 修改业务状态或数据 | 不作为默认工具权限 | 二次确认、最小范围和回滚路径 | +| 删除、发布、外发 | 不交给通用 Agent | 专用流程与人工最终确认 | + +
+ +
+自定义 Agent 的工具选择只是第一层;MCP 工具自身仍要定义调用级的权限与确认边界。 +
+ + + +--- + +# MCP 的验收不只检查“调用成功” + +
+ +| 验收夹具 | 预期行为 | +|---|---| +| 合法完整请求 | 返回结构符合契约,来源和时间可追溯 | +| 缺少必填参数 | 拒绝并说明字段缺口,不自动补造参数 | +| 越权请求 | 拒绝或要求确认,不泄露超范围数据 | +| 限流、超时或服务不可用 | 区分故障类型,输出降级结论,不伪装成无结果 | +| 敏感字段返回 | 按契约脱敏,日志不写入密钥、Token 或完整隐私数据 | +| 有副作用的重复调用 | 符合幂等或明确阻止重复执行 | + +
+ +
+MCP 失败时,Agent 应该报告“外部事实未取得”,而不是用已有代码或常识补出一个答案。 +
+ + + +--- + +# MCP 能力资产的交付标准 + +
+一份 MCP 资产的完成标准,不是 Agent 调用成功,而是能力、契约、权限、故障处置和验证边界均可被复核。 +
+ +
+
适用边界
+
工具契约
+
调用权限
+
故障降级
+
验收夹具
+
+ + diff --git a/slidev/pages/day5/m13-historical-code-governance.md b/slidev/pages/day5/m13-historical-code-governance.md new file mode 100644 index 0000000..44bdbf7 --- /dev/null +++ b/slidev/pages/day5/m13-historical-code-governance.md @@ -0,0 +1,210 @@ +# 模块 13:历史代码治理、类型契约与测试补齐 + +
+ +Rule、Skill、Agent 和 MCP 解决的是“如何沉淀判断”。 + +回到真实项目,历史代码治理还需要回答:哪些结构可以调整、哪些行为必须保持、类型是否真的表达了接口契约,以及测试到底覆盖到哪里。 + +
+ +
+
职责结构
+
行为基线
+
命令模型
+
类型契约
+
测试矩阵
+
断言边界
+
+ + + +--- + +# 派车重构的候选职责结构 + +
+ +当前 `assign` 保持事务编排入口,候选拆分只调整内部职责,不改变 Controller、接口或写入顺序: + +```text +assign(vo) @Transactional + |- 校验申请:存在、已审批、未重复派车 + |- 校验车辆:存在、可用、时间不冲突 + |- 校验司机:按 needDriver 决定必填、可用、时间不冲突 + |- 创建派车任务:写入申请、车辆、司机、计划时间与状态 + `- 预占车辆:AVAILABLE -> RESERVED +``` + +| 可调整的内部边界 | 不能借拆分改变的行为 | +|---|---| +| 私有校验方法、局部命名、组装对象的位置 | 事务边界、拒绝顺序、接口字段、派车与车辆的写入语义 | + +
+ + + +--- + +# 用行为基线验收无损重构 + +
+ +`assign` 的当前源码可先整理为行为基线;重构后的代码和测试都应逐项回到这张表。 + +| 输入条件 | 预期结果 | 允许的写入 | +|---|---|---| +| 申请不存在、未完成或已派车 | 拒绝派车 | 无派车任务、无车辆状态变更 | +| 车辆不存在、不可用或时间冲突 | 拒绝派车 | 无派车任务、无车辆状态变更 | +| 需要司机但未提供,或司机不可用/冲突 | 拒绝派车 | 无派车任务、无车辆状态变更 | +| 所有前置条件满足 | 创建 `DISPATCHED` 派车任务 | 车辆由 `AVAILABLE` 变为 `RESERVED` | + +
+ +
+“无损”不是代码未报错,而是每个拒绝路径和成功副作用仍与基线一致。 +
+ + + +--- + +# 类型治理要分开命令、实体与展示模型 + +
+ +派车模块已有一组可对照的模型边界: + +| 模型 | 当前字段范围 | 应承担的职责 | +|---|---|---| +| `OfficialVehicleDispatchVo` | `applyId`、`carId`、可选 `driverId`、`remark` | 派车命令的唯一输入 | +| `OfficialVehicleDispatch` | ID、调度人、时间、状态、取消原因等 | 服务端持久化与列表展示结果 | +| 前端 `OfficialVehicleDispatchVo` | 与派车命令相同的四类字段 | 提交时只发送用户可提供的内容 | +| 前端 `OfficialVehicleDispatch` | 读取列表中的状态、时间和调度人等 | 展示与操作条件判断 | + +
+ +
+命令类型不是实体类型的 `Partial`:状态、时间和操作人不应由派车表单决定。 +
+ + + +--- + +# 类型差异必须形成可执行的核验用例 + +
+ +审批结果和申请状态的定义差异,不能只记录在问题单中,应转换为可验证的契约。 + +| 核验对象 | 输入或观察值 | 需要确认的结论 | +|---|---|---| +| 审批命令 | `pass`、`reject`、`PASS`、`REJECT` | 哪些值能被接口接受;拒绝时返回什么语义 | +| 审批表单 | 单选组件实际提交的 `result` | 页面提交值是否与接口契约一致 | +| 申请状态 | `1`、`"1"` 及接口返回值 | 前端渲染和状态判断使用的唯一表示 | +| 类型定义 | 联合类型、表单选项、后端常量 | 三处是否指向同一组允许值 | + +
+ +
+类型契约完成的标准是“能执行的接受与拒绝用例”,不是文档里多了一行枚举。 +
+ + + +--- + +# 取消派车的测试用例应覆盖状态与副作用 + +
+ +| 场景 | 期望行为 | 当前证据状态 | +|---|---|---| +| `cancelReason` 全空白 | 立即拒绝,不访问后续依赖 | 已有 Service 单测 | +| 派车任务不存在 | 拒绝,不更新派车或车辆 | 待补 | +| 派车状态不是 `DISPATCHED` | 拒绝,不释放车辆 | 待补 | +| 任务可取消且车辆存在 | 记录取消原因,派车变 `CANCELLED`,车辆变 `AVAILABLE` | 待补 | +| 任务可取消但车辆记录缺失 | 派车仍完成取消,避免空指针 | 待补,当前源码有空值保护 | + +
+ +
+测试用例的粒度应跟随状态与副作用,不以“覆盖了 cancel 方法”作为完成标准。 +
+ + + +--- + +# 断言要与被测层级匹配 + +
+ +| 被测层级 | 当前可见证据 | 应断言什么 | 不能替代什么 | +|---|---|---|---| +| Controller | Mockito 验证 `cancel` 的委派与结果消息 | 接口是否交给正确的 Service 参数 | Service 内部状态和持久化行为 | +| Service 拒绝路径 | 空白原因抛出异常 | 不满足条件时无后续写入或资源释放 | 正常取消的双对象更新 | +| Service 成功路径 | 当前尚无定向单测 | 派车状态、取消原因、车辆状态及更新次数 | 前端实际请求载荷 | +| API / 集成层 | 当前尚无契约验证记录 | 前后端值、权限、序列化与错误响应 | 未执行环境的全链路结论 | + +
+ +
+Controller Mock 测试通过,不等于业务状态机已经被测试。 +
+ + + +--- + +# 从案例到可执行资产 + +
+案例提供事实;资产保存判断;重构守住行为;类型表达契约;测试证明副作用;MCP 将外部能力纳入同一套边界。 +
+ +
+
事实
+
Rule
+
任务契约
+
Skill
+
Agent
+
MCP
+
类型
+
测试
+
+ + diff --git a/slidev/pages/day5/m2-project-knowledge.md b/slidev/pages/day5/m2-project-knowledge.md new file mode 100644 index 0000000..b626637 --- /dev/null +++ b/slidev/pages/day5/m2-project-knowledge.md @@ -0,0 +1,125 @@ +# 模块 2:从案例判断资产边界 + +
+ +资产化最难的不是给文件命名,而是把可长期复用的判断,与一次任务中的项目事实分开。 + +稳定约束可以沉淀;字段名、路径、权限码和未验证结论必须留在运行时输入中。 + +
+ +
+
稳定约束
+
运行时事实
+
已知反例
+
证据缺口
+
+ + + +--- + +# 资产选型先看变化与风险 + +
+ +| 判断维度 | 信号 | 更合适的承载方式 | +|---|---|---| +| 约束变化频率 | 长期稳定,违反代价高 | Rule:明确必须项、禁止项与验收 | +| 任务输入变化 | 问法稳定,但每次事实材料不同 | 任务上下文契约:固定约束,替换运行时输入 | +| 漏检风险 | 风险项有限且常被忽略 | Checklist:让关键检查可以逐项确认 | +| 决策复杂度 | 需要多步读取、判断、交接和验证 | Skill:编排资产与停止条件 | +| 误解成本 | 正确做法容易被错误套用 | Example:保留正例、反例和适用边界 | + +
+ +
+同一条经验不一定只对应一种资产;选择依据是变化速度、误用风险和决策复杂度。 +
+ + + +--- + +# 公务用车案例能沉淀什么? + +
+ +| 案例中的发现 | 不应直接沉淀为 | 更合适的资产 | +|---|---|---| +| 前端必填不能代替后端业务保护 | “取消原因不能为空”的全局 Rule | 状态变更必要信息后端保护 Rule | +| 分析时要同时看页面、API、权限和 Service | 一段固定答案 | 影响分析任务上下文的输入清单 | +| 状态写入前要检查必需字段 | 一次性代码片段 | 状态变更检查单与反例 | +| 需要读源码、判断边界、列验证项 | 超长 Prompt | 多步状态变更审查 Skill | +| 数据库 NOT NULL 在此场景不合适 | “永远不要改数据库” | 条件化反例与取舍说明 | + +
+ + + +--- + +# 资产候选必须有证据来源 + +
+ +| 证据来源 | 可以升级的资产 | 还需要补什么 | +|---|---|---| +| 明确的项目规范、配置或接口契约 | Rule | 适用范围和验收方式 | +| 多次出现的同类提问与交付结构 | Prompt / Template | 必填输入与输出 Schema | +| 已发生的漏检、误用或缺陷 | Checklist / Example | 反例、触发条件和修复依据 | +| 可重复的多步分析与验证路径 | Skill | 各步骤输入、交接和停止条件 | +| 未经核对的模型建议或单次对话 | 暂不沉淀 | 先补项目事实或验证记录 | + +
+ +
+资产不是知识收藏;没有来源证据的内容只能是候选建议。 +
+ + + +--- + +# 不值得沉淀的内容 + +
+
+
单次对话结论
+
没有输入、证据和适用范围,下一次无法复核。
+
+
+
项目特有字段或权限码
+
换项目后很可能失效,必须留在运行时输入中。
+
+
+
没有验证过的模型建议
+
只能作为候选方案,不能升级为规则。
+
+
+
一次性检查列表
+
重复价值不足时,保留在任务记录即可。
+
+
+ + diff --git a/slidev/pages/day5/m3-task-delegation.md b/slidev/pages/day5/m3-task-delegation.md new file mode 100644 index 0000000..047e480 --- /dev/null +++ b/slidev/pages/day5/m3-task-delegation.md @@ -0,0 +1,112 @@ +# 模块 3:Rule 的设计契约 + +
+ +Rule 不是“代码要规范”这类口号。 + +一条可执行 Rule 必须说明:它基于什么证据、在哪些场景生效、必须做什么、不能做什么,以及如何验收。 + +
+ +
+
来源可信
+
边界明确
+
结果可验
+
+ + + +--- + +# 先给 Rule 标注证据等级 + +
+ +| 等级 | 什么时候使用 | 典型写法 | +|---|---|---| +| 硬约束 | 配置、构建、CI 或显式规范直接证明 | 必须 / 禁止 | +| 观察性约束 | 多处源码反复出现,但没有书面规范 | 默认采用 / 优先复用 | +| 待确认约束 | 对交付很重要,但当前项目无法证明 | 需确认 / 不下结论 | + +
+ +
+不要把“一个代码样例”或“通用最佳实践”直接升级成项目硬规则。 +
+ + + +--- + +# 一条 Rule 的六个字段 + +
+ +```md +## 状态变更必要信息保护 + +- 级别:观察性约束 +- 适用场景:Service 方法会写入业务状态或释放、占用资源。 +- 必须:在任何写入前校验本次状态变更必需的信息。 +- 禁止:仅依赖前端必填;将条件必填扩大为全表非空。 +- 来源证据:状态更新、资源更新与现有调用链。 +- 验收方式:无效输入被拒绝;正常路径及副作用单独验证。 +``` + +
+ + + +--- + +# 常见的 Rule 失效方式 + +
+ +| 写法 | 为什么失效 | 改写方向 | +|---|---|---| +| “后端要做好校验” | 没有触发点、位置和验收 | 指出状态写入前的必要信息保护 | +| “所有字段都不能空” | 把条件规则扩大为数据模型规则 | 写清具体状态与字段条件 | +| “统一使用 Service” | 把架构偏好误当业务约束 | 说明为什么该规则需要覆盖复用入口 | +| “严格测试” | 无法判断是否满足 | 写出无效输入、正常路径和副作用验证 | + +
+ +
+好的 Rule 约束行为,不替人猜业务,也不替项目做架构重写。 +
+ + + +--- + +# Rule 的验收不是“读起来像规范” + +
+
能触发
知道什么时候应用
+
能约束
必须与禁止清楚
+
能取证
来源不是主观偏好
+
能验证
可用代码、测试或检查确认
+
+ + diff --git a/slidev/pages/day5/m4-refactor-safety.md b/slidev/pages/day5/m4-refactor-safety.md new file mode 100644 index 0000000..24592f5 --- /dev/null +++ b/slidev/pages/day5/m4-refactor-safety.md @@ -0,0 +1,126 @@ +# 模块 4:任务上下文契约 + +
+ +任务上下文契约不是一句“帮我分析”。它规定任务需要哪些事实、遵守哪些约束、缺少什么时停止,以及结果如何交给下一步使用。 + +Prompt 只是这份契约的常见承载形式之一。 + +
+ +
+
固定约束
+
运行时事实
+
输出结构
+
停止边界
+
+ + + +--- + +# 固定约束与运行时事实要分开 + +
+ +| 固定上下文 | 每次任务重新提供的事实 | +|---|---| +| 先取证、再判断、最后给方案的要求 | 本次变更目标 | +| 证据不足时标记待确认 | 已读文件、diff、日志和测试结果 | +| 输出必须区分事实、建议和未知项 | 当前接口、字段、状态和权限信息 | +| 不得编造运行结果或业务结论 | 本次允许与禁止修改的范围 | + +
+ +
+可复用的是判断框架;项目事实必须作为运行时上下文重新提供。 +
+ + + +--- + +# 影响分析的输出契约 + +
+ +```md +任务:基于提供的源码与记录分析本次变更,不要修改代码。 + +必备上下文:变更目标、已读源码、状态或权限规则、diff 或测试记录。 + +输出必须包含: +1. 已确认事实:每项关联来源路径或输入证据。 +2. 影响链路:入口、业务边界、状态/数据副作用。 +3. 候选方案:范围、优缺点、验证方式。 +4. 最小改动建议:允许修改与明确不改内容。 +5. 待确认项:证据缺口以及不能推出的结论。 + +约束:没有证据时写“待确认”;不得补充测试、接口或业务结果。 +``` + +
+ + + +--- + +# 任务上下文契约也需要测试用例 + +
+ +| 输入情况 | 期望行为 | 不能接受的行为 | +|---|---|---| +| 入口、Service 与测试齐全 | 输出可追溯的影响分析 | 只给泛化最佳实践 | +| 只有页面截图或单个文件 | 明确缺少 API、状态或验证证据 | 自行补全调用链 | +| 要求跨层重构但无依据 | 标记范围风险并给出最小方案 | 默认修改数据库、权限和页面 | +| 测试结果缺失 | 输出待执行验证项 | 写成“测试已通过” | + +
+ +
+一份任务上下文契约在不同事实输入下都能保持边界,才具备复用价值。 +
+ + + +--- + +# 上下文契约失控的三个信号 + +
+
+
输入缺失仍结论完整
+
说明模型在补猜,缺少待确认出口。
+
+
+
输出无法进入下一步
+
说明没有固定输出结构或验收字段。
+
+
+
每次都要重写长背景
+
说明固定约束和变量输入没有拆开。
+
+
+ + diff --git a/slidev/pages/day5/m5-testing-safety-net.md b/slidev/pages/day5/m5-testing-safety-net.md new file mode 100644 index 0000000..f9230a0 --- /dev/null +++ b/slidev/pages/day5/m5-testing-safety-net.md @@ -0,0 +1,137 @@ +# 模块 5:Skill 的难点是决策边界 + +
+ +Skill 的价值不在于堆叠说明,而在于把任务拆成可核对的输入条件、决策顺序、交接结果和停止边界。 + +只有这些边界被显式定义,复杂任务才能在不同项目事实下保持可控。 + +
+ +
+
输入条件
+
决策顺序
+
交接结果
+
停止边界
+
+ + + +--- + +# 一个真实 Skill 包如何按职责拆开 + +
+ +```text +skill-name/ +├── SKILL.md # 触发条件、输入、工作流、输出与门禁 +├── agents/ +│ └── openai.yaml # 展示名、简介与默认触发提示 +├── assets/ +│ ├── 生成.prompt.md # 可复用任务 Prompt +│ ├── 检查清单.md # 固定遗漏点 +│ └── 报告.template.md # 结构化输出模板 +├── references/ # 稳定背景知识与兼容性说明 +└── scripts/ # 可重复执行的机械检查 +``` + +
+ +
+`SKILL.md` 负责决策和编排;模板、检查单、参考资料和脚本各自承担单一职责,避免同一约束出现多个版本。 +
+ + + +--- + +# SKILL.md 的核心结构 + +
+ +```md +--- +name: state-change-safety +description: 当任务涉及业务状态写入、资源占用或释放时使用。 +--- + +## Inputs +变更目标、相关源码、状态规则、测试或 diff、项目约束。 + +## Workflow +提取事实 -> 判断状态与副作用 -> 比较候选方案 -> 生成验证清单。 + +## Output Contract +事实表、影响链路、最小改动建议、验证项、待确认事项。 + +## Stop Conditions +证据不足、规则冲突、范围越界或关键验证失败时停止并说明原因。 +``` + +
+ + + +--- + +# agent 配置只解决“如何被发现” + +
+ +```yaml +interface: + display_name: "状态变更安全审查" + short_description: "分析状态写入、资源副作用与验证边界" + default_prompt: "Use $state-change-safety to review this change." + +policy: + allow_implicit_invocation: true +``` + +
+ +
+
配置文件:让 Agent 找得到
+
SKILL.md:让 Agent 做得对
+
+ + + +--- + +# 什么不该做成 Skill? + +
+ +| 情况 | 更合适的资产 | 原因 | +|---|---|---| +| 只有一条永远成立的约束 | Rule | 没有多步决策必要 | +| 只需固定格式生成一段内容 | Prompt 或 Command | 不需要上下文编排 | +| 只检查五个固定项目 | Checklist | 不需要 Agent 主动分析 | +| 只有一个项目的临时异常 | 任务记录或 Example | 复用价值不足 | +| 需要读取、判断、生成、验证并输出结构化结果 | Skill | 需要多个资产协同 | + +
+ + diff --git a/slidev/pages/day5/m6-asset-skill-writing.md b/slidev/pages/day5/m6-asset-skill-writing.md new file mode 100644 index 0000000..123d1dc --- /dev/null +++ b/slidev/pages/day5/m6-asset-skill-writing.md @@ -0,0 +1,131 @@ +# 模块 6:Skill 如何组合 Rule、Prompt 与检查单 + +
+ +Skill 的难点不是写步骤,而是把每一步需要的判断、材料和停止条件拆开。 + +一次状态变更审查,至少要完成“事实、方案、验证、边界”四类判断。 + +
+ +
+
事实
+
方案
+
验证
+
边界
+
+ + + +--- + +# 状态变更 Skill 的四个决策阶段 + +
+ +```text +1. 事实提取 + 读取入口、状态、写入顺序、权限、审计和已有验证。 + +2. 方案比较 + 用 Rule 判断最终保护边界;排除范围越界方案。 + +3. 验证设计 + 用检查单覆盖无效输入、正常路径和关键副作用。 + +4. 结论输出 + 区分已确认、待执行和待确认;证据不足时停止。 +``` + +
+ + + +--- + +# 每一步应加载什么资产? + +
+ +| 阶段 | 使用的资产 | 作用 | +|---|---|---| +| 事实提取 | 影响分析 Prompt、项目约束 | 防止遗漏入口和把猜测写成事实 | +| 方案比较 | 状态变更 Rule、反例 | 判断保护位置与禁止范围 | +| 验证设计 | 状态流转检查单、测试模板 | 覆盖正常、异常与副作用 | +| 结论输出 | 报告模板、输出契约 | 使后续 Review 或 PR 能继续使用 | + +
+ +
+Skill 的每一步都应能指出“为什么读这个资产、产出交给谁”。 +
+ + + +--- + +# Output Contract 决定 Skill 是否可用 + +
+ +```md +## 已确认事实 +<来源路径 / 记录 / 证据> + +## 方案比较 +<候选方案、覆盖范围、取舍理由> + +## 验证清单 +<已覆盖、待执行、阻断条件> + +## 待确认事项 +<证据缺口与不能下结论的原因> + +## 资产建议 + +``` + +
+ + + +--- + +# Stop Conditions 比步骤更重要 + +
+ +| 停止信号 | Skill 应做什么 | 不应做什么 | +|---|---|---| +| 缺少入口或状态源码 | 列出缺失材料并停止结论 | 自行补全调用链 | +| 业务规则互相矛盾 | 标记待确认并列出冲突来源 | 擅自选一个规则执行 | +| 方案超出允许范围 | 明确指出越界位置 | 顺手改数据库、权限或页面 | +| 关键验证没有证据 | 输出待执行项 | 写成“已验证” | + +
+ +
+能在证据不足时停下来的 Skill,比能不断生成内容的 Skill 更可靠。 +
+ + diff --git a/slidev/pages/day5/m7-delivery-review.md b/slidev/pages/day5/m7-delivery-review.md new file mode 100644 index 0000000..b002e9d --- /dev/null +++ b/slidev/pages/day5/m7-delivery-review.md @@ -0,0 +1,115 @@ +# 模块 7:资产也要验证与演进 + +
+ +代码需要测试,Rule、Prompt 和 Skill 同样需要验证。 + +一份资产能否长期使用,不取决于措辞是否漂亮,而取决于面对完整信息、缺失信息和越界请求时能否保持正确边界。 + +
+ +
+
完整输入
+
缺失证据
+
范围冲突
+
跨项目复用
+
+ + + +--- + +# 用四类输入测试资产边界 + +
+ +| 测试输入 | 期望结果 | 失败信号 | +|---|---|---| +| 入口、状态与测试材料齐全 | 输出事实、方案和验证项都可追溯 | 只给泛化建议,无法指向输入 | +| 缺少 Controller 或状态规则 | 明确列出缺失证据 | 擅自补全权限或调用链 | +| 请求修改数据库、权限和页面 | 标出范围冲突与风险 | 把扩展改动当作默认方案 | +| 换到另一个项目 | 重新扫描项目事实和约束 | 直接复制字段名、路径和异常文案 | + +
+ + + +--- + +# 资产更新只接受两类证据 + +
+ +| 更新依据 | 应如何处理 | +|---|---| +| 项目规则、框架、接口或测试基座发生变化 | 更新适用范围、来源路径和验证方式 | +| 资产在真实任务中造成误用、遗漏或无效输出 | 补充反例、停止条件或拆分更小资产 | +| 只是觉得“提示词不够顺” | 保留意见,不能直接升级为规则 | +| 想把另一个项目的细节抄进来 | 先标记为待确认,并重新获取证据 | + +
+ +
+资产演进依赖新证据,不依赖个人偏好或模型建议本身。 +
+ + + +--- + +# 可复用与必须重查的内容 + +
+ +| 可以跨项目复用 | 换项目必须重新获取 | +|---|---| +| 事实、方案、验证、边界的输出结构 | 路径、模块名、字段、接口与权限码 | +| 证据不足时停止的规则 | 状态枚举、异常映射和事务边界 | +| Prompt 的输入输出契约 | 构建命令、测试基座和目录结构 | +| Skill 的质量门禁与测试矩阵 | 业务必填条件与发布前置条件 | + +
+ +
+资产复用的是判断方法,不是上一次项目的结论。 +
+ + + +--- + +# 资产验证后,进入 Agent 封装 + +
+案例提供事实和反例;资产把事实背后的判断方法保存下来。 +
+ +
+
资产选型
+
Rule 契约
+
Prompt 契约
+
Skill 组合
+
资产验证
+
+ + diff --git a/slidev/pages/day5/m8-custom-agent-asset.md b/slidev/pages/day5/m8-custom-agent-asset.md new file mode 100644 index 0000000..bf8492c --- /dev/null +++ b/slidev/pages/day5/m8-custom-agent-asset.md @@ -0,0 +1,138 @@ +# 模块 8:自定义 Agent:把稳定角色做成资产 + +
+ +自定义 Agent 不是另起一套 Prompt。 + +它把一个稳定的职责、可用工具、默认指令和交付边界封装为可选择的角色,让每次任务不必从“你是谁、能做什么”重新说明。 + +
+ +
+
职责固定
+
工具受控
+
指令稳定
+
结果可验
+
+ + + +--- + +# 它和其他资产分别解决什么? + +
+ +| 资产 | 固化的内容 | 不承担的内容 | +|---|---|---| +| Rule | 必须遵守的长期约束 | 多步任务编排 | +| Prompt | 一类提问的输入输出结构 | 角色权限与默认工具 | +| Checklist | 固定遗漏点 | 源码分析与方案判断 | +| Skill | 多步工作流、资源与停止条件 | 对话入口的默认身份 | +| 自定义 Agent | 角色、指令、模型和工具边界 | 某个任务的项目事实 | + +
+ +
+自定义 Agent 负责“谁来做、能做什么”;Skill 负责“这类任务按什么流程完成”。 +
+ + + +--- + +# 一个自定义 Agent 的配置契约 + +
+ +```md +--- +name: 状态变更审查 +description: 分析状态写入、资源副作用与验证边界。 +argument-hint: "变更目标、相关源码、测试或 diff" +model: <当前环境允许的模型> +tools: <仅选择完成该职责所需的工具> +--- + +先提取事实;证据不足时列出缺口; +不修改代码;输出影响链路、方案比较、验证项和待确认事项。 +``` + +
+ +
+
标头:让 Trae 识别角色、入口与能力边界
+
正文:让 Agent 在任务中保持判断与交付边界
+
+ + + +--- + +# 固定配置与运行时输入必须分开 + +
+ +| 固定在自定义 Agent 中 | 每次任务重新提供 | +|---|---| +| 角色名称与职责范围 | 本次变更目标 | +| 先取证、后判断的工作原则 | 相关目录、文件、diff、日志 | +| 可用工具与停止条件 | 当前状态、字段、接口与权限事实 | +| 结构化输出要求 | 实际执行过的测试或验证结果 | +| 引用哪些 Rule / Skill | 本次允许与禁止的改动范围 | + +
+ +
+把项目路径、字段名和历史结论写死在 Agent 中,会让它离开原项目后继续错误复用。 +
+ + + +--- + +# 什么时候不该创建自定义 Agent? + +
+ +| 需求 | 更合适的做法 | 原因 | +|---|---|---| +| 只要一句固定约束 | Rule | 不需要独立角色 | +| 只生成一段固定格式文本 | Prompt / Command | 无需工具与长期指令 | +| 只核对少数固定项 | Checklist | 无需自主检索判断 | +| 只有一次临时排查 | 普通对话与任务记录 | 长期复用价值不足 | +| 需要稳定职责、工具边界和结构化交付 | 自定义 Agent | 角色本身有重复价值 | + +
+ +
+“每个任务一个 Agent”会造成角色泛滥、说明重复和难以维护。 +
+ + diff --git a/slidev/pages/day5/m9-agent-boundaries.md b/slidev/pages/day5/m9-agent-boundaries.md new file mode 100644 index 0000000..bb49b86 --- /dev/null +++ b/slidev/pages/day5/m9-agent-boundaries.md @@ -0,0 +1,121 @@ +# 模块 9:Agent 的权限与停止边界 + +
+ +给 Agent 更多工具,不会让结论更可靠,只会扩大它可能造成的影响。 + +角色、上下文和工具权限必须同时收敛,才能让一份 Agent 资产在不同任务中保持可控。 + +
+ +
+
角色
允许承担什么判断
+
上下文
允许读取什么材料
+
工具
允许产生什么副作用
+
+ + + +--- + +# 按风险选择最小工具集 + +
+ +| Agent 类型 | 典型职责 | 最小能力方向 | 不应默认拥有 | +|---|---|---|---| +| 代码理解 Agent | 定位入口、调用链与事实 | 代码搜索、文件读取 | 写文件、执行发布 | +| 方案审查 Agent | 比较方案与范围风险 | 读取源码、diff、规则 | 直接修改业务代码 | +| 测试设计 Agent | 生成测试点与验证方案 | 读取需求、代码、测试 | 宣称测试已经通过 | +| CI 分析 Agent | 分层解释失败日志 | 读取日志、配置、diff | 修改环境或密钥 | +| 实施 Agent | 在确认范围内生成变更 | 受控编辑与验证 | 自行扩大任务范围 | + +
+ +
+先定义角色,再分配最少工具;不要先打开全部权限,再期待提示词自己约束行为。 +
+ + + +--- + +# 用真实案例设计一个只读审查 Agent + +
+ +```text +角色:状态变更审查 Agent +输入:变更目标、入口与 Service 源码、状态定义、相关测试或 diff +可读材料:页面/API、权限与审计入口、状态写入代码、测试结构 +输出:事实表、影响链路、最小改动建议、验证清单、待确认项 +禁止:修改代码、执行发布、把前端必填当作最终业务保护 +停止:缺少状态写入点、关键规则冲突、验证证据缺失 +``` + +
+ +
+公务用车案例在这里只贡献“状态变更要先取证”的素材,不把任何字段名或接口路径固化为通用规则。 +
+ + + +--- + +# Stop Conditions 是 Agent 的安全阀 + +
+ +| 停止信号 | Agent 应输出 | Agent 不应做 | +|---|---|---| +| 缺少入口或关键 Service | 缺失材料清单 | 补猜调用链 | +| 状态规则与需求冲突 | 冲突来源与待确认问题 | 自行选择业务规则 | +| 请求超出角色权限 | 范围风险与转交建议 | 临时取得写入或发布权限 | +| 验证记录不完整 | 待执行验证项 | 写成“已通过” | +| 需要外部系统事实 | 所需日志、接口或外部确认项 | 将源码推断包装成运行结论 | + +
+ +
+可靠 Agent 的表现不是总能继续,而是知道何时不能再继续。 +
+ + + +--- + +# 自定义 Agent 的验收标准 + +
+
职责清楚
知道何时选择它
+
权限收敛
只拥有必要工具
+
输出稳定
可被后续 Skill 或 Review 使用
+
会正确停止
证据不足不虚构
+
+ +
+不能说明“为什么需要这个角色、为什么给这些工具”的 Agent,不应进入共享资产库。 +
+ + diff --git a/slidev/pages/day6/m1-team-flow.md b/slidev/pages/day6/m1-team-flow.md new file mode 100644 index 0000000..6138bc0 --- /dev/null +++ b/slidev/pages/day6/m1-team-flow.md @@ -0,0 +1,124 @@ +# 模块 1:AI 改动的工程闭环 + +
+ +AI 辅助完成的代码,仍然要经过研发协作中的基本检验:范围清晰、变更可审查、结果可验证、结论可追溯。 + +这些工程要素共同约束 AI 改动的速度、质量与责任边界。 + +
+ +
+ +```text +影响分析 + -> 变更实现 + -> PR 说明 + -> Git 审查与合并 + -> CI 构建与测试 + -> 发布或回退判断 + -> 可追溯技术结论 +``` + +
+ + + +--- + +# CI/CD 在交付链路中的位置 + +
+
+
CI:持续集成
+
代码提交后自动编译、检查和测试,尽早发现无法合入的问题。
+
+
+
持续交付
+
CI 通过后形成可部署版本,进入测试或预生产环境;生产发布仍需人工确认。
+
+
+
持续部署
+
CI 通过后自动发布生产环境,需要成熟的监控、自动化门禁和回退能力。
+
+
+ +
+提交 PR -> CI 构建与测试 -> 可部署版本 -> 测试环境验证 -> 人工确认 -> 生产发布或回退 +
+ +
+CI 负责尽早暴露问题;是否发布仍需要结合业务风险和验证证据作出判断。 +
+ + + +--- + +# AI 改动进入协作后的四类信息 + +
+
+改动目标 +
解决什么问题,明确不解决什么问题
+
+
+影响范围 +
涉及哪些入口、数据、状态、权限或外部依赖
+
+
+验证证据 +
哪些行为已经由测试、日志或人工检查证实
+
+
+已知边界 +
哪些场景尚未覆盖,哪些结论仍需要确认
+
+
+ + + +--- + +# 工程质量的最终责任仍在人 + +
+ +| AI 可以加速 | 人必须确认 | +|---|---| +| 搜索代码、归纳调用链、生成候选实现 | 业务规则是否正确 | +| 生成测试草稿、解释日志、提出修复假设 | 权限、数据和状态边界是否完整 | +| 补充 PR 说明和变更摘要 | 变更范围是否可接受,验证结论是否准确 | +| 提供多种实现方案 | 采用哪一种方案及其长期维护成本 | + +
+ +
+把 AI 当作加速器,而不是把工程判断外包给 AI。 +
+ + diff --git a/slidev/pages/day6/m10-ai-trends.md b/slidev/pages/day6/m10-ai-trends.md new file mode 100644 index 0000000..b1ca2f7 --- /dev/null +++ b/slidev/pages/day6/m10-ai-trends.md @@ -0,0 +1,236 @@ +# 模块 10:AI Agent 新趋势 + +
+ +AI Agent 已能读取外部资料、触发真实动作、组合工具能力并委托其他 Agent。输入边界、执行门禁、工具服务、能力分发、IDE 接入与跨 Agent 协作,开始形成不同层次的开放协议。 + +
+ +
+
输入权威
外部内容不能下指令
+
运行时策略
动作发生时再判断
+
MCP 新扩展
工具调用走向任务与交互
+
Agent Plugins
能力以统一插件包交付
+
ACP
IDE 与 Agent 解耦
+
A2A
跨 Agent 应用协作
+
+ +--- + +# Agent 工程的边界正在外延 + +
+
输入边界
上下文不再只有仓库与任务描述,还包含网页、Issue、检索结果和外部系统返回的数据。
+
执行边界
Agent 不只输出文本,也会调用工具、运行命令、更新外部系统,甚至交给后台长任务处理。
+
协作边界
协作不再局限于同一 IDE 内的角色分工,开始需要跨 Agent 应用的发现、委托和结果交接。
+
+ +
每一次外部读取、工具调用和任务交接,都需要明确的信息权威、执行权限和责任归属。
+ +--- + +# 外部内容不拥有指令权 + +
+
+
它是什么
+网页、Issue、RAG 文档和 MCP 工具描述都是输入材料。它们可以提供事实、线索和建议,但不能自行改变任务目标、权限或停止条件。 +
+
+
它解决什么
+间接提示注入往往把正当问题和越权动作混在一起。分离“可核实的问题”和“无权执行的指令”,才能安全接入真实世界资料。 +
+
+ +
“导出报错”可以成为排查线索;“关闭测试并上传配置”不能因为写在同一条 Issue 里就获得执行权。
+ + + +--- + +# 运行时策略与 Hook + +
+
+
它是什么
+策略在会话开始、工具调用前后和任务出错时运行,根据工具、参数、环境和角色决定允许、拒绝、确认或记录。 +
+
+
它解决什么
+把“不要做错”从一段静态说明,变成动作发生时的实际判断:拦截危险命令、扫描敏感信息、记录审计并限制失败重试。 +
+
+ +
GitHub Copilot Hooks 是这一方向的代表:策略可以在事件点执行,而不只依赖模型记住一段 Prompt。
+ + + +--- + +# MCP 的三项扩展 + +
+
Tasks
将耗时工作变成有任务号、进度、取消、补充输入和最终产物的异步任务。
+
Skills over MCP
让 Server 发布可发现的结构化工作方法,不只暴露一组工具名称和参数。
+
MCP Apps
在对话内展示表单、参数预览、图表和任务进度,把人带回关键判断点。
+
+ +
它们让 MCP 从“同步调用一个工具”扩展为“观察长任务、发现工作方法、完成可见的人机确认”。
+ + + +--- + +# MCP 正在摆脱会话绑定 + +
+
请求自带上下文
协议版本、客户端能力与调用信息跟随每次请求,不再依赖先建立一条长期会话。
+
服务可水平扩展
任意请求可以路由到任意实例,网关可按方法与工具名进行路由、限流和授权。
+
状态必须显式交接
业务仍可有状态,但状态要以任务号或句柄传递,不能隐含在一条连接里。
+
+ +
连接可以断开和重连;任务与业务状态必须可见、可恢复、可审计。
+ + + +--- + +# Agent Plugins:把一组能力装进同一个包 + +
+
+
它是什么
+
+reports-plugin/
+├─ plugin.json
+├─ skills/     工作方法与参考资料
+├─ mcp.json    工具与服务连接声明
+└─ com.vendor.client/   客户端扩展 +
+
+
+
它解决什么
+将相互配合的 Skill 与 MCP Server 按固定目录和清单一起交付。不同客户端不必各自发明目录、清单和连接配置,能力包也不必维护多份分叉版本。 +
+
+ +
Plugin 只规定“怎么打包”;安装、权限、信任校验、沙箱和界面仍由每个客户端负责。
+ + + +--- + +# IDE 与 Coding Agent 正在解耦 + +
+
IDE / 编辑器
负责工作区、文件编辑、Diff 展示、终端入口和人工确认。
+
ACP
统一会话、消息与编码任务交互,让编辑器和 Agent 不必逐对定制集成。
+
Coding Agent
负责理解任务、制定计划、修改代码、调用工具并返回可审查的变更。
+
+ +
选择 Agent 不必同时接受唯一界面;选择 IDE 也不必被单一 Agent 绑定。
+ + + +--- + +# Agent 的输出开始变成交互界面 + +
+
Agent
输出当前任务需要展示的字段、选项、进度与下一步,而不是直接执行一段界面代码。
+
A2UI
用声明式组件蓝图描述界面,可随 Agent 的流式输出逐步更新。
+
宿主应用
按自身设计系统渲染原生组件,并决定哪些控件、数据和交互可以出现。
+
+ +
Agent 决定“现在需要什么交互”;宿主应用决定“允许如何呈现和执行”。
+ + + +--- + +# A2A:多 Agent 协作的协议层 + +
+
+
它是什么
+多 Agent 协作是多个角色分工和交接任务的广义工作方式。A2A 是其中的协议层,让独立 Agent 应用按标准方式发现彼此、委托工作和返回结果。 +
+
+
它解决什么
+跨产品、跨框架协作不再依赖共享所有 Prompt、记忆和工具实现,而是交接身份、能力、任务状态、结果和失败原因。 +
+
+ +
A2A 管 Agent 到 Agent 的任务协作;MCP 管 Agent 到工具、数据和资源。一个 A2A Agent 可以在内部使用 MCP。
+ + + +--- + +# 工程落地的三项前提 + +
+
来源有边界
外部内容只能补充事实与线索,不能自行改变任务、权限或停止条件。
+
执行有门禁
每次写入、发布或高风险工具调用,都要有最小权限、确认与可回退条件。
+
协作可交接
跨角色或跨系统协作必须明确任务状态、验证结果、失败原因与责任归属。
+
+ +
外部内容不能授予权限;工具动作必须有执行门禁;跨 Agent 协作必须交接任务、状态、结果与责任。
+ + diff --git a/slidev/pages/day6/m11-six-day-retrospective.md b/slidev/pages/day6/m11-six-day-retrospective.md new file mode 100644 index 0000000..e313ff4 --- /dev/null +++ b/slidev/pages/day6/m11-six-day-retrospective.md @@ -0,0 +1,221 @@ +# 复盘:六天能力图谱 + +
+ +六天内容只有一条主线:让 AI 参与研发时,团队如何持续获得事实、控制范围、验证行为,并把可靠经验沉淀下来。 + +
+ +
+
看懂项目
+-> +
组织上下文
+-> +
处理复杂业务
+-> +
沉淀团队资产
+-> +
进入工程闭环
+
+ + + +--- + +# 第 1 天:先把陌生项目变成可追溯的事实 + +
+ +| 学到的动作 | 不能跳过的原因 | 交付物 | +|---|---|---| +| 找模块、入口与关键对象 | 不知道系统在哪里,就无法讨论改动影响 | 模块认知图、项目结构图 | +| 追认证、路由、权限和日志链路 | 后台系统的风险常在跨层调用关系中 | 调用链路图 | +| 区分代码事实、业务事实与推断 | AI 的流畅表达不等于项目真实行为 | 输出校验清单 | +| 写代码理解报告 | 让团队的理解可被交接和审查 | 代码理解报告 | + +
+ +
+项目理解必须回到文件、函数、调用关系和不确定点,不能停在泛泛总结。 +
+ + + +--- + +# 第 2 天:把一次会话变成可复用的上下文与规则 + +
+ +| 学到的动作 | 解决的问题 | 交付物 | +|---|---|---| +| 用目标、范围、约束、输出、验收组织任务 | 避免 AI 泛泛回答和无边界修改 | 上下文组织清单 | +| 组合 @workspace、#File、#Code、#Web | 让检索从宽到窄、从事实到细节 | 上下文引用模板 | +| 先收集证据,再判断和给方案 | 切断幻觉和错误归因 | 证据收集 Prompt | +| 编写安全、编码、测试与前端 Rule | 让团队隐性习惯变成可执行约束 | rules.md v0.1 与检查单 | + +
+ +
+好规则不是“写得严格”,而是能说明何时触发、必须检查什么、不能做什么、如何验收。 +
+ + + +--- + +# 第 3 天:从工具功能转向任务编排能力 + +
+ +| 学到的动作 | 关键判断 | 交付物 | +|---|---|---| +| 理解 Rule、Cue、模型和 Token 的位置 | 资源要匹配任务风险与信息复杂度 | 成本与任务匹配表 | +| 区分 Command、Skill、MCP 与 CLI | 高频输入、多步决策和外部工具不是同一问题 | 能力封装决策表 | +| 设计 Agent 分工 | 输入、输出、验收和停止条件都要明确 | Agent 分工表 | +| 使用 Browser、文档与 Plan | 先计划、再执行、再记录验证 | 前端验证清单与 Plan 检查项 | + +
+ +
+任务越复杂,越需要先把边界显式化,不能因为有 Agent 就跳过计划。 +
+ + + +--- + +# 第 4 天:复杂业务先建立控制面,再讨论实现 + +
+ +| 业务难点 | 先产出的控制面 | 为什么不能直接生成代码 | +|---|---|---| +| 权限 | RBAC 矩阵、页面/按钮/接口/数据范围方案 | 角色与数据范围是业务事实 | +| 状态 | 状态流转表、非法流转清单 | 允许和拒绝路径都必须明确 | +| 审计 | 字段表、前后值、SOP | “记录日志”不足以保证可追溯 | +| 异常态 | 前后端行为与最小测试集 | 正常流程跑通不代表业务安全 | +| 看板 | 指标字典、接口契约和验收 | 数字必须有口径、来源和权限范围 | + +
+ +
+复杂业务最危险的顺序是“先让 AI 写代码,再回头补权限、状态和审计”。 +
+ + + +--- + +# 第 5 天:把可靠判断沉淀为可维护资产 + +
+ +| 资产 | 适合保存什么 | 不应保存什么 | +|---|---|---| +| Rule | 长期稳定、违反代价高的约束 | 一次任务的临时路径和猜测 | +| 任务上下文契约 | 输入、输出、证据缺口和停止条件 | 没有来源的项目事实 | +| Checklist / Template | 固定遗漏点和稳定输出结构 | 所有判断逻辑的重复副本 | +| Skill | 多步骤、需要证据和验收的工作流 | 只有一句指令的简单需求 | +| Agent / MCP | 稳定角色、权限边界和外部能力 | 未声明读写性质的工具连接 | + +
+ +
+资产化不是把聊天记录保存下来,而是把可复用的判断、边界和验证方式保存下来。 +
+ + + +--- + +# 第 6 天:让 AI 改动进入真实工程闭环 + +
+ +| 工程环节 | AI 可以辅助的工作 | 人必须负责的判断 | +|---|---|---| +| PR 与 Git | 归纳 Diff、生成说明、检查范围 | 改动是否该合入、如何回退 | +| Review | 发现风险、汇总证据缺口、生成问题草稿 | 问题是否成立、优先级与修复决策 | +| CI | 分类日志、关联配置与 Diff、建议验证命令 | 区分代码缺陷与环境处置 | +| 证据链 | 整理需求、实现、测试和边界 | 结论能否被当前证据支持 | +| 资产变更 | 检查引用、夹具和行为变化 | 权限和兼容性是否可接受 | + +
+ +
+AI 产出必须进入已有的工程审查、验证和责任体系。 +
+ + + +--- + +# 六项能力必须形成闭环 + +
+
事实
代码、配置、日志和业务确认。
+
上下文
目标、范围、约束、输出和验收。
+
计划
影响范围、最小方案、风险和验证。
+
控制面
权限、状态、审计、异常和指标。
+
验证
测试、CI、浏览器和人工观察。
+
沉淀
PR、证据链、Rule、Skill 和资产库。
+
+ + + +--- + +# 六天里最应该避免的六种误用 + +
+ +| 误用 | 正确替代动作 | +|---|---| +| 让 AI 泛泛总结整个项目 | 指定入口、问题、范围和证据格式 | +| 把模型回答当作业务规则 | 区分代码事实、业务确认、推断与待确认 | +| 直接接受大范围代码生成 | 先审阅 Plan,选择最小可验证 Diff | +| 只隐藏前端按钮就认为有权限控制 | 验证页面、按钮、接口和数据范围四层边界 | +| CI 变红就修改业务代码 | 先定位最早错误,区分代码、依赖、配置和环境 | +| 把一次有效 Prompt 直接升级为团队标准 | 先验证适用范围、停止条件和代表性夹具 | + +
+ + + +--- + +# 最终交付不应只有代码 + +
+
项目结构、调用链与事实记录
+
上下文包、Rule、Prompt 和检查单
+
权限、状态、审计、异常与指标控制面
+
Plan、Diff、测试、浏览器与 CI 证据
+
PR 说明、Review、修复记录和未覆盖边界
+
可验证、可维护、可演进的工程资产
+
+ +
代码是结果之一;可复用、可验证、可维护的工程资产,才能支撑长期协作。
+ + diff --git a/slidev/pages/day6/m2-pr-template-ai-record.md b/slidev/pages/day6/m2-pr-template-ai-record.md new file mode 100644 index 0000000..164128d --- /dev/null +++ b/slidev/pages/day6/m2-pr-template-ai-record.md @@ -0,0 +1,162 @@ +# 模块 2:标准 PR 说明与 AI 使用记录 + +
+ +PR 是团队协作里最稳定的交付节点。 + +如果 TRAE 参与了需求拆解、代码修改、测试和 Review,PR 说明里就要能看见这些证据。 + +
+ +
+
需求背景
+
实现说明
+
测试结果
+
风险与 AI 记录
+
+ + + +--- + +# PR 模板 + +
+ +```md +## 需求背景 +<为什么做,本次解决什么> + +## 实现说明 +<改了哪些模块、接口、页面、规则> + +## 测试结果 +- [ ] 单元测试: +- [ ] 接口验证: +- [ ] 浏览器验证: +- [ ] 手工验收: + +## 风险点 +<权限、状态、审计、异常、兼容性、数据范围> + +## AI 使用记录 + + +## 关联资产 +<使用了哪些 rules、任务上下文契约、skills、checklists> +``` + +
+ + + +--- + +# PR 说明示例 + +
+ +```md +## 需求背景 +取消已派车任务时,后端必须拒绝空白取消原因。 + +## 实现说明 +- `OfficialVehicleDispatchServiceImpl.cancel` 在状态写入前加入 `Assert.hasText`。 +- 新增全空白取消原因的 Service 单元测试。 +- 未改前端必填、权限、审计、SQL、派车和行程逻辑。 + +## 测试结果 +- [x] 单元测试:覆盖空白取消原因的拒绝行为。 +- [ ] 接口、数据库、浏览器和审计验证:按实际执行情况记录。 + +## 风险点 +统一异常映射、非空正常取消路径和外部调用方需要单独确认。 +``` + +
+ + + +--- + +# TRAE 生成 PR 说明的任务上下文契约 + +
+ +```md +请基于以下材料生成标准 PR 说明:任务背景、 +main 与当前分支的 git diff、实际测试结果、Review 结论、 +使用过的 rules、任务上下文契约、skills。 + +输出必须包含:需求背景、实现说明、测试结果、风险点、 +AI 使用记录、关联资产。 + +要求:不要编造测试结果;没有证据的测试项标记“未执行”; +风险必须具体到模块或文件;不要将训练约束写成客户既有需求。 +``` + +
+ + + +--- + +# AI 使用记录表 + +
+ +| 环节 | TRAE 做了什么 | 人工判断 | 沉淀内容 | +|---|---|---|---| +| 代码理解 | 定位前端必填、取消 API、权限、审计和 Service | 用源码逐条核对 | 影响分析任务上下文契约 | +| 方案选择 | 建议 Service 作为最终保护边界 | 因状态变更和车辆释放都在此处而采纳 | 状态变更 Rule | +| 测试设计 | 提出空白输入与正常取消回归点 | 仅将实际执行的空白单测标为通过 | 测试安全网 | +| 交付说明 | 整理最小 diff、风险和待确认项 | 删除任何无证据的“已验证”表述 | PR 模板 | + +
+ + + +--- + +# AI 使用记录不是聊天记录 + +
+ +| 应保留 | 不必保留 | +|---|---| +| 影响实现或验证结论的 AI 建议 | 完整对话逐字稿 | +| 人工采纳或否决建议的理由 | 与本次改动无关的探索 | +| 关联的 Rule、任务上下文契约、Skill 或检查单 | 无法复查的笼统评价 | +| 未解决风险和待确认边界 | 把推断包装成已验证结论 | + +
+ +
+好的 PR 说明,让评审者看到工程结论,而不是看到一段 AI 对话。 +
+ + diff --git a/slidev/pages/day6/m3-ai-code-review.md b/slidev/pages/day6/m3-ai-code-review.md new file mode 100644 index 0000000..53123d4 --- /dev/null +++ b/slidev/pages/day6/m3-ai-code-review.md @@ -0,0 +1,111 @@ +# 模块 3:AI 变更的 Git 协作与合并控制 + +
+ +TRAE 可以加快修改,但不能绕开 Git 的可追溯、可审查和可回退要求。 + +AI 参与的改动越多,越要让分支、提交和合并边界保持清楚。 + +
+ +
+
分支范围
+
提交粒度
+
冲突处理
+
合并控制
+
+ + + +--- + +# 分支和提交要服务于审查 + +
+ +| 做法 | 为什么重要 | 公务用车案例 | +|---|---|---| +| 一个分支只解决一个问题 | diff 容易审查和回退 | 取消原因保护不混入无关重构 | +| 一个提交对应一个可验证动作 | 可定位问题和选择性回退 | 参数保护与对应测试保持一致 | +| 提交前排除无关文件 | 噪声不会掩盖业务风险 | 构建生成物不应混入业务变更 | +| 描述真实变更与验证情况 | 后续定位有证据 | 提交信息和 PR 只写有依据的验证 | + +
+ + + +--- + +# 合并前的四道检查 + +
+ +| 检查点 | 要确认什么 | 当前案例的检查问题 | +|---|---|---| +| 范围 | diff 是否只包含已说明的改动 | 是否只有 Service 校验和相应测试? | +| 证据 | PR 是否如实标记测试、风险和未确认项 | 是否把 API 或浏览器验证误写为已通过? | +| 冲突 | 是否理解双方业务意图 | 是否保留“写入前校验”的顺序? | +| 回退 | 是否知道回退哪个提交、是否涉及数据补偿 | 代码改动可以选择性回退;集成数据仍需另行核对 | + +
+ + + +--- + +# 用 TRAE 审查变更范围 + +
+ +```md +请检查以下 git diff 是否符合本次变更范围。 + +目标:取消派车必须由后端拒绝空白取消原因。 +允许修改:Dispatch Service 的参数保护和对应的聚焦测试。 +禁止修改:SQL、权限、菜单、前端页面、派车/行程状态协议、无关格式。 +证据:实际 Maven 定向测试结果;API 和浏览器验证尚未执行。 + +请输出:直接相关改动、可能无关或风险较高改动、 +合并前必须补充的验证、回退点。 +``` + +
+ + + +--- + +# Git 历史也是工程接口 + +
+
看得懂
目标与范围一致
+
审得动
每次改动足够聚焦
+
退得回
影响与恢复路径清楚
+
查得到
结论能对应 diff 与证据
+
+ +
+AI 可以生成改动,Git 负责把改动保留为可理解、可审查的工程历史。 +
+ + diff --git a/slidev/pages/day6/m4-ci-failure-loop.md b/slidev/pages/day6/m4-ci-failure-loop.md new file mode 100644 index 0000000..828e887 --- /dev/null +++ b/slidev/pages/day6/m4-ci-failure-loop.md @@ -0,0 +1,284 @@ +# 模块 4:CI 失败日志分析 + +
+ +CI 失败时,首先要判断失败来自代码、配置、依赖还是运行环境。 + +TRAE 可提取日志中的直接错误、关联改动和缺失证据;修复前仍要先确认失败来源。 + +
+ +
+
代码
+
测试
+
配置
+
依赖
+
环境
+
+ + + +--- + +# 先按失败来源分类 + +
+ +| 失败来源 | 常见信号 | 首先核对的材料 | 处理边界 | +|---|---|---|---| +| 代码与类型 | 编译、lint、类型报错指向具体文件 | diff、调用方、类型定义 | 只修改能解释错误的实现 | +| 测试断言 | 预期与实际结果不同 | 测试用例、业务规则、测试数据 | 不能只为通过而改断言 | +| 构建配置 | 脚本、工作流、环境变量缺失 | package/pom、workflow、项目规则 | 先确认配置是否属于本次改动 | +| 依赖解析 | 版本冲突、锁文件不一致、制品缺失 | lockfile、依赖树、仓库配置 | 区分项目依赖与制品服务异常 | +| Runner 与环境 | 权限、磁盘、网络、系统工具失败 | Runner 日志、环境配置、重试结果 | 不把环境故障归因到源码 | + +
+ +
+分类不是为了快速贴标签,而是为了确定下一份该看的证据。 +
+ + + +--- + +# 日志必须和 diff 一起读 + +
+ +| 日志现象 | 与 diff 的关系 | 正确结论 | +|---|---|---| +| 新增类型错误指向本次修改文件 | 直接命中新增或改动行 | 优先检查该改动与接口契约 | +| 测试失败在相邻模块,diff 未触及该模块 | 关联尚未建立 | 标记待确认,不把失败直接归因于本次改动 | +| 依赖下载失败,代码尚未进入编译 | 没有代码执行证据 | 按环境或依赖问题排查 | +| 同一失败在未改动分支也可复现 | 与本次 diff 无关 | 记录为基线问题或外部问题 | + +
+ +
+
日志给出直接错误
+
diff 说明影响范围
+
两者共同支持根因
+
+ + + +--- + +# 代码修复与环境处置要分开 + +
+ +| 场景 | 代码侧动作 | 环境侧动作 | 不应做的事 | +|---|---|---|---| +| 类型或编译失败 | 修正实现、类型或调用契约 | 保持环境不变 | 顺手升级无关依赖 | +| 测试预期冲突 | 复核业务规则和测试数据 | 保留失败记录 | 只改断言让流水线通过 | +| 依赖解析失败 | 核对锁文件与版本声明 | 核对制品源、缓存和网络 | 修改业务逻辑 | +| Runner 权限或资源不足 | 不改项目源码 | 调整 Runner 或重试并留存结果 | 误报为代码缺陷 | + +
+ +
+修复范围必须和失败来源一致;CI 通过也不应掩盖未验证的业务风险。 +
+ + + +--- + +# 公务用车模块:已执行的定向测试门禁 + +
+ +```bash +mvn -pl jeecg-boot-module/jeecg-module-officialvehicle -am \ + -DskipTests=false \ + -Dtest=OfficialVehicleDispatchServiceImplTest \ + -Dsurefire.failIfNoSpecifiedTests=false test +``` + +| 项目事实 | 该命令支持的结论 | 不能外推的结论 | +|---|---|---| +| 公务用车模块启用了 Maven Surefire 测试 | 模块依赖与指定 Service 用例能够被执行 | 接口、数据库、浏览器与审计链路已验证 | +| 用例传入全空白取消原因 | `cancel` 会在状态写入前拒绝空白原因 | 非空正常取消路径已经回归 | +| 2026-08-06 记录为 `BUILD SUCCESS`,1 项用例零失败 | 本次定向 Service 验证通过 | 全模块或生产可发布 | + +
+ +
+可执行命令、实际结果和结论边界一起保存,才构成一项可复核的质量门禁证据。 +
+ + + +--- + +# 真实失败案例:Maven 本地仓库无写入权限 + +
+ +
+ +```text +[WARNING] Failed to write tracking file +$HOME/.m2/repository/.../resolver-status.properties + +[ERROR] java.io.FileNotFoundException +(Operation not permitted) +[ERROR] BUILD FAILURE +``` + +
+ +
+ +| 判断项 | 本次结论 | +|---|---| +| 首个直接错误 | Maven 更新本地仓库跟踪文件时被系统拒绝 | +| 失败来源 | 本地环境 / 文件权限 | +| 与业务 diff 的关联 | 未建立源码关联证据 | +| 正确处置 | 恢复 Maven 仓库写入条件,保留日志后重跑 | +| 不应做的事 | 修改派车 Service、测试断言或业务状态逻辑 | + +
+ +
+ +
+构建失败发生在依赖元数据写入阶段时,不能据此推断公务用车业务代码存在缺陷。 +
+ + + +--- + +# CI 分析任务上下文契约 + +
+ +```md +请分析以下 CI 失败日志。 + +输入材料:CI 日志、相关 diff、package/pom/workflow 配置、锁文件、项目 rules。 + +请输出: +1. 首个失败阶段与原始错误;2. 失败来源分类; +3. 与 diff 的关联证据;4. 有依据的根因判断; +5. 需要查看的文件或环境信息;6. 候选处置方案;7. 验证命令; +8. 是否适合沉淀为规则或检查清单。 + +要求:先解释失败原因,再给方案;不能只根据最后一行日志下结论; +区分代码修复与环境处置;证据不足时标记待确认。 +``` + +
+ + + +--- + +# CI 修复记录 + +
+ +| 字段 | 内容 | +|---|---| +| 失败阶段 | lint / typecheck / test / build / dependency / environment | +| 原始错误 | 日志中的直接报错,不改写 | +| 失败来源 | 代码、测试、配置、依赖、Runner 或外部服务 | +| diff 关联 | 命中改动 / 无关联证据 / 待确认 | +| 根因 | 支持判断的日志、配置或代码证据 | +| 处置范围 | 代码修复或环境处置,涉及哪些位置 | +| 验证命令 | 本地或 CI 重新执行命令 | +| 结果 | 通过 / 未通过 / 待确认 | +| 资产沉淀 | rule / checklist / 任务上下文契约 / example / 不沉淀 | + +
+ + + +--- + +# CI 质量门禁分别覆盖什么? + +
+ +| 门禁 | 作用 | 不能替代什么 | +|---|---|---| +| lint | 保证基础格式和规范 | 业务规则审查 | +| typecheck | 提前发现接口和类型不匹配 | 运行时行为验证 | +| unit test | 保护核心逻辑 | 完整集成链路 | +| coverage | 发现测试覆盖缺口 | 测试质量判断 | +| manual checklist | 覆盖权限、审计、状态和页面验收 | 可重复的自动化验证 | + +
+ +
+质量门禁不是越多越好,而是每道门禁都要清楚自己覆盖和不覆盖什么。 +
+ + + +--- + +# 一条 CI 结论应当写清什么? + +
+ +| 信息 | 说明 | +|---|---| +| 失败阶段 | lint、类型检查、测试、构建、依赖或环境 | +| 原始错误 | 保留日志中的直接报错,不提前改写 | +| diff 关联 | 说明是否命中本次改动及依据 | +| 根因判断 | 说明对应的配置、代码或环境依据 | +| 处置范围 | 只指出确实需要调整的位置 | +| 验证方式 | 使用可重复的命令或 CI 结果确认 | +| 结论边界 | 证据不足时明确标记待确认 | + +
+ +
+先区分直接错误、失败来源和根因,再决定是否修改代码。 +
+ + diff --git a/slidev/pages/day6/m5-asset-governance.md b/slidev/pages/day6/m5-asset-governance.md new file mode 100644 index 0000000..c9acb8d --- /dev/null +++ b/slidev/pages/day6/m5-asset-governance.md @@ -0,0 +1,149 @@ +# 模块 5:共享资产的版本与兼容性 + +
+ +Rule、任务上下文契约、Skill、自定义 Agent 和 MCP 契约一旦被共享使用,就会影响后续任务的判断与行为。 + +共享资产治理要明确资产变更后的可用范围、引用迁移与旧版本退出方式。 + +
+ +
+
版本
+
兼容
+
迁移
+
回退
+
+ + + +--- + +# 哪些资产变更属于行为变更? + +
+ +| 资产 | 变更示例 | 可能改变的行为 | +|---|---|---| +| Rule | 增加必须检查的前置条件 | Agent 的允许范围和停止条件 | +| 任务上下文契约 | 删除一个必填输入字段 | 输出是否仍有足够事实基础 | +| Skill | 调整步骤或输出 Schema | 下游任务能否继续消费结果 | +| 自定义 Agent | 增加工具或修改默认指令 | 可访问的能力和风险边界 | +| MCP 契约 | 更改读写标识或错误语义 | 调用权限与失败后的处置 | + +
+ +
+只要会改变 Agent 的输入、判断、输出或权限,就不应把它当成普通文案修改。 +
+ + + +--- + +# 版本号应表达使用边界 + +
+ +| 变更类型 | 使用建议 | 对既有调用方的要求 | +|---|---|---| +| 仅修正文案,不改变输入输出或约束 | 小版本 | 保持原有验证夹具通过 | +| 增加可选字段或补充反例 | 小版本,并说明新增行为 | 检查是否需要提供新材料 | +| 修改必填输入、输出结构、权限或停止条件 | 新版本边界 | 明确迁移方式和不兼容点 | +| 移除能力或结束支持 | 弃用版本 | 保留替代资产和退出期限 | + +
+ + + +--- + +# 兼容性说明的最小字段 + +
+ +| 字段 | 要回答的问题 | +|---|---| +| 资产与版本 | 当前使用的是哪一份可识别资产? | +| 适用范围 | 面向哪些项目、任务或技术栈? | +| 行为变化 | 输入、输出、约束、工具权限中变了什么? | +| 不兼容点 | 哪些旧引用、模板或 Agent 不能直接继续使用? | +| 迁移方式 | 使用者需要替换什么、补充什么或调整什么? | +| 回退条件 | 遇到何种结果时应回到哪个稳定版本? | +| 验证证据 | 哪些夹具、测试或实际记录支持本次变化? | + +
+ +
+兼容性说明服务于正确替换,不是为了增加文档数量。 +
+ + + +--- + +# 弃用不是删除旧文件 + +
+ +| 需要保留的信息 | 目的 | +|---|---| +| 被替代的版本与适用范围 | 识别历史引用,不误用旧能力 | +| 替代资产及目标版本 | 提供明确迁移方向 | +| 不再适用的原因 | 防止未来又恢复同一错误做法 | +| 已知引用位置 | 便于逐项替换与复核 | +| 回退条件与稳定版本 | 变更异常时能够恢复已验证行为 | + +
+ +
+
直接删除:历史引用失去解释
+
明确弃用:新旧边界仍可追溯
+
+ + + +--- + +# 版本发布前需要确认什么? + +
+ +| 确认项 | 要回答的问题 | 形成的版本结论 | +|---|---|---| +| 当前稳定版本 | 哪个版本仍可作为已知可用的回退点? | 明确 source version | +| 目标版本边界 | 新版本改变了哪些使用前提与适用范围? | 明确 target version | +| 使用者与引用 | 哪些 Rule、Skill、Agent、MCP 或模板仍依赖旧版本? | 明确迁移清单 | +| 退出与回退 | 旧版本何时停止默认使用,异常时回到哪里? | 明确弃用与恢复路径 | + +
+ +
+模块 5 解决“发布哪个版本、谁需要迁移、出现异常退回哪里”;单次资产 diff 的验证证据由模块 8 处理。 +
+ + diff --git a/slidev/pages/day6/m6-onboarding-rollout.md b/slidev/pages/day6/m6-onboarding-rollout.md new file mode 100644 index 0000000..8f6cce9 --- /dev/null +++ b/slidev/pages/day6/m6-onboarding-rollout.md @@ -0,0 +1,207 @@ +# 模块 6:变更证据链与可追溯说明 + +
+ +AI 可以帮助定位和实现,但不能替代研发交付中的说明责任。 + +一次改动是否值得合入,取决于阅读者能否快速判断:改了什么、为什么改、验证到哪里、还存在哪些边界。 + +
+ +
+
目标
+
范围
+
证据
+
边界
+
+ + + +--- + +# PR 模板不等于证据链 + +
+ +| PR 模板解决什么 | 证据链解决什么 | +|---|---| +| 让评审者快速阅读改动说明 | 让每条结论回到可核对的事实 | +| 组织目标、实现、测试和风险 | 关联需求、diff、测试、CI、日志与人工观察 | +| 呈现本次交付 | 支撑结论强度与后续排查 | +| 可以由 AI 协助起草 | 必须由真实记录持续校验 | + +
+ +
+PR 是阅读入口;证据链是结论能够被复核的依据。 +
+ + + +--- + +# 一条变更证据链包含什么? + +
+ +| 证据节点 | 能支持的判断 | 不能替代的判断 | +|---|---|---| +| 需求与规则 | 改动目标、约束和非目标 | 实现已正确运行 | +| diff 与代码阅读 | 修改范围、调用关系与关键分支 | 线上或全链路行为 | +| 自动化测试 | 对应方法、接口或场景的行为 | 未覆盖路径的行为 | +| CI 结果与日志 | 构建、检查和已运行用例的结果 | 所有环境和业务风险 | +| API、浏览器或人工观察 | 特定环境下的真实交互结果 | 未观察到的条件组合 | +| 已知边界 | 仍需确认的依赖与缺口 | 已完成验证的替代结论 | + +
+ + + +--- + +# 用证据地图连接结论 + +
+ +
需求与规则
+-> +
diff 与关键代码
+-> +
测试与 CI
+-> +
技术结论
+ +
+ +
+
每个结论都能回指来源
+
每项证据都有适用边界
+
缺口不能被推断补全
+
+ + + +--- + +# 技术结论要和证据强度匹配 + +
+ +| 信息类别 | 可以形成的结论 | 常见误区 | +|---|---|---| +| 代码阅读 | 已定位到调用入口、状态写入点或权限判断 | 把源码推断当作线上运行结论 | +| 实际变更 | 本次修改了哪些文件、条件和分支 | 用“优化”“完善”等词掩盖改动范围 | +| 自动化验证 | 已覆盖某个方法、接口或用例 | 用局部测试替代全链路验证 | +| 待确认事项 | 业务规则、外部调用、环境差异仍需核对 | 用“应该没有影响”替代事实 | + +
+ +
+证据能支持多大的结论,就只写多大的结论。 +
+ + + +--- + +# 证据丢失时,结论也会失效 + +
+ +| 缺失的证据 | 常见后果 | 正确处理 | +|---|---|---| +| 原始 CI 日志 | 无法判断失败来自代码还是环境 | 保留失败阶段、原始错误与关联链接 | +| 已执行命令与结果 | “已测试”无法复核 | 记录命令、环境和结果摘要 | +| 需求或规则依据 | 不清楚为什么改、是否越界 | 关联明确的约束与非目标 | +| diff 与关键位置 | 无法核对实际影响范围 | 说明模块、入口和关键条件 | +| 未覆盖边界 | 被误解为全量验证 | 明确写出待确认项和原因 | + +
+ +
+没有证据的“已完成”,在工程上等同于不可审查的结论。 +
+ + + +--- + +# 一份可审查的改动说明 + +
+ +```md +## 改动目标 +解决什么业务问题或风险,并关联需求或规则依据。 + +## 实现范围 +涉及的模块、入口和关键判断;明确未修改的相邻范围。 + +## 验证证据 +已执行的测试、检查项、CI 或实际观察,以及对应结果。 + +## 已知边界 +尚未覆盖的场景、依赖的前提和需要业务确认的事项。 +``` + +
+ +
+
避免:功能已完成、已充分测试
+
写清:已覆盖范围与未覆盖范围
+
+ + + +--- + +# 让 AI 产出成为工程知识 + +
+ +| 只保留对话 | 留下可追溯说明 | +|---|---| +| 结论散落在窗口和临时文件里 | 关键结论随代码或 PR 一起保存 | +| 难以判断 AI 建议是否被采纳 | 清楚说明人工选择了什么、舍弃了什么 | +| 后续排查需要重新理解上下文 | 可直接定位目标、范围、证据和边界 | +| 相同问题容易重复分析 | 可复用判断方式,但重新核对项目事实 | + +
+ +
+AI 的价值不止是更快产出代码,也包括留下经得起复核的技术结论。 +
+ + diff --git a/slidev/pages/day6/m7-maintainability-review.md b/slidev/pages/day6/m7-maintainability-review.md new file mode 100644 index 0000000..8bf5c0a --- /dev/null +++ b/slidev/pages/day6/m7-maintainability-review.md @@ -0,0 +1,179 @@ +# 模块 7:AI 改动的可演进性审查 + +
+ +业务行为通过验证,只说明本次任务得到了正确结果。 + +可演进性关心的是下一次规则变化时,能否找到唯一入口、控制影响范围、替换依赖并准确定位回归。 + +
+ +
+
修改入口
+
单一权威
+
依赖隔离
+
回归定位
+
+ + + +--- + +# 四类会放大后续修改成本的风险 + +
+ +| 风险 | 典型表现 | 更好的处理 | +|---|---|---| +| 修改入口分散 | 一个业务变化要在多个页面、接口或服务中同步修改 | 明确唯一的规则入口和调用边界 | +| 平行规则 | 同一校验在多个位置各自维护一份 | 让关键规则只有一个权威实现 | +| 隐藏依赖 | 逻辑依赖环境、全局状态或外部服务却不可见 | 把依赖显式注入、记录或隔离 | +| 回归难定位 | 失败只能靠全项目搜索和猜测 | 保留可观察结果、场景夹具与最小影响范围 | + +
+ + + +--- + +# 从“本次通过”到“下次可改” + +
+ +| 本次交付已确认 | 可演进性还要确认 | +|---|---| +| 当前行为符合预期 | 下一次规则变化从哪里修改最清楚 | +| 本次 diff 已通过构建与测试 | 影响范围能否被调用关系和夹具限制 | +| 新逻辑放进了可运行的位置 | 关键规则是否有唯一权威位置 | +| 用例覆盖当前结果 | 失败时能否回到具体场景和依赖定位 | + +
+ +
+可演进的改动,不是为未来过度设计,而是不给下一次变化留下多份解释。 +
+ + + +--- + +# 可演进性审查的四个问题 + +
+
规则变化时,第一处应修改哪里?
+
关键约束是否只有一个权威实现?
+
外部依赖能否替换、观察或明确失败?
+
回归能否缩小到具体场景、diff 与依赖?
+
+ +
+审查的目标,是让 AI 生成的改动成为下一次可靠修改的起点。 +
+ + + +--- + +# 公务用车案例:规则入口与状态写入相邻 + +
+ +| 已读取的实现事实 | 对后续修改的意义 | +|---|---| +| `OfficialVehicleDispatchServiceImpl.cancel` 首行使用 `Assert.hasText` 校验取消原因 | 取消原因规则有明确的后端最终保护入口 | +| 同一方法随后读取派车单、校验 `DISPATCHED` 状态、写入取消状态与原因,并释放车辆 | 规则与其保护的状态写入相邻,影响范围容易阅读 | +| `OfficialVehicleDispatchServiceImplTest` 直接传入全空白原因并断言异常 | 规则变化可以从同一测试文件开始定位回归 | + +
+ +
+下一次调整“取消原因”规则时,应先从 Service 与这项聚焦测试开始;不应在页面、Controller 和 Service 中维护多份独立规则。 +
+ + + +--- + +# 案例结论:已获得的可维护性证据 + +
+ +
+ +
已经由源码与测试支持
+ +| 证据 | 可形成的结论 | +|---|---| +| 本次训练分支仅改动 Service 参数保护和对应测试 | 改动范围聚焦,未混入页面、SQL 或状态协议调整 | +| 空白原因在读取派车单与写状态前被拒绝 | 无效输入不会进入后续状态变更路径 | +| 定向 Maven 测试记录为成功 | 该 Service 场景具备可重复的回归起点 | + +
+ +
+ +
尚不能由现有材料证明
+ +| 缺少的证据 | 不能写成的结论 | +|---|---| +| 取消接口的 HTTP / 统一异常验证 | 前端会收到何种错误码和提示 | +| 非空正常取消的集成验证 | 派车、车辆状态与审计已完整回归 | +| 浏览器与数据库观察记录 | 页面交互和持久化链路已通过 | + +
+ +
+ +
+可维护性审查也要按证据说话:明确规则入口与回归起点,不把局部验证扩大成全链路结论。 +
+ + + +--- + +# 何时需要重构,何时保持聚焦? + +
+ +| 观察到的证据 | 适合的处理 | 不应做的事 | +|---|---|---| +| 当前只新增一条取消原因非空规则,且规则入口就在状态变更方法中 | 保持 Service 中的最小改动,并维护同一聚焦测试 | 为单一校验新建没有复用对象的抽象层 | +| 后续出现多个取消入口且重复表达同一规则 | 先核对全部调用方、状态效果与测试,再评估抽取唯一规则入口 | 未确认重复语义就机械合并代码 | +| 发现外部调用方、异常协议或审计要求会影响规则 | 补齐接口与集成证据,再调整边界 | 用猜测替代兼容性和回归验证 | + +
+ +
+可演进性不是预先堆叠抽象,而是让每次重构都有明确的重复、耦合或回归证据。 +
+ + diff --git a/slidev/pages/day6/m8-asset-change-validation.md b/slidev/pages/day6/m8-asset-change-validation.md new file mode 100644 index 0000000..e85fe83 --- /dev/null +++ b/slidev/pages/day6/m8-asset-change-validation.md @@ -0,0 +1,195 @@ +# 模块 8:资产变更也要走工程验证 + +
+ +代码变更会进入 Review、测试和 CI;会影响 Agent 行为的资产变更也应接受同样严肃的验证。 + +Rule、任务上下文契约、Skill、自定义 Agent 和 MCP 契约不是静态文档,而是研发过程中的可执行配置。 + +
+ +
+
Rule
+
上下文契约
+
Skill
+
Agent
+
MCP
+
+ + + +--- + +# 为什么资产变更会改变行为? + +
+ +| 改动位置 | 直接影响 | 需要防范的风险 | +|---|---|---| +| Rule 的必须与禁止项 | 任务允许做什么、何时停止 | 放宽关键约束或误伤正常任务 | +| 任务上下文契约的输入与输出 Schema | 模型可依据什么、结果如何被消费 | 事实缺失、下游解析失效 | +| Skill 的步骤与引用 | 判断顺序、加载的资产和交接结果 | 漏掉验证步骤或引用旧资产 | +| Agent 的指令与工具 | 角色职责、可调用能力和权限 | 越权、职责重叠或不可控输出 | +| MCP 的能力契约 | 外部读写范围与失败语义 | 把高风险调用当成只读查询 | + +
+ +
+审查资产 diff 的重点,是行为变化,而不是措辞是否顺畅。 +
+ + + +--- + +# 资产变更审查的四个问题 + +
+
输入、输出、停止条件或权限改变了吗?
+
名称、路径、引用和元数据仍能正确解析吗?
+
实际行为变化与本次变更目的一致吗?
+
完整、缺失、冲突和越界输入都重新验证了吗?
+
+ +
+四个问题覆盖语义、引用、变更意图与边界验证。 +
+ + + +--- + +# 用测试夹具做资产回归 + +
+ +| 夹具类型 | 输入特征 | 预期行为 | +|---|---|---| +| 完整输入 | 事实、规则和验证材料齐全 | 输出结构完整,结论能关联证据 | +| 缺失输入 | 缺少关键代码、日志或规则 | 明确列出缺口并停止结论 | +| 冲突输入 | 需求、规则和实现信息不一致 | 标识冲突,不擅自选择一方 | +| 越界输入 | 请求超出定义的项目或权限范围 | 拒绝越界操作,解释允许范围 | + +
+ +
+资产回归验证边界是否正确,不要求模型每次生成完全相同的文字。 +
+ + + +--- + +# 修改前后都要检查引用完整性 + +
+ +| 检查对象 | 典型问题 | 验证方式 | +|---|---|---| +| Skill 元数据 | 名称、触发描述、入口路径失效 | 读取目录与发现配置 | +| Rule 与任务上下文契约引用 | 指向旧路径、旧字段或旧版本 | 搜索引用并运行代表性任务 | +| Agent 调用 Skill | 角色名或交接 Schema 改变 | 检查调用参数与结构化输出 | +| MCP 工具契约 | 工具名、读写标记、错误语义不一致 | 契约校对与权限夹具 | +| 下游交付模板 | 新输出字段未被消费或旧字段仍被依赖 | 对照输入输出 Schema | + +
+ + + +--- + +# 三类不能直接合入的资产变更 + +
+ +| 信号 | 风险 | 需要补齐的内容 | +|---|---|---| +| 新增工具权限,却没有用途和停止边界 | Agent 可能越权调用外部能力 | 权限说明、允许场景和失败降级 | +| 修改必填输入或输出结构,却没有代表性夹具 | 行为变化无法被回归判断 | 覆盖正常、缺失与冲突材料的验证记录 | +| 更新规则或任务上下文契约,却没有代表性夹具 | 无法判断是修复还是引入新误用 | 完整、缺失、冲突和越界输入验证 | + +
+ +
+没有行为说明、引用检查和验证证据的资产 diff,不具备合入条件。 +
+ + + +--- + +# 资产变更的最小交付说明 + +
+ +```md +## 变更目的 +解决的重复问题、误用风险或项目事实变化。 + +## 行为变化 +输入、输出、约束、工具权限、停止条件中发生了什么变化。 + +## 变更范围与引用结果 +受影响的文件、调用方、模板或配置,以及名称、路径和 Schema 的检查结果。 + +## 验证证据 +已运行的夹具、引用检查和可复核结果;未覆盖项如实列出。 +``` + +
+ + + +--- + +# 资产也纳入工程质量闭环 + +
+ +
识别行为变化
+-> +
检查引用与解析结果
+-> +
验证夹具与权限边界
+-> +
留下可追溯结论
+ +
+ +
+代码和 AI 工程资产都需要以事实、边界和验证作为进入共享使用的依据。 +
+ + diff --git a/slidev/pages/day6/m8-final-demo.md b/slidev/pages/day6/m8-final-demo.md new file mode 100644 index 0000000..9661ab5 --- /dev/null +++ b/slidev/pages/day6/m8-final-demo.md @@ -0,0 +1,83 @@ +# 模块 9:AI 编程的工程底线 + +
+ +AI 让研发更快,但不改变软件工程的基本要求。 + +范围、规则、验证、协作和可维护性,决定一段 AI 生成代码能否真正进入项目。 + +
+ +
+
理解范围
+
保护规则
+
验证行为
+
说明结论
+
保持可维护
+
+ + + +--- + +# AI 参与研发时,什么没有改变? + +
+ +| AI 可以改变 | 工程原则不变 | +|---|---| +| 更快搜索代码和生成候选方案 | 先理解现有系统,再决定改动范围 | +| 更快补齐实现和测试草稿 | 业务规则仍要在正确边界保护 | +| 更快解释日志和定位假设 | 验证证据必须与结论强度匹配 | +| 更快生成说明和文档初稿 | 人工仍对合入与发布结论负责 | + +
+ +
+工具能力会持续演进,工程判断的价值反而更高。 +
+ + + +--- + +# 高质量 AI 编程的共同语言 + +
+
+
事实优先
+
区分已读到、已验证和仍待确认的信息。
+
+
+
最小改动
+
以明确目标约束改动范围,避免无关重写。
+
+
+
验证闭环
+
让测试、日志和人工检查分别支持相应结论。
+
+
+
可追溯说明
+
让后续维护者理解目标、范围、证据和边界。
+
+
+ + diff --git a/slidev/slides.md b/slidev/slides.md index 554e5b7..d3e9dd7 100644 --- a/slidev/slides.md +++ b/slidev/slides.md @@ -162,3 +162,115 @@ src: ./pages/day4/m6-dashboard-metrics.md --- src: ./pages/day4/m7-review-delivery.md --- + +--- +layout: new-section +--- + +# 第 5 天:可沉淀的 AI 工程资产 + +真实案例 · 规则 · 任务上下文契约 · Skill / Agent / MCP + +--- +src: ./pages/day5/m1-project-workshop.md +--- + +--- +src: ./pages/day5/m2-project-knowledge.md +--- + +--- +src: ./pages/day5/m3-task-delegation.md +--- + +--- +src: ./pages/day5/m4-refactor-safety.md +--- + +--- +src: ./pages/day5/m5-testing-safety-net.md +--- + +--- +src: ./pages/day5/m6-asset-skill-writing.md +--- + +--- +src: ./pages/day5/m7-delivery-review.md +--- + +--- +src: ./pages/day5/m8-custom-agent-asset.md +--- + +--- +src: ./pages/day5/m9-agent-boundaries.md +--- + +--- +src: ./pages/day5/m10-agent-skill-composition.md +--- + +--- +src: ./pages/day5/m11-asset-library-delivery.md +--- + +--- +src: ./pages/day5/m12-mcp-asset-governance.md +--- + +--- +src: ./pages/day5/m13-historical-code-governance.md +--- + +--- +layout: new-section +--- + +# 第 6 天:工程协作与质量闭环 + +PR · Git 协作 · CI 诊断 · 证据链 · 资产变更验证 + +--- +src: ./pages/day6/m1-team-flow.md +--- + +--- +src: ./pages/day6/m2-pr-template-ai-record.md +--- + +--- +src: ./pages/day6/m3-ai-code-review.md +--- + +--- +src: ./pages/day6/m4-ci-failure-loop.md +--- + +--- +src: ./pages/day6/m5-asset-governance.md +--- + +--- +src: ./pages/day6/m6-onboarding-rollout.md +--- + +--- +src: ./pages/day6/m7-maintainability-review.md +--- + +--- +src: ./pages/day6/m8-asset-change-validation.md +--- + +--- +src: ./pages/day6/m8-final-demo.md +--- + +--- +src: ./pages/day6/m10-ai-trends.md +--- + +--- +src: ./pages/day6/m11-six-day-retrospective.md +--- diff --git a/第 4 天:JeecgBoot 工单复杂业务开发:权限、审计、异常态与看板.md b/第 4 天:JeecgBoot 工单复杂业务开发:权限、审计、异常态与看板.md index 195db96..d20f454 100644 --- a/第 4 天:JeecgBoot 工单复杂业务开发:权限、审计、异常态与看板.md +++ b/第 4 天:JeecgBoot 工单复杂业务开发:权限、审计、异常态与看板.md @@ -387,6 +387,59 @@ AI 可以帮助我们补漏,比如提醒我们“导出”“查看审计记 --- +## Slide 1-5:可直接复制到 rules:权限安全 + +### PPT 页面内容 + +```Plain Text +权限安全 rules: + +敏感操作必须四层校验 +权限字符必须有来源或标记为建议 +数据权限不得被查询条件替代 + +每条 rule 都要写清: +适用场景、必须、禁止、验收方式 +``` + +### 讲师逐字稿 + +这里把权限矩阵进一步沉淀成 rules。 + +rules 不是写给人看的口号,而是写给 AI 和团队共同执行的约束。 + +权限相关至少要有三条硬规则。 + +第一,敏感操作必须四层校验:菜单、按钮、接口、数据范围都要覆盖。 + +第二,权限字符必须有来源。如果是新建议,要明确标记为建议,不能让 AI 凭空编一个权限字符就当成事实。 + +第三,数据权限不能被前端查询条件替代。后端必须校验当前用户的数据范围。 + +后面权限方案、代码修改和 Review 都要回到这些 rules。 + +### 课堂产出 + +```md +## 敏感操作必须四层校验 +- 级别:硬约束 +- 适用场景:新增、编辑、删除、指派、状态流转、查看审计、查看看板。 +- 必须:同时覆盖页面/菜单权限、按钮权限、后端接口权限、数据范围权限。 +- 禁止:只做页面或按钮控制。 +- 验收方式:授权角色能完成;未授权角色前端不可见且直接请求接口失败;越权数据访问失败。 + +## 权限字符必须有来源或标记为建议 +- 必须:来自现有菜单、权限配置、前端权限指令、后端鉴权注解或团队规范。 +- 禁止:凭空编造权限字符并当成事实。 +- 验收方式:权限字符在前端、菜单配置、后端接口之间一致。 + +## 数据权限不得被查询条件替代 +- 必须:后端统一校验当前用户可访问的数据范围。 +- 禁止:只依赖前端传入 userId、deptId、tenantId 或筛选条件。 +``` + +--- + # 模块 2:权限控制方案生成 --- @@ -661,6 +714,49 @@ RBAC 权限矩阵 --- +## Slide 2-7:rules 要落到这些位置 + +### PPT 页面内容 + +```Plain Text +rule 不是单独文件 + +要落到: +菜单配置 +按钮权限 +Controller +Service 查询 +审计展示 +验收记录 +``` + +### 讲师逐字稿 + +很多团队写了 rules,但代码生成时没有真正用上。 + +第四天要强调:让 TRAE 改权限前,必须同时给它当前项目 rules、权限矩阵、权限控制方案和实际文件路径。 + +比如“四层校验”这条 rule,就要落到菜单配置、按钮权限、Controller、Service 查询和验收记录。 + +“权限字符有来源”这条 rule,要落到菜单表、路由、按钮指令和后端注解。 + +“数据权限不得被查询条件替代”这条 rule,要落到列表、详情、导出和看板查询。 + +这样 rule 才不是口号,而是代码修改和 Review 的检查依据。 + +### 课堂产出 + +```md +| rule 条款 | 代码落点 | 课堂验收 | +|---|---|---| +| 四层校验 | 菜单配置、按钮权限、Controller、Service 查询 | 无权限账号不可见且接口失败 | +| 权限字符有来源 | 菜单表、路由、按钮指令、后端注解 | 同一字符贯穿前后端 | +| 数据权限后端校验 | 列表、详情、导出、看板查询 | 改请求参数不能越权 | +| 敏感字段最小返回 | 列表接口、详情接口、审计展示 | 不暴露不必要 old/new value | +``` + +--- + # 模块 3:工单状态流转规则实现 --- @@ -995,6 +1091,57 @@ POST /workOrder/transition --- +## Slide 3-8:可直接复制到 rules:状态流转 + +### PPT 页面内容 + +```Plain Text +状态流转 rules: + +复杂业务状态必须先建模 +非法状态流转必须拒绝且状态不变 + +验证重点: +合法路径成功 +非法路径失败 +状态不被错误修改 +失败不写成功审计 +``` + +### 讲师逐字稿 + +状态流转表不是一次性课堂表格。 + +它应该沉淀成团队 rules,后续遇到审批、发布、上下架、支付、归档等状态型业务都可以复用。 + +第一条 rule 是复杂业务状态必须先建模。 + +也就是先产出状态流转表,再进入代码实现,不能直接在代码里堆 if else。 + +第二条 rule 是非法状态流转必须拒绝且状态不变。 + +这条非常关键。非法请求失败后,数据库状态不能被改坏,也不能写一条成功审计。 + +这类 rules 会直接进入后续 Review 和测试清单。 + +### 课堂产出 + +```md +## 复杂业务状态必须先建模 +- 级别:硬约束 +- 适用场景:工单指派、处理、验证、驳回、关闭、重开。 +- 必须:先产出状态流转表,列出当前状态、动作、目标状态、允许角色、前置条件、后置动作和验收方式。 +- 禁止:直接在代码中堆状态判断而没有状态表。 +- 验收方式:每个状态动作都有合法路径和非法路径验证。 + +## 非法状态流转必须拒绝且状态不变 +- 必须:集中校验非法流转;失败时返回明确错误,业务状态不变,不写成功审计。 +- 禁止:仅靠前端禁用按钮防止非法流转。 +- 验收方式:构造非法状态动作,接口失败且数据库状态不变。 +``` + +--- + # 模块 4:审计日志设计 --- @@ -1267,6 +1414,57 @@ SOP 要写清: --- +## Slide 4-7:可直接复制到 rules:审计日志 + +### PPT 页面内容 + +```Plain Text +审计日志 rules: + +关键业务操作必须写审计 +审计和业务写入必须一致 + +不能只写: +“记录日志” + +必须说明: +字段、时机、事务、失败行为、验收方式 +``` + +### 讲师逐字稿 + +审计 rules 要比“记录日志”具体得多。 + +关键业务操作必须写审计,适用于创建、编辑关键字段、指派、状态流转、驳回、关闭、删除、导出、权限变更。 + +审计字段要包含操作人、动作、目标对象、修改前、修改后、来源、请求 ID 或项目里的等价字段。 + +第二条 rule 是审计和业务写入必须一致。 + +如果状态更新成功但审计丢了,后续无法追溯。 + +如果审计写成功但业务失败,也会产生错误记录。 + +所以必须说明审计写入时机、事务关系、失败处理和验证方式。 + +### 课堂产出 + +```md +## 关键业务操作必须写审计 +- 级别:硬约束 +- 适用场景:创建、编辑关键字段、指派、状态流转、驳回、关闭、删除、导出、权限变更。 +- 必须:记录 actor、action、target_id、old_value、new_value、source、request_id、created_at 或项目等价字段。 +- 禁止:只记录普通日志而没有业务审计;操作失败后写成功审计。 +- 验收方式:执行关键操作后可查询审计记录,字段与实际变更一致。 + +## 审计和业务写入必须一致 +- 必须:明确审计写入时机、事务关系、失败处理和重试策略。 +- 禁止:业务成功审计丢失,或审计成功但业务失败。 +- 验收方式:模拟业务失败和审计失败,结果符合一致性策略。 +``` + +--- + # 模块 5:异常态与防御性逻辑 --- @@ -1535,6 +1733,59 @@ Service 要保证失败时状态不变。 --- +## Slide 5-7:可直接复制到 rules:异常态测试 + +### PPT 页面内容 + +```Plain Text +测试 rules: + +核心逻辑必须有测试或豁免说明 +权限必须做正反角色验证 +状态流转必须覆盖合法和非法路径 + +禁止: +只用管理员账号验证 +只说“已验证” +只跑正常主链路 +``` + +### 讲师逐字稿 + +异常态模块最后要沉淀成测试 rules。 + +第一,核心逻辑必须有测试或豁免说明。 + +如果没法写自动化测试,也要写清手工验收步骤、证据和剩余风险,不能只说“已验证”。 + +第二,权限必须做正反角色验证。 + +至少一个授权角色、一个未授权角色;如果涉及数据范围,还要验证越权数据。 + +第三,状态流转必须覆盖合法和非法路径。 + +不能只测从创建到关闭的成功链路,也要构造 closed 重新处理、pending_verify 驳回无原因这类失败路径。 + +### 课堂产出 + +```md +## 核心逻辑必须有测试或豁免说明 +- 级别:待确认约束 +- 适用场景:权限校验、状态流转、审计、看板指标、异常态修复。 +- 必须:优先补自动化测试;无法测试时说明原因、风险和手工验收方式。 +- 禁止:只说“已验证”但没有命令、样例、截图、接口响应或手工步骤。 + +## 权限必须做正反角色验证 +- 必须:至少验证一个授权角色和一个未授权角色;涉及数据范围时验证越权数据。 +- 禁止:只用管理员账号验证。 + +## 状态流转必须覆盖合法和非法路径 +- 必须:每个关键动作至少覆盖一条合法路径和一条非法路径。 +- 禁止:只测试从初始状态到成功终态的主链路。 +``` + +--- + # 模块 6:研发效能看板指标开发 --- @@ -1801,6 +2052,52 @@ trend 返回每日新增和每日关闭趋势。 --- +## Slide 6-7:可直接复制到 rules:看板指标 + +### PPT 页面内容 + +```Plain Text +看板指标 rule: + +看板指标必须手工抽样校验 + +必须有: +含义、公式、过滤条件 +时间字段、数据范围、样例数据 + +禁止: +只验证接口 200 +只验证页面有图表 +``` + +### 讲师逐字稿 + +看板最容易出现“页面很漂亮,但数字说不清”的问题。 + +所以看板指标也要写成 rules。 + +每个关键指标必须有含义、公式、过滤条件、时间字段、数据范围,并且用样例数据手工校验。 + +比如平均关闭周期,至少要拿三条样例工单,人工算一次,再和接口返回对上。 + +接口返回 200 不代表指标正确,页面有图表也不代表指标可信。 + +课堂交付时,看板必须同时交付指标字典、接口契约和至少一组手工核对样例。 + +### 课堂产出 + +```md +## 看板指标必须手工抽样校验 +- 级别:硬约束 +- 适用场景:统计卡片、趋势图、分布图、排行、效能指标。 +- 必须:每个关键指标有含义、公式、过滤条件、时间字段、数据范围,并用样例数据手工校验。 +- 禁止:只验证接口返回 200 或页面有图表。 +- 来源证据:指标字典、接口契约、样例数据、数据库查询或列表数据。 +- 验收方式:至少一组样例数据的手工计算结果与接口返回一致。 +``` + +--- + # 模块 7:阶段代码审查与修复 --- @@ -1874,6 +2171,94 @@ AI Review 不能只给 diff。 --- +## Slide 7-2A:第 4 天要用的三个 Skill + +### PPT 页面内容 + +```Plain Text +三个 Skill: + +design-complex-business-module +开工前设计控制面 + +review-business-delivery +阶段 Review 检查风险 + +track-review-fix-loop +修复后闭环并沉淀资产 +``` + +### 讲师逐字稿 + +前三天已经讲过 Skill 的概念。 + +第四天要让大家看到具体怎么用。 + +第一个 Skill 是 `design-complex-business-module`,用在开始写代码前,先设计权限、状态、审计、异常态和看板指标。 + +第二个 Skill 是 `review-business-delivery`,用在阶段 Review 时,检查权限缺口、非法状态流转、审计缺失、异常处理薄弱、看板口径不清和测试缺失。 + +第三个 Skill 是 `track-review-fix-loop`,用在修复 Review 问题后,把问题状态、修改文件、验证证据和资产沉淀记录下来。 + +Skill 负责流程和质量门禁;rule 负责长期约束;template 和 checklist 负责稳定输出。 + +### 课堂产出 + +```md +| Skill | 什么时候用 | 输入材料 | 输出产物 | +|---|---|---|---| +| design-complex-business-module | 开始写代码前 | 需求、项目规则、现有角色/API/表结构 | RBAC、权限方案、状态表、审计 SOP、异常清单、指标字典 | +| review-business-delivery | 阶段 Review 时 | diff、业务产物、测试结果、验收记录 | 问题清单、证据缺口、待确认项 | +| track-review-fix-loop | 修 Review 后 | Review 报告、修改文件、验证证据 | 修复记录、未闭环事项、资产更新建议 | +``` + +--- + +## Slide 7-2B:design-complex-business-module 怎么用 + +### PPT 页面内容 + +```Plain Text +用于开工前: + +先设计控制面 +再进入代码实现 + +输出: +RBAC、权限方案、状态表、非法流转、 +审计 SOP、异常清单、指标字典、接口验收记录 +``` + +### 讲师逐字稿 + +`design-complex-business-module` 的核心原则是:先设计控制面,再设计代码。 + +它不是让 AI 直接写实现,而是先把复杂业务应该有的控制资产产出。 + +课堂里可以把工单需求、项目规则、现有角色、菜单权限、接口、数据表和相似模块作为输入。 + +如果缺少源码或配置证据,输出必须标记为“建议”或“待确认”,不能让 AI 把推断当成事实。 + +### 推荐 Prompt + +```md +请使用 design-complex-business-module,基于 JeecgBoot 工单模块设计复杂业务控制面。 + +输入材料: +1. 需求范围:工单权限、状态流转、审计、异常态、看板。 +2. 项目规则:security-permission、state-audit、testing、delivery。 +3. 现有证据:角色、菜单权限、接口、数据表、相似模块。 + +输出必须包含: +RBAC 权限矩阵、权限控制方案、状态流转表、非法流转清单、 +审计日志 SOP、异常态清单、看板指标字典、看板接口验收记录。 + +要求: +没有源码或配置证据的内容标记为“建议”或“待确认”。 +``` + +--- + ## Slide 7-3:阶段 Review 报告模板 ### PPT 页面内容 @@ -1919,6 +2304,59 @@ Review 输出必须结构化。 --- +## Slide 7-3A:review-business-delivery 怎么用 + +### PPT 页面内容 + +```Plain Text +用于阶段 Review: + +输入不能只有 diff + +必须同时给: +业务产物 +测试结果 +验收记录 +项目 rules + +输出问题优先,按 P0/P1/P2/P3 排序 +``` + +### 讲师逐字稿 + +`review-business-delivery` 的作用是防止 AI Review 只做代码风格点评。 + +第四天 Review 的对象是业务交付,不只是代码 diff。 + +所以输入材料必须包括当天 diff、RBAC 矩阵、权限控制方案、状态流转表、非法流转清单、审计 SOP、异常态清单、看板指标字典和测试结果。 + +输出要问题优先,按 P0/P1/P2/P3 排序。 + +每个问题必须有证据、风险、修复建议和验收方式。 + +如果缺少证据,就列到“证据缺口”,不能让 AI 猜。 + +### 推荐 Prompt + +```md +请使用 review-business-delivery 对第四天工单模块交付做阶段 Review。 + +输入材料: +- 当天 diff 或修改文件清单 +- RBAC 权限矩阵和权限控制方案 +- 状态流转表和非法流转清单 +- 审计日志 SOP 和异常态清单 +- 看板指标字典和接口验收记录 +- 测试命令、接口响应、浏览器验证或手工验收记录 + +输出格式: +先列问题清单,按 P0/P1/P2/P3 排序; +每个问题必须有证据、风险、修复建议和验收方式; +缺少证据时单独列“证据缺口”,不要猜测。 +``` + +--- + ## Slide 7-4:修复闭环 ### PPT 页面内容 @@ -1994,6 +2432,58 @@ Review 问题 --- +## Slide 7-5A:track-review-fix-loop 怎么用 + +### PPT 页面内容 + +```Plain Text +用于 Review 修复后: + +每个问题只能标记为: +成立-已修复 +成立-暂不修复 +不成立 +需确认 +未闭环 + +没有验证证据不能关闭 +``` + +### 讲师逐字稿 + +`track-review-fix-loop` 负责把 Review 问题真正闭环。 + +它要求每个问题不能只写“已修复”。 + +必须判断问题是否成立,列出修改文件或产物,提供验证证据,说明剩余风险,并判断是否要沉淀成资产。 + +如果没有验证证据,就不能标记为完成,只能标记为未闭环。 + +如果问题依赖业务决策,就标记为需确认。 + +如果问题不成立,也要保留原因和证据,不要直接删除。 + +### 推荐 Prompt + +```md +请使用 track-review-fix-loop,把阶段 Review 问题转成修复闭环记录。 + +输入材料: +- 带编号的 Review 报告 +- 修改文件或产物 +- 测试输出、接口响应、浏览器截图或手工验收步骤 +- 现有 rules、checklists、prompts、examples、skills 目录 + +每个问题状态只能是: +成立-已修复 / 成立-暂不修复 / 不成立 / 需确认 / 未闭环 + +输出必须包含: +修复记录、未闭环事项、资产更新建议。 +资产类型从 rule、checklist、prompt、command、skill、example 中选择。 +``` + +--- + ## Slide 7-6:第四天完整实操链路 ### PPT 页面内容 @@ -2128,6 +2618,59 @@ Review 问题 --- +## Slide 8-2A:第 4 天资产包目录 + +### PPT 页面内容 + +```Plain Text +第 4 天资产包: + +rules +skills +templates +checklists +prompts +examples + +交付给团队的不是聊天记录 +而是可复用工程资产包 +``` + +### 讲师逐字稿 + +第四天最后要让学员看到:课堂产物不是聊天记录。 + +真正交付给团队的,是一套可安装、可复用、可审查的工程资产包。 + +rules 是长期约束,比如权限四层校验、状态先建模、审计一致性、看板手工抽样。 + +skills 是可触发流程,比如复杂业务设计、阶段 Review、修复闭环。 + +templates、checklists、prompts 和 examples 是 Skill 运行时会用到的材料。 + +这样团队下次做审批、报销、发布、合同、库存这些复杂业务模块时,就不需要重新从聊天记录里找经验。 + +### 课堂产出 + +```text +ai-dev-assets/ +├── rules/ +│ ├── security-permission.md +│ ├── state-audit.md +│ ├── testing.md +│ └── delivery.md +├── skills/ +│ ├── design-complex-business-module/ +│ ├── review-business-delivery/ +│ └── track-review-fix-loop/ +├── templates/jeecgboot/ +├── checklists/jeecgboot/ +├── prompts/jeecgboot/ +└── examples/jeecgboot/ +``` + +--- + ## Slide 8-3:交付物验收标准 ### PPT 页面内容 diff --git a/项目实操-影响分析提示词与TRAE返回结果.md b/项目实操-影响分析提示词与TRAE返回结果.md new file mode 100644 index 0000000..6758f28 --- /dev/null +++ b/项目实操-影响分析提示词与TRAE返回结果.md @@ -0,0 +1,133 @@ +# 项目实操:公务用车取消派车影响分析与实际验证记录 + +> 案例来自当前 JeecgBoot 项目已接入的“公务用车”模块,不代表客户提出过这一条具体业务需求。培训中设置的改造要求是:**取消已派车任务时,取消原因必须由后端校验为非空;前端必填不能替代后端业务保护。** + +## 可直接发送给 TRAE 的提示词 + +```text +请基于当前打开的 JeecgBoot 项目,且只基于你实际读取到的源码,完成一次变更影响分析;不要修改代码,不要猜测项目外逻辑。 + +训练改造目标:取消已派车任务时,取消原因必须为非空文本。前端表单已设为必填,但后端也必须拒绝空值和全空白值,避免直接调用接口绕过页面校验。 + +已知入口: +- 前端列表:jeecgboot-vue3/src/views/official/vehicle/dispatch/DispatchList.vue +- 前端弹窗与表单:jeecgboot-vue3/src/views/official/vehicle/dispatch/DispatchModal.vue、dispatch.data.ts +- 前端 API:jeecgboot-vue3/src/api/official/vehicle/dispatch.ts +- 后端接口:POST /official/vehicle/dispatch/cancel +- 后端 Controller:OfficialVehicleDispatchController.java +- 后端 Service:OfficialVehicleDispatchServiceImpl.java + +请再读取取消参数 VO、派车实体、现有测试、模块 pom 和必要的数据库初始化 SQL,并按以下结构输出: +1. 已读取的源码。 +2. 已确认事实,每条附文件和行号依据。 +3. 必须检查的文件或模块,以及为什么。 +4. 可能受影响的接口、数据、权限、状态、配置和测试。 +5. 本次明确不应修改的内容。 +6. 不超过 4 步的最小改动计划:修改目标、完成证据、验证方式、回退条件。 +7. 人工待确认事项。 + +要求: +- 事实与推测分开;无源码证据的内容标记为“待确认”。 +- 不要编造测试结果、全局异常语义、权限链路或项目外调用方。 +- 不要直接实施修改。 +``` + +## 本次实际核对记录 + +以下结论来自本地实际读取的源码、训练分支 diff 和实际 Maven 命令结果。没有执行过的数据库、接口和浏览器验证均明确标记为未执行。 + +### 已读取的源码 + +- `jeecgboot-vue3/src/views/official/vehicle/dispatch/DispatchList.vue` +- `jeecgboot-vue3/src/views/official/vehicle/dispatch/DispatchModal.vue` +- `jeecgboot-vue3/src/views/official/vehicle/dispatch/dispatch.data.ts` +- `jeecgboot-vue3/src/api/official/vehicle/dispatch.ts` +- `jeecg-boot/jeecg-boot-module/jeecg-module-officialvehicle/src/main/java/org/jeecg/modules/officialvehicle/controller/OfficialVehicleDispatchController.java` +- `jeecg-boot/jeecg-boot-module/jeecg-module-officialvehicle/src/main/java/org/jeecg/modules/officialvehicle/service/impl/OfficialVehicleDispatchServiceImpl.java` +- `jeecg-boot/jeecg-boot-module/jeecg-module-officialvehicle/src/main/java/org/jeecg/modules/officialvehicle/vo/OfficialVehicleCancelDispatchVo.java` +- `jeecg-boot/jeecg-boot-module/jeecg-module-officialvehicle/src/main/java/org/jeecg/modules/officialvehicle/entity/OfficialVehicleDispatch.java` +- `jeecg-boot/jeecg-boot-module/jeecg-module-officialvehicle/src/test/java/org/jeecg/modules/officialvehicle/controller/OfficialVehicleDispatchControllerTest.java` +- `jeecg-boot/jeecg-boot-module/jeecg-module-officialvehicle/pom.xml` +- `jeecg-boot/jeecg-boot-module/pom.xml` +- `jeecg-boot/db/officialvehicle/official_vehicle_mvp_mysql.sql` + +### 已确认事实 + +1. 公务用车为项目现有模块,而非 `demo` 示例:聚合模块 `jeecg-boot-module/pom.xml` 包含 `jeecg-module-officialvehicle`,数据库脚本同时提供菜单、权限和演示数据。 + - 依据:`jeecg-boot/jeecg-boot-module/pom.xml` 第 18 行;`db/officialvehicle/official_vehicle_mvp_mysql.sql` 第 134 至 218 行。 + +2. 前端仅当用户具有 `vehicle:dispatch:cancel` 权限、且派车状态为 `DISPATCHED` 时显示“取消派车”操作。 + - 依据:`DispatchList.vue` 第 26 至 30 行。 + +3. 取消弹窗的 `cancelReason` 已配置 `required: true`,提交时会调用前端 API 的 `cancelDispatch`。 + - 依据:`dispatch.data.ts` 第 72 至 75 行;`DispatchModal.vue` 第 28 至 36 行。 + +4. 前端 API 只是向 `POST /official/vehicle/dispatch/cancel` 提交 `dispatchId` 和 `cancelReason`,因此直接调用接口不经过页面必填校验。 + - 依据:`src/api/official/vehicle/dispatch.ts` 第 4 至 12 行。 + +5. 后端 Controller 有 `@AutoLog` 和 `@RequiresPermissions("vehicle:dispatch:cancel")`,但只将 VO 中两个字段转交给 Service,没有参数校验注解。 + - 依据:`OfficialVehicleDispatchController.java` 第 55 至 61 行;`OfficialVehicleCancelDispatchVo.java` 第 14 至 20 行。 + +6. 原有 Service 会校验派车任务存在且状态为 `DISPATCHED`,随后把状态改为 `CANCELLED`、写入 `cancelReason`,并把关联车辆状态改为 `AVAILABLE`;原实现没有非空或空白校验。 + - 依据:改造前 `OfficialVehicleDispatchServiceImpl.java` 第 84 至 96 行。 + +7. `cancel_reason` 在 `official_vehicle_dispatch` 表中允许为空,因此仅靠当前表结构不能保证取消原因留痕。 + - 依据:`official_vehicle_mvp_mysql.sql` 第 82 至 105 行。 + +8. 模块已有 JUnit 基座和 Controller 测试;原有取消测试只验证带“计划变更”原因时 Controller 转交 Service,未覆盖空白原因。 + - 依据:模块 `pom.xml` 的 Surefire 配置;`OfficialVehicleDispatchControllerTest.java` 第 48 至 59 行。 + +### 最小改动及实际完成证据 + +| 步骤 | 实际改动 | 完成证据 | 验证状态 | 回退条件 | +|---|---|---|---|---| +| 1 | 在 Service 的 `cancel` 首行加入 `Assert.hasText(cancelReason, "取消原因不能为空")` | 空值或全空白值在读取派车单、改变状态、释放车辆前被拒绝 | 已执行单元测试 | 若业务确认允许无原因取消,撤回该校验并重新定义留痕方式 | +| 2 | 新增 `OfficialVehicleDispatchServiceImplTest`,传入全空白原因并断言抛出 `IllegalArgumentException` | 测试直接调用 Service,不依赖前端表单 | 已执行 | 若异常约定需要调整,更新断言及统一异常验证 | +| 3 | 保持前端必填、Controller 权限/审计、派车状态流转和数据库表结构不变 | `main...training/require-cancel-reason` 仅含上述两个后端文件 | 已核对 diff | 若接口回归显示兼容问题,只回退提交 `d357de7` | + +### 本次明确不应修改 + +- `official_vehicle_dispatch` 的字段、索引和初始化 SQL。 +- 派车、取消派车后的车辆可用状态处理、申请状态流转和行程模块。 +- 已存在的 `vehicle:dispatch:cancel` 权限和 `@AutoLog` 审计注解。 +- 前端弹窗、路由、菜单和无关页面。前端已经有必填规则,本次不重复实现。 +- 未读取到源码依据的全局异常、数据权限、网关、审批或外部调用方逻辑。 + +### 可能受影响的内容 + +| 维度 | 结论 | 当前状态 | +|---|---|---| +| 接口 | 空值、`""` 或全空白 `cancelReason` 将不再被正常取消流程接受 | 已通过 Service 单测验证 | +| 数据 | 被拒绝的请求不会进入派车状态和车辆状态更新;表字段仍允许空值 | 代码顺序与单测已验证;数据库级验证未执行 | +| 权限与审计 | 原有权限和 `@AutoLog` 保持不变 | 已读取源码,未做运行环境验证 | +| 正常取消 | 非空原因仍应按原有流程取消并释放车辆 | 未做数据库/API 回归 | +| 统一响应 | `Assert` 的异常如何映射 HTTP/`Result` 取决于项目全局异常处理 | 待确认 | + +### 实际验证结果 + +训练分支:`training/require-cancel-reason` +提交:`d357de7 fix: require reason when cancelling dispatch` + +执行命令: + +```bash +mvn -pl jeecg-boot-module/jeecg-module-officialvehicle -am \ + -DskipTests=false \ + -Dtest=OfficialVehicleDispatchServiceImplTest \ + -Dsurefire.failIfNoSpecifiedTests=false test +``` + +实际结果:2026-08-06 再次执行完成,`BUILD SUCCESS`;`OfficialVehicleDispatchServiceImplTest` 共 1 项,`Failures: 0`、`Errors: 0`、`Skipped: 0`。 + +未执行且不能写成已通过: + +- 已派车数据提交非空取消原因后的数据库状态、车辆状态和审计记录。 +- 直接调用取消接口时空值/全空白值的 HTTP/`Result` 响应。 +- 浏览器中取消弹窗的必填提示和成功取消路径。 + +### 人工待确认事项 + +1. 取消原因是否仅要求非空,还是需要最小长度、枚举原因或特殊角色豁免。 +2. `Assert.hasText` 抛出的异常在当前服务中的错误码、HTTP 状态和前端提示是否符合统一约定。 +3. 正常取消后派车状态、车辆状态和审计记录的集成环境回归结果。 +4. 是否存在项目外系统直接调用取消接口,以及它们对空原因的历史依赖。