@zat-design/sisyphus-scene 1.0.0 → 4.5.7-beta.2
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/README.md +24 -31
- package/mcp/sisyphus-react.json +4 -1
- package/package.json +4 -5
- package/scripts/sync-platforms.mjs +28 -8
- package/skills/sisyphus-scene/SKILL.md +14 -12
- package/skills/sisyphus-scene/assets/ai-contract.json +14 -0
- package/skills/sisyphus-scene/evals/fixtures/valid-decisions.json +26 -5
- package/skills/sisyphus-scene/evals/generation-cases.yaml +9 -0
- package/skills/sisyphus-scene/references/approved-examples.yaml +1 -2
- package/skills/sisyphus-scene/references/integration-contracts.md +23 -6
- package/skills/sisyphus-scene/references/platform-claude.md +1 -2
- package/skills/sisyphus-scene/references/platform-codex.md +2 -2
- package/skills/sisyphus-scene/references/platform-cursor.md +1 -2
- package/skills/sisyphus-scene/scripts/resolve-versions.mjs +77 -72
- package/skills/sisyphus-scene/scripts/validate-scaffold.mjs +127 -22
- package/skills/sisyphus-scene/scripts/validate-skill.mjs +20 -1
package/README.md
CHANGED
|
@@ -1,44 +1,37 @@
|
|
|
1
1
|
# Sisyphus Scene
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## 解决什么问题
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
从截图或需求识别保险业务场景,查询真实 Sisyphus API/demo,生成可验证的 React/TypeScript 页面骨架。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## 前置版本
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
- Node.js 18+
|
|
10
|
+
- Sisyphus 组件库、MCP 和 Scene:`4.5.7-beta.2`
|
|
11
|
+
- Ant Design `>=6 <7`
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
## 三步安装
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
| `--skill sisyphus-scene-legacy` | 安装 Sisyphus v3 + antd4 技能,不配置 4.x MCP |
|
|
19
|
-
| `--platform claude,cursor,codex` | 仅安装指定平台 |
|
|
20
|
-
| `--skip-mcp` | 只安装 Skill |
|
|
21
|
-
| `--check` | 只检查,不写文件 |
|
|
15
|
+
1. `npx -y @zat-design/sisyphus-scene@4.5.7-beta.2 install --check`
|
|
16
|
+
2. `npx -y @zat-design/sisyphus-scene@4.5.7-beta.2 install`
|
|
17
|
+
3. 重启 AI 工具并调用 `sisyphus-get-meta-status`
|
|
22
18
|
|
|
23
|
-
|
|
19
|
+
常用参数:`--global`、`--platform claude,cursor,codex`、`--skip-mcp`、`--skill sisyphus-scene-legacy`。
|
|
24
20
|
|
|
25
|
-
##
|
|
21
|
+
## 推荐调用顺序
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
npm run pack:check
|
|
34
|
-
```
|
|
23
|
+
1. `sisyphus-get-meta-status`
|
|
24
|
+
2. `sisyphus-list-components`
|
|
25
|
+
3. `sisyphus-get-component-api`
|
|
26
|
+
4. 按需 `sisyphus-get-type`
|
|
27
|
+
5. `sisyphus-list-examples`
|
|
28
|
+
6. `sisyphus-get-example`
|
|
35
29
|
|
|
36
|
-
`mcp:check
|
|
30
|
+
开发校验使用 `npm test && npm run validate && npm run mcp:check && npm run pack:check`。发布必须从仓库根目录使用 `yarn release:beta` 或 `yarn release`。
|
|
37
31
|
|
|
38
|
-
|
|
39
|
-
npm run mcp:check:codex
|
|
40
|
-
```
|
|
32
|
+
## 常见错误
|
|
41
33
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
- MCP 冲突:安装器不覆盖现有异构配置。
|
|
35
|
+
- 版本不同:三包必须使用完全相同 SemVer。
|
|
36
|
+
- Cursor 找不到 Skill:检查 `.cursor/skills/sisyphus-scene`。
|
|
37
|
+
- 发布中断:回到仓库根目录重跑同一命令,已发布的同版本包会被跳过。
|
package/mcp/sisyphus-react.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zat-design/sisyphus-scene",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.5.7-beta.2",
|
|
4
4
|
"description": "Sisyphus 业务场景 Agent 技能,跨 Claude Code、Codex、Cursor 分发 Sisyphus 4 + Ant Design 6 场景与 MCP 配置",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,13 +24,12 @@
|
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build:plugin": "node scripts/build-plugin-package.mjs",
|
|
27
|
-
"validate": "node scripts/validate-
|
|
27
|
+
"validate:skill": "node skills/sisyphus-scene/scripts/validate-skill.mjs",
|
|
28
|
+
"validate": "npm run validate:skill && node scripts/validate-distribution.mjs",
|
|
28
29
|
"test": "node --test tests/*.test.mjs",
|
|
29
30
|
"pack:check": "npm pack --dry-run",
|
|
30
31
|
"mcp:check": "node scripts/install-mcp.mjs --platform claude,cursor --project . --check",
|
|
31
32
|
"mcp:check:codex": "node scripts/install-mcp.mjs --platform codex --project . --check",
|
|
32
|
-
"sync:check": "node scripts/sync-platforms.mjs --platform all --scope user --check"
|
|
33
|
-
"release:check": "node scripts/release.mjs --check",
|
|
34
|
-
"prepublishOnly": "npm run build:plugin && npm test && npm run validate && npm run mcp:check && npm run release:check && npm run pack:check"
|
|
33
|
+
"sync:check": "node scripts/sync-platforms.mjs --platform all --scope user --check"
|
|
35
34
|
}
|
|
36
35
|
}
|
|
@@ -26,30 +26,47 @@ const parseArgs = (argv) => {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
const args = parseArgs(process.argv.slice(2));
|
|
29
|
-
const platforms =
|
|
29
|
+
const platforms =
|
|
30
|
+
String(args.platform || 'all') === 'all'
|
|
31
|
+
? ['claude', 'codex', 'cursor']
|
|
32
|
+
: String(args.platform).split(',');
|
|
30
33
|
const scope = String(args.scope || 'user');
|
|
31
34
|
const skills = String(args.skill || 'sisyphus-scene').split(',');
|
|
32
35
|
const write = Boolean(args.write);
|
|
33
36
|
const check = Boolean(args.check);
|
|
34
37
|
if (write === check || (!write && !check)) throw new Error('必须且只能指定 --check 或 --write。');
|
|
35
38
|
if (!['user', 'project'].includes(scope)) throw new Error(`不支持的 scope:${scope}`);
|
|
36
|
-
for (const platform of platforms)
|
|
39
|
+
for (const platform of platforms)
|
|
40
|
+
if (!['claude', 'codex', 'cursor'].includes(platform))
|
|
41
|
+
throw new Error(`不支持的平台:${platform}`);
|
|
37
42
|
for (const skill of skills) {
|
|
38
|
-
if (!['sisyphus-scene', 'sisyphus-scene-legacy'].includes(skill))
|
|
43
|
+
if (!['sisyphus-scene', 'sisyphus-scene-legacy'].includes(skill))
|
|
44
|
+
throw new Error(`不支持的 skill:${skill}`);
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
const projectRoot = path.resolve(String(args.project || process.cwd()));
|
|
42
48
|
const baseDirectories = {
|
|
43
|
-
claude:
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
claude:
|
|
50
|
+
scope === 'user'
|
|
51
|
+
? path.join(os.homedir(), '.claude', 'skills')
|
|
52
|
+
: path.join(projectRoot, '.claude', 'skills'),
|
|
53
|
+
codex:
|
|
54
|
+
scope === 'user'
|
|
55
|
+
? path.join(os.homedir(), '.agents', 'skills')
|
|
56
|
+
: path.join(projectRoot, '.agents', 'skills'),
|
|
57
|
+
cursor:
|
|
58
|
+
scope === 'user'
|
|
59
|
+
? path.join(os.homedir(), '.cursor', 'skills')
|
|
60
|
+
: path.join(projectRoot, '.cursor', 'skills'),
|
|
46
61
|
};
|
|
47
62
|
|
|
48
63
|
const hashDirectory = (directory) => {
|
|
49
64
|
if (!fs.existsSync(directory)) return null;
|
|
50
65
|
const hash = crypto.createHash('sha256');
|
|
51
66
|
const visit = (current, relative = '') => {
|
|
52
|
-
for (const entry of fs
|
|
67
|
+
for (const entry of fs
|
|
68
|
+
.readdirSync(current, { withFileTypes: true })
|
|
69
|
+
.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
53
70
|
const nextRelative = path.join(relative, entry.name);
|
|
54
71
|
const next = path.join(current, entry.name);
|
|
55
72
|
if (entry.isDirectory()) visit(next, nextRelative);
|
|
@@ -84,4 +101,7 @@ for (const platform of platforms) {
|
|
|
84
101
|
}
|
|
85
102
|
}
|
|
86
103
|
|
|
87
|
-
process.stdout.write(
|
|
104
|
+
process.stdout.write(
|
|
105
|
+
JSON.stringify({ sourceRoot, scope, skills, mode: write ? 'write' : 'check', results }, null, 2) +
|
|
106
|
+
'\n',
|
|
107
|
+
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sisyphus-scene
|
|
3
|
-
description:
|
|
3
|
+
description: 基于与 Scene Agent 完全同版的 @zat-design/sisyphus-react 4.x 和 Ant Design 6,从页面截图或需求描述识别保险业务场景并生成 React/TypeScript 脚手架。用于查询列表、Tab 分组列表、列表弹窗、多 Tab 详情、分区详情、列表跳转详情、步骤条分区表单、参考其他应用实现,以及无法命中既有模板时基于 MCP demo 组合新场景;支持 Claude Code、Codex、Cursor,不绑定 Umi Max。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Sisyphus 业务场景生成
|
|
@@ -9,7 +9,7 @@ description: 基于最新 @zat-design/sisyphus-react 4.x 与 Ant Design 6,从
|
|
|
9
9
|
|
|
10
10
|
## 不可跳过的约束
|
|
11
11
|
|
|
12
|
-
- 仅支持 React + TypeScript
|
|
12
|
+
- 仅支持 React + TypeScript、`assets/ai-contract.json` 声明的 Sisyphus 完全同版组合和 Ant Design 6。
|
|
13
13
|
- Umi Max、React Router、请求库、权限和状态库都属于目标项目适配,不是技能依赖。
|
|
14
14
|
- 无论场景是否命中模板,先查询 Sisyphus MCP 的组件列表、完整 API 和 demo。
|
|
15
15
|
- 只有 Sisyphus MCP 明确给出 `missing` 的能力才可进入 Ant Design 补全;`unverified` 必须先修复 MCP 连接或版本问题。
|
|
@@ -41,13 +41,12 @@ description: 基于最新 @zat-design/sisyphus-react 4.x 与 Ant Design 6,从
|
|
|
41
41
|
运行:
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
node scripts/resolve-versions.mjs --root <项目根目录>
|
|
44
|
+
node scripts/resolve-versions.mjs --root <项目根目录>
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
- Ant Design
|
|
48
|
-
- Sisyphus
|
|
49
|
-
-
|
|
50
|
-
- 项目版本、registry 最新版本、MCP 报告版本不一致时停止,并输出独立升级建议。
|
|
47
|
+
- Ant Design 本地安装版本必须满足 `>=6 <7`。
|
|
48
|
+
- Sisyphus 本地安装版本、MCP 内置元数据、MCP 和 Scene 契约版本必须完全相等。
|
|
49
|
+
- registry latest 默认只警告;用户明确要求追踪最新版时增加 `--strict-latest`。
|
|
51
50
|
|
|
52
51
|
### 3. 从截图或需求建立 SceneSpec
|
|
53
52
|
|
|
@@ -65,11 +64,14 @@ node scripts/resolve-versions.mjs --root <项目根目录> --channel stable
|
|
|
65
64
|
|
|
66
65
|
对本次候选组件依次执行:
|
|
67
66
|
|
|
68
|
-
1. `sisyphus-
|
|
69
|
-
2. `sisyphus-
|
|
70
|
-
3. `sisyphus-get-
|
|
67
|
+
1. `sisyphus-get-meta-status`
|
|
68
|
+
2. `sisyphus-list-components`
|
|
69
|
+
3. `sisyphus-get-component-api(<组件名>)`
|
|
70
|
+
4. 需要类型声明时调用 `sisyphus-get-type(<类型名>)`
|
|
71
|
+
5. `sisyphus-list-examples(<组件名>, <场景关键词>)`
|
|
72
|
+
6. `sisyphus-get-example(<示例 ID>)`
|
|
71
73
|
|
|
72
|
-
|
|
74
|
+
记录结构化 API/demo 证据,包含工具名、目标、完整版本、schema v2 和 demo ID。禁止为找一个用法一次拉取全部 demo 源码。
|
|
73
75
|
|
|
74
76
|
### 5. 匹配或构造场景
|
|
75
77
|
|
|
@@ -146,4 +148,4 @@ node scripts/validate-scaffold.mjs \
|
|
|
146
148
|
- 生成后校验规则:`references/validation-rules.md`
|
|
147
149
|
- 可引用的团队批准样例:`references/approved-examples.yaml`
|
|
148
150
|
- 结构模板注册表:`assets/templates/scene-registry.json`
|
|
149
|
-
|
|
151
|
+
- 同版本 MCP/Scene 机器契约:`assets/ai-contract.json`
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "4.5.7-beta.2",
|
|
3
|
+
"schemaVersion": 2,
|
|
4
|
+
"tools": [
|
|
5
|
+
"sisyphus-get-meta-status",
|
|
6
|
+
"sisyphus-list-components",
|
|
7
|
+
"sisyphus-get-component-api",
|
|
8
|
+
"sisyphus-get-type",
|
|
9
|
+
"sisyphus-list-examples",
|
|
10
|
+
"sisyphus-get-example",
|
|
11
|
+
"sisyphus-search",
|
|
12
|
+
"sisyphus-get-examples"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -5,8 +5,19 @@
|
|
|
5
5
|
"status": "covered",
|
|
6
6
|
"source": "sisyphus",
|
|
7
7
|
"component": "ProForm",
|
|
8
|
-
"apiEvidence":
|
|
9
|
-
|
|
8
|
+
"apiEvidence": {
|
|
9
|
+
"tool": "sisyphus-get-component-api",
|
|
10
|
+
"target": "ProForm",
|
|
11
|
+
"libraryVersion": "4.5.7-beta.2",
|
|
12
|
+
"schemaVersion": 2
|
|
13
|
+
},
|
|
14
|
+
"demoEvidence": {
|
|
15
|
+
"tool": "sisyphus-get-example",
|
|
16
|
+
"target": "ProForm/demos/base.tsx",
|
|
17
|
+
"libraryVersion": "4.5.7-beta.2",
|
|
18
|
+
"schemaVersion": 2,
|
|
19
|
+
"exampleId": "ProForm/demos/base.tsx"
|
|
20
|
+
}
|
|
10
21
|
},
|
|
11
22
|
{
|
|
12
23
|
"requirementId": "data-table",
|
|
@@ -14,8 +25,18 @@
|
|
|
14
25
|
"status": "covered",
|
|
15
26
|
"source": "sisyphus",
|
|
16
27
|
"component": "ProTable",
|
|
17
|
-
"apiEvidence":
|
|
18
|
-
|
|
28
|
+
"apiEvidence": {
|
|
29
|
+
"tool": "sisyphus-get-component-api",
|
|
30
|
+
"target": "ProTable",
|
|
31
|
+
"libraryVersion": "4.5.7-beta.2",
|
|
32
|
+
"schemaVersion": 2
|
|
33
|
+
},
|
|
34
|
+
"demoEvidence": {
|
|
35
|
+
"tool": "sisyphus-get-example",
|
|
36
|
+
"target": "ProTable/demos/base.tsx",
|
|
37
|
+
"libraryVersion": "4.5.7-beta.2",
|
|
38
|
+
"schemaVersion": 2,
|
|
39
|
+
"exampleId": "ProTable/demos/base.tsx"
|
|
40
|
+
}
|
|
19
41
|
}
|
|
20
42
|
]
|
|
21
|
-
|
|
@@ -18,6 +18,15 @@ cases:
|
|
|
18
18
|
- id: unverified-is-not-missing
|
|
19
19
|
sisyphus_status: unverified
|
|
20
20
|
expected_result: generation-blocked
|
|
21
|
+
- id: string-evidence-is-invalid
|
|
22
|
+
evidence_shape: string
|
|
23
|
+
expected_result: validation-failure
|
|
24
|
+
- id: mismatched-mcp-evidence-version
|
|
25
|
+
evidence_version: '4.5.7-beta.2'
|
|
26
|
+
expected_result: validation-failure
|
|
27
|
+
- id: demo-evidence-without-id
|
|
28
|
+
evidence_tool: sisyphus-get-example
|
|
29
|
+
expected_result: validation-failure
|
|
21
30
|
- id: project-component-fallback
|
|
22
31
|
sisyphus_status: missing
|
|
23
32
|
antd_status: missing
|
|
@@ -7,10 +7,9 @@ examples: []
|
|
|
7
7
|
# status: approved
|
|
8
8
|
# application: xk-wjs-claim-web
|
|
9
9
|
# capabilities: [search-form, data-table, table-tabs, drawer-form]
|
|
10
|
-
#
|
|
10
|
+
# sisyphus_version: "4.5.7-beta.2"
|
|
11
11
|
# antd_range: ">=6 <7"
|
|
12
12
|
# paths:
|
|
13
13
|
# - src/pages/litigationManagement/index.tsx
|
|
14
14
|
# verified_at: 2026-07-10
|
|
15
15
|
# notes: 只复用结构,不覆盖 MCP API 证据
|
|
16
|
-
|
|
@@ -71,14 +71,22 @@ interface CapabilityRequirement {
|
|
|
71
71
|
required: boolean;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
interface ComponentEvidence {
|
|
75
|
+
tool: string;
|
|
76
|
+
target: string;
|
|
77
|
+
libraryVersion: string;
|
|
78
|
+
schemaVersion?: number;
|
|
79
|
+
exampleId?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
74
82
|
interface ComponentDecision {
|
|
75
83
|
requirementId: string;
|
|
76
84
|
requirement: string;
|
|
77
85
|
status: CapabilityStatus;
|
|
78
86
|
source: ComponentSource;
|
|
79
87
|
component?: string;
|
|
80
|
-
apiEvidence?:
|
|
81
|
-
demoEvidence?:
|
|
88
|
+
apiEvidence?: ComponentEvidence;
|
|
89
|
+
demoEvidence?: ComponentEvidence;
|
|
82
90
|
fallbackReason?: string;
|
|
83
91
|
}
|
|
84
92
|
```
|
|
@@ -86,7 +94,8 @@ interface ComponentDecision {
|
|
|
86
94
|
规则:
|
|
87
95
|
|
|
88
96
|
- 每个必需 requirement 恰好一条 decision。
|
|
89
|
-
- `sisyphus`
|
|
97
|
+
- `sisyphus` 来源必须使用结构化 API/demo 证据,记录完全相同的组件版本与 schema v2。
|
|
98
|
+
- API 证据必须来自 `sisyphus-get-component-api`;demo 证据必须来自 `sisyphus-get-example` 并记录 `exampleId`。
|
|
90
99
|
- `antd` 来源必须有 API/demo 证据与 fallbackReason。
|
|
91
100
|
- `project` 来源必须有源码路径与调用示例证据。
|
|
92
101
|
- `unresolved` 或 `unverified` 阻断生成。
|
|
@@ -95,10 +104,19 @@ interface ComponentDecision {
|
|
|
95
104
|
|
|
96
105
|
```ts
|
|
97
106
|
interface ComponentDocsProvider {
|
|
107
|
+
getMetaStatus(): Promise<ComponentMetaStatus>;
|
|
98
108
|
listComponents(): Promise<ComponentSummary[]>;
|
|
99
109
|
getComponentApi(name: string): Promise<ComponentApi>;
|
|
100
|
-
|
|
101
|
-
|
|
110
|
+
getType(name: string): Promise<ComponentType>;
|
|
111
|
+
listExamples(
|
|
112
|
+
name: string,
|
|
113
|
+
scenario?: string,
|
|
114
|
+
cursor?: string,
|
|
115
|
+
): Promise<ComponentExampleSummary[]>;
|
|
116
|
+
getExample(id: string): Promise<ComponentExample>;
|
|
117
|
+
search(query: string): Promise<ComponentSearchResult[]>;
|
|
118
|
+
/** 仅用于兼容旧 MCP 工具,新流程不得使用。 */
|
|
119
|
+
getExamples?(name: string, scenario?: string): Promise<ComponentExample[]>;
|
|
102
120
|
}
|
|
103
121
|
```
|
|
104
122
|
|
|
@@ -109,4 +127,3 @@ interface ComponentDocsProvider {
|
|
|
109
127
|
- `Context7AntdProvider`
|
|
110
128
|
|
|
111
129
|
这里只定义能力,不把任何平台的 MCP 工具前缀写入生成代码。
|
|
112
|
-
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
- 用户级安装目录:`~/.claude/skills/sisyphus-scene`
|
|
4
4
|
- 项目级安装目录:`.claude/skills/sisyphus-scene`
|
|
5
5
|
- 使用 `${CLAUDE_SKILL_DIR}` 定位 scripts、references 和 assets。
|
|
6
|
-
- 首先通过 ToolSearch 发现 `sisyphus-list-components`、`sisyphus-get-component-api`、`sisyphus-get-examples` 和 `sisyphus-search`。
|
|
6
|
+
- 首先通过 ToolSearch 发现 `sisyphus-get-meta-status`、`sisyphus-list-components`、`sisyphus-get-component-api`、`sisyphus-get-type`、`sisyphus-list-examples`、`sisyphus-get-example` 和 `sisyphus-search`。
|
|
7
7
|
- Ant Design MCP 不可用时,通过 Context7 查询 antd 6 官方文档。
|
|
8
8
|
- 读取项目 `CLAUDE.md`,同时显式检查 `AGENTS.md` 和 `.cursor/rules`;同级冲突时停止。
|
|
9
9
|
- 不使用 Claude 专属 frontmatter 驱动共享核心行为,保证同一 SKILL.md 可被其他平台读取。
|
|
10
|
-
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
- 用户级安装目录:`~/.agents/skills/sisyphus-scene`
|
|
4
4
|
- 项目级安装目录:`.agents/skills/sisyphus-scene`
|
|
5
5
|
- 以当前加载的 SKILL.md 所在目录解析相对资源。
|
|
6
|
-
-
|
|
6
|
+
- 按 `get-meta-status` → `list-components` → `get-component-api` →按需 `get-type` → `list-examples` → `get-example` 的顺序调用 Sisyphus MCP。
|
|
7
|
+
- 工具未安装时停止并给出配置建议,不读取 node_modules 替代。
|
|
7
8
|
- `agents/openai.yaml` 只承载 UI 元数据。当前 Sisyphus MCP 是本地 stdio 命令,没有可验证的 streamable HTTP URL,因此不伪造 Codex MCP dependency。
|
|
8
9
|
- 读取最近层级的 `AGENTS.md`,并显式检查 `CLAUDE.md` 和 `.cursor/rules`。
|
|
9
10
|
- 修改已有代码符号前遵循项目 GitNexus impact 约束,完成后执行 detect changes。
|
|
10
|
-
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
- 用户级安装目录:`~/.cursor/skills/sisyphus-scene`
|
|
4
4
|
- 项目级安装目录:`.cursor/skills/sisyphus-scene`
|
|
5
5
|
- 以当前技能目录解析 scripts、references 和 assets。
|
|
6
|
-
- 从 Cursor MCP 配置发现 `sisyphus-react
|
|
6
|
+
- 从 Cursor MCP 配置发现 `sisyphus-react`,按 `get-meta-status` → `list-components` → `get-component-api` →按需 `get-type` → `list-examples` → `get-example` 调用。
|
|
7
7
|
- 读取并应用 `.cursor/rules/*.mdc`,同时检查项目根 `AGENTS.md` 与 `CLAUDE.md`。
|
|
8
8
|
- Cursor Rules 是持久项目规范,Skill 是按需工作流;不要把整份团队规范复制进 SKILL.md。
|
|
9
9
|
- MCP 工具未出现或缓存版本与目标包不一致时,停止并要求刷新 MCP,不使用旧缓存继续生成。
|
|
10
|
-
|
|
@@ -4,11 +4,12 @@ import { execFileSync } from 'node:child_process';
|
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import process from 'node:process';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
10
|
+
const contract = JSON.parse(
|
|
11
|
+
fs.readFileSync(path.join(skillRoot, 'assets', 'ai-contract.json'), 'utf8'),
|
|
12
|
+
);
|
|
12
13
|
|
|
13
14
|
const parseArgs = (argv) => {
|
|
14
15
|
const args = {};
|
|
@@ -57,8 +58,12 @@ const readPackageJson = (root) => {
|
|
|
57
58
|
};
|
|
58
59
|
|
|
59
60
|
const getDeclaredVersion = (packageJson, packageName) => {
|
|
60
|
-
const
|
|
61
|
-
|
|
61
|
+
for (const section of [
|
|
62
|
+
'dependencies',
|
|
63
|
+
'devDependencies',
|
|
64
|
+
'peerDependencies',
|
|
65
|
+
'optionalDependencies',
|
|
66
|
+
]) {
|
|
62
67
|
const value = packageJson[section]?.[packageName];
|
|
63
68
|
if (value) return value;
|
|
64
69
|
}
|
|
@@ -66,27 +71,23 @@ const getDeclaredVersion = (packageJson, packageName) => {
|
|
|
66
71
|
};
|
|
67
72
|
|
|
68
73
|
const getPackageLockVersion = (root, packageName) => {
|
|
69
|
-
const
|
|
70
|
-
if (!fs.existsSync(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return lock.packages?.[`node_modules/${packageName}`]?.version;
|
|
74
|
-
} catch {
|
|
75
|
-
return undefined;
|
|
76
|
-
}
|
|
74
|
+
const file = path.join(root, 'package-lock.json');
|
|
75
|
+
if (!fs.existsSync(file)) return undefined;
|
|
76
|
+
return JSON.parse(fs.readFileSync(file, 'utf8')).packages?.[`node_modules/${packageName}`]
|
|
77
|
+
?.version;
|
|
77
78
|
};
|
|
78
79
|
|
|
79
80
|
const getYarnLockVersion = (root, packageName, declared) => {
|
|
80
|
-
const
|
|
81
|
-
if (!fs.existsSync(
|
|
82
|
-
const content = fs.readFileSync(
|
|
81
|
+
const file = path.join(root, 'yarn.lock');
|
|
82
|
+
if (!fs.existsSync(file)) return undefined;
|
|
83
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
83
84
|
const escaped = packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
84
|
-
const
|
|
85
|
+
const selector = declared
|
|
85
86
|
? `${escaped}@${String(declared).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`
|
|
86
87
|
: undefined;
|
|
87
88
|
for (const block of content.split(/\n{2,}/)) {
|
|
88
89
|
const header = block.split('\n', 1)[0];
|
|
89
|
-
if (
|
|
90
|
+
if (selector && !new RegExp(`(?:^|["',\\s])${selector}(?:["',:]|$)`).test(header)) continue;
|
|
90
91
|
if (!new RegExp(`(?:^|["',\\s])${escaped}@`).test(header)) continue;
|
|
91
92
|
const version = block.match(/^\s{2}version\s+["']?([^"'\n]+)["']?$/m);
|
|
92
93
|
if (version) return version[1];
|
|
@@ -95,29 +96,27 @@ const getYarnLockVersion = (root, packageName, declared) => {
|
|
|
95
96
|
};
|
|
96
97
|
|
|
97
98
|
const getPnpmLockVersion = (root, packageName) => {
|
|
98
|
-
const
|
|
99
|
-
if (!fs.existsSync(
|
|
100
|
-
const content = fs.readFileSync(
|
|
99
|
+
const file = path.join(root, 'pnpm-lock.yaml');
|
|
100
|
+
if (!fs.existsSync(file)) return undefined;
|
|
101
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
101
102
|
const escaped = packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
102
|
-
const
|
|
103
|
+
for (const pattern of [
|
|
103
104
|
new RegExp(`(?:^|\\n)\\s{2,}['"]?${escaped}@([^:'"\\s]+)`, 'm'),
|
|
104
105
|
new RegExp(`(?:^|\\n)\\s{2,}/?${escaped}/([^:\\s]+):`, 'm'),
|
|
105
|
-
]
|
|
106
|
-
for (const pattern of patterns) {
|
|
106
|
+
]) {
|
|
107
107
|
const match = content.match(pattern);
|
|
108
108
|
if (match) return match[1];
|
|
109
109
|
}
|
|
110
110
|
return undefined;
|
|
111
111
|
};
|
|
112
112
|
|
|
113
|
-
const
|
|
113
|
+
const getResolution = (root, packageName, declared) => {
|
|
114
114
|
const values = {
|
|
115
115
|
packageLock: getPackageLockVersion(root, packageName),
|
|
116
116
|
yarnLock: getYarnLockVersion(root, packageName, declared),
|
|
117
117
|
pnpmLock: getPnpmLockVersion(root, packageName),
|
|
118
118
|
};
|
|
119
|
-
const
|
|
120
|
-
const unique = [...new Set(present)];
|
|
119
|
+
const unique = [...new Set(Object.values(values).filter(Boolean))];
|
|
121
120
|
return {
|
|
122
121
|
values,
|
|
123
122
|
resolved: unique.length === 1 ? unique[0] : undefined,
|
|
@@ -125,80 +124,86 @@ const getResolvedVersions = (root, packageName, declared) => {
|
|
|
125
124
|
};
|
|
126
125
|
};
|
|
127
126
|
|
|
128
|
-
const
|
|
129
|
-
const output = execFileSync(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
return Array.isArray(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
versions
|
|
140
|
-
.filter((version) => {
|
|
141
|
-
const parsed = parseVersion(version);
|
|
142
|
-
if (!parsed || parsed.major !== major) return false;
|
|
143
|
-
return channel === 'beta' || !parsed.prerelease;
|
|
127
|
+
const queryLatest = (root, packageName, major, channel) => {
|
|
128
|
+
const output = execFileSync(
|
|
129
|
+
process.env.NPM_EXECUTABLE || 'npm',
|
|
130
|
+
['view', packageName, 'versions', '--json'],
|
|
131
|
+
{ cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] },
|
|
132
|
+
);
|
|
133
|
+
const values = JSON.parse(output);
|
|
134
|
+
return (Array.isArray(values) ? values : [values])
|
|
135
|
+
.filter((value) => {
|
|
136
|
+
const parsed = parseVersion(value);
|
|
137
|
+
return parsed?.major === major && (channel === 'beta' || !parsed.prerelease);
|
|
144
138
|
})
|
|
145
139
|
.sort(compareVersions)
|
|
146
140
|
.at(-1);
|
|
147
|
-
|
|
148
|
-
const evaluateStatus = ({ declared, resolved, latest, major, lockConflict }) => {
|
|
149
|
-
if (lockConflict) return 'lock-conflict';
|
|
150
|
-
const active = parseVersion(resolved || declared);
|
|
151
|
-
if (!active) return 'unknown';
|
|
152
|
-
if (active.major !== major) return 'unsupported-major';
|
|
153
|
-
if (!latest) return 'unknown';
|
|
154
|
-
return compareVersions(active.raw, latest) === 0 ? 'ok' : 'outdated';
|
|
155
141
|
};
|
|
156
142
|
|
|
157
143
|
const args = parseArgs(process.argv.slice(2));
|
|
158
144
|
const root = path.resolve(String(args.root || process.cwd()));
|
|
159
|
-
const
|
|
145
|
+
const expected = parseVersion(contract.version);
|
|
146
|
+
const channel = String(args.channel || (expected.prerelease ? 'beta' : 'stable'));
|
|
160
147
|
if (!['stable', 'beta'].includes(channel)) throw new Error(`不支持的 channel:${channel}`);
|
|
161
148
|
const offline = Boolean(args.offline);
|
|
149
|
+
const strictLatest = Boolean(args['strict-latest']);
|
|
162
150
|
const packageJson = readPackageJson(root);
|
|
151
|
+
const warnings = [];
|
|
163
152
|
|
|
164
|
-
const
|
|
153
|
+
const packages = [
|
|
154
|
+
{ name: 'antd', major: 6 },
|
|
155
|
+
{ name: '@zat-design/sisyphus-react', major: expected.major },
|
|
156
|
+
].map(({ name, major }) => {
|
|
165
157
|
const declared = getDeclaredVersion(packageJson, name);
|
|
166
|
-
const
|
|
167
|
-
|
|
158
|
+
const resolution = getResolution(root, name, declared);
|
|
159
|
+
const active = resolution.resolved || parseVersion(declared)?.raw;
|
|
160
|
+
let latest;
|
|
168
161
|
let registryError;
|
|
169
162
|
if (!offline) {
|
|
170
163
|
try {
|
|
171
|
-
|
|
164
|
+
latest = queryLatest(root, name, major, channel);
|
|
172
165
|
} catch (error) {
|
|
173
166
|
registryError = error.stderr?.trim() || error.message;
|
|
174
167
|
}
|
|
175
168
|
}
|
|
176
|
-
|
|
169
|
+
|
|
170
|
+
let status = 'ok';
|
|
171
|
+
if (resolution.conflict) status = 'lock-conflict';
|
|
172
|
+
else if (!parseVersion(active)) status = 'unknown';
|
|
173
|
+
else if (name === 'antd' && parseVersion(active).major !== 6) status = 'unsupported-major';
|
|
174
|
+
else if (name === '@zat-design/sisyphus-react' && active !== contract.version)
|
|
175
|
+
status = 'version-mismatch';
|
|
176
|
+
else if (strictLatest && (!latest || active !== latest)) status = 'outdated';
|
|
177
|
+
else if (latest && active !== latest)
|
|
178
|
+
warnings.push(`${name} 当前 ${active},registry ${channel} 最新 ${latest}`);
|
|
179
|
+
if (registryError) warnings.push(`${name} registry 查询失败:${registryError}`);
|
|
180
|
+
|
|
177
181
|
return {
|
|
178
182
|
name,
|
|
179
|
-
|
|
180
|
-
channel,
|
|
183
|
+
required: name === 'antd' ? '>=6 <7' : contract.version,
|
|
181
184
|
declared,
|
|
182
|
-
resolved:
|
|
183
|
-
resolvedVersions:
|
|
185
|
+
resolved: resolution.resolved,
|
|
186
|
+
resolvedVersions: resolution.values,
|
|
187
|
+
active,
|
|
184
188
|
latest,
|
|
185
|
-
status
|
|
186
|
-
declared,
|
|
187
|
-
resolved: lockResolution.resolved,
|
|
188
|
-
latest,
|
|
189
|
-
major,
|
|
190
|
-
lockConflict: lockResolution.conflict,
|
|
191
|
-
}),
|
|
189
|
+
status,
|
|
192
190
|
...(registryError ? { registryError } : {}),
|
|
193
191
|
};
|
|
194
192
|
});
|
|
195
193
|
|
|
196
194
|
const report = {
|
|
197
195
|
root,
|
|
196
|
+
contract,
|
|
198
197
|
channel,
|
|
199
|
-
|
|
200
|
-
|
|
198
|
+
strictLatest,
|
|
199
|
+
metadata: {
|
|
200
|
+
source: '@zat-design/sisyphus-react-mcp',
|
|
201
|
+
version: contract.version,
|
|
202
|
+
schemaVersion: contract.schemaVersion,
|
|
203
|
+
},
|
|
204
|
+
ok: packages.every((item) => item.status === 'ok'),
|
|
205
|
+
warnings,
|
|
206
|
+
packages,
|
|
201
207
|
};
|
|
202
|
-
|
|
203
208
|
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
204
209
|
if (!report.ok) process.exitCode = 2;
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import process from 'node:process';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
|
+
const contract = JSON.parse(
|
|
10
|
+
fs.readFileSync(path.join(skillRoot, 'assets', 'ai-contract.json'), 'utf8'),
|
|
11
|
+
);
|
|
6
12
|
|
|
7
13
|
const parseArgs = (argv) => {
|
|
8
14
|
const args = {};
|
|
@@ -32,11 +38,18 @@ const readDecisions = (filePath) => {
|
|
|
32
38
|
|
|
33
39
|
const extractNamedImports = (content, source) => {
|
|
34
40
|
const escaped = source.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
35
|
-
const regex = new RegExp(
|
|
41
|
+
const regex = new RegExp(
|
|
42
|
+
`import\\s+(?:type\\s+)?\\{([^}]+)\\}\\s+from\\s+['"]${escaped}['"]`,
|
|
43
|
+
'g',
|
|
44
|
+
);
|
|
36
45
|
const names = [];
|
|
37
46
|
for (const match of content.matchAll(regex)) {
|
|
38
47
|
for (const token of match[1].split(',')) {
|
|
39
|
-
const name = token
|
|
48
|
+
const name = token
|
|
49
|
+
.trim()
|
|
50
|
+
.replace(/^type\s+/, '')
|
|
51
|
+
.split(/\s+as\s+/)[0]
|
|
52
|
+
.trim();
|
|
40
53
|
if (name) names.push(name);
|
|
41
54
|
}
|
|
42
55
|
}
|
|
@@ -60,25 +73,92 @@ const checks = [];
|
|
|
60
73
|
|
|
61
74
|
const addError = (file, rule, message) => errors.push({ file, rule, message });
|
|
62
75
|
const addWarning = (file, rule, message) => warnings.push({ file, rule, message });
|
|
76
|
+
const isEvidence = (value) =>
|
|
77
|
+
value &&
|
|
78
|
+
typeof value === 'object' &&
|
|
79
|
+
!Array.isArray(value) &&
|
|
80
|
+
[value.tool, value.target, value.libraryVersion].every(
|
|
81
|
+
(item) => typeof item === 'string' && item.trim(),
|
|
82
|
+
);
|
|
83
|
+
const validateSisyphusEvidence = (decision, key, tool, requireExampleId = false) => {
|
|
84
|
+
const evidence = decision[key];
|
|
85
|
+
if (!isEvidence(evidence)) {
|
|
86
|
+
addError(
|
|
87
|
+
decisionsPath,
|
|
88
|
+
'sisyphus-evidence',
|
|
89
|
+
`${decision.requirement} 的 ${key} 必须是结构化证据`,
|
|
90
|
+
);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (evidence.tool !== tool) {
|
|
94
|
+
addError(
|
|
95
|
+
decisionsPath,
|
|
96
|
+
'sisyphus-evidence-tool',
|
|
97
|
+
`${decision.requirement} 的 ${key} 必须来自 ${tool}`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (
|
|
101
|
+
evidence.libraryVersion !== contract.version ||
|
|
102
|
+
evidence.schemaVersion !== contract.schemaVersion
|
|
103
|
+
) {
|
|
104
|
+
addError(
|
|
105
|
+
decisionsPath,
|
|
106
|
+
'sisyphus-evidence-version',
|
|
107
|
+
`${decision.requirement} 的 ${key} 版本/schema 必须为 ${contract.version}/v${contract.schemaVersion}`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (requireExampleId && (!evidence.exampleId || evidence.exampleId !== evidence.target)) {
|
|
111
|
+
addError(
|
|
112
|
+
decisionsPath,
|
|
113
|
+
'sisyphus-demo-id',
|
|
114
|
+
`${decision.requirement} 的 demo 证据必须记录与 target 一致的 exampleId`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
63
118
|
|
|
64
119
|
for (const decision of decisions) {
|
|
65
120
|
if (!decision.requirementId || !decision.requirement) {
|
|
66
|
-
addError(
|
|
121
|
+
addError(
|
|
122
|
+
decisionsPath,
|
|
123
|
+
'decision-schema',
|
|
124
|
+
'每条 decision 必须包含 requirementId 和 requirement',
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
if (!['covered', 'partial', 'missing', 'unverified'].includes(decision.status)) {
|
|
128
|
+
addError(decisionsPath, 'decision-schema', `非法的能力状态:${decision.status}`);
|
|
67
129
|
}
|
|
68
|
-
if (
|
|
69
|
-
addError(
|
|
130
|
+
if (decision.status === 'unverified' || decision.source === 'unresolved') {
|
|
131
|
+
addError(
|
|
132
|
+
decisionsPath,
|
|
133
|
+
'unresolved-capability',
|
|
134
|
+
`能力未解决:${decision.requirement || decision.requirementId}`,
|
|
135
|
+
);
|
|
70
136
|
}
|
|
71
|
-
if (decision.source === 'sisyphus'
|
|
72
|
-
|
|
137
|
+
if (decision.source === 'sisyphus') {
|
|
138
|
+
validateSisyphusEvidence(decision, 'apiEvidence', 'sisyphus-get-component-api');
|
|
139
|
+
validateSisyphusEvidence(decision, 'demoEvidence', 'sisyphus-get-example', true);
|
|
73
140
|
}
|
|
74
141
|
if (
|
|
75
142
|
decision.source === 'antd' &&
|
|
76
|
-
(!decision.apiEvidence ||
|
|
143
|
+
(!isEvidence(decision.apiEvidence) ||
|
|
144
|
+
!isEvidence(decision.demoEvidence) ||
|
|
145
|
+
!decision.fallbackReason)
|
|
77
146
|
) {
|
|
78
|
-
addError(
|
|
147
|
+
addError(
|
|
148
|
+
decisionsPath,
|
|
149
|
+
'antd-fallback-evidence',
|
|
150
|
+
`Ant Design fallback 证据不完整:${decision.requirement}`,
|
|
151
|
+
);
|
|
79
152
|
}
|
|
80
|
-
if (
|
|
81
|
-
|
|
153
|
+
if (
|
|
154
|
+
decision.source === 'project' &&
|
|
155
|
+
(!isEvidence(decision.apiEvidence) || !isEvidence(decision.demoEvidence))
|
|
156
|
+
) {
|
|
157
|
+
addError(
|
|
158
|
+
decisionsPath,
|
|
159
|
+
'project-evidence',
|
|
160
|
+
`项目组件决策缺少源码或调用示例:${decision.requirement}`,
|
|
161
|
+
);
|
|
82
162
|
}
|
|
83
163
|
}
|
|
84
164
|
|
|
@@ -105,31 +185,49 @@ for (const filePath of files) {
|
|
|
105
185
|
}
|
|
106
186
|
|
|
107
187
|
if (/\bProForm\b/.test(content) && /<Form\.Item\b/.test(content)) {
|
|
108
|
-
addError(
|
|
188
|
+
addError(
|
|
189
|
+
relative,
|
|
190
|
+
'proform-form-item',
|
|
191
|
+
'ProForm 中混入 Form.Item,必须提供明确组件证据或改用配置列',
|
|
192
|
+
);
|
|
109
193
|
}
|
|
110
194
|
|
|
111
|
-
const sisyphusImports = extractNamedImports(content, '@zat-design/sisyphus-react').filter(
|
|
112
|
-
/^Pro[A-Z]/.test(name) && !/(Type|Props)$/.test(name),
|
|
195
|
+
const sisyphusImports = extractNamedImports(content, '@zat-design/sisyphus-react').filter(
|
|
196
|
+
(name) => /^Pro[A-Z]/.test(name) && !/(Type|Props)$/.test(name),
|
|
113
197
|
);
|
|
114
198
|
for (const component of sisyphusImports) {
|
|
115
199
|
const decision = decisions.find(
|
|
116
|
-
(item) =>
|
|
200
|
+
(item) =>
|
|
201
|
+
item.source === 'sisyphus' && item.component === component && item.status !== 'unverified',
|
|
117
202
|
);
|
|
118
203
|
if (!decision) {
|
|
119
|
-
addError(
|
|
204
|
+
addError(
|
|
205
|
+
relative,
|
|
206
|
+
'sisyphus-component-decision',
|
|
207
|
+
`组件 ${component} 缺少 ComponentDecision 证据`,
|
|
208
|
+
);
|
|
120
209
|
}
|
|
121
210
|
}
|
|
122
211
|
|
|
123
|
-
const structuralAntdComponents = new Set([
|
|
124
|
-
|
|
212
|
+
const structuralAntdComponents = new Set([
|
|
213
|
+
'Tabs',
|
|
214
|
+
'Modal',
|
|
215
|
+
'Drawer',
|
|
216
|
+
'Table',
|
|
217
|
+
'Steps',
|
|
218
|
+
'Collapse',
|
|
219
|
+
]);
|
|
220
|
+
const antdImports = extractNamedImports(content, 'antd').filter((name) =>
|
|
221
|
+
structuralAntdComponents.has(name),
|
|
222
|
+
);
|
|
125
223
|
for (const component of antdImports) {
|
|
126
224
|
const decision = decisions.find(
|
|
127
225
|
(item) =>
|
|
128
226
|
item.source === 'antd' &&
|
|
129
227
|
item.component === component &&
|
|
130
228
|
item.fallbackReason &&
|
|
131
|
-
item.apiEvidence &&
|
|
132
|
-
item.demoEvidence,
|
|
229
|
+
isEvidence(item.apiEvidence) &&
|
|
230
|
+
isEvidence(item.demoEvidence),
|
|
133
231
|
);
|
|
134
232
|
if (!decision) {
|
|
135
233
|
addError(relative, 'antd-fallback-decision', `结构组件 ${component} 缺少完整 fallback 证据`);
|
|
@@ -139,8 +237,15 @@ for (const filePath of files) {
|
|
|
139
237
|
const routerImports = ['@umijs/max', 'react-router-dom', 'next/navigation'].filter((source) =>
|
|
140
238
|
new RegExp(`from\\s+['"]${source.replace('/', '\\/')}['"]`).test(content),
|
|
141
239
|
);
|
|
142
|
-
if (
|
|
143
|
-
|
|
240
|
+
if (
|
|
241
|
+
routerImports.length > 0 &&
|
|
242
|
+
!decisions.some((item) => /导航|路由|返回|跳转/.test(item.requirement))
|
|
243
|
+
) {
|
|
244
|
+
addWarning(
|
|
245
|
+
relative,
|
|
246
|
+
'router-without-decision',
|
|
247
|
+
`存在路由 import,但没有导航能力决策:${routerImports.join(', ')}`,
|
|
248
|
+
);
|
|
144
249
|
}
|
|
145
250
|
|
|
146
251
|
checks.push({ file: relative, status: 'checked' });
|
|
@@ -35,14 +35,33 @@ for (const required of [
|
|
|
35
35
|
'references/integration-contracts.md',
|
|
36
36
|
'references/validation-rules.md',
|
|
37
37
|
'assets/templates/scene-registry.json',
|
|
38
|
+
'assets/ai-contract.json',
|
|
38
39
|
'agents/openai.yaml',
|
|
40
|
+
'evals/generation-cases.yaml',
|
|
41
|
+
'evals/trigger-cases.yaml',
|
|
42
|
+
'evals/fixtures/valid-decisions.json',
|
|
43
|
+
'evals/fixtures/invalid-decisions.json',
|
|
39
44
|
]) {
|
|
40
45
|
if (!fs.existsSync(path.join(skillRoot, required))) errors.push(`缺少资源:${required}`);
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
JSON.parse(fs.readFileSync(path.join(skillRoot, 'assets/templates/scene-registry.json'), 'utf8'));
|
|
49
|
+
const contract = JSON.parse(
|
|
50
|
+
fs.readFileSync(path.join(skillRoot, 'assets/ai-contract.json'), 'utf8'),
|
|
51
|
+
);
|
|
52
|
+
if (contract.schemaVersion !== 2 || !Array.isArray(contract.tools) || contract.tools.length !== 8) {
|
|
53
|
+
errors.push('ai-contract 必须声明 schema v2 和 8 个 MCP 工具');
|
|
54
|
+
}
|
|
55
|
+
for (const relative of ['evals/generation-cases.yaml', 'evals/trigger-cases.yaml']) {
|
|
56
|
+
const value = fs.readFileSync(path.join(skillRoot, relative), 'utf8');
|
|
57
|
+
if (!/^schema_version:\s*1$/m.test(value)) errors.push(`${relative} 缺少 schema_version: 1`);
|
|
58
|
+
}
|
|
59
|
+
for (const relative of [
|
|
60
|
+
'evals/fixtures/valid-decisions.json',
|
|
61
|
+
'evals/fixtures/invalid-decisions.json',
|
|
62
|
+
])
|
|
63
|
+
JSON.parse(fs.readFileSync(path.join(skillRoot, relative), 'utf8'));
|
|
44
64
|
|
|
45
65
|
const report = { skillRoot, ok: errors.length === 0, errors };
|
|
46
66
|
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
47
67
|
if (!report.ok) process.exitCode = 1;
|
|
48
|
-
|