@zat-design/sisyphus-scene 4.6.3-beta.2 → 4.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/mcp/sisyphus-react.json +1 -1
- package/package.json +1 -1
- package/skills/sisyphus-scene/SKILL.md +48 -9
- package/skills/sisyphus-scene/agents/openai.yaml +3 -3
- package/skills/sisyphus-scene/assets/ai-contract.json +1 -1
- package/skills/sisyphus-scene/evals/fixtures/review-gate/invalid/components/DetailPanel/index.tsx +11 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate/invalid/config/index.tsx +18 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate/invalid/constants/index.ts +1 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate/invalid/index.tsx +36 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate/invalid/services/index.ts +3 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate/invalid/utils/request.ts +10 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate/valid/components/DetailPanel/index.tsx +24 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate/valid/index.tsx +35 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate/valid/services/index.ts +3 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate/valid/shared/constants.ts +1 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate/valid/shared/consumer-a.ts +3 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate/valid/shared/consumer-b.ts +3 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate-v1.5/invalid/config/formColumns.ts +13 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate-v1.5/invalid/flowHelper.ts +3 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate-v1.5/invalid/package.json +7 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate-v1.5/valid/components/DirectControl.tsx +7 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate-v1.5/valid/config/formColumns.ts +20 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate-v1.5/valid/context/BusinessContext.tsx +3 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate-v1.5/valid/hooks/useBusinessState.ts +3 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate-v1.5/valid/package.json +7 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate-v1.5/valid/protocol.ts +4 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate-v1.5/valid/services/index.ts +1 -0
- package/skills/sisyphus-scene/evals/fixtures/review-gate-v1.5/valid/utils/flowHelper.ts +3 -0
- package/skills/sisyphus-scene/evals/fixtures/valid-decisions.json +4 -4
- package/skills/sisyphus-scene/evals/review-gate-cases.yaml +140 -0
- package/skills/sisyphus-scene/evals/review-routing-cases.yaml +33 -0
- package/skills/sisyphus-scene/evals/trigger-cases.yaml +3 -0
- package/skills/sisyphus-scene/references/approved-examples.yaml +1 -1
- package/skills/sisyphus-scene/references/platform-claude.md +7 -0
- package/skills/sisyphus-scene/references/platform-codex.md +7 -0
- package/skills/sisyphus-scene/references/platform-cursor.md +7 -0
- package/skills/sisyphus-scene/references/review-guard.md +151 -0
- package/skills/sisyphus-scene/references/review-standard.md +130 -0
- package/skills/sisyphus-scene/references/validation-rules.md +6 -5
- package/skills/sisyphus-scene/references/workflow.md +24 -0
- package/skills/sisyphus-scene/scripts/evaluate-review-gate.mjs +222 -0
- package/skills/sisyphus-scene/scripts/validate-skill.mjs +43 -1
|
@@ -7,7 +7,7 @@ examples: []
|
|
|
7
7
|
# status: approved
|
|
8
8
|
# application: xk-wjs-claim-web
|
|
9
9
|
# capabilities: [search-form, data-table, table-tabs, drawer-form]
|
|
10
|
-
# sisyphus_version: "4.6.3
|
|
10
|
+
# sisyphus_version: "4.6.3"
|
|
11
11
|
# antd_range: ">=6 <7"
|
|
12
12
|
# paths:
|
|
13
13
|
# - src/pages/litigationManagement/index.tsx
|
|
@@ -7,3 +7,10 @@
|
|
|
7
7
|
- Ant Design MCP 不可用时,通过 Context7 查询 antd 6 官方文档。
|
|
8
8
|
- 读取项目 `CLAUDE.md`,同时显式检查 `AGENTS.md` 和 `.cursor/rules`;同级冲突时停止。
|
|
9
9
|
- 不使用 Claude 专属 frontmatter 驱动共享核心行为,保证同一 SKILL.md 可被其他平台读取。
|
|
10
|
+
|
|
11
|
+
## 规范门卫
|
|
12
|
+
|
|
13
|
+
- 明确“审查 + 路径”时直接进入 `path-review`,不调用 Scene 生成和 MCP 链路;仍使用平台原生独立只读子代理。
|
|
14
|
+
- `validate-scaffold.mjs` 通过后,使用平台原生的子代理能力创建独立只读审查子代理,并加载 `references/review-guard.md`。
|
|
15
|
+
- 向子代理传入用户需求与豁免摘要、`SceneSpec`、本次文件清单、项目根目录、目标目录、`ProjectProfile`、`ComponentDecision` 和审查轮次;不传入修改权限。
|
|
16
|
+
- 无法调用平台原生子代理时阻断交付,不得降级为主 Agent 自审。
|
|
@@ -8,3 +8,10 @@
|
|
|
8
8
|
- `agents/openai.yaml` 只承载 UI 元数据。当前 Sisyphus MCP 是本地 stdio 命令,没有可验证的 streamable HTTP URL,因此不伪造 Codex MCP dependency。
|
|
9
9
|
- 读取最近层级的 `AGENTS.md`,并显式检查 `CLAUDE.md` 和 `.cursor/rules`。
|
|
10
10
|
- 修改已有代码符号前遵循项目 GitNexus impact 约束,完成后执行 detect changes。
|
|
11
|
+
|
|
12
|
+
## 规范门卫
|
|
13
|
+
|
|
14
|
+
- 明确“审查 + 路径”时直接进入 `path-review`,不调用 Scene 生成和 MCP 链路;仍使用平台原生独立只读子代理。
|
|
15
|
+
- `validate-scaffold.mjs` 通过后,使用平台原生的子代理能力创建独立只读审查子代理,并加载 `references/review-guard.md`。
|
|
16
|
+
- 向子代理传入用户需求与豁免摘要、`SceneSpec`、本次文件清单、项目根目录、目标目录、`ProjectProfile`、`ComponentDecision` 和审查轮次;明确其只读职责,不授予修改权限。
|
|
17
|
+
- 无法调用平台原生子代理时阻断交付,不得降级为主 Agent 自审。
|
|
@@ -7,3 +7,10 @@
|
|
|
7
7
|
- 读取并应用 `.cursor/rules/*.mdc`,同时检查项目根 `AGENTS.md` 与 `CLAUDE.md`。
|
|
8
8
|
- Cursor Rules 是持久项目规范,Skill 是按需工作流;不要把整份团队规范复制进 SKILL.md。
|
|
9
9
|
- MCP 工具未出现或缓存版本与目标包不一致时,停止并要求刷新 MCP,不使用旧缓存继续生成。
|
|
10
|
+
|
|
11
|
+
## 规范门卫
|
|
12
|
+
|
|
13
|
+
- 明确“审查 + 路径”时直接进入 `path-review`,不调用 Scene 生成和 MCP 链路;仍使用平台原生独立只读子代理。
|
|
14
|
+
- `validate-scaffold.mjs` 通过后,使用平台原生的子代理能力创建独立只读审查子代理,并加载 `references/review-guard.md`。
|
|
15
|
+
- 向子代理传入用户需求与豁免摘要、`SceneSpec`、本次文件清单、项目根目录、目标目录、`ProjectProfile`、`ComponentDecision` 和审查轮次;门卫只返回审查证据和修复计划。
|
|
16
|
+
- 无法调用平台原生子代理时阻断交付,不得降级为主 Agent 自审。
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Sisyphus CR 规范门卫
|
|
2
|
+
|
|
3
|
+
## 来源与定位
|
|
4
|
+
|
|
5
|
+
- 来源:`sisyphus-cr@1.5.0`,快照日期为 2026-08-20;源 `SKILL.md` SHA-256 为 `1b9730edad78a754de2320938dc7969d0f0b8084781933d802a75af5a793c34d`,源 `CHANGELOG.md` SHA-256 为 `ed2a11215d7894e70584cd383828d1cf2d6d5764631b624c198d12a90a2afae0`,源审查基线 SHA-256 为 `361e3d818a7ed07677db5c9415d244bd5cc895ffccf6d262ef51a671a49f38e3`。
|
|
6
|
+
- 本文件是 Scene 包内可独立分发的规则副本,不在运行时查找或调用用户全局 `sisyphus-cr`。
|
|
7
|
+
- `references/review-standard.md` 是同一快照的完整通用基线;门卫必须完整读取 `references/review-standard.md`,不能只依赖本文件的核心摘要。
|
|
8
|
+
- 门卫必须由独立只读子代理执行。门卫只审查并给出修复计划;主 Scene Agent 才能修改代码。
|
|
9
|
+
- 门卫补充 `validate-scaffold.mjs` 难以判断的语义和调用链问题,不重复替代版本、组件证据、目录和函数语法等确定性校验。
|
|
10
|
+
|
|
11
|
+
## 输入契约
|
|
12
|
+
|
|
13
|
+
主 Scene Agent 必须先声明 `reviewMode`。两种模式共享项目根目录、目标路径、本次文件清单、用户需求与豁免摘要、完整 `ProjectProfile` 和规则来源。
|
|
14
|
+
|
|
15
|
+
- `generation-gate`:额外提供本轮 `SceneSpec`、全部 `ComponentDecision`、当前审查轮次,以及复审时的上一轮问题和修复摘要。
|
|
16
|
+
- `path-review`:提供用户明确指定的文件或目录绝对路径;不建立 `SceneSpec`,不需要 `ComponentDecision`,审查轮次固定为 `0`。
|
|
17
|
+
|
|
18
|
+
输入缺失、路径不存在或不可读时,结论为“无法判断”。不得通过扩大范围或猜测内容补齐证据。
|
|
19
|
+
|
|
20
|
+
## 范围与权限
|
|
21
|
+
|
|
22
|
+
- 只报告本次生成或修改的文件清单内的问题。
|
|
23
|
+
- `path-review` 的文件目标只报告该文件问题;目录目标递归审查目录内源码,并应用相同忽略目录规则。
|
|
24
|
+
- 可以读取清单中文件的直接 import、类型、配置、项目指令、相邻 service 和统一请求层,以证明规则、API 或调用关系;不得把问题扩大到清单之外。
|
|
25
|
+
- 忽略 `.git`、`node_modules`、`dist`、`build`、`coverage`、`.umi`、缓存和生成目录。
|
|
26
|
+
- 严禁编辑、创建或删除文件,严禁生成补丁,严禁运行格式化、`lint --fix`、安装、构建或浏览器验收。
|
|
27
|
+
- 不得自动进入修复阶段,不得把审查任务转交回主 Agent,也不得降级为主 Agent 自审。
|
|
28
|
+
|
|
29
|
+
## 规则优先级
|
|
30
|
+
|
|
31
|
+
冲突时按以下顺序决定适用规则:
|
|
32
|
+
|
|
33
|
+
1. 用户本次明确提出的需求和豁免。
|
|
34
|
+
2. 本门卫的 Sisyphus 核心边界。
|
|
35
|
+
3. 目标路径最近的 `AGENTS.md`、`CLAUDE.md`、`.cursor/rules` 和它们引用的完整规范。
|
|
36
|
+
4. 目标项目 `package.json`、TypeScript 配置,以及实际安装版本的 Sisyphus/Ant Design 类型与 API。
|
|
37
|
+
5. `references/review-standard.md` 的完整通用审查基线。
|
|
38
|
+
|
|
39
|
+
至少通过以下一项确认目标属于 Sisyphus 项目:最近 `package.json` 声明 `@zat-design/sisyphus-react`、清单内源码直接导入该包,或项目规范明确要求使用该组件库。证据不足时结论为“无法判断”。
|
|
40
|
+
|
|
41
|
+
## 核心审查规则
|
|
42
|
+
|
|
43
|
+
### 目录与职责
|
|
44
|
+
|
|
45
|
+
- 先枚举页面或组件根目录中的非入口源码,沿导出、直接依赖和调用方判定职责。纯函数工具、DTO/payload 转换、解析与序列化、克隆、布局和历史计算进入 `utils/`;类型、配置/注册表、服务、组件和 Context 进入各自责任层。不得以根目录 `utils.ts` 或业务化文件名规避。
|
|
46
|
+
- React 组件、状态 Hook、请求函数以及项目明确独立的协议/基础设施模块不机械移入 `utils/`;混合文件只拆分工具职责。
|
|
47
|
+
- 普通页面或组件模块不建立模块私有 `constants` 或 `constants.ts`。固定选项、业务码值和静态配置并入 `config`;只在一个实现中使用的常量留在责任文件;只有被多个模块真实复用时才上收到共享常量层。
|
|
48
|
+
- `index.tsx` 保持编排职责,不承载大段静态配置、传输定义或 DTO 清洗。
|
|
49
|
+
- 页面独占接口进入所属 `services`,纯 payload 转换进入 `utils`;不为形式完整创建空目录。
|
|
50
|
+
- 子组件、样式、类型文件和 barrel 遵循 `ProjectProfile.fileStructureAdapter` 及其证据文件。
|
|
51
|
+
|
|
52
|
+
### 状态、loading 与自有组件 API
|
|
53
|
+
|
|
54
|
+
- ahooks `useRequest`、`useAntdTable` 等已经提供的请求 loading 必须直接消费,不得复制到本地 state 后手工切换。
|
|
55
|
+
- loading 或其他非函数业务输入跨自有组件传递时必须收敛到对象型 `dataSource`;接收组件再把它传给内部第三方组件的公开 loading API。
|
|
56
|
+
- antd、Sisyphus 等第三方组件保持公开的 `loading`、`spinning`、`confirmLoading` API,不机械包成 `dataSource`。
|
|
57
|
+
- `form`、`mode`、`open`、`disabled`、`value` 等控制属性及 `onXxx` 回调保持平铺。
|
|
58
|
+
- 非请求业务 loading 允许存在,但仍需根据来源、切换者和消费点判断,不得只按变量名判错。
|
|
59
|
+
|
|
60
|
+
### 表单、表格与配置
|
|
61
|
+
|
|
62
|
+
- Sisyphus columns 的字段值变化回调使用 `fieldProps.onFieldChange`;直接控件的公开 `onChange` 保持不变。
|
|
63
|
+
- 简单必填只配置 `required: true`;自定义校验器、正则、长度和跨字段约束等复杂校验继续使用 `rules`。
|
|
64
|
+
- columns 字段已有 `label` 时不重复配置默认 placeholder;用户要求、项目规则或特殊业务语义明确时保留。
|
|
65
|
+
- 最终由 `ProForm`、`ProDrawerForm` 等 Sisyphus 表单消费的实例必须由 `ProForm.useForm()` 创建;只由纯 antd Form 消费时保留 `Form.useForm()`。
|
|
66
|
+
- 表单字段不在页面 state 中镜像第二份值;可派生数据保持为派生值。
|
|
67
|
+
- 含 JSX、操作按钮、页面回调、权限或运行时 loading 的表格列留在消费页面;`config` 只保存可复用的基础列,操作列不得进入配置工厂。
|
|
68
|
+
- 固定配置和动态配置工厂遵循项目命名及类型规则,不因个人偏好制造问题。
|
|
69
|
+
|
|
70
|
+
### 服务、类型与数据适配
|
|
71
|
+
|
|
72
|
+
- 请求参数、响应和 DTO 使用明确类型,不用 `any` 绕过契约;纯类型使用 `import type`。
|
|
73
|
+
- service 只表达传输契约,不引用 Form、message 或页面状态,不吞异常或伪装成功。
|
|
74
|
+
- 项目要求 service 注释时,JSDoc、导出声明和相邻 service 块分别独占行且块间保留空行。
|
|
75
|
+
- 动态 URL 片段使用 `encodeURIComponent`;标识字段按后端契约传递,不因类型相同而混用。
|
|
76
|
+
- payload 转换移除界面临时字段,并清理当前条件下不应提交的互斥字段。
|
|
77
|
+
|
|
78
|
+
### 错误处理与异步安全
|
|
79
|
+
|
|
80
|
+
- 接口失败统一展示响应中的 `message`。统一请求层已经反馈时,页面或组件不得再追加 `message.error`、`notification.error` 等重复失败提示。
|
|
81
|
+
- 统一请求层没有负责错误反馈时,页面保留可观察的真实失败反馈是合理场景,不得误报。
|
|
82
|
+
- 保存、删除或状态更新只在真实成功后关闭界面、提示成功并刷新。
|
|
83
|
+
- 弹层可快速关闭或切换记录时,旧请求不得覆盖新视图;连续提交或状态切换必须防止重复请求。
|
|
84
|
+
- 禁止空 catch、静默失败、裸 `void promise()` 掩盖异步结果,以及失败后伪装成功。
|
|
85
|
+
|
|
86
|
+
### 导入、样式与测试
|
|
87
|
+
|
|
88
|
+
- import 分组、路径别名、类型导入、类名风格和测试目录以目标项目最近规范为准。
|
|
89
|
+
- 测试覆盖 payload 适配、动态列条件、service 契约、竞态保护和高风险操作门禁等可观察行为;只匹配标题、注释或实现措辞不能证明行为正确。
|
|
90
|
+
|
|
91
|
+
## 证据与误报控制
|
|
92
|
+
|
|
93
|
+
- 每个问题必须包含清单内真实的 `文件:行号`、适用规则、证据和可观察影响。
|
|
94
|
+
- 文本命中只能证明结构事实;loading、表单、错误提示和异步问题必须沿实际调用链确认来源与消费点。
|
|
95
|
+
- 同一根因的多个现象合并为一个问题,不按命中次数重复计数。
|
|
96
|
+
- 不推广样板页面的字段、接口、字典、文案、分页、固定宽度或弹层数量。
|
|
97
|
+
- 第三方 loading、纯 antd Form、真实共享常量、非请求业务 loading,以及没有全局错误反馈时的页面失败提示都是必须保留的合理反例。
|
|
98
|
+
|
|
99
|
+
## 问题分级
|
|
100
|
+
|
|
101
|
+
- `P0`:可能造成错误提交、数据丢失、竞态覆盖或高风险操作失控。
|
|
102
|
+
- `P1`:违反明确项目规范、实际组件 API、传输契约或职责边界。
|
|
103
|
+
- `P2`:不影响当前正确性,但偏离适用的 Sisyphus 标杆并增加维护成本。
|
|
104
|
+
|
|
105
|
+
不得把纯个人偏好列为问题。没有明确规则或基线不适用时标记为不适用,不进入问题清单。
|
|
106
|
+
|
|
107
|
+
## 唯一输出格式
|
|
108
|
+
|
|
109
|
+
最终回答只能包含以下三个一级部分,不增加其他一级标题:
|
|
110
|
+
|
|
111
|
+
### 规范审查
|
|
112
|
+
|
|
113
|
+
先以独占一行的 `门卫元数据:<JSON>` 输出机器元数据,再写审查说明,并以独占一行的 `结论:<值>` 输出结论。值只能是:`通过`、`部分通过`、`不通过`、`无法判断`。
|
|
114
|
+
|
|
115
|
+
元数据必须符合:
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
门卫元数据:{"reviewId":"CRG-<唯一编号>","reviewer":"sisyphus-cr-readonly-subagent","reviewMode":"<generation-gate|path-review>","ruleVersion":"sisyphus-cr@1.5.0","reviewRound":<0|1|2>,"targetPath":"<目标绝对路径>","files":["<本次文件绝对路径>"],"ruleSources":["<规则来源>"],"sisyphusEvidence":"<Sisyphus 识别证据>"}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`reviewRound` 必须与当前轮次一致;`targetPath` 和 `files` 必须位于项目根目录内,文件清单不能为空。不得输出缺少真实目标、规则来源或 Sisyphus 证据的空壳“通过”报告。
|
|
122
|
+
|
|
123
|
+
### 问题清单
|
|
124
|
+
|
|
125
|
+
按 `P0 → P1 → P2` 排序:
|
|
126
|
+
|
|
127
|
+
| 编号 | 优先级 | 文件:行号 | 违反规则 | 证据与影响 |
|
|
128
|
+
| --- | --- | --- | --- | --- |
|
|
129
|
+
|
|
130
|
+
问题使用稳定编号 `CR-001`、`CR-002`。未发现问题时只写“未发现规范问题”。
|
|
131
|
+
|
|
132
|
+
### 修复计划
|
|
133
|
+
|
|
134
|
+
按执行依赖排序,每一步关联问题编号,写明修改目标、调整方向和验证方式。计划必须保持现有路由、接口、权限、公开组件 API 和无关业务行为。通过时写“无需修复”;无法判断时写“证据不足,暂不制定修复计划”。
|
|
135
|
+
|
|
136
|
+
## 主 Agent 修复闭环
|
|
137
|
+
|
|
138
|
+
1. `validate-scaffold.mjs` 通过后,主 Agent 创建独立只读子代理并传入上述完整输入。
|
|
139
|
+
2. 把门卫三段式输出保存到任务临时目录,并运行 `node scripts/evaluate-review-gate.mjs --mode generation-gate --root <项目根目录> --report-root <任务临时目录> --report <报告文件> --round <0|1|2>`。
|
|
140
|
+
3. 只有门禁脚本返回 `status: passed` 才算门卫通过;输出自相矛盾、缺少固定段落或缺少结论都会阻断。
|
|
141
|
+
4. 门禁脚本返回 `status: repair` 时,主 Agent 按问题编号做最小修复;修复权限只覆盖本次文件清单,并使用脚本返回的 `nextRound`。
|
|
142
|
+
5. 每次修复后重新运行 `validate-scaffold.mjs`,再创建新的独立只读子代理复审,不能复用旧结论。
|
|
143
|
+
6. 自动修复最多 2 轮。门禁脚本返回 `status: blocked` 时立即停止;任何 P0、P1、P2 遗留问题和“无法判断”都会进入该状态。
|
|
144
|
+
7. 修复若需要改动文件清单之外的源码、公共 API、路由、权限、依赖或全局布局,立即停止并报告,不自动扩大权限。
|
|
145
|
+
8. 门卫通过后才运行目标项目 typecheck、lint、相关测试、页面验收和 GitNexus `detect_changes`。
|
|
146
|
+
|
|
147
|
+
三种允许的闭环结果:首次审查通过;第一或第二轮修复后复审通过;两轮后仍不通过并停止报告。不存在带 P2 问题继续交付或平台无法创建子代理时降级自审的路径。
|
|
148
|
+
|
|
149
|
+
## 指定路径只读审查
|
|
150
|
+
|
|
151
|
+
`path-review` 只执行一次独立只读审查:解析并验证用户指定路径,读取最近项目规则、依赖、类型和必要调用链,输出三段式报告,再运行 `evaluate-review-gate.mjs --mode path-review --round 0` 校验报告身份、规则版本和范围。合法报告返回 `status: reviewed`;无论发现 P0、P1 或 P2,都不进入 repair、不修改文件。路径缺失、不可读或缺少 Sisyphus 识别证据时输出“无法判断”。
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Sisyphus 代码审查完整基线
|
|
2
|
+
|
|
3
|
+
本文件是 `sisyphus-cr@1.5.0` 在 2026-08-20 的包内规则快照,源 `sisyphus-review-standard.md` SHA-256 为 `361e3d818a7ed07677db5c9415d244bd5cc895ffccf6d262ef51a671a49f38e3`。目标项目没有更具体规则时使用;它不替代目标路径最近的项目指令,也不把某个样板页面的业务字段、接口或交互细节推广为通用规范。
|
|
4
|
+
|
|
5
|
+
## 规则等级
|
|
6
|
+
|
|
7
|
+
- **项目硬规则**:目标项目文档、类型声明或 API 明确要求。违反时通常为 `P1`,影响数据正确性时为 `P0`。
|
|
8
|
+
- **适用基线**:目标代码具备对应职责且项目没有相反约定时使用。违反时按实际影响评为 `P1` 或 `P2`。
|
|
9
|
+
- **标杆建议**:仅改善一致性或维护性,最多为 `P2`。
|
|
10
|
+
- **不适用**:目标没有对应职责,或项目明确采用其他合理方案,不进入问题清单。
|
|
11
|
+
|
|
12
|
+
## 1. 项目识别与审查边界
|
|
13
|
+
|
|
14
|
+
- 必须先确认目标使用 `@zat-design/sisyphus-react`,并读取最近项目规则和实际依赖版本。
|
|
15
|
+
- 组件 API 以目标项目的类型声明和文档为准,不能凭记忆套用旧版 Sisyphus 或 Ant Design API。
|
|
16
|
+
- 只报告文件清单内的问题。清单外文件可用于证明调用关系、类型契约或规则来源,不扩大整改范围。
|
|
17
|
+
- 生成目录、构建产物、依赖目录和缓存不参与源码规范审查。
|
|
18
|
+
|
|
19
|
+
## 2. 页面目录与职责分层
|
|
20
|
+
|
|
21
|
+
- 查询列表页按职责拆分入口、配置、服务、页面类型和测试;不为形式完整创建未使用的空目录。
|
|
22
|
+
- `index.tsx` 是编排层,不承载大段静态配置、传输定义或 DTO 清洗。
|
|
23
|
+
- 页面或组件根目录中的非入口源码必须按实际内容归位:纯函数工具、DTO/payload 转换、解析与序列化、克隆、布局、历史计算等进入 `utils/`;页面共享类型进入最近项目约定的类型层;静态配置、选项和注册表进入 `config`;请求进入 `services`;React 组件与 Context 分别进入组件和上下文责任层。不得用根目录 `utils.ts`、`flowHelper.ts` 或业务化文件名平铺工具实现,也不得因文件只有一个调用方而豁免归位。
|
|
24
|
+
- 普通页面或组件模块不建立模块私有 `constants`。本模块固定选项、业务码值和静态配置并入 `config`;只在单一实现使用的常量留在责任文件;被多个模块真实共享时才上收到项目共享层。基础设施、协议、全局 API 路径和统一码表不适用此限制。
|
|
25
|
+
- 请求函数放在所属页面或领域的 `services`;页面独占接口不被其他页面跨私有目录引用,两处以上真实复用时再上收。
|
|
26
|
+
- ProForm、ProTable、ProEditTable 的基础配置放在 `config`;纯 payload 适配和业务判定放在 `utils`;页面共享类型放在统一类型文件。
|
|
27
|
+
- 新增或重构的自有子组件使用同名目录和 `index.tsx`,样式就近放置。组件聚合出口使用显式命名导出;项目要求场景注释时,每个导出紧邻真实用途说明,不使用无说明的 `export *`。
|
|
28
|
+
- 目录名或文件长度不能单独证明问题,必须结合模块职责和引用关系判断。
|
|
29
|
+
- 只在目标确实是业务页面或发生重构时应用完整目录基线;小型叶子组件不要求页面全套目录。
|
|
30
|
+
- 先枚举模块根目录文件,再读取其导出、直接依赖和调用方判定职责;文件名只能发现候选,不能单独证明它是入口、配置或工具。业务化命名文件若主要导出无副作用纯函数,仍按工具文件审查。
|
|
31
|
+
- 不把 React 组件、状态 Hook、请求函数或项目明确要求独立存在的协议/基础设施模块机械移动到 `utils/`;混合文件只拆分其中的工具职责,其余内容保留在对应责任层。
|
|
32
|
+
- “文件较长”本身不是问题,只有出现职责混杂、跨层依赖或难以独立验证时才列项。
|
|
33
|
+
|
|
34
|
+
## 3. 自有组件 API 与职责边界
|
|
35
|
+
|
|
36
|
+
- 自有组件的非函数业务数据收敛到对象型 `dataSource`。
|
|
37
|
+
- ahooks `useRequest`、`useAntdTable` 等已经提供的请求 loading 必须直接消费,不得复制进本地 state 后手工切换。
|
|
38
|
+
- loading 作为非函数输入跨自有组件传递时,必须随关联数据收敛到 `dataSource`;接收组件可以再传给第三方公开 loading API。
|
|
39
|
+
- antd、Sisyphus 等第三方组件保持其 `loading`、`spinning`、`confirmLoading` API。
|
|
40
|
+
- 不由请求 Hook 管理的本地计算、复合流程或逐行操作锁仍需按真实来源审查,不机械归类为请求 loading。
|
|
41
|
+
- `form`、`mode`、`open`、`disabled`、`value`、`defaultValue` 等控制属性保持平铺;回调以 `onXxx` 命名并平铺。
|
|
42
|
+
- 页面拥有请求、保存、删除、反馈、刷新和跨弹层协调;子组件拥有展示或表单装配并通过事件通知页面。
|
|
43
|
+
- 含 JSX、操作按钮、页面回调、权限或运行时 loading 的列留在消费页面;`config` 只保留可复用基础列,操作列不得进入配置工厂。
|
|
44
|
+
- 先追溯 loading 的真实来源和切换者,并检查所有消费点,不能只按变量名或声明位置判断;镜像字段和手工切换代码合并为同一根因。
|
|
45
|
+
- 根据导入来源区分自有组件与第三方组件:相对路径、项目别名或项目内部模块按自有组件审查,外部依赖包按第三方公开 API 审查。
|
|
46
|
+
- 直接把 Hook loading 传给第三方组件不列问题;作为平铺 prop 传给自有组件则违反组件 API 边界,即使没有镜像本地 state。
|
|
47
|
+
- 非请求业务忙碌状态跨自有组件时仍收敛到 `dataSource`,但不得机械改成请求 Hook loading。
|
|
48
|
+
- 新增等列表级动作优先放入 ProTable 工具栏能力,避免页面标题区和表格区重复表达同一动作。
|
|
49
|
+
- 编辑表单适合抽屉时优先使用 ProDrawerForm 的标准提交与取消能力;只读详情按信息层级选择容器,不强制所有页面使用同一种弹层。
|
|
50
|
+
|
|
51
|
+
## 4. 状态、表单与副作用
|
|
52
|
+
|
|
53
|
+
- 同一生命周期内两个及以上相关本地状态使用项目约定的合并状态方案;采用 `useSetState` 时使用能表达页面职责的状态类型。
|
|
54
|
+
- 单一 primitive、第三方专用 hook、Form 实例和不驱动渲染的 ref 不为形式统一强行合并。
|
|
55
|
+
- ProTable 列表、分页与 loading 由 `useAntdTable` 或等价方案管理;详情和辅助查询使用 `useRequest({ manual: true })` 或等价方案。
|
|
56
|
+
- 同一请求不得同时使用 Hook loading 和页面 state 中手工切换的 loading;请求生命周期由 Hook 收敛,页面只处理业务数据、竞态结果和必要界面状态。
|
|
57
|
+
- 表单字段由 Form 管理,不在页面 state 中镜像第二份字段值。
|
|
58
|
+
- 最终由 Sisyphus 表单消费的实例使用 `ProForm.useForm()`;只有最终由纯 antd Form 消费时才保留 `Form.useForm()`。确认误用且无高优先级豁免时按 `P2` 报告并计划改为 `ProForm.useForm()`。
|
|
59
|
+
- 不驱动渲染的请求序号、重复提交锁和进行中 key 集合放在 ref 或等价非渲染状态中。
|
|
60
|
+
- effect 集中在组件返回之前,不散落在业务函数之间;由现有值可计算的数据保持派生值。
|
|
61
|
+
- 不使用裸 `void promise()` 掩盖异步结果,不写无意义裸表达式。
|
|
62
|
+
- 弹层可快速关闭或切换记录、旧请求可能覆盖新视图时,必须废弃或取消过期响应,但不限定必须使用请求 id;连续提交或状态切换必须阻止重复请求并反馈失败。
|
|
63
|
+
|
|
64
|
+
## 5. 表单、表格与编辑表格配置
|
|
65
|
+
|
|
66
|
+
- 固定配置按组件类型命名为 `formColumns`、`tableColumns`、`editTableColumns`;同文件多组同类配置增加用途前缀。
|
|
67
|
+
- 动态工厂命名为 `create<Business><Form|Table|EditTable>Columns`,参数使用对象解构和具名 interface,并标注返回类型。
|
|
68
|
+
- 即使只有一个运行时参数,也避免位置参数导致后续扩展产生顺序耦合。
|
|
69
|
+
- ProForm 使用 Sisyphus 提供的 columns 配置能力;能用组件原生 `valueType`、`component` 或 `viewRender` 表达时,不重复手写低层渲染。
|
|
70
|
+
- Sisyphus columns 表单字段需要响应值变化时,必须在 `fieldProps` 中使用 `onFieldChange`,不得使用 `onChange`。直接渲染的 antd、React 或其他第三方控件仍保持其公开 `onChange` API。
|
|
71
|
+
- 简单必填字段只配置 `required: true`,不得再重复配置仅表达简单必填的 `rules`。只有字段存在自定义校验器、正则、长度或跨字段约束时才使用 `rules`,且不得为覆盖必填文案重复建模同一约束。
|
|
72
|
+
- Sisyphus columns 表单字段已有 `label` 且没有特别说明时,不配置 `fieldProps.placeholder`,由组件按字段类型和 `label` 自动生成默认提示。只有用户明确要求、最近项目规范规定,或默认文案不能表达必要业务语义时才保留自定义 placeholder。
|
|
73
|
+
- 基础数据列不随意固定宽度;操作列、固定列或有布局证据的列才配置宽度。
|
|
74
|
+
- `config` 只保留不依赖 JSX、页面回调、权限或运行时 loading 的基础列;页面展开基础列后追加业务渲染列。
|
|
75
|
+
- 不机械改名第三方 `columns` props、`fieldProps.columns`、遍历参数或测试抽象集合。
|
|
76
|
+
- 只使用组件原生 `valueType` 等声明式能力的基础列不因视觉格式化被移出 `config`;包含 JSX、操作按钮或页面运行时交互依赖的 `render`、`viewRender` 等配置必须留在消费页面。
|
|
77
|
+
- 发现 Sisyphus columns 的 `fieldProps.onChange` 时,必须确认回调用于字段值变化;满足条件时按 `P1` 报告并计划替换为 `onFieldChange`,不能只凭文本命中判错。
|
|
78
|
+
- 字段同时配置 `required: true` 和仅含简单必填的 `rules` 时,按 `P1` 合并为一个重复校验问题;复杂规则只移除重复必填部分,不删除自定义校验。
|
|
79
|
+
- `fieldProps.placeholder` 仅重复“请输入/请选择 + label”时按 `P2` 报告并计划删除;存在特殊格式、业务含义或交互指引时不列问题。
|
|
80
|
+
|
|
81
|
+
## 6. 类型、服务与数据适配
|
|
82
|
+
|
|
83
|
+
- 请求参数、响应和 DTO 使用明确类型,不以 `any` 绕过契约;纯类型使用 `import type`。
|
|
84
|
+
- service 使用项目统一 request,只表达传输契约,不引用 Form、message 或页面状态。
|
|
85
|
+
- 动态 URL 片段使用 `encodeURIComponent`;id、业务 key 和其他标识按后端契约分别传递,不能因类型相同而混用。
|
|
86
|
+
- 项目要求 service 注释时,注释包含业务动作、全大写 HTTP method、路径与消费语义。
|
|
87
|
+
- 每个 service 的 JSDoc 与 `export` 声明各自独占一行,相邻 service 块之间保留空行;项目没有更严格格式规则时按 `P2` 报告。
|
|
88
|
+
- service 排版修复只调整布局,不改请求方法、路径、参数、返回类型或调用关系。
|
|
89
|
+
- DTO 到 payload 的纯转换放在工具层,移除界面临时字段并清理不应提交的互斥字段。
|
|
90
|
+
- 成功、失败、确认、关闭和刷新反馈由编排层处理;service 不吞异常或伪装成功。
|
|
91
|
+
- 删除、停用或修改会影响已有引用时,先获取影响范围并阻止或确认操作;影响查询失败时不继续高风险动作。
|
|
92
|
+
- 状态接口优先表达目标状态而不是简单取反,防止重试导致结果反转;最终以实际后端契约为准。
|
|
93
|
+
|
|
94
|
+
## 7. 空值、错误与异步安全
|
|
95
|
+
|
|
96
|
+
- 可选数据通过可选链、空值合并或入口默认值兜底;数组默认空数组,展示文案提供可读回退。
|
|
97
|
+
- `0`、空字符串和 `false` 有业务意义时,不用 `||` 错误覆盖。
|
|
98
|
+
- 异步失败必须向上抛出或提供可观察反馈,禁止空 catch 和静默伪成功。
|
|
99
|
+
- 接口失败以响应 `message` 为准。统一请求层已经展示时,页面不得再调用 `message.error`、`notification.error` 等追加固定失败提示。
|
|
100
|
+
- 页面可以恢复 loading、保留弹层或执行必要清理,但不得用固定文案覆盖接口 `message`,也不得为消除重复提示而吞异常。
|
|
101
|
+
- 保存、删除和状态更新只在真实成功后关闭界面、提示成功并刷新。
|
|
102
|
+
- 二次确认的异步保存准确返回成功、取消和失败,避免表单容器误判保存结果;竞态保护覆盖主详情及其关联分页、引用和辅助数据,而不是只保护一个请求。
|
|
103
|
+
- 文本搜索不能单独证明重复错误提示或 loading 镜像,必须沿请求链确认来源与消费点。
|
|
104
|
+
- 已确认统一请求层负责错误反馈后,页面或组件追加固定失败文案按 `P1` 报告。
|
|
105
|
+
- 已确认请求由 Hook 承载后,手工 loading state、状态类型字段和置位代码合并作为一个 `P1` 根因;自有组件平铺 loading prop 及其 Props 声明合并作为另一个 `P1` 根因;第三方公开 loading API 不列问题。
|
|
106
|
+
|
|
107
|
+
## 8. 导入、样式与测试
|
|
108
|
+
|
|
109
|
+
- import 分组、相对路径深度、类型导入、类名风格和测试目录以目标项目最近规范为准。
|
|
110
|
+
- 项目无具体规则时,建议按 React、antd、其他第三方、项目别名、当前目录、父目录、样式分组。
|
|
111
|
+
- 深层相对路径优先使用项目别名;运行时导入与类型导入保持清晰。
|
|
112
|
+
- 自定义类名保持项目风格;采用 kebab-case 的项目中不混入 camelCase、snake_case 或 BEM 拼接。
|
|
113
|
+
- 聚焦测试覆盖 payload 适配、动态列条件、service 方法与路径、竞态保护和高风险操作门禁。
|
|
114
|
+
- 测试证明可观察行为或契约,不只匹配标题、注释或实现措辞。
|
|
115
|
+
|
|
116
|
+
## 9. 不得推广的样板细节
|
|
117
|
+
|
|
118
|
+
- 不推广样板的业务字段、字典 code、接口地址、文案、分页大小和固定宽度。
|
|
119
|
+
- 不要求所有页面具有相同数量的抽屉、弹窗、请求或状态字段。
|
|
120
|
+
- 不强制所有字典使用同一种组件,也不强制所有详情使用同一种展示组件。
|
|
121
|
+
- 不要求所有状态变更都二次确认;只有存在引用、权限、数据破坏或相似风险时才需要保护。
|
|
122
|
+
- 仅因为实现与样板不同不能判错,必须说明适用规则和实际影响。
|
|
123
|
+
|
|
124
|
+
## 10. 证据与修复计划质量
|
|
125
|
+
|
|
126
|
+
- 每条问题至少包含一个文件清单内的 `文件:行号`;清单外证据只用于证明规则或调用关系。
|
|
127
|
+
- 文本搜索只证明结构事实。运行时、类型或数据问题需要调用链、类型声明、接口契约或测试证据。
|
|
128
|
+
- 同一根因导致的多个现象合并为一个问题。
|
|
129
|
+
- 修复计划关联问题编号,保持现有路由、接口、权限、公开组件 API 和无关业务行为。
|
|
130
|
+
- 修复顺序为正确性风险、职责与契约、可维护性,并为每步指定聚焦验证;门卫不在计划中实现代码。
|
|
@@ -40,10 +40,11 @@
|
|
|
40
40
|
## 验证命令
|
|
41
41
|
|
|
42
42
|
1. `validate-scaffold.mjs --profile <ProjectProfile JSON>`
|
|
43
|
-
2.
|
|
44
|
-
3. 目标项目
|
|
45
|
-
4.
|
|
46
|
-
5.
|
|
47
|
-
6.
|
|
43
|
+
2. 按 `references/review-guard.md` 创建独立只读子代理,并使用 `scripts/evaluate-review-gate.mjs` 判定通过、修复或阻断;最多 2 轮自动修复,每轮后重新执行第 1、2 步
|
|
44
|
+
3. 目标项目 typecheck
|
|
45
|
+
4. 目标项目 lint
|
|
46
|
+
5. 相关单元/集成测试
|
|
47
|
+
6. 有截图时进行页面视觉核对
|
|
48
|
+
7. GitNexus detect changes
|
|
48
49
|
|
|
49
50
|
任一检查未执行都必须在交付报告中标注“未验证”,不能省略。
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
- Sisyphus 校准
|
|
9
9
|
- 能力缺口检查
|
|
10
10
|
- Ant Design 与项目组件补全
|
|
11
|
+
- 确定性校验与规范门卫
|
|
11
12
|
- 停止条件
|
|
12
13
|
|
|
13
14
|
## 输入归一化
|
|
@@ -91,6 +92,25 @@ Sisyphus 证据:<查询过的组件/API/demo>
|
|
|
91
92
|
|
|
92
93
|
Ant Design 仍无法覆盖时检索项目公共组件。只使用有源码、调用示例和目标项目兼容证据的组件。修改已有符号前执行 GitNexus impact。
|
|
93
94
|
|
|
95
|
+
## 确定性校验与规范门卫
|
|
96
|
+
|
|
97
|
+
代码生成后先运行 `validate-scaffold.mjs`,传入本轮使用的同一份 `ProjectProfile` 和全部 `ComponentDecision`。确定性校验失败时先修复对应错误,不创建规范门卫。
|
|
98
|
+
|
|
99
|
+
确定性校验通过后读取 `references/review-guard.md` 和 `references/review-standard.md`,并通过平台原生能力创建独立只读规范门卫子代理。输入严格限定为用户需求与豁免摘要、`SceneSpec`、项目根目录、目标目录、本次生成或修改的文件清单、`ProjectProfile`、`ComponentDecision`、审查轮次,以及复审时的上一轮问题和修复摘要。门卫可读取直接依赖和项目规则取证,但只能报告清单内问题,不能编辑文件。
|
|
100
|
+
|
|
101
|
+
门卫输出保存到任务临时目录,再通过 `scripts/evaluate-review-gate.mjs --root <项目根目录> --report-root <任务临时目录>` 校验门卫身份、真实目标与文件清单、规则/Sisyphus 证据、固定段落、结论、问题等级和轮次。主 Agent 不自行解释或放宽不一致的报告。
|
|
102
|
+
|
|
103
|
+
门卫结论的处理规则:
|
|
104
|
+
|
|
105
|
+
- `通过` 且问题清单为“未发现规范问题”:进入项目 typecheck、lint、相关测试、页面验收和 GitNexus 检查。
|
|
106
|
+
- `部分通过` 或 `不通过`:主 Agent 按问题编号在文件清单内执行最小修复,然后重新运行确定性校验并创建新的门卫子代理复审。
|
|
107
|
+
- `无法判断`:立即阻断,不猜测缺失证据。
|
|
108
|
+
- 自动修复最多 2 轮;两轮后 P0、P1、P2 任一遗留问题都阻断交付,不允许忽略 P2。
|
|
109
|
+
- 修复需要变更文件清单之外的源码、公共 API、路由、权限、依赖或全局布局时停止并报告,不扩大范围。
|
|
110
|
+
- 平台无法创建独立只读子代理时阻断;不得降级为主 Agent 自审。
|
|
111
|
+
|
|
112
|
+
每次复审必须创建新子代理并基于当前文件重新取证,不能复用上一轮“通过”或问题清单。
|
|
113
|
+
|
|
94
114
|
## 停止条件
|
|
95
115
|
|
|
96
116
|
出现任一情况即停止代码生成:
|
|
@@ -101,5 +121,9 @@ Ant Design 仍无法覆盖时检索项目公共组件。只使用有源码、调
|
|
|
101
121
|
- 项目指令文件存在同级冲突
|
|
102
122
|
- Sisyphus、Ant Design 和项目组件均无法覆盖必需能力
|
|
103
123
|
- 无法确认会改变公共接口或业务数据流的关键需求
|
|
124
|
+
- 平台无法创建独立只读规范门卫子代理
|
|
125
|
+
- 门卫结论为“无法判断”
|
|
126
|
+
- 最多 2 轮自动修复后仍存在 P0、P1 或 P2 问题
|
|
127
|
+
- 门卫修复要求超出本次生成或修改的文件清单
|
|
104
128
|
|
|
105
129
|
停止时输出已完成的证据、未解决能力、建议的独立前置任务,不用占位代码伪装完成。
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
|
|
7
|
+
const parseArgs = (argv) => {
|
|
8
|
+
const args = {};
|
|
9
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
10
|
+
const token = argv[index];
|
|
11
|
+
if (!token.startsWith('--')) continue;
|
|
12
|
+
const key = token.slice(2);
|
|
13
|
+
const value = argv[index + 1];
|
|
14
|
+
if (!value || value.startsWith('--')) args[key] = true;
|
|
15
|
+
else {
|
|
16
|
+
args[key] = value;
|
|
17
|
+
index += 1;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return args;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const args = parseArgs(process.argv.slice(2));
|
|
24
|
+
const mode = String(args.mode || 'generation-gate');
|
|
25
|
+
const root = path.resolve(String(args.root || process.cwd()));
|
|
26
|
+
const reportRoot = path.resolve(String(args['report-root'] || root));
|
|
27
|
+
const round = Number(args.round);
|
|
28
|
+
const reasons = [];
|
|
29
|
+
const isWithin = (parent, target) => {
|
|
30
|
+
const relative = path.relative(parent, target);
|
|
31
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
if (!args.report) reasons.push('missing-report');
|
|
35
|
+
if (!['generation-gate', 'path-review'].includes(mode)) reasons.push('invalid-mode');
|
|
36
|
+
if (
|
|
37
|
+
!Number.isInteger(round) ||
|
|
38
|
+
round < 0 ||
|
|
39
|
+
round > 2 ||
|
|
40
|
+
(mode === 'path-review' && round !== 0)
|
|
41
|
+
)
|
|
42
|
+
reasons.push('invalid-round');
|
|
43
|
+
|
|
44
|
+
let report = '';
|
|
45
|
+
if (args.report) {
|
|
46
|
+
const reportPath = path.resolve(reportRoot, String(args.report));
|
|
47
|
+
if (!isWithin(reportRoot, reportPath)) reasons.push('report-outside-root');
|
|
48
|
+
else if (!fs.existsSync(reportPath)) reasons.push('report-not-found');
|
|
49
|
+
else {
|
|
50
|
+
try {
|
|
51
|
+
const realReportRoot = fs.realpathSync(reportRoot);
|
|
52
|
+
const realReportPath = fs.realpathSync(reportPath);
|
|
53
|
+
const reportStat = fs.statSync(realReportPath);
|
|
54
|
+
if (!isWithin(realReportRoot, realReportPath)) reasons.push('report-outside-root');
|
|
55
|
+
else if (!reportStat.isFile()) reasons.push('report-unreadable');
|
|
56
|
+
else if (reportStat.size > 1024 * 1024) reasons.push('report-too-large');
|
|
57
|
+
else report = fs.readFileSync(realReportPath, 'utf8');
|
|
58
|
+
} catch {
|
|
59
|
+
reasons.push('report-unreadable');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const headings = ['### 规范审查', '### 问题清单', '### 修复计划'];
|
|
65
|
+
const headingIndexes = headings.map((heading) => report.indexOf(heading));
|
|
66
|
+
if (
|
|
67
|
+
headingIndexes.some((index) => index < 0) ||
|
|
68
|
+
!(headingIndexes[0] < headingIndexes[1] && headingIndexes[1] < headingIndexes[2])
|
|
69
|
+
)
|
|
70
|
+
reasons.push('invalid-report-sections');
|
|
71
|
+
|
|
72
|
+
const allHeadings = [...report.matchAll(/^#{1,6}\s+.+$/gm)].map((match) => match[0]);
|
|
73
|
+
if (allHeadings.length !== headings.length || allHeadings.some((heading, index) => heading !== headings[index]))
|
|
74
|
+
reasons.push('unexpected-report-sections');
|
|
75
|
+
|
|
76
|
+
const hasValidSections = headingIndexes.every((index) => index >= 0);
|
|
77
|
+
const reviewSection = hasValidSections
|
|
78
|
+
? report.slice(headingIndexes[0] + headings[0].length, headingIndexes[1])
|
|
79
|
+
: '';
|
|
80
|
+
const findingsSection = hasValidSections
|
|
81
|
+
? report.slice(headingIndexes[1] + headings[1].length, headingIndexes[2])
|
|
82
|
+
: '';
|
|
83
|
+
const conclusionMatch = reviewSection.match(
|
|
84
|
+
/^结论[::]\s*(通过|部分通过|不通过|无法判断)\s*$/m,
|
|
85
|
+
);
|
|
86
|
+
const conclusion = conclusionMatch?.[1] || null;
|
|
87
|
+
if (!conclusion) reasons.push('missing-conclusion');
|
|
88
|
+
|
|
89
|
+
let metadata = null;
|
|
90
|
+
let realTargetPath = null;
|
|
91
|
+
let targetIsFile = false;
|
|
92
|
+
const reviewedFilePaths = new Set();
|
|
93
|
+
const metadataMatch = reviewSection.match(/^门卫元数据[::]\s*(\{.+\})\s*$/m);
|
|
94
|
+
if (!metadataMatch) reasons.push('missing-review-metadata');
|
|
95
|
+
else {
|
|
96
|
+
try {
|
|
97
|
+
metadata = JSON.parse(metadataMatch[1]);
|
|
98
|
+
} catch {
|
|
99
|
+
reasons.push('invalid-review-metadata');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (metadata) {
|
|
104
|
+
if (!/^CRG-[A-Za-z0-9_-]+$/.test(String(metadata.reviewId || '')))
|
|
105
|
+
reasons.push('invalid-review-id');
|
|
106
|
+
if (metadata.reviewer !== 'sisyphus-cr-readonly-subagent')
|
|
107
|
+
reasons.push('invalid-reviewer');
|
|
108
|
+
if (metadata.reviewMode !== mode) reasons.push('review-mode-mismatch');
|
|
109
|
+
if (metadata.ruleVersion !== 'sisyphus-cr@1.5.0')
|
|
110
|
+
reasons.push('review-rule-version-mismatch');
|
|
111
|
+
if (metadata.reviewRound !== round) reasons.push('review-round-mismatch');
|
|
112
|
+
if (!path.isAbsolute(String(metadata.targetPath || '')))
|
|
113
|
+
reasons.push('invalid-target-path');
|
|
114
|
+
else {
|
|
115
|
+
try {
|
|
116
|
+
const realRoot = fs.realpathSync(root);
|
|
117
|
+
const realTarget = fs.realpathSync(metadata.targetPath);
|
|
118
|
+
if (!isWithin(realRoot, realTarget)) reasons.push('target-outside-root');
|
|
119
|
+
else {
|
|
120
|
+
const targetStat = fs.statSync(realTarget);
|
|
121
|
+
if (!targetStat.isFile() && !targetStat.isDirectory())
|
|
122
|
+
reasons.push('invalid-target-path');
|
|
123
|
+
else {
|
|
124
|
+
realTargetPath = realTarget;
|
|
125
|
+
targetIsFile = targetStat.isFile();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
reasons.push('invalid-target-path');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (!Array.isArray(metadata.files) || metadata.files.length === 0)
|
|
133
|
+
reasons.push('missing-reviewed-files');
|
|
134
|
+
else {
|
|
135
|
+
for (const filePath of metadata.files) {
|
|
136
|
+
if (!path.isAbsolute(String(filePath || ''))) {
|
|
137
|
+
reasons.push('invalid-reviewed-file');
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const realRoot = fs.realpathSync(root);
|
|
142
|
+
const realFile = fs.realpathSync(filePath);
|
|
143
|
+
if (!isWithin(realRoot, realFile) || !fs.statSync(realFile).isFile())
|
|
144
|
+
reasons.push('invalid-reviewed-file');
|
|
145
|
+
else if (
|
|
146
|
+
realTargetPath &&
|
|
147
|
+
(targetIsFile ? realFile !== realTargetPath : !isWithin(realTargetPath, realFile))
|
|
148
|
+
)
|
|
149
|
+
reasons.push('reviewed-file-outside-target');
|
|
150
|
+
else reviewedFilePaths.add(realFile);
|
|
151
|
+
} catch {
|
|
152
|
+
reasons.push('invalid-reviewed-file');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (
|
|
157
|
+
!Array.isArray(metadata.ruleSources) ||
|
|
158
|
+
metadata.ruleSources.length === 0 ||
|
|
159
|
+
metadata.ruleSources.some((source) => typeof source !== 'string' || source.length === 0)
|
|
160
|
+
)
|
|
161
|
+
reasons.push('missing-rule-sources');
|
|
162
|
+
if (typeof metadata.sisyphusEvidence !== 'string' || metadata.sisyphusEvidence.length === 0)
|
|
163
|
+
reasons.push('missing-sisyphus-evidence');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const findingRows = [
|
|
167
|
+
...findingsSection.matchAll(
|
|
168
|
+
/^\|\s*(CR-\d{3})\s*\|\s*(P[012])\s*\|\s*([^|]+?)\s*\|/gm,
|
|
169
|
+
),
|
|
170
|
+
];
|
|
171
|
+
const priorities = [...new Set(findingRows.map((match) => match[2]))].sort();
|
|
172
|
+
const issueCount = findingRows.length;
|
|
173
|
+
for (const finding of findingRows) {
|
|
174
|
+
const location = finding[3].trim();
|
|
175
|
+
const locationMatch = location.match(/^(.+):(\d+)(?::\d+)?$/);
|
|
176
|
+
if (!locationMatch || !path.isAbsolute(locationMatch[1])) {
|
|
177
|
+
reasons.push('invalid-finding-location');
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
const realFindingFile = fs.realpathSync(locationMatch[1]);
|
|
182
|
+
if (!reviewedFilePaths.has(realFindingFile))
|
|
183
|
+
reasons.push('finding-outside-reviewed-files');
|
|
184
|
+
} catch {
|
|
185
|
+
reasons.push('invalid-finding-location');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const noFindings = /(?:^|\n)未发现规范问题\s*(?:\n|$)/.test(findingsSection);
|
|
189
|
+
|
|
190
|
+
let status = 'blocked';
|
|
191
|
+
let nextRound = null;
|
|
192
|
+
if (reasons.length === 0) {
|
|
193
|
+
if (mode === 'path-review') {
|
|
194
|
+
if (conclusion === '通过' && issueCount === 0 && noFindings) status = 'reviewed';
|
|
195
|
+
else if (conclusion === '通过') reasons.push('inconsistent-passed-report');
|
|
196
|
+
else if (conclusion === '无法判断' && issueCount === 0) status = 'reviewed';
|
|
197
|
+
else if (issueCount > 0) status = 'reviewed';
|
|
198
|
+
else reasons.push('missing-findings');
|
|
199
|
+
} else if (conclusion === '无法判断') reasons.push('unable-to-determine');
|
|
200
|
+
else if (conclusion === '通过' && issueCount === 0 && noFindings) status = 'passed';
|
|
201
|
+
else if (conclusion === '通过') reasons.push('inconsistent-passed-report');
|
|
202
|
+
else if (issueCount === 0) reasons.push('missing-findings');
|
|
203
|
+
else if (round < 2) {
|
|
204
|
+
status = 'repair';
|
|
205
|
+
nextRound = round + 1;
|
|
206
|
+
} else reasons.push('repair-rounds-exhausted');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const output = {
|
|
210
|
+
status,
|
|
211
|
+
mode,
|
|
212
|
+
ruleVersion: metadata?.ruleVersion || null,
|
|
213
|
+
round: Number.isInteger(round) ? round : null,
|
|
214
|
+
nextRound,
|
|
215
|
+
conclusion,
|
|
216
|
+
issueCount,
|
|
217
|
+
priorities,
|
|
218
|
+
reasons,
|
|
219
|
+
reviewId: metadata?.reviewId || null,
|
|
220
|
+
};
|
|
221
|
+
process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
|
|
222
|
+
if (status === 'blocked') process.exitCode = 2;
|