@zat-design/sisyphus-scene 1.0.0 → 4.5.7

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 CHANGED
@@ -1,44 +1,37 @@
1
1
  # Sisyphus Scene
2
2
 
3
- `sisyphus-agent` 下的保险业务场景 Agent 包,跨 Claude Code、Codex、Cursor 分发 Agent Skills 与 Sisyphus MCP 配置。
3
+ ## 解决什么问题
4
4
 
5
- ## 安装
5
+ 从截图或需求识别保险业务场景,查询真实 Sisyphus API/demo,生成可验证的 React/TypeScript 页面骨架。
6
6
 
7
- Node.js 18 及以上版本执行:
7
+ ## 前置版本
8
8
 
9
- ```bash
10
- npx -y @zat-design/sisyphus-scene@1.0.0 install
11
- ```
9
+ - Node.js 18+
10
+ - Sisyphus 组件库、MCP 和 Scene:`4.5.7`
11
+ - Ant Design `>=6 <7`
12
12
 
13
- 默认在当前项目为 Claude Code、Cursor、Codex 安装最新 Skill 与 MCP。常用参数:
13
+ ## 三步安装
14
14
 
15
- | 参数 | 用途 |
16
- | --- | --- |
17
- | `--global` | 安装到用户级目录 |
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 install --check`
16
+ 2. `npx -y @zat-design/sisyphus-scene@4.5.7 install`
17
+ 3. 重启 AI 工具并调用 `sisyphus-get-meta-status`
22
18
 
23
- 后续可将 `@1.0.0` 改为 `@latest`。Skill 唯一事实来源仍为 `skills/`;MCP 唯一事实来源为 `mcp/sisyphus-react.json`。
19
+ 常用参数:`--global`、`--platform claude,cursor,codex`、`--skip-mcp`、`--skill sisyphus-scene-legacy`。
24
20
 
25
- ## 发布检查
21
+ ## 推荐调用顺序
26
22
 
27
- ```bash
28
- npm run build:plugin
29
- npm test
30
- npm run validate
31
- npm run mcp:check
32
- npm run release:check
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` 只检查发布所需的 Claude/Cursor 配置,不要求发布机安装 Codex CLI。需要验证本机 Codex MCP 时执行:
30
+ 开发校验使用 `npm test && npm run validate && npm run mcp:check && npm run pack:check`。发布必须从仓库根目录使用 `yarn release:beta` `yarn release`。
37
31
 
38
- ```bash
39
- npm run mcp:check:codex
40
- ```
32
+ ## 常见错误
41
33
 
42
- 在仓库根目录可直接执行 `npm run release:scene`;子包的 `prepublishOnly` 会自动重复执行上述校验,然后发布到公共 npm registry。
43
-
44
- 全部通过后,使用具备 `@zat-design` 权限的账号执行 `npm publish --access public`。
34
+ - MCP 冲突:安装器不覆盖现有异构配置。
35
+ - 版本不同:三包必须使用完全相同 SemVer。
36
+ - Cursor 找不到 Skill:检查 `.cursor/skills/sisyphus-scene`。
37
+ - 发布中断:回到仓库根目录重跑同一命令,已发布的同版本包会被跳过。
@@ -2,7 +2,10 @@
2
2
  "mcpServers": {
3
3
  "sisyphus-react": {
4
4
  "command": "npx",
5
- "args": ["-y", "@zat-design/sisyphus-react-mcp@1.0.2"]
5
+ "args": [
6
+ "-y",
7
+ "@zat-design/sisyphus-react-mcp@4.5.7"
8
+ ]
6
9
  }
7
10
  }
8
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zat-design/sisyphus-scene",
3
- "version": "1.0.0",
3
+ "version": "4.5.7",
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-distribution.mjs",
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 = String(args.platform || 'all') === 'all' ? ['claude', 'codex', 'cursor'] : String(args.platform).split(',');
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) if (!['claude', 'codex', 'cursor'].includes(platform)) throw new Error(`不支持的平台:${platform}`);
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)) throw new Error(`不支持的 skill:${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: scope === 'user' ? path.join(os.homedir(), '.claude', 'skills') : path.join(projectRoot, '.claude', 'skills'),
44
- codex: scope === 'user' ? path.join(os.homedir(), '.agents', 'skills') : path.join(projectRoot, '.agents', 'skills'),
45
- cursor: scope === 'user' ? path.join(os.homedir(), '.agents', 'skills') : path.join(projectRoot, '.agents', 'skills'),
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.readdirSync(current, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
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(JSON.stringify({ sourceRoot, scope, skills, mode: write ? 'write' : 'check', results }, null, 2) + '\n');
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: 基于最新 @zat-design/sisyphus-react 4.x Ant Design 6,从页面截图或需求描述识别保险业务场景并生成 React/TypeScript 脚手架。用于查询列表、Tab 分组列表、列表弹窗、多 Tab 详情、分区详情、列表跳转详情、步骤条分区表单、参考其他应用实现,以及无法命中既有模板时基于 MCP demo 组合新场景;支持 Claude Code、Codex、Cursor,不绑定 Umi Max。
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、最新目标版本的 `@zat-design/sisyphus-react` 4.x Ant Design 6。
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 <项目根目录> --channel stable
44
+ node scripts/resolve-versions.mjs --root <项目根目录>
45
45
  ```
46
46
 
47
- - Ant Design 必须满足最新稳定 `>=6 <7`。
48
- - Sisyphus 必须满足最新稳定 `>=4 <5`。
49
- - beta 仅在用户明确要求时使用 `--channel beta`。
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-list-components`
69
- 2. `sisyphus-get-component-api(<组件名>)`
70
- 3. `sisyphus-get-examples(<组件名>, <场景关键词>)`
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
- 记录 MCP 报告版本、API 证据和 demo 证据。模板与 MCP 冲突时以匹配目标版本的 MCP 为准。
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",
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": "sisyphus-get-component-api(ProForm)",
9
- "demoEvidence": "sisyphus-get-examples(ProForm, 查询)"
8
+ "apiEvidence": {
9
+ "tool": "sisyphus-get-component-api",
10
+ "target": "ProForm",
11
+ "libraryVersion": "4.5.7",
12
+ "schemaVersion": 2
13
+ },
14
+ "demoEvidence": {
15
+ "tool": "sisyphus-get-example",
16
+ "target": "ProForm/demos/base.tsx",
17
+ "libraryVersion": "4.5.7",
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": "sisyphus-get-component-api(ProTable)",
18
- "demoEvidence": "sisyphus-get-examples(ProTable, 查询列表)"
28
+ "apiEvidence": {
29
+ "tool": "sisyphus-get-component-api",
30
+ "target": "ProTable",
31
+ "libraryVersion": "4.5.7",
32
+ "schemaVersion": 2
33
+ },
34
+ "demoEvidence": {
35
+ "tool": "sisyphus-get-example",
36
+ "target": "ProTable/demos/base.tsx",
37
+ "libraryVersion": "4.5.7",
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
- # sisyphus_range: ">=4 <5"
10
+ # sisyphus_version: "4.5.7"
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?: string;
81
- demoEvidence?: string;
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` 来源必须有 API demo 证据。
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
- getExamples(name: string, scenario?: string): Promise<ComponentExample[]>;
101
- getVersion(): Promise<string>;
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
- - 从当前工具列表或 MCP 资源中发现 Sisyphus 工具;工具未安装时停止并给出配置建议,不读取 node_modules 替代。
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`、Ant Design 文档提供器和 Context7。
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 PACKAGES = [
9
- { name: 'antd', major: 6 },
10
- { name: '@zat-design/sisyphus-react', major: 4 },
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 sections = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
61
- for (const section of sections) {
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 packageLockPath = path.join(root, 'package-lock.json');
70
- if (!fs.existsSync(packageLockPath)) return undefined;
71
- try {
72
- const lock = JSON.parse(fs.readFileSync(packageLockPath, 'utf8'));
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 yarnLockPath = path.join(root, 'yarn.lock');
81
- if (!fs.existsSync(yarnLockPath)) return undefined;
82
- const content = fs.readFileSync(yarnLockPath, 'utf8');
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 escapedSelector = declared
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 (escapedSelector && !new RegExp(`(?:^|["',\\s])${escapedSelector}(?:["',:]|$)`).test(header)) continue;
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 pnpmLockPath = path.join(root, 'pnpm-lock.yaml');
99
- if (!fs.existsSync(pnpmLockPath)) return undefined;
100
- const content = fs.readFileSync(pnpmLockPath, 'utf8');
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 patterns = [
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 getResolvedVersions = (root, packageName, declared) => {
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 present = Object.values(values).filter(Boolean);
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 queryVersions = (root, packageName) => {
129
- const output = execFileSync('npm', ['view', packageName, 'versions', '--json'], {
130
- cwd: root,
131
- encoding: 'utf8',
132
- stdio: ['ignore', 'pipe', 'pipe'],
133
- });
134
- const parsed = JSON.parse(output);
135
- return Array.isArray(parsed) ? parsed : [parsed];
136
- };
137
-
138
- const chooseLatest = (versions, major, channel) =>
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 channel = String(args.channel || 'stable');
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 results = PACKAGES.map(({ name, major }) => {
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 lockResolution = getResolvedVersions(root, name, declared);
167
- let versions = [];
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
- versions = queryVersions(root, name);
164
+ latest = queryLatest(root, name, major, channel);
172
165
  } catch (error) {
173
166
  registryError = error.stderr?.trim() || error.message;
174
167
  }
175
168
  }
176
- const latest = chooseLatest(versions, major, channel);
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
- requiredRange: `>=${major} <${major + 1}`,
180
- channel,
183
+ required: name === 'antd' ? '>=6 <7' : contract.version,
181
184
  declared,
182
- resolved: lockResolution.resolved,
183
- resolvedVersions: lockResolution.values,
185
+ resolved: resolution.resolved,
186
+ resolvedVersions: resolution.values,
187
+ active,
184
188
  latest,
185
- status: evaluateStatus({
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
- ok: results.every((item) => item.status === 'ok'),
200
- packages: results,
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(`import\\s+(?:type\\s+)?\\{([^}]+)\\}\\s+from\\s+['"]${escaped}['"]`, 'g');
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.trim().replace(/^type\s+/, '').split(/\s+as\s+/)[0].trim();
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(decisionsPath, 'decision-schema', '每条 decision 必须包含 requirementId 和 requirement');
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 (['unverified', 'unresolved'].includes(decision.status) || decision.source === 'unresolved') {
69
- addError(decisionsPath, 'unresolved-capability', `能力未解决:${decision.requirement || decision.requirementId}`);
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' && (!decision.apiEvidence || !decision.demoEvidence)) {
72
- addError(decisionsPath, 'sisyphus-evidence', `Sisyphus 决策缺少 API 或 demo 证据:${decision.requirement}`);
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 || !decision.demoEvidence || !decision.fallbackReason)
143
+ (!isEvidence(decision.apiEvidence) ||
144
+ !isEvidence(decision.demoEvidence) ||
145
+ !decision.fallbackReason)
77
146
  ) {
78
- addError(decisionsPath, 'antd-fallback-evidence', `Ant Design fallback 证据不完整:${decision.requirement}`);
147
+ addError(
148
+ decisionsPath,
149
+ 'antd-fallback-evidence',
150
+ `Ant Design fallback 证据不完整:${decision.requirement}`,
151
+ );
79
152
  }
80
- if (decision.source === 'project' && (!decision.apiEvidence || !decision.demoEvidence)) {
81
- addError(decisionsPath, 'project-evidence', `项目组件决策缺少源码或调用示例:${decision.requirement}`);
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(relative, 'proform-form-item', 'ProForm 中混入 Form.Item,必须提供明确组件证据或改用配置列');
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((name) =>
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) => item.source === 'sisyphus' && item.component === component && item.status !== 'unverified',
200
+ (item) =>
201
+ item.source === 'sisyphus' && item.component === component && item.status !== 'unverified',
117
202
  );
118
203
  if (!decision) {
119
- addError(relative, 'sisyphus-component-decision', `组件 ${component} 缺少 ComponentDecision 证据`);
204
+ addError(
205
+ relative,
206
+ 'sisyphus-component-decision',
207
+ `组件 ${component} 缺少 ComponentDecision 证据`,
208
+ );
120
209
  }
121
210
  }
122
211
 
123
- const structuralAntdComponents = new Set(['Tabs', 'Modal', 'Drawer', 'Table', 'Steps', 'Collapse']);
124
- const antdImports = extractNamedImports(content, 'antd').filter((name) => structuralAntdComponents.has(name));
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 (routerImports.length > 0 && !decisions.some((item) => /导航|路由|返回|跳转/.test(item.requirement))) {
143
- addWarning(relative, 'router-without-decision', `存在路由 import,但没有导航能力决策:${routerImports.join(', ')}`);
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
-