From 6baa4cd10e0fc338bebbb1dbb4837cd00f47025b Mon Sep 17 00:00:00 2001 From: smart Date: Fri, 3 Jul 2026 00:26:47 +0800 Subject: [PATCH] feat(slidev): update 3 4 --- .../ai-dev-assets/README.md | 3 +- .../ai-engineering-skills-delivery-utf8.zip | Bin 45142 -> 59302 bytes .../ai-engineering-skills-delivery/INSTALL.md | 12 +- .../ai-engineering-skills-delivery/README.md | 21 +- .../assets/RBAC权限矩阵.template.md | 31 +- .../assets/RBAC权限矩阵生成.prompt.md | 18 +- .../assets/审计日志SOP.template.md | 21 +- .../assets/审计日志检查清单.md | 5 +- .../assets/异常态检查清单.md | 3 +- .../assets/异常态清单.template.md | 24 +- .../assets/异常态补齐.prompt.md | 15 +- .../assets/权限上线前检查清单.md | 9 +- .../assets/权限控制方案.template.md | 17 +- .../assets/权限控制方案生成.prompt.md | 6 +- .../assets/状态流转检查清单.md | 3 +- .../assets/状态流转表.template.md | 31 +- .../assets/状态流转设计.prompt.md | 33 +- .../assets/看板指标字典.template.md | 19 +- .../assets/看板指标设计.prompt.md | 25 +- .../assets/看板接口验收.prompt.md | 6 +- .../assets/看板接口验收清单.md | 5 +- .../assets/看板接口验收记录.template.md | 15 +- .../generate-project-constraints/SKILL.md | 13 +- .../assets/项目约束验收清单.md | 2 +- .../skills/project-knowledge-builder/SKILL.md | 133 ++++++++ .../agents/openai.yaml | 4 + .../references/compatibility.md | 65 ++++ .../references/output-contract.md | 142 ++++++++ .../scripts/project_inventory.py | 313 ++++++++++++++++++ .../assets/业务交付验收清单.md | 59 ++++ .../assets/审计日志检查清单.md | 5 +- .../assets/异常态检查清单.md | 3 +- .../assets/权限上线前检查清单.md | 9 +- .../assets/状态流转检查清单.md | 3 +- .../assets/看板接口验收清单.md | 5 +- .../assets/阶段Review.prompt.md | 6 +- .../assets/阶段Review报告.template.md | 3 +- .../assets/验收清单.md | 71 ---- .../assets/修复记录.template.md | 5 +- .../assets/审计日志缺失反例.md | 21 +- .../assets/权限遗漏反例.md | 15 +- .../assets/看板指标口径不清反例.md | 15 +- .../assets/非法状态流转反例.md | 7 +- deliverables/客户AI工程Skill交付包/README.md | 12 +- .../客户AI工程Skill交付包/安装说明.md | 6 +- slidev/pages/day3/m3-commands-skills-mcp.md | 206 ++++++++---- slidev/pages/day4/m1-rbac-permission-model.md | 122 +++++-- 47 files changed, 1185 insertions(+), 382 deletions(-) create mode 100644 deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/SKILL.md create mode 100644 deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/agents/openai.yaml create mode 100644 deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/references/compatibility.md create mode 100644 deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/references/output-contract.md create mode 100644 deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/scripts/project_inventory.py create 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 diff --git a/cases/jeecgboot-work-order/ai-dev-assets/README.md b/cases/jeecgboot-work-order/ai-dev-assets/README.md index f58f756..f6dbf48 100644 --- a/cases/jeecgboot-work-order/ai-dev-assets/README.md +++ b/cases/jeecgboot-work-order/ai-dev-assets/README.md @@ -1,6 +1,5 @@ # JeecgBoot 工单可复用资产包 -这个目录里的文件不是大纲,而是课后可以直接复制到团队 `ai-dev-assets/` 的资产。 ## 使用方式 @@ -9,7 +8,7 @@ 3. 需要把流程封装成团队能力时,复制 `skills/`。 4. 需要课堂或项目交付表时,复制 `templates/` 和 `templates/jeecgboot/`。 5. 需要验收和上线检查时,复制 `checklists/` 和 `checklists/jeecgboot/`。 -6. 需要反例教学时,复制 `examples/` 和 `examples/jeecgboot/`。 + ## 资产清单 diff --git a/deliverables/ai-engineering-skills-delivery-utf8.zip b/deliverables/ai-engineering-skills-delivery-utf8.zip index aa02287911a085c9112484eb258106b886b9ce72..648057c7175653b1bb0658d16cd609209c4993f3 100644 GIT binary patch delta 38187 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 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 | | +## 使用说明 +将列名中的 `<角色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 index 84a9100..0ff18f8 100644 --- 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 @@ -1,18 +1,20 @@ # RBAC 权限矩阵生成 Prompt ```text -请基于 JeecgBoot 工单模块设计 RBAC 权限矩阵。 +请基于目标业务模块设计 RBAC 权限矩阵。 输入材料: -- 工单模块需求:<粘贴需求> -- 角色列表:管理员、工单负责人、研发处理人、测试验证人、只读观察者 -- 功能点:查看列表、查看详情、新建、编辑、指派、状态流转、关闭、删除、查看审计、查看看板、导出 -- 项目依据:<粘贴权限、路由、菜单相关路径> +- 模块名称:<填写> +- 业务对象:<填写,例如订单、客户、申请、任务> +- 需求说明或用户故事:<粘贴> +- 角色列表:<粘贴目标项目真实角色> +- 功能点:<粘贴页面、按钮、接口、数据动作> +- 项目依据:<粘贴权限、路由、菜单、接口、后端校验相关路径> 输出要求: -1. 先列事实依据:JeecgBoot 权限、菜单、动态路由相关路径。 +1. 先列事实依据:权限、菜单、动态路由、接口、后端校验相关路径。 2. 输出 RBAC 权限矩阵:角色 x 功能点,允许、拒绝、条件允许。 -3. 输出权限字符建议,必须标注为建议。 +3. 输出权限字符建议,并标注哪些是事实、哪些是建议。 4. 输出页面级、按钮级、接口级、数据范围四层控制。 5. 输出不确定点:哪些权限必须业务方确认。 6. 输出验收方式:如何证明权限确实生效。 @@ -21,5 +23,5 @@ - 不要直接改代码。 - 不要把前端按钮隐藏当作安全边界。 - 所有结论必须区分事实、推断、建议。 +- 不要把示例模块、示例角色或示例权限字符当成目标项目事实。 ``` - 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 index 876d692..bb815dc 100644 --- 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 @@ -1,18 +1,19 @@ -# 工单审计日志 SOP +# <模块名称> 审计日志 SOP ## 记录原则 -所有会改变工单关键业务状态、负责人、可见性或结论的动作都必须记录审计日志。 +所有会改变目标业务对象关键状态、负责人/处理人、可见性、金额、结论或对外结果的动作都必须记录审计日志。 ## 审计字段 | 字段 | 含义 | 示例 | | --- | --- | --- | -| ticket_id | 工单 ID | WO-001 | +| target_type | 业务对象类型 | order / customer / request | +| target_id | 业务对象 ID | 100001 | | actor_id | 操作人 ID | 1001 | | actor_name | 操作人名称 | zhangsan | -| action | 操作类型 | ASSIGN | -| field_name | 变更字段 | assignee | +| action | 操作类型 | ASSIGN / APPROVE / CLOSE | +| field_name | 变更字段 | status / owner_id / amount | | old_value | 旧值 | lisi | | new_value | 新值 | wangwu | | source | 来源 | web | @@ -23,12 +24,13 @@ | 动作 | 写入时机 | oldValue | newValue | 验收 | | --- | --- | --- | --- | --- | -| 创建工单 | 创建成功后 | 空 | 工单摘要 | 审计列表可见 | +| 创建记录 | 创建成功后 | 空 | 业务摘要 | 审计列表可见 | | 编辑字段 | 保存成功后 | 修改前字段 | 修改后字段 | 字段前后值正确 | -| 指派负责人 | 指派成功后 | 原负责人 | 新负责人 | 审计记录负责人变化 | +| 分配/转交 | 分配成功后 | 原处理人 | 新处理人 | 审计记录处理人变化 | | 状态流转 | 状态变更成功后 | 原状态 | 新状态 | 审计记录状态变化 | -| 评论新增 | 评论保存后 | 空 | 评论摘要 | 审计记录评论动作 | -| 关闭工单 | 关闭成功后 | 原状态 | 已关闭 | 审计记录关闭时间 | +| 结论/审核 | 结论保存成功后 | 原结论 | 新结论 | 审计记录结论变化 | +| 评论/备注新增 | 保存成功后 | 空 | 摘要或脱敏内容 | 审计记录评论动作 | +| 关闭/归档 | 操作成功后 | 原状态 | 目标终态 | 审计记录关闭或归档时间 | ## 验收方式 @@ -37,4 +39,3 @@ 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 index 0e271a4..1eebc8f 100644 --- 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 @@ -2,12 +2,11 @@ | 检查项 | 通过标准 | 结果 | | --- | --- | --- | -| 创建记录 | 创建工单后能查询审计记录 | | +| 创建记录 | 创建业务记录后能查询审计记录 | | | 字段变更 | 编辑关键字段记录 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 index e1b6496..d53299c 100644 --- 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 @@ -3,11 +3,10 @@ | 异常态 | 必须验证 | 结果 | | --- | --- | --- | | 空列表 | 页面显示空状态,不报错 | | -| 详情不存在 | 提示工单不存在,页面不崩溃 | | +| 详情不存在 | 提示记录不存在,页面不崩溃 | | | 权限不足 | 前端提示或隐藏,后端拒绝 | | | 非法状态 | 返回非法流转,状态不变 | | | 重复提交 | 只成功一次,只写一条有效审计 | | | 参数缺失 | 返回参数错误,业务数据不变 | | | 并发修改 | 拒绝覆盖或提示刷新 | | | 看板无数据 | 图表显示空态,不显示假数据 | | - 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 index 286eaff..f591b2e 100644 --- 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 @@ -1,13 +1,13 @@ -# 工单异常态清单 - -| 场景 | 触发条件 | 前端表现 | 后端响应 | 验收 | -| --- | --- | --- | --- | --- | -| 权限不足 | 无权限操作 | 按钮不可见或提示无权限 | 403 或业务拒绝 | 不完成操作 | -| 工单不存在 | 传入错误 ID | 显示不存在 | 404 或业务错误 | 页面不崩溃 | -| 非法状态流转 | closed -> processing | 提示非法流转 | 状态不变 | 数据状态不变 | -| 重复提交 | 连续点击提交 | 按钮 loading/禁用 | 只处理一次 | 只生成一条有效审计 | -| 参数缺失 | 驳回缺少原因 | 表单校验提示 | 参数错误 | 不改变状态 | -| 并发修改 | 数据版本过期 | 提示刷新 | 拒绝覆盖 | 旧数据不覆盖新数据 | -| 看板无数据 | 无匹配记录 | 空状态 | 返回空集合 | 页面不报错 | -| 审计日志为空 | 无审计记录 | 空状态 | 返回空集合 | 页面不报错 | +# <模块名称> 异常态清单 +| 场景 | 触发条件 | 前端表现 | 后端响应 | 验收 | 来源证据 | +| --- | --- | --- | --- | --- | --- | +| 权限不足 | 无权限操作敏感功能 | 按钮不可见或提示无权限 | 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 index 17bd793..3dca924 100644 --- 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 @@ -1,24 +1,31 @@ # 异常态补齐 Prompt ```text -请基于 JeecgBoot 工单模块,列出状态流转、指派、删除、查询详情、审计日志、看板相关的异常态和防御性逻辑。 +请基于目标业务模块,列出状态流转、分配/转交、删除/作废、查询详情、审计日志、统计看板相关的异常态和防御性逻辑。 + +输入材料: +- 模块名称:<填写> +- 业务对象:<填写> +- 角色与权限:<粘贴> +- 状态流转表:<粘贴> +- 接口契约或页面说明:<粘贴> 输出表格: 场景 | 触发条件 | 前端表现 | 后端响应 | 用户提示 | 测试点 | 验收 必须覆盖: - 权限不足 -- 工单不存在 +- 记录不存在 - 非法状态流转 - 重复提交 - 参数缺失 - 并发修改 -- 看板无数据 +- 统计无数据 - 审计日志为空 +- 下游服务失败 约束: - 先列异常态,不要直接改代码。 - 给出每个异常态的验收步骤。 - 标注事实、推断、建议和不确定点。 ``` - 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 index cacf893..e7bf421 100644 --- 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 @@ -2,11 +2,10 @@ | 检查项 | 通过标准 | 结果 | | --- | --- | --- | -| 菜单权限 | 工单菜单只对授权角色可见 | | -| 按钮权限 | 新增、编辑、删除、指派、状态按钮按权限显示 | | +| 菜单权限 | 目标模块菜单只对授权角色可见 | | +| 按钮权限 | 新增、编辑、删除、分配、状态按钮按权限显示 | | | 接口权限 | 无权限账号直接请求敏感接口失败 | | -| 数据权限 | 普通用户只能看到授权范围内工单 | | +| 数据权限 | 普通用户只能看到授权范围内数据 | | | 权限字符 | 前端按钮、菜单配置、后端接口命名一致 | | | 审计 | 权限敏感操作成功后有审计记录 | | -| 反例验证 | 无权限账号不能绕过前端执行删除/指派 | | - +| 反例验证 | 无权限账号不能绕过前端执行删除/分配 | | 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 index d7d0910..adbf43f 100644 --- 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 @@ -1,12 +1,11 @@ -# 工单权限控制方案 +# <模块名称> 权限控制方案 | 操作 | 前端入口 | 权限字符 | 后端接口 | 后端校验 | 数据权限 | 验收 | | --- | --- | --- | --- | --- | --- | --- | -| 新增工单 | 新增按钮 | 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` | `` | 权限注解或统一鉴权 | 通常无数据范围 | 无权限账号看不到按钮且直接请求失败 | +| 编辑记录 | 编辑按钮 | `::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 index 7dcf834..d568350 100644 --- 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 @@ -1,9 +1,11 @@ # 权限控制方案生成 Prompt ```text -请基于工单模块 RBAC 权限矩阵,生成 JeecgBoot 权限控制方案。 +请基于目标业务模块 RBAC 权限矩阵,生成权限控制方案。 输入材料: +- 模块名称:<填写> +- 业务对象:<填写> - RBAC 权限矩阵:<粘贴> - 接口契约表:<粘贴> - 当前项目权限相关路径:<粘贴> @@ -20,5 +22,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 index 2be5688..759c879 100644 --- 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 @@ -6,7 +6,6 @@ | 合法流转 | 状态流转表中的合法路径可执行 | | | 非法流转 | 非法路径被拒绝且状态不变 | | | 角色限制 | 不同角色只能执行被授权动作 | | -| 前置条件 | 驳回原因、关闭条件、负责人等校验完整 | | +| 前置条件 | 驳回原因、关闭条件、处理人、审批意见等校验完整 | | | 审计日志 | 每次成功流转写审计 | | | 重复提交 | 重复点击不会产生重复有效流转 | | - 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 index 29456b8..8a737e6 100644 --- 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 @@ -1,22 +1,21 @@ -# 工单状态流转表 +# <模块名称> 状态流转表 -| 当前状态 | 动作 | 目标状态 | 允许角色 | 前置条件 | 后置动作 | 验收 | -| --- | --- | --- | --- | --- | --- | --- | -| 新建 | 指派 | 新建 | 管理员/负责人 | 工单未关闭 | 写审计日志 | 指派人变化且状态不变 | -| 新建 | 开始处理 | 处理中 | 研发处理人/负责人 | 已指派处理人 | 写审计日志 | 状态变为处理中 | -| 处理中 | 提交验证 | 待验证 | 研发处理人/负责人 | 处理说明不为空 | 写审计日志 | 状态变为待验证 | -| 待验证 | 验证通过 | 已关闭 | 测试验证人/负责人 | 验证结果通过 | 写审计日志和关闭时间 | 状态变为已关闭 | -| 待验证 | 验证驳回 | 已驳回 | 测试验证人/负责人 | 驳回原因不为空 | 写审计日志 | 状态变为已驳回 | -| 已驳回 | 重新处理 | 处理中 | 研发处理人/负责人 | 驳回原因已读 | 写审计日志 | 状态变为处理中 | -| 已关闭 | 重新打开 | 新建 | 管理员 | 重开原因不为空 | 写审计日志 | 状态变为新建 | +| 当前状态 | 动作 | 目标状态 | 允许角色 | 前置条件 | 后置动作 | 验收 | 来源证据 | +| --- | --- | --- | --- | --- | --- | --- | --- | +| 草稿/新建 | 提交 | 待处理/待审核 | 创建人/提交人 | 必填字段完整 | 写审计日志 | 状态变为目标状态 | | +| 待处理/待审核 | 分配/领取 | 处理中 | 管理员/负责人/处理人 | 处理人存在且有权限 | 写审计日志 | 处理人变化且状态正确 | | +| 处理中 | 提交结果 | 待确认/待复核 | 处理人/负责人 | 处理说明或结果不为空 | 写审计日志 | 状态变为待确认 | | +| 待确认/待复核 | 通过 | 已完成/已关闭 | 审核人/负责人 | 审核结论通过 | 写审计日志和完成时间 | 状态进入终态 | | +| 待确认/待复核 | 驳回 | 已驳回/待修改 | 审核人/负责人 | 驳回原因不为空 | 写审计日志 | 状态变为驳回态 | | +| 已驳回/待修改 | 重新提交 | 待处理/待审核 | 创建人/处理人 | 修改说明不为空 | 写审计日志 | 回到处理链路 | | +| 已完成/已关闭 | 重新打开 | 草稿/新建/处理中 | 管理员/负责人 | 重开原因不为空 | 写审计日志 | 状态按规则回退 | | ## 非法流转清单 | 当前状态 | 尝试动作 | 问题 | 系统响应 | 验收 | | --- | --- | --- | --- | --- | -| 已关闭 | 开始处理 | 关闭后不能直接处理 | 返回非法流转 | 状态不变 | -| 新建 | 验证通过 | 未处理不能验证 | 返回非法流转 | 状态不变 | -| 待验证 | 提交验证 | 重复提交验证 | 返回非法流转 | 只保留原状态 | -| 处理中 | 验证通过 | 未进入待验证 | 返回非法流转 | 状态不变 | -| 已驳回 | 关闭 | 驳回后不能直接关闭 | 返回非法流转 | 状态不变 | - +| 已完成/已关闭 | 直接处理 | 终态不能直接进入处理中 | 返回非法流转 | 状态不变 | +| 草稿/新建 | 直接通过 | 未提交或未处理不能通过 | 返回非法流转 | 状态不变 | +| 待确认/待复核 | 重复提交结果 | 重复动作不应再次生效 | 返回非法流转或幂等结果 | 只保留原状态 | +| 处理中 | 直接通过 | 未进入确认/复核节点 | 返回非法流转 | 状态不变 | +| 已驳回/待修改 | 直接关闭 | 驳回后不能绕过修改或确认 | 返回非法流转 | 状态不变 | 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 index 58b609b..683b298 100644 --- 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 @@ -1,23 +1,22 @@ # 状态流转设计 Prompt ```text -请为 JeecgBoot 工单模块设计状态流转规则。 +请为目标业务模块设计状态流转规则。 -状态候选: -- 新建 +输入材料: +- 模块名称:<填写> +- 业务对象:<填写> +- 角色列表:<粘贴> +- 当前需求或已有状态枚举:<粘贴> +- 关键动作:<粘贴> + +可参考但必须按项目替换的状态类型: +- 草稿/新建 +- 待处理/待审核 - 处理中 -- 待验证 -- 已关闭 -- 已驳回 - -动作候选: -- 指派 -- 开始处理 -- 提交验证 -- 验证通过 -- 验证驳回 -- 关闭 -- 重新打开 +- 待确认/待复核 +- 已完成/已关闭 +- 已驳回/待修改 输出要求: 1. 状态流转表:当前状态、动作、目标状态、允许角色、前置条件、后置动作、验收。 @@ -28,7 +27,7 @@ 约束: - 先出状态表,不要直接写 if else。 -- 已关闭、越权、缺少说明、重复提交必须覆盖。 +- 终态、越权、缺少说明、重复提交必须覆盖。 - 每条规则必须有验收方式。 +- 没有需求依据的状态或动作必须标注为建议或待确认。 ``` - 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 index 32da66b..980755b 100644 --- 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 @@ -1,13 +1,12 @@ -# 工单看板指标字典 +# <模块名称> 看板指标字典 | 指标 | 含义 | 口径 | 数据来源 | 展示方式 | 权限 | 验收 | | --- | --- | --- | --- | --- | --- | --- | -| 工单总数 | 当前筛选范围内工单数量 | 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(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 index 9851ec7..80815d9 100644 --- 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 @@ -1,16 +1,23 @@ # 看板指标设计 Prompt ```text -请为 JeecgBoot 工单模块设计研发效能看板指标。 +请为目标业务模块设计业务看板指标。 -指标方向: -- 工单总数 -- 未关闭工单数 -- 平均关闭周期 -- 按优先级分布 +输入材料: +- 模块名称:<填写> +- 业务对象:<填写> +- 角色与数据范围:<粘贴> +- 状态枚举与时间字段:<粘贴> +- 列表筛选条件:<粘贴> + +指标方向可参考: +- 记录总数 +- 未完成数量 +- 平均完成周期 +- 按状态分布 - 按类型分布 -- 缺陷趋势 -- 逾期工单数 +- 趋势指标 +- 逾期数量 - 人员处理负载 输出要求: @@ -24,5 +31,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 index b3f2e75..4ba40fc 100644 --- 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 @@ -1,9 +1,11 @@ # 看板接口验收 Prompt ```text -请为 JeecgBoot 工单看板统计接口生成验收记录。 +请为目标业务模块看板统计接口生成验收记录。 输入材料: +- 模块名称:<填写> +- 业务对象:<填写> - 看板指标字典:<粘贴> - 看板接口契约:<粘贴> - 权限矩阵:<粘贴> @@ -23,5 +25,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 index 95e740a..d1f43b4 100644 --- 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 @@ -2,11 +2,10 @@ | 验收项 | 请求条件 | 通过标准 | 结果 | | --- | --- | --- | --- | -| 默认查询 | 管理员默认条件 | 返回完整指标 | | +| 默认查询 | 管理员或授权角色默认条件 | 返回完整指标 | | | 时间过滤 | 指定 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 index 6ddf072..91cb3e0 100644 --- 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 @@ -1,11 +1,10 @@ -# 工单看板接口验收记录 +# <模块名称> 看板接口验收记录 | 验收项 | 账号/条件 | 请求参数 | 预期 | 实际 | 证据 | 结论 | | --- | --- | --- | --- | --- | --- | --- | -| 工单总数 | 管理员 | 默认筛选 | 返回总数 | | | | -| 空数据 | 管理员 + 无匹配条件 | 不存在的日期范围 | 返回空集合或 0 | | | | -| 无权限 | 只读外角色 | 默认筛选 | 返回 403 或业务拒绝 | | | | -| 时间过滤 | 管理员 | start/end | 只统计范围内数据 | | | | -| 数据范围 | 普通研发 | 默认筛选 | 只返回本人相关数据 | | | | -| 指标口径 | 管理员 | 固定样例数据 | 与手工计算一致 | | | | - +| 记录总数 | 管理员或授权角色 | 默认筛选 | 返回总数 | | | | +| 空数据 | 授权角色 + 无匹配条件 | 不存在的日期范围或筛选条件 | 返回空集合或 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 index d5fb05b..27f43b1 100644 --- a/deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/SKILL.md +++ b/deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/SKILL.md @@ -95,17 +95,6 @@ Good wording: 本项目未发现 `AGENTS.md`、`CLAUDE.md`、`.trae/rules` 或 `README.md` 中的明确 AI 协作规则。以下约束来自配置文件和源码模式;未能由仓库证明的内容已标记为待确认。 ``` -## JeecgBoot Example - -For the training case only, JeecgBoot evidence can generate rules such as: - -- `硬约束`: backend uses `Result` response wrapper, from backend `CLAUDE.md`. -- `硬约束`: frontend uses `defHttp` and backend menu permission mode, from frontend `CLAUDE.md`. -- `硬约束`: backend modification blocks require `update-begin` / `update-end`, from backend `CLAUDE.md`. -- `观察性约束`: business features should follow existing controller/service/mapper layering after verifying representative modules. - -Keep these examples out of generic outputs unless the target project is JeecgBoot or explicitly similar. - ## Quality Gates Before claiming completion: @@ -122,4 +111,4 @@ Before claiming completion: - 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 JeecgBoot-specific constraints into unrelated projects. +- Do not copy one project's specific constraints into unrelated projects. 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 index 7c8f339..78e13c6 100644 --- a/deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/assets/项目约束验收清单.md +++ b/deliverables/ai-engineering-skills-delivery/skills/generate-project-constraints/assets/项目约束验收清单.md @@ -31,7 +31,7 @@ ## 可复用性 -- [ ] JeecgBoot 等项目特有规则没有写进通用规则。 +- [ ] 某个项目特有规则没有写进通用规则。 - [ ] 每条规则都标注了“可直接复用 / 需重新扫描 / 不可迁移”。 - [ ] 换项目时必须重新扫描的内容已经列出。 - [ ] 可以从本清单反推出生成步骤。 diff --git a/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/SKILL.md b/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/SKILL.md new file mode 100644 index 0000000..b4335fc --- /dev/null +++ b/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/SKILL.md @@ -0,0 +1,133 @@ +--- +name: project-knowledge-builder +description: Use when a user asks “项目知识库”, “看项目”, “整理项目”, or otherwise wants to inspect a software project deeply, build/refresh a project knowledge base, create onboarding docs, architecture maps, domain glossaries, ADRs, generated API/config/database indexes, or RAG-ready repository documentation. +--- + +# Project Knowledge Builder + +## Overview + +Build a source-grounded project knowledge base that helps humans and AI agents understand, extend, troubleshoot, and index a codebase. Treat the knowledge base as a maintained system artifact, not a README rewrite. + +## Quick Use + +Users should not need to remember commands, script paths, or `$project-knowledge-builder`. + +The shortest trigger is enough: + +- `项目知识库` + +Treat the current repository as the target and `docs/project-knowledge/` as the output path unless the user says otherwise. Run the bundled inventory script yourself and write the docs. Only show manual commands when the user explicitly asks for command-line usage. + +## Language And Audience Defaults + +If the user writes in Chinese or asks for Chinese, write human-facing narrative docs in Chinese by default: README, context, architecture, runtime, backend, frontend, data, auth/permissions, extension, troubleshooting, RAG guidance and ADRs. Keep source paths, class names, commands, API paths, config keys, table names, permission codes and metadata enum values unchanged. + +Treat beginners as a primary audience. A useful knowledge base should let a new maintainer answer four questions without extra coaching: + +1. What is this project and where are the main modules? +2. How do I run it locally or identify the active runtime mode? +3. How do I find the backend endpoint, frontend page, database table and permission code for a feature? +4. What must change when adding a normal feature, especially menu/button permissions? + +Do not make generated indexes overly friendly at the cost of precision. Keep generated files machine-refreshable; make narrative docs readable. + +## Core Rule + +Every claim must be traceable to the repository or to an explicitly marked inference. Prefer source paths, commands, generated inventories, and unresolved-question lists over confident prose. + +## Workflow + +1. **Set scope.** Confirm or assume the output location. Default to `docs/project-knowledge/`. If the user mentions RAG, Feishu/Lark, Dify, Notion, onboarding, architecture review, or API docs, tailor the output contract. +2. **Read local instructions first.** Check root docs plus `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `.codex/`, `.agents/`, package manifests, build files, env examples, Docker files, and module READMEs. +3. **Generate an inventory.** Run `scripts/project_inventory.py` from this skill against the repo. Use its output as a factual base, then inspect important files manually. +4. **Map contexts.** Identify bounded contexts, runtime modes, major modules, owners implied by paths, external systems, and data stores. Use `CONTEXT.md` and `CONTEXT-MAP.md` style docs. +5. **Extract generated indexes.** Produce machine-refreshable docs where possible: endpoints, frontend routes/views, config keys, env variables, database tables, permission codes, scheduled jobs, queues, and extension points. +6. **Write human guides.** Produce onboarding, architecture, local run/deploy, data model, auth/permission, extension, troubleshooting, and glossary docs. Match the user's language for narrative docs; for Chinese users, use Chinese section titles such as `已观察来源`, `核心事实`, `操作影响`, and `未知与风险`. +7. **Create ADRs for real decisions.** Record important architecture decisions already embodied in the code. Do not invent future decisions unless marked as proposed. +8. **Add RAG metadata guidance.** Include chunking, source-path metadata, stale-data policy, and exclusion rules. +9. **Verify.** Re-run the inventory, scan docs for placeholders, broken relative links, stale paths, and unsupported claims. Report what was verified and what remains uncertain. + +## Output Contract + +Read `references/output-contract.md` when creating or upgrading a full knowledge base. + +Minimum useful set: + +```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 +``` + +Scale down only if the repo is small or the user requests a lighter artifact. + +## Source-Grounding Standards + +- Cite source paths inline for non-obvious claims. +- Distinguish **Observed** from **Inferred** and **Unknown**. +- Do not summarize generated code, dependencies, or configs from memory. Read them. +- If external docs are needed for unstable facts, browse primary sources and link them. +- Preserve project-specific conventions. If a repo requires trace comments, ADR format, or issue IDs, document and follow that. + +## Cross-Platform Compatibility + +Read `references/compatibility.md` when the user asks about macOS, Windows, Linux, CI, portability, or installing/running the skill across machines. The bundled inventory script is intended to run on Python 3.9+ with only the standard library. + +## Inventory Script + +Use this only for manual refreshes or when the user asks for the command. Run from the target repo: + +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 +``` + +Useful options: + +```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 +``` + +The script is a starting point, not a replacement for reading core files. Inspect representative files before writing narrative docs. + +## Quality Gates + +Before final response: + +- List generated docs with a platform-appropriate command, such as `find docs/project-knowledge -type f | sort` on macOS/Linux or `Get-ChildItem docs/project-knowledge -Recurse -File` on PowerShell. +- `rg -n "TODO|TBD|待补|unknown|your/path|foo|bar" docs/project-knowledge` +- Re-run the inventory script after edits. +- Check that top-level README links resolve. +- If the user is Chinese, scan narrative docs for English-only structural headings such as `Observed Sources`, `Core Facts`, `Operational Implications`, `Unknowns/Risks`, `Architecture`, `Runtime`, `Backend`, `Frontend`, `Troubleshooting`, and translate them unless they are code, metadata, or generated output. +- Check newcomer usefulness: README gives a reading order; runtime doc names local/Docker/cloud modes; auth doc explains menu/button permission flow; extension doc explains backend CRUD, frontend page and permission changes. +- `git status --short` to report changed files. + +If the user asked for “deep” or “落地”, do not stop at a single overview. Deliver generated indexes plus human guides. diff --git a/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/agents/openai.yaml b/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/agents/openai.yaml new file mode 100644 index 0000000..7edef17 --- /dev/null +++ b/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "项目知识库" + short_description: "深度整理代码仓库知识库" + default_prompt: "Use $project-knowledge-builder 为当前项目生成 docs/project-knowledge 项目知识库。" diff --git a/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/references/compatibility.md b/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/references/compatibility.md new file mode 100644 index 0000000..b225f91 --- /dev/null +++ b/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/references/compatibility.md @@ -0,0 +1,65 @@ +# Cross-Platform Compatibility + +Use this reference when running or installing the skill on macOS, Windows, Linux, or CI. + +## Compatibility Summary + +| Area | macOS | Linux | Windows | +| --- | --- | --- | --- | +| `SKILL.md` workflow | Supported | Supported | Supported | +| `project_inventory.py` | Supported with Python 3.9+ | Supported with Python 3.9+ | Supported with Python 3.9+ | +| Python dependencies | Standard library only | Standard library only | Standard library only | +| Path handling | `pathlib` | `pathlib` | `pathlib` | +| Shell dependency | None in script | None in script | None in script | +| Generated Markdown paths | POSIX-style relative paths | POSIX-style relative paths | POSIX-style relative paths | + +The script intentionally emits repository-relative paths with `/` separators so generated Markdown is stable across platforms and friendlier for URLs, GitHub, and RAG metadata. + +## Python Invocation + +macOS and Linux usually have one of these: + +```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 usually uses: + +```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 should prefer an explicit 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 +``` + +## Command Substitutions + +| Task | macOS/Linux | Windows PowerShell | +| --- | --- | --- | +| List docs | `find docs/project-knowledge -type f \| sort` | `Get-ChildItem docs/project-knowledge -Recurse -File \| Sort-Object FullName` | +| Search placeholders | `rg -n "TODO|TBD" docs/project-knowledge` | `rg -n "TODO|TBD" docs/project-knowledge` | +| Show git status | `git status --short` | `git status --short` | +| Remove temp output | `rm -rf ./kb-inventory` | `Remove-Item .\kb-inventory -Recurse -Force` | + +`rg` is preferred where available. If `rg` is not installed, use the platform search available in the environment. + +## Avoid In Skill Examples + +- Hard-coded `/tmp` paths when the command should work on Windows. +- POSIX-only command chains such as `cmd1 && cmd2` in user-facing instructions when a PowerShell equivalent is needed. +- Shell glob assumptions that behave differently between zsh, bash, PowerShell, and cmd. +- Python code that depends on executable shebang behavior; Windows users should call `py -3 script.py`. + +## Known Limits + +- Java endpoint extraction is regex-based and intentionally conservative. It does not fully resolve constants, composed annotations, or inherited controller mappings. +- SQL table extraction looks for `CREATE TABLE`; tables created dynamically or through migrations in another DSL need manual documentation. +- Frontend route extraction scans route-like literals and view files; runtime-generated route data from APIs still requires backend menu/permission data or database export. +- Very large repositories may need `--max-files` tuning or additional ignore directories. + diff --git a/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/references/output-contract.md b/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/references/output-contract.md new file mode 100644 index 0000000..853edcc --- /dev/null +++ b/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/references/output-contract.md @@ -0,0 +1,142 @@ +# Project Knowledge Builder Output Contract + +Use this reference when producing a full codebase knowledge base. + +## Language Policy + +Match the user's language for human-facing narrative docs. If the user writes in Chinese or asks for Chinese, write README, context, architecture, runtime, backend, frontend, data, auth/permissions, extension, troubleshooting, RAG guidance and ADRs in Chinese. + +Keep technical anchors unchanged: source paths, commands, class names, method names, API paths, config keys, table names, permission codes, JSON keys and metadata enum values. + +Generated indexes may remain technical and source-like. Do not hand-translate generated values if it reduces refreshability. + +## Document Roles + +| File | Purpose | +| --- | --- | +| `README.md` | Entry point, reading order, high-value links, freshness note. For Chinese output, title and usage guidance should be Chinese. | +| `CONTEXT.md` | Domain language, business capabilities, user roles, core nouns, invariants. | +| `CONTEXT-MAP.md` | Bounded contexts, module ownership, upstream/downstream dependencies, runtime boundaries. | +| `01-architecture.md` | System shape, module graph, request/data/control flows, extension seams. | +| `02-runtime.md` | Local run, build, env vars, Docker, deployment modes, process topology. Must identify local, Docker standalone and cloud modes when present. | +| `03-backend.md` | Backend modules, entrypoints, API conventions, service/mapper/entity patterns. | +| `04-frontend.md` | Frontend bootstrap, routing, state, API client, component/page conventions. | +| `05-data-and-migrations.md` | Databases, migrations, schema sources, seed data, table families. | +| `06-auth-permissions.md` | Authentication, authorization, token/session flow, permission-code lifecycle. Must explain menu/button permission flow when the repo has backend-driven menus. | +| `07-extension-guide.md` | How to add a feature end-to-end, with source-grounded examples. Must cover backend CRUD, frontend page and permission/menu data changes when applicable. | +| `08-troubleshooting.md` | Symptom-driven runbook with commands, files, and likely causes. | +| `09-rag-indexing.md` | Chunking, metadata, exclusions, refresh cadence, stale-data policy. | +| `adr/*.md` | Architecture decisions observed in the current code. | +| `generated/*.md` | Data-derived indexes that can be regenerated. | + +## Required Sections + +Each narrative doc should include: + +- **Observed Sources:** concrete files or commands used. +- **Core Facts:** concise source-backed statements. +- **Operational Implications:** what developers must do differently because of those facts. +- **Unknowns/Risks:** gaps that need runtime access, DB export, product owner input, or external docs. + +For Chinese output, use equivalent section labels: + +- **已观察来源** +- **核心事实** +- **操作影响** +- **未知与风险** + +## Newcomer Usefulness Gate + +Before considering the knowledge base useful for handoff, verify that a new maintainer can answer: + +1. What is the product/project, and which directories matter first? +2. How do I start or identify the active runtime mode? +3. Where are backend endpoints, frontend routes/pages, config, tables and permission codes indexed? +4. How do menu, button and backend permission checks connect? +5. When adding a normal feature, which backend, frontend, database and permission artifacts usually change? + +If the answer is not obvious from README plus the narrative docs, add or revise human-facing guidance before finishing. Do not solve this by making generated indexes longer. + +## Generated Indexes + +### `project-inventory.md` + +Include: + +- repository roots +- build/package manifests +- primary languages +- major directories +- scripts +- Docker/compose files +- env files +- test files + +### `backend-endpoints.md` + +Include route method, path, class/file, approximate line, auth annotations, and notes. Do not invent full route paths when class-level and method-level mappings require Java expression resolution; show observed fragments and mark composition limitations. + +### `frontend-routes.md` + +Include static router files, dynamic route loaders, `src/views` index, route guard files, and external route package loaders. + +### `config-reference.md` + +Include env files, YAML/properties files, relevant keys, default values, and risk notes. Redact secrets. + +### `database-dictionary.md` + +Include SQL files, migration files, table names, table families, and whether the table is initialized, migrated, or inferred. Avoid loading huge SQL into prose; summarize and link. + +### `permission-codes.md` + +Include backend annotations, frontend permission helpers/usages, menu/permission SQL occurrences, and auth lifecycle notes. + +## ADR Template + +```markdown +# ADR NNNN: Title + +Date: YYYY-MM-DD +Status: Observed + +## Context + +Source-backed facts that show the decision exists. + +## Decision + +The architecture decision embodied by the current code. + +## Consequences + +Benefits, tradeoffs, operational constraints, and migration risks. + +## Sources + +- `path/to/file` +``` + +## RAG Metadata + +Recommended metadata per 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" +} +``` + +## Anti-Patterns + +- A long essay without generated indexes. +- “Best practice” advice unrelated to the repo. +- Copying README marketing text as architecture documentation. +- Claiming an endpoint, table, permission, or runtime mode exists without a source path. +- Hiding unknowns instead of listing them. diff --git a/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/scripts/project_inventory.py b/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/scripts/project_inventory.py new file mode 100644 index 0000000..7ec31ab --- /dev/null +++ b/deliverables/ai-engineering-skills-delivery/skills/project-knowledge-builder/scripts/project_inventory.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python3 +"""Generate a source-grounded project inventory for knowledge-base work.""" + +from __future__ import annotations + +import argparse +import json +import os +import re +from collections import Counter, defaultdict +from pathlib import Path +from typing import Iterable + +IGNORE_DIRS = { + ".git", + ".idea", + ".vscode", + "node_modules", + "dist", + "build", + "target", + ".gradle", + ".mvn/wrapper", + "coverage", + ".next", + ".nuxt", + "__pycache__", +} + +MANIFEST_NAMES = { + "package.json", + "pnpm-lock.yaml", + "yarn.lock", + "package-lock.json", + "pom.xml", + "build.gradle", + "settings.gradle", + "Cargo.toml", + "pyproject.toml", + "requirements.txt", + "go.mod", + "composer.json", + "Gemfile", +} + +CONFIG_PATTERNS = ( + re.compile(r".*\.env(\..*)?$"), + re.compile(r".*application[-\w]*\.(yml|yaml|properties)$"), + re.compile(r".*\.(ya?ml|toml|ini|properties|conf)$"), +) + +HTTP_MAPPING_RE = re.compile( + r"@(RequestMapping|GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping)\s*(?:\((.*?)\))?" +) +PERMISSION_RE = re.compile(r"@(?:RequiresPermissions|RequiresRoles)\s*\((.*?)\)") +TABLE_NAME_RE = re.compile( + r"\bCREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?`?([A-Za-z0-9_.$-]+)`?", + re.IGNORECASE, +) +VUE_ROUTE_RE = re.compile(r"\b(path|name|component)\s*:\s*['\"]([^'\"]+)['\"]") + + +def is_ignored(path: Path, root: Path) -> bool: + rel_parts = path.relative_to(root).parts + for i in range(len(rel_parts)): + joined = "/".join(rel_parts[: i + 1]) + if joined in IGNORE_DIRS or rel_parts[i] in IGNORE_DIRS: + return True + return False + + +def iter_files(root: Path, max_files: int) -> Iterable[Path]: + count = 0 + for dirpath, dirnames, filenames in os.walk(root): + current = Path(dirpath) + dirnames[:] = [d for d in dirnames if not is_ignored(current / d, root)] + for name in filenames: + path = current / name + if is_ignored(path, root): + continue + count += 1 + if count > max_files: + return + yield path + + +def rel(path: Path, root: Path) -> str: + return path.relative_to(root).as_posix() + + +def read_text(path: Path, limit: int = 400_000) -> str: + try: + data = path.read_bytes() + except OSError: + return "" + if len(data) > limit: + data = data[:limit] + try: + return data.decode("utf-8") + except UnicodeDecodeError: + try: + return data.decode("utf-8", errors="ignore") + except Exception: + return "" + + +def detect_config(path: Path) -> bool: + name = path.name + return any(pattern.fullmatch(name) for pattern in CONFIG_PATTERNS) + + +def extract_mapping_value(raw: str | None) -> str: + if not raw: + return "" + value_match = re.search(r"['\"]([^'\"]+)['\"]", raw) + if value_match: + return value_match.group(1) + return raw.strip().replace("\n", " ")[:120] + + +def scan(root: Path, max_files: int) -> dict: + files = list(iter_files(root, max_files)) + ext_counts = Counter(path.suffix.lower() or "[none]" for path in files) + manifests = [] + configs = [] + docker = [] + tests = [] + java_endpoints = [] + permission_codes = [] + vue_routes = [] + views = [] + sql_tables = [] + major_dirs = defaultdict(int) + + for path in files: + r = rel(path, root) + parts = Path(r).parts + if parts: + major_dirs[parts[0]] += 1 + if path.name in MANIFEST_NAMES: + manifests.append(r) + lower = path.name.lower() + if lower.startswith("dockerfile") or "docker-compose" in lower: + docker.append(r) + if detect_config(path): + configs.append(r) + if re.search(r"(^|/)(test|tests|__tests__)(/|$)", r) or re.search(r"\.(test|spec)\.[A-Za-z0-9]+$", r): + tests.append(r) + if "/src/views/" in r and path.suffix.lower() in {".vue", ".tsx", ".ts"}: + views.append(r) + + suffix = path.suffix.lower() + if suffix not in {".java", ".ts", ".tsx", ".vue", ".js", ".jsx", ".sql", ".yml", ".yaml", ".properties"}: + continue + text = read_text(path) + if not text: + continue + + if suffix == ".java": + lines = text.splitlines() + class_mapping = "" + class_name = "" + pending_annotations: list[tuple[int, str, str]] = [] + for idx, line in enumerate(lines, start=1): + class_match = re.search(r"\bclass\s+([A-Za-z0-9_]+)", line) + if class_match: + class_name = class_match.group(1) + for match in HTTP_MAPPING_RE.finditer(line): + value = extract_mapping_value(match.group(2)) + annotation = match.group(1) + pending_annotations.append((idx, annotation, value)) + if annotation == "RequestMapping" and not class_mapping and ("class " not in line): + class_mapping = value + if re.search(r"\b(public|private|protected)\b.*\(", line) and pending_annotations: + method = line.strip()[:160] + for ann_line, annotation, value in pending_annotations: + java_endpoints.append( + { + "file": r, + "line": ann_line, + "class": class_name, + "annotation": annotation, + "class_mapping": class_mapping, + "mapping": value, + "method_signature": method, + } + ) + pending_annotations = [] + for perm in PERMISSION_RE.finditer(line): + permission_codes.append({"file": r, "line": idx, "value": perm.group(1).strip()}) + + if suffix in {".ts", ".tsx", ".vue", ".js", ".jsx"}: + for idx, line in enumerate(text.splitlines(), start=1): + if "path:" in line or "component:" in line or "name:" in line: + for match in VUE_ROUTE_RE.finditer(line): + vue_routes.append({"file": r, "line": idx, "key": match.group(1), "value": match.group(2)}) + + if suffix == ".sql": + for match in TABLE_NAME_RE.finditer(text): + sql_tables.append({"file": r, "table": match.group(1)}) + + return { + "root": str(root), + "file_count": len(files), + "extensions": dict(ext_counts.most_common(40)), + "major_dirs": dict(sorted(major_dirs.items(), key=lambda item: (-item[1], item[0]))), + "manifests": sorted(manifests), + "configs": sorted(configs), + "docker": sorted(docker), + "tests": sorted(tests), + "views": sorted(views), + "java_endpoints": java_endpoints, + "permission_codes": permission_codes, + "vue_routes": vue_routes, + "sql_tables": sql_tables, + } + + +def write_md(data: dict, out_dir: Path) -> None: + out_dir.mkdir(parents=True, exist_ok=True) + + def bullet(items: list[str], limit: int | None = None) -> str: + selected = items if limit is None else items[:limit] + if not selected: + return "- None found\n" + body = "".join(f"- `{item}`\n" for item in selected) + if limit is not None and len(items) > limit: + body += f"- ... {len(items) - limit} more\n" + return body + + inventory = [ + "# Project Inventory\n\n", + f"Root: `{data['root']}`\n\n", + f"Files scanned: {data['file_count']}\n\n", + "## Major Directories\n\n", + ] + inventory.extend(f"- `{name}`: {count} files\n" for name, count in data["major_dirs"].items()) + inventory.extend(["\n## Extensions\n\n"]) + inventory.extend(f"- `{name}`: {count}\n" for name, count in data["extensions"].items()) + inventory.extend(["\n## Manifests\n\n", bullet(data["manifests"])]) + inventory.extend(["\n## Config Files\n\n", bullet(data["configs"], 200)]) + inventory.extend(["\n## Docker Files\n\n", bullet(data["docker"])]) + inventory.extend(["\n## Test Files\n\n", bullet(data["tests"], 200)]) + (out_dir / "project-inventory.md").write_text("".join(inventory), encoding="utf-8") + + endpoints = ["# Backend Endpoints\n\n"] + if data["java_endpoints"]: + endpoints.append("| File | Line | Class | Annotation | Class Mapping | Mapping | Method |\n") + endpoints.append("| --- | ---: | --- | --- | --- | --- | --- |\n") + for item in data["java_endpoints"]: + method_signature = item["method_signature"].replace("|", "\\|") + endpoints.append( + f"| `{item['file']}` | {item['line']} | `{item['class']}` | `{item['annotation']}` | " + f"`{item['class_mapping']}` | `{item['mapping']}` | `{method_signature}` |\n" + ) + else: + endpoints.append("No Java endpoint annotations found.\n") + (out_dir / "backend-endpoints.md").write_text("".join(endpoints), encoding="utf-8") + + routes = ["# Frontend Routes And Views\n\n", "## Views\n\n", bullet(data["views"], 400), "\n## Route-like Literals\n\n"] + if data["vue_routes"]: + routes.append("| File | Line | Key | Value |\n| --- | ---: | --- | --- |\n") + for item in data["vue_routes"][:1000]: + routes.append(f"| `{item['file']}` | {item['line']} | `{item['key']}` | `{item['value']}` |\n") + if len(data["vue_routes"]) > 1000: + routes.append(f"\nTruncated: {len(data['vue_routes']) - 1000} more route-like literals.\n") + else: + routes.append("No route-like literals found.\n") + (out_dir / "frontend-routes.md").write_text("".join(routes), encoding="utf-8") + + configs = ["# Config Reference\n\n", bullet(data["configs"], 500)] + (out_dir / "config-reference.md").write_text("".join(configs), encoding="utf-8") + + tables = ["# Database Dictionary\n\n"] + if data["sql_tables"]: + tables.append("| Table | Source |\n| --- | --- |\n") + for item in sorted(data["sql_tables"], key=lambda x: (x["table"], x["file"])): + tables.append(f"| `{item['table']}` | `{item['file']}` |\n") + else: + tables.append("No SQL CREATE TABLE statements found in scanned files.\n") + (out_dir / "database-dictionary.md").write_text("".join(tables), encoding="utf-8") + + perms = ["# Permission Codes\n\n"] + if data["permission_codes"]: + perms.append("| File | Line | Value |\n| --- | ---: | --- |\n") + for item in data["permission_codes"]: + permission_value = item["value"].replace("|", "\\|") + perms.append(f"| `{item['file']}` | {item['line']} | `{permission_value}` |\n") + else: + perms.append("No permission annotations found.\n") + (out_dir / "permission-codes.md").write_text("".join(perms), encoding="utf-8") + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", default=".", help="Repository root to scan") + parser.add_argument("--out", required=True, help="Output directory for generated Markdown") + parser.add_argument("--json", action="store_true", help="Also write inventory.json") + parser.add_argument("--max-files", type=int, default=30000, help="Maximum files to scan") + args = parser.parse_args() + + root = Path(args.root).resolve() + out_dir = Path(args.out).resolve() + data = scan(root, args.max_files) + write_md(data, out_dir) + if args.json: + (out_dir / "inventory.json").write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8") + print(f"Wrote project inventory to {out_dir}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) 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 new file mode 100644 index 0000000..cea4742 --- /dev/null +++ b/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/业务交付验收清单.md @@ -0,0 +1,59 @@ +# 业务交付验收清单 + +本清单用于判断复杂业务模块是否达到可验收状态,适用于后台管理、审批流、运营系统、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 index 0e271a4..1eebc8f 100644 --- a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/审计日志检查清单.md +++ b/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/审计日志检查清单.md @@ -2,12 +2,11 @@ | 检查项 | 通过标准 | 结果 | | --- | --- | --- | -| 创建记录 | 创建工单后能查询审计记录 | | +| 创建记录 | 创建业务记录后能查询审计记录 | | | 字段变更 | 编辑关键字段记录 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 index e1b6496..d53299c 100644 --- a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/异常态检查清单.md +++ b/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/异常态检查清单.md @@ -3,11 +3,10 @@ | 异常态 | 必须验证 | 结果 | | --- | --- | --- | | 空列表 | 页面显示空状态,不报错 | | -| 详情不存在 | 提示工单不存在,页面不崩溃 | | +| 详情不存在 | 提示记录不存在,页面不崩溃 | | | 权限不足 | 前端提示或隐藏,后端拒绝 | | | 非法状态 | 返回非法流转,状态不变 | | | 重复提交 | 只成功一次,只写一条有效审计 | | | 参数缺失 | 返回参数错误,业务数据不变 | | | 并发修改 | 拒绝覆盖或提示刷新 | | | 看板无数据 | 图表显示空态,不显示假数据 | | - 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 index cacf893..e7bf421 100644 --- a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/权限上线前检查清单.md +++ b/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/权限上线前检查清单.md @@ -2,11 +2,10 @@ | 检查项 | 通过标准 | 结果 | | --- | --- | --- | -| 菜单权限 | 工单菜单只对授权角色可见 | | -| 按钮权限 | 新增、编辑、删除、指派、状态按钮按权限显示 | | +| 菜单权限 | 目标模块菜单只对授权角色可见 | | +| 按钮权限 | 新增、编辑、删除、分配、状态按钮按权限显示 | | | 接口权限 | 无权限账号直接请求敏感接口失败 | | -| 数据权限 | 普通用户只能看到授权范围内工单 | | +| 数据权限 | 普通用户只能看到授权范围内数据 | | | 权限字符 | 前端按钮、菜单配置、后端接口命名一致 | | | 审计 | 权限敏感操作成功后有审计记录 | | -| 反例验证 | 无权限账号不能绕过前端执行删除/指派 | | - +| 反例验证 | 无权限账号不能绕过前端执行删除/分配 | | 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 index 2be5688..759c879 100644 --- a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/状态流转检查清单.md +++ b/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/状态流转检查清单.md @@ -6,7 +6,6 @@ | 合法流转 | 状态流转表中的合法路径可执行 | | | 非法流转 | 非法路径被拒绝且状态不变 | | | 角色限制 | 不同角色只能执行被授权动作 | | -| 前置条件 | 驳回原因、关闭条件、负责人等校验完整 | | +| 前置条件 | 驳回原因、关闭条件、处理人、审批意见等校验完整 | | | 审计日志 | 每次成功流转写审计 | | | 重复提交 | 重复点击不会产生重复有效流转 | | - 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 index 95e740a..d1f43b4 100644 --- a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/看板接口验收清单.md +++ b/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/看板接口验收清单.md @@ -2,11 +2,10 @@ | 验收项 | 请求条件 | 通过标准 | 结果 | | --- | --- | --- | --- | -| 默认查询 | 管理员默认条件 | 返回完整指标 | | +| 默认查询 | 管理员或授权角色默认条件 | 返回完整指标 | | | 时间过滤 | 指定 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 index 99c3843..57a6da6 100644 --- 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 @@ -1,9 +1,11 @@ # 阶段 Review Prompt ```text -请对 JeecgBoot 工单模块阶段交付物做 Review。 +请对目标业务模块阶段交付物做 Review。 输入材料: +- 模块名称:<填写> +- 业务对象:<填写> - RBAC 权限矩阵 - 权限控制方案 - 状态流转表 @@ -26,5 +28,5 @@ 约束: - 不要只说整体不错。 - 不要直接改代码。 +- 不要把示例模块、示例角色或示例接口当成目标项目事实。 ``` - 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 index eba0738..5a56f75 100644 --- 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 @@ -15,7 +15,7 @@ | 等级 | 模块 | 问题 | 触发规则 | 风险 | 修复建议 | 验收 | | --- | --- | --- | --- | --- | --- | --- | -| P1 | 权限 | 删除接口只隐藏按钮,后端缺少权限校验 | 前端按钮不是安全边界 | 可绕过前端删除工单 | 后端补权限校验 | 无权限账号请求返回 403 | +| P1 | 权限 | 敏感操作只做前端按钮隐藏,后端缺少权限校验 | 前端按钮不是安全边界 | 可绕过前端直接调用接口完成高风险操作 | 后端补权限校验和数据范围校验 | 无权限账号直接请求返回 403 或业务拒绝 | ## 不确定点 @@ -25,4 +25,3 @@ ## 结论 通过 / 有条件通过 / 不通过 - 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 230cd33..0000000 --- a/deliverables/ai-engineering-skills-delivery/skills/review-business-delivery/assets/验收清单.md +++ /dev/null @@ -1,71 +0,0 @@ -# 验收清单 - -本清单用于判断学员是否真正掌握 JeecgBoot 工单案例,而不是只复制了 TRAE 输出。建议讲师在小组展示时逐项打分。 - -## 项目理解 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 能说明 JeecgBoot 前后端目录 | 至少指出 `jeecg-boot`、`jeecgboot-vue3`、`docs/project-knowledge` 的用途 | | -| 能定位后端入口 | 能指出 `JeecgSystemApplication.java` 和后端启动模块 | | -| 能定位前端入口 | 能指出 `src/main.ts`、路由、Pinia 和 API 请求层 | | -| 能区分事实和推断 | 对没有源码依据的业务规则标注为推断或待确认 | | -| 能记录不确定点 | 不确定内容没有被包装成确定事实 | | - -## 需求与接口 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 用户故事完整 | 覆盖管理员、负责人、研发、测试、只读用户 | | -| 非目标范围明确 | 明确课堂不做完整编码、生产部署、真实数据迁移 | | -| 接口契约完整 | 覆盖列表、详情、新增、编辑、指派、状态、审计、看板 | | -| 失败场景明确 | 每个关键接口至少有权限、参数、状态或数据不存在失败场景 | | -| 验收方式明确 | 每个关键需求都有可执行检查方法 | | - -## 权限与状态 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 权限矩阵完整 | 覆盖页面级、按钮级、接口级和数据范围 | | -| 权限字符合理 | 命名统一,并标注为建议而非源码事实 | | -| 后端权限未缺失 | 没有用前端按钮隐藏替代后端接口控制 | | -| 状态流转表完整 | 覆盖新建、处理中、待验证、已关闭、已驳回 | | -| 非法流转明确 | 至少列出 5 条非法流转和拒绝原因 | | - -## 审计、异常态与看板 - -| 检查项 | 通过标准 | 结果 | -| --- | --- | --- | -| 审计操作覆盖关键动作 | 创建、编辑、指派、状态流转、关闭、评论至少覆盖 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。 -- 优先看不确定点是否被诚实标出。 -- 优先看能否复用到团队资产库。 -- 对没有依据的自信结论扣分。 - 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 index 37732cc..b98ac20 100644 --- 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 @@ -2,7 +2,6 @@ | Review 编号 | 问题 | 是否成立 | 修改文件 | 修改说明 | 验证结果 | 资产沉淀 | | --- | --- | --- | --- | --- | --- | --- | -| R01 | 删除接口缺少后端权限校验 | 是 | WorkOrderController | 增加权限校验 | 无权限请求被拒绝 | 权限检查清单 | -| R02 | closed 可重新处理 | 是 | WorkOrderService | 补非法流转校验 | closed -> processing 失败 | 状态流转检查清单 | +| R01 | 敏感操作接口缺少后端权限校验 | 是 | `` | 增加权限校验和数据范围校验 | 无权限请求被拒绝 | 权限检查清单 | +| R02 | 终态记录可被直接改回处理中 | 是 | `` | 补非法流转校验 | terminal -> processing 失败 | 状态流转检查清单 | | R03 | 看板关闭周期口径不清 | 是 | 指标字典/看板接口 | 明确 create_time 到 close_time | 指标说明已更新 | 看板指标口径清单 | - 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 index 8dba8d6..f3982b1 100644 --- 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 @@ -2,31 +2,30 @@ ## 场景 -工单指派负责人成功,但没有记录审计日志。 +业务记录分配处理人成功,但没有记录审计日志。 ## 问题表现 | 操作 | 结果 | | --- | --- | -| 张三将工单指派给李四 | 工单负责人变更成功 | -| 查询审计日志 | 没有负责人变更记录 | +| 张三将记录分配给李四 | 处理人变更成功 | +| 查询审计日志 | 没有处理人变更记录 | ## 风险 -无法追溯负责人变化,出现延误或争议时无法定位责任。 +无法追溯处理人变化,出现延误或争议时无法定位责任。 ## 修复要求 -1. 指派成功后写审计日志。 -2. 记录原负责人、新负责人、操作人、时间、来源。 -3. 指派失败不写成功审计。 +1. 分配成功后写审计日志。 +2. 记录原处理人、新处理人、操作人、时间、来源。 +3. 分配失败不写成功审计。 4. 多步写入需要事务一致性。 ## 验收 | 操作 | 预期 | | --- | --- | -| 指派成功 | 审计列表出现 ASSIGN 记录 | -| 指派失败 | 不出现成功审计 | -| 查询详情 | 审计记录可在工单详情查看 | - +| 分配成功 | 审计列表出现 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 index 0e3cfb7..d1406a1 100644 --- 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 @@ -2,19 +2,19 @@ ## 场景 -工单删除按钮在前端按权限隐藏,但后端删除接口没有权限校验。 +敏感删除或作废按钮在前端按权限隐藏,但后端接口没有权限校验。 ## 问题代码表现 | 层级 | 表现 | | --- | --- | -| 前端 | 普通研发账号看不到删除按钮 | -| 后端 | 直接请求 `/workOrder/delete` 仍可删除 | +| 前端 | 普通业务账号看不到删除按钮 | +| 后端 | 直接请求删除或作废接口仍可成功 | | 审计 | 删除记录可能缺失 | ## 风险 -攻击者或误操作用户可以绕过前端直接调用接口删除工单。 +攻击者或误操作用户可以绕过前端直接调用接口删除或作废业务数据。 ## 修复要求 @@ -27,7 +27,6 @@ | 账号 | 操作 | 预期 | | --- | --- | --- | -| 管理员 | 删除允许删除的工单 | 成功并写审计 | -| 普通研发 | 直接请求删除接口 | 失败 | -| 只读用户 | 前端访问删除按钮 | 不可见 | - +| 管理员或授权角色 | 删除允许删除的数据 | 成功并写审计 | +| 普通业务角色 | 直接请求删除接口 | 失败 | +| 只读角色 | 前端访问删除按钮 | 不可见 | 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 index be5ee62..d636fcc 100644 --- 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 @@ -2,22 +2,22 @@ ## 场景 -看板展示“平均关闭周期”,但没有说明从哪个时间到哪个时间。 +看板展示“平均完成周期”,但没有说明从哪个时间到哪个时间。 ## 问题表现 | 指标 | 当前说明 | | --- | --- | -| 平均关闭周期 | 最近工单平均处理时间 | +| 平均完成周期 | 最近记录平均处理时间 | ## 风险 -不同人理解不同:可能是创建到关闭,也可能是开始处理到关闭,导致管理判断失真。 +不同人理解不同:可能是创建到完成,也可能是开始处理到完成,导致管理判断失真。 ## 修复要求 -1. 明确定义为 `close_time - create_time` 或其他确定口径。 -2. 明确是否排除未关闭工单。 +1. 明确定义为 `completed_at - created_at`、`closed_at - created_at` 或其他确定口径。 +2. 明确是否排除未完成记录。 3. 明确时间范围过滤字段。 4. 用样例数据手工校验。 @@ -25,7 +25,6 @@ | 样例 | 预期 | | --- | --- | -| 工单 A 创建 1 日、关闭 3 日 | 周期 2 天 | -| 未关闭工单 | 不计入平均关闭周期 | +| 记录 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 index e675156..0606610 100644 --- 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 @@ -2,7 +2,7 @@ ## 场景 -已关闭工单可以直接被改回处理中。 +已完成或已关闭的业务记录可以直接被改回处理中。 ## 问题表现 @@ -12,12 +12,12 @@ ## 风险 -已关闭工单被重新打开但没有重开原因、没有审计、没有负责人确认,导致统计和责任链失真。 +终态记录被重新打开但没有重开原因、没有审计、没有负责人确认,导致统计和责任链失真。 ## 修复要求 1. Service 层集中校验状态流转。 -2. 已关闭工单只能通过“重新打开”动作变为新建。 +2. 已完成或已关闭记录只能通过“重新打开”动作回到指定状态。 3. 重新打开必须填写原因。 4. 所有成功流转写审计日志。 @@ -28,4 +28,3 @@ | closed -> processing | 失败,状态不变 | | closed -> reopen -> new | 成功,必须有原因 | | 重开成功 | 审计记录 oldValue=closed, newValue=new | - diff --git a/deliverables/客户AI工程Skill交付包/README.md b/deliverables/客户AI工程Skill交付包/README.md index e7f7c62..b5ff6e0 100644 --- a/deliverables/客户AI工程Skill交付包/README.md +++ b/deliverables/客户AI工程Skill交付包/README.md @@ -21,16 +21,16 @@ ## Skill 列表 -| Skill | 用途 | 典型产物 | -| --- | --- | --- | -| `generate-project-constraints` | 从项目源码、配置、CI、文档中提取项目约束 | rules、约束提取表、验收清单 | +| Skill | 用途 | 典型产物 | +| -------------------------------- | ------------------------------------------------ | --------------------------------------------- | +| `generate-project-constraints` | 从项目源码、配置、CI、文档中提取项目约束 | rules、约束提取表、验收清单 | | `design-complex-business-module` | 设计复杂业务模块的权限、状态、审计、异常态和看板 | RBAC 矩阵、状态流转表、审计 SOP、看板指标字典 | -| `review-business-delivery` | 审查复杂业务交付物或 PR 的业务风险 | 阶段 Review 报告、风险清单、验收建议 | -| `track-review-fix-loop` | 跟踪 Review 问题修复、验证和资产沉淀 | 修复记录、未闭环清单、资产更新建议 | +| `review-business-delivery` | 审查复杂业务交付物或 PR 的业务风险 | 阶段 Review 报告、风险清单、验收建议 | +| `track-review-fix-loop` | 跟踪 Review 问题修复、验证和资产沉淀 | 修复记录、未闭环清单、资产更新建议 | ## 推荐使用顺序 -1. 使用 `generate-project-constraints` 为客户项目生成项目规则。 +1. 使用 `generate-project-constraints` 为项目生成项目规则。 2. 使用 `design-complex-business-module` 生成复杂业务设计资产。 3. 使用 `review-business-delivery` 审查设计、代码或阶段交付物。 4. 使用 `track-review-fix-loop` 跟踪修复、验证结果和资产沉淀。 diff --git a/deliverables/客户AI工程Skill交付包/安装说明.md b/deliverables/客户AI工程Skill交付包/安装说明.md index 5a50df8..e931a84 100644 --- a/deliverables/客户AI工程Skill交付包/安装说明.md +++ b/deliverables/客户AI工程Skill交付包/安装说明.md @@ -1,17 +1,17 @@ # 安装说明 -## 方式一:安装到 Codex Skills 目录 +## 方式一:安装到 TRAE Skills 目录 把 `skills/` 下的 4 个 Skill 文件夹复制到本机 Skills 目录: ```text -~/.codex/skills/ +~/.trae/skills/ ``` 安装后目录应类似: ```text -~/.codex/skills/ +~/.trae/skills/ ├── generate-project-constraints/ ├── design-complex-business-module/ ├── review-business-delivery/ diff --git a/slidev/pages/day3/m3-commands-skills-mcp.md b/slidev/pages/day3/m3-commands-skills-mcp.md index 6cd2f64..d7f54c5 100644 --- a/slidev/pages/day3/m3-commands-skills-mcp.md +++ b/slidev/pages/day3/m3-commands-skills-mcp.md @@ -2,7 +2,7 @@
-Commands 把常用话术封装成快捷入口,Skills 把完整能力封装成可复用资产。 +Commands 解决高频输入,Skills 解决可复用交付。两者都不是“把 Prompt 存起来”这么简单,而是把团队经验变成可触发、可验收、可维护的资产。
@@ -10,25 +10,26 @@ Commands 把常用话术封装成快捷入口,Skills 把完整能力封装成
Commands
-- `.trae/commands/` -- Markdown 定义 -- 斜杠触发 -- 适合固定提示词 +- 固定入口 +- 固定输入 +- 固定输出 +- 低风险、短流程
Skills
-- `.trae/skills/` -- 描述按需加载 -- 可带脚本、模板、示例 -- 适合完整工作流 +- 多步流程 +- 可带模板和检查清单 +- 可沉淀项目经验 +- 适合交付给团队或客户
--- @@ -37,23 +38,47 @@ Skills 是第二代封装,核心是把完成任务所需的说明、脚本、
-| 场景 | 示例 | -|---|---| -| 固定问法 | 总结当前 PR | -| 固定格式 | 生成 commit message | -| 固定检查 | 跑一次代码审查 | -| 固定输出 | 生成日报、周报、变更说明 | +| 判断标准 | 适合 Command 的特征 | 示例 | +|---|---|---| +| 问法固定 | 每次提问差异很小 | 生成 PR 说明 | +| 输入固定 | 只需要 diff、日志或单个文件 | 总结当前变更 | +| 输出固定 | 表格或固定段落即可 | commit message | +| 风险较低 | 不直接改核心逻辑 | 生成日报 | +| 不需要多步判断 | 不需要跨多个规则来决策 | 格式化说明 |
-Command 的关键价值是降低重复输入成本。 +Command 的关键价值是降低重复输入成本,并让轻量任务输出稳定。
+ +--- + +# Command 的坏味道 + +
+ +| 坏 Command | 问题 | 更好的方向 | +|---|---|---| +| `/检查代码` | 范围太大,输出不可控 | `/代码生成Review` | +| `/优化一下` | 没有目标和验收标准 | `/按性能规则检查列表页` | +| `/生成文档` | 不知道文档对象和读者 | `/生成PR说明` | +| `/帮我看看` | 没有输入边界 | 改成带输入材料的 Skill | + +
+ + --- @@ -64,71 +89,115 @@ Commands 很适合固定流程。
按需加载
-Agent 先扫描 skill 描述,任务相关时再读取完整说明。 +Agent 先看 Skill 描述,任务相关时再读取完整说明。
-
可携带资源
+
携带资源
-Skill 可以包含脚本、模板、示例、参考文件和检查清单。 +Skill 可以带模板、Prompt、检查清单、反例和脚本。
+
+Skill 不是单个 Markdown,而是一组能被复制、安装和维护的工作流资产。 +
+ --- -# Skill 的两种气质 +# 什么值得做成 Skill? -
-
-
文科型 Skill
+
-- 品牌风格 -- 文档结构 -- 设计规范 -- 沟通口径 -
-
-
理科型 Skill
+| 判断问题 | 是,就更适合 Skill | +|---|---| +| 是否需要多步判断? | 权限、状态、审计、异常态、看板需要串起来 | +| 是否需要项目证据? | 需要读源码、配置、规则和验收记录 | +| 是否需要多个输出? | 矩阵、清单、Review 报告、修复记录 | +| 是否需要资产复用? | 客户或团队下次还会做类似任务 | +| 是否有质量门槛? | 必须能验收,不只是生成文字 | -- 自动化脚本 -- 构建命令 -- 测试流程 -- 数据处理工具链 -
--- -# MCP 的价值 +# Skill 交付结构 -
+
-MCP 的历史价值是推动工具面向 AI 开放接口,让 Agent 能连接浏览器、数据库、文件系统、设计工具和内部平台。 +```text +skill-name/ +├── SKILL.md +├── agents/ +│ └── openai.yaml +└── assets/ + ├── templates/ + ├── checklists/ + ├── prompts/ + └── examples/ +```
-
-
打开连接可能性
-
统一工具描述方式
-
推动工具生态改造
+
+给客户交付 Skill 时,交付的是完整文件夹,不是单个 `.md`。
+ +--- + +# Skill 的三种交付形态 + +
+
+
知识型
+ +- 项目约束 +- 编码规范 +- 业务口径 +
+
+
流程型
+ +- 复杂业务设计 +- 阶段 Review +- 修复闭环 +
+
+
工具型
+ +- 脚本 +- CLI +- 测试和报告 +
+
+ + --- @@ -139,16 +208,17 @@ MCP 要讲它的历史功绩。 | 接入方式 | 适合场景 | 课堂判断 | |---|---|---| -| CLI | 命令稳定、脚本成熟、团队已有运维习惯 | 优先做轻量封装 | -| MCP | 需要交互式工具、长连接、结构化能力描述 | 适合平台化接入 | -| Skill | 需要把使用方法、模板、脚本一起交付 | 适合团队复用 | +| Command | 固定话术、固定格式 | 降低重复输入 | +| CLI | 命令稳定、脚本成熟 | 优先轻量封装 | +| MCP | 交互式工具、结构化能力描述 | 适合平台化接入 | +| Skill | 需要流程、模板、检查清单和示例 | 适合团队复用 |
--- @@ -160,13 +230,15 @@ MCP 是一种协议形态,CLI 是一种通用执行方式,Skill 是一种知 | 需求 | 推荐封装 | 理由 | |---|---|---| | 每天生成日报 | Command | 固定话术、固定格式 | -| 代码审查流程 | Skill | 需要规则、模板、示例 | +| 项目约束生成 | Skill | 需要扫描源码、配置、规则并输出验收清单 | +| 复杂业务模块设计 | Skill | 权限、状态、审计、看板需要串联判断 | | 内部平台查询 | CLI 或 MCP | 取决于现有接口形态 | -| 设计规范落地 | Skill | 需要风格、样例、检查清单 | +| 代码审查报告 | Skill | 需要规则、模板、反例和风险分级 |
diff --git a/slidev/pages/day4/m1-rbac-permission-model.md b/slidev/pages/day4/m1-rbac-permission-model.md index 4bcb3d3..7d9350d 100644 --- a/slidev/pages/day4/m1-rbac-permission-model.md +++ b/slidev/pages/day4/m1-rbac-permission-model.md @@ -2,7 +2,7 @@
-工单模块不是普通 CRUD。不同角色看到的数据不同、能做的动作不同、承担的责任也不同。 +工单模块不是普通 CRUD。复杂度不在字段多少,而在谁能看、谁能改、什么条件下能改,以及出问题后能否追溯。
@@ -15,9 +15,42 @@
+ +--- + +# 先矩阵,后代码 + +
+
+
错误顺序
+ +1. 先生成页面和接口 +2. 再临时隐藏按钮 +3. 最后补权限说明 +
+
+
推荐顺序
+ +1. 先定义角色和动作 +2. 再生成权限矩阵 +3. 最后落到菜单、按钮、接口、数据范围 +
+
+ +
+权限矩阵是后续代码生成、Review 和验收的依据,不是交付后的补充说明。 +
+ + --- @@ -28,23 +61,47 @@ | 功能 | 管理员 | 负责人 | 研发 | 测试 | 只读 | |---|---|---|---|---|---| -| 查看列表 | Y | Y | Y | Y | Y | -| 新增工单 | Y | Y | N | Y | N | -| 编辑工单 | Y | Y | 条件允许 | 条件允许 | N | +| 查看列表 | Y | 条件允许 | 条件允许 | 条件允许 | 条件允许 | +| 查看详情 | Y | 条件允许 | 条件允许 | 条件允许 | 条件允许 | +| 新增工单 | Y | Y | Y | Y | N | +| 编辑工单 | Y | 条件允许 | 条件允许 | 条件允许 | N | | 删除工单 | Y | N | N | N | N | -| 指派工单 | Y | Y | N | N | N | -| 提交验证 | Y | Y | Y | N | N | -| 关闭工单 | Y | Y | N | Y | N | -| 驳回工单 | Y | Y | N | Y | N | -| 查看审计 | Y | Y | 条件允许 | 条件允许 | N | -| 查看看板 | Y | Y | N | N | N | +| 指派工单 | Y | 条件允许 | N | N | N | +| 提交验证 | Y | 条件允许 | Y | N | N | +| 关闭工单 | Y | 条件允许 | N | Y | N | +| 查看审计 | Y | 条件允许 | 条件允许 | 条件允许 | N | +| 查看看板 | Y | Y | 条件允许 | 条件允许 | 条件允许 | + +--- + +# 条件允许要展开 + +
+ +| 条件类型 | 示例 | 验收方式 | +|---|---|---| +| 数据范围 | 只能看本人、本人团队、本人负责的工单 | 不同账号列表数量不同 | +| 状态限制 | 只有待验证才能关闭 | 非目标状态接口返回拒绝 | +| 角色叠加 | 负责人兼管理员时按更高权限 | 用双角色账号验证 | +| 字段限制 | 研发只能改处理说明,不能改优先级 | 提交非法字段被拒绝 | +| 时间限制 | 已关闭工单超过窗口不能重开 | 超时重开失败 | + +
+ + --- @@ -60,13 +117,15 @@ AI 可以生成初稿,但最终必须由业务和团队确认。 管理员、负责人、研发、测试、只读用户。 功能点: -查看列表、查看详情、新增、编辑、删除、指派、提交验证、关闭、驳回、查看审计记录、查看看板。 +查看列表、查看详情、新增、编辑、删除、指派、 +提交验证、关闭、驳回、查看审计记录、查看看板。 要求: -1. 区分允许、禁止、条件允许。 -2. 条件允许必须说明条件。 -3. 每个敏感操作给出验收方式。 -4. 不要直接生成代码。 +1. 区分允许、拒绝、条件允许。 +2. 条件允许必须展开为数据、状态、字段或时间条件。 +3. 每个敏感操作给出后端接口验收方式。 +4. 标注哪些权限属于业务待确认。 +5. 不要直接生成代码。 ``` @@ -74,4 +133,29 @@ AI 可以生成初稿,但最终必须由业务和团队确认。 + +--- + +# AI 容易漏掉的权限风险 + +
+ +| 风险 | 表现 | Review 问法 | +|---|---|---| +| 前端按钮替代权限 | 按钮隐藏,但接口可直接调 | 无权限账号直接请求会怎样 | +| 数据范围缺失 | 所有人看到同一批工单 | 普通用户能否看到他人数据 | +| 条件允许泛化 | “条件允许”被实现成允许 | 条件在哪里校验 | +| 审计权限遗漏 | 审计日志对不该看的人开放 | 谁能看 old/new value | +| 删除权限过宽 | 负责人或研发能删除工单 | 删除是否只限管理员 | + +
+ +