@givolt/i3-composer-agent 2.3.0 → 2.3.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.
Files changed (122) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude/commands/i3-prototype.md +5 -7
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/.claude-plugin/plugin.json +1 -1
  5. package/.codex-plugin/plugin.json +1 -1
  6. package/.gemini/commands/i3-prototype.toml +5 -7
  7. package/README.md +12 -7
  8. package/bin/i3-composer-agent.cjs +33 -2
  9. package/bin/install.cjs +14 -0
  10. package/bin/native-migration.cjs +27 -3
  11. package/commands/i3-prototype.toml +5 -7
  12. package/docs/codebuddy-setup.md +65 -2
  13. package/docs/codex-setup.md +4 -0
  14. package/docs/other-hosts.md +1 -1
  15. package/docs/ui-prototype-native/MAIN-SESSION-REPORT.md +66 -0
  16. package/docs/ui-prototype-native/USAGE.md +91 -0
  17. package/docs/workbuddy-setup.md +4 -0
  18. package/evals/cases/i3-frontend-ui-engineering.json +16 -0
  19. package/evals/cases/i3-ui-prototype.json +109 -0
  20. package/evals/cases/i3-using-agent-skills.json +28 -0
  21. package/evals/fixtures/i3-ui-prototype/admin-list/assets/reference.css +3 -0
  22. package/evals/fixtures/i3-ui-prototype/admin-list/assets/reference.js +8 -0
  23. package/evals/fixtures/i3-ui-prototype/admin-list/capability-definition.json +67 -0
  24. package/evals/fixtures/i3-ui-prototype/admin-list/index.html +89 -0
  25. package/evals/fixtures/i3-ui-prototype/admin-list/readme.md +52 -0
  26. package/evals/fixtures/i3-ui-prototype/admin-list/template.json +8 -0
  27. package/evals/zh-CN/cases/i3-frontend-ui-engineering.json +17 -0
  28. package/evals/zh-CN/cases/i3-ui-prototype.json +115 -0
  29. package/evals/zh-CN/cases/i3-using-agent-skills.json +30 -0
  30. package/package.json +35 -1
  31. package/plugin.json +1 -1
  32. package/scripts/design-provider-discovery-test.js +190 -0
  33. package/scripts/design-provider-test.js +75 -0
  34. package/scripts/enterprise-template-test.js +67 -1
  35. package/scripts/lib/prototype-fixture.cjs +17 -0
  36. package/scripts/prototype-locations-test.js +58 -0
  37. package/scripts/ui-prototype-content-test.js +123 -0
  38. package/scripts/ui-prototype-dependencies-test.js +213 -0
  39. package/scripts/ui-prototype-devtools-test.js +53 -0
  40. package/scripts/ui-prototype-native-test.js +504 -0
  41. package/scripts/ui-prototype-template-test.js +261 -0
  42. package/scripts/ui-prototype-test.js +10 -0
  43. package/skills/i3-ui-prototype/SKILL.md +142 -23
  44. package/skills/i3-ui-prototype/assets/capabilities/management.json +294 -0
  45. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/acceptance.json +51 -0
  46. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/component-map.json +13 -0
  47. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/index.html +11 -0
  48. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/package-lock.json +2298 -0
  49. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/package.json +22 -0
  50. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/public/prototype-config.json +6 -0
  51. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/App.vue +136 -0
  52. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/components/DeviceEditor.vue +111 -0
  53. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/config.ts +19 -0
  54. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/content.ts +28 -0
  55. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/domain/devices.ts +50 -0
  56. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/domain/theme.ts +25 -0
  57. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/main.ts +18 -0
  58. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/style.css +50 -0
  59. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/useDevices.ts +66 -0
  60. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/tests/devices.test.ts +44 -0
  61. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/tests/theme.test.ts +26 -0
  62. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/tsconfig.json +15 -0
  63. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/vite.config.ts +4 -0
  64. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/profile.json +174 -0
  65. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/readme.md +61 -0
  66. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/index.html +11 -0
  67. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/package-lock.json +2298 -0
  68. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/package.json +22 -0
  69. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/public/prototype-config.json +21 -0
  70. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/App.vue +51 -0
  71. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/config.ts +18 -0
  72. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/domain/theme.ts +25 -0
  73. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/main.ts +18 -0
  74. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/style.css +13 -0
  75. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/tests/theme.test.ts +26 -0
  76. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/tsconfig.json +15 -0
  77. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/vite.config.ts +4 -0
  78. package/skills/i3-ui-prototype/assets/optional-skills/host-targets.json +737 -0
  79. package/skills/i3-ui-prototype/assets/optional-skills/sources.json +27 -0
  80. package/skills/i3-ui-prototype/assets/templates/catalog.js +149 -0
  81. package/skills/i3-ui-prototype/assets/templates/catalog.json +3 -2
  82. package/skills/i3-ui-prototype/assets/templates/enterprise-admin/capability-definition.json +294 -0
  83. package/skills/i3-ui-prototype/assets/templates/enterprise-admin/design-system.md +3 -3
  84. package/skills/i3-ui-prototype/assets/templates/enterprise-admin/index.html +507 -2
  85. package/skills/i3-ui-prototype/assets/templates/enterprise-admin/manifest.json +1 -1
  86. package/skills/i3-ui-prototype/assets/templates/enterprise-admin/offline-build.json +265 -0
  87. package/skills/i3-ui-prototype/assets/templates/enterprise-admin/offline.js +8888 -0
  88. package/skills/i3-ui-prototype/assets/templates/enterprise-admin/template.json +9 -0
  89. package/skills/i3-ui-prototype/assets/templates/gallery/index.html +6 -6
  90. package/skills/i3-ui-prototype/assets/templates/index.html +3 -0
  91. package/skills/i3-ui-prototype/assets/templates/serve-gallery.cjs +7 -0
  92. package/skills/i3-ui-prototype/references/artifact-contract.md +25 -0
  93. package/skills/i3-ui-prototype/references/content-template.md +76 -0
  94. package/skills/i3-ui-prototype/references/enterprise-admin.md +9 -3
  95. package/skills/i3-ui-prototype/references/entry-contract.md +71 -0
  96. package/skills/i3-ui-prototype/references/expert-adapter.md +39 -5
  97. package/skills/i3-ui-prototype/references/framework-contract.md +76 -0
  98. package/skills/i3-ui-prototype/references/interaction-acceptance.md +165 -0
  99. package/skills/i3-ui-prototype/references/legacy-native.md +58 -0
  100. package/skills/i3-ui-prototype/references/optional-skills.md +59 -0
  101. package/skills/i3-ui-prototype/references/template-adapters.md +1 -1
  102. package/skills/i3-ui-prototype/references/workflow-contract.md +58 -4
  103. package/skills/i3-ui-prototype/scripts/adapters/easel-html.cjs +3 -3
  104. package/skills/i3-ui-prototype/scripts/content-template.cjs +98 -0
  105. package/skills/i3-ui-prototype/scripts/design-provider-discovery.cjs +128 -0
  106. package/skills/i3-ui-prototype/scripts/enterprise-template.cjs +30 -14
  107. package/skills/i3-ui-prototype/scripts/manage-baseline.cjs +1 -1
  108. package/skills/i3-ui-prototype/scripts/native-browser.cjs +785 -0
  109. package/skills/i3-ui-prototype/scripts/native-build.cjs +91 -0
  110. package/skills/i3-ui-prototype/scripts/native-delivery.cjs +91 -0
  111. package/skills/i3-ui-prototype/scripts/native-devtools.cjs +91 -0
  112. package/skills/i3-ui-prototype/scripts/native-files.cjs +35 -0
  113. package/skills/i3-ui-prototype/scripts/native-pilot-lib.cjs +244 -0
  114. package/skills/i3-ui-prototype/scripts/native-pilot.cjs +34 -0
  115. package/skills/i3-ui-prototype/scripts/optional-skill-install.cjs +203 -0
  116. package/skills/i3-ui-prototype/scripts/prototype-dependencies.cjs +81 -0
  117. package/skills/i3-ui-prototype/scripts/prototype-lib.cjs +50 -26
  118. package/skills/i3-ui-prototype/scripts/prototype-locations.cjs +65 -0
  119. package/skills/i3-ui-prototype/scripts/sync-template.cjs +19 -0
  120. package/skills/i3-ui-prototype/scripts/template-contract.cjs +398 -0
  121. package/skills/i3-ui-prototype/scripts/validate-result.cjs +117 -0
  122. package/skills/i3-using-agent-skills/SKILL.md +2 -5
@@ -17,6 +17,26 @@
17
17
  {
18
18
  "prompt": "根据评审反馈迭代交互原型,保留已确认的原型基线。",
19
19
  "top_k": 1
20
+ },
21
+ {
22
+ "prompt": "参考这份可编辑 HTML 内容模板,用 Vue 3 和 antDv-next 生成设备管理交互原型。",
23
+ "top_k": 1
24
+ },
25
+ {
26
+ "prompt": "先分析内容模板与业务需求如何形成交互原型方案,不修改文件。",
27
+ "top_k": 1
28
+ },
29
+ {
30
+ "prompt": "根据反馈修改交互原型,保留我手改的代码并检查抽屉退出动画。",
31
+ "top_k": 1
32
+ },
33
+ {
34
+ "prompt": "请当前会话直接使用原型技能检查待评审候选,不启动其他智能体。",
35
+ "top_k": 1
36
+ },
37
+ {
38
+ "prompt": "请主会话根据 HTML 内容参考和业务说明分析交互原型方案,保持只读。",
39
+ "top_k": 1
20
40
  }
21
41
  ],
22
42
  "negative": [
@@ -27,6 +47,10 @@
27
47
  {
28
48
  "prompt": "产品页面有一千条数据渲染很慢,请定位性能瓶颈并优化。",
29
49
  "owner": "i3-performance-optimization"
50
+ },
51
+ {
52
+ "prompt": "昨天通过的测试现在失败,请复现并定位根因。",
53
+ "owner": "i3-debugging-and-error-recovery"
30
54
  }
31
55
  ]
32
56
  },
@@ -45,6 +69,97 @@
45
69
  "The prototype remains pending human review and no approved baseline is invented",
46
70
  "人类可读的追问、说明、进度、报告与生成文档使用简体中文;保留代码、路径、工具参数、JSON 协议及机器状态的原始格式。"
47
71
  ]
72
+ },
73
+ {
74
+ "id": 1002,
75
+ "kind": "dialogue",
76
+ "prompt": "请当前会话直接使用 i3-ui-prototype 技能,分析可编辑 HTML 内容模板和通用能力清单如何生成 Vue 3+antDv-next 设备列表原型。仅分析:不改文件、不安装升级、不持久化设计结果、不启动另一个智能体。",
77
+ "expected_output": "A read-only prototype analysis that keeps direct Skill execution valid and states the framework, content, capability and acceptance boundaries",
78
+ "expectations": [
79
+ "The current executor runs the shared preflight without redispatching to an Agent, command or meta-skill",
80
+ "The response preserves analysis-only scope and does not initialize, install, persist design output or update project execution state",
81
+ "Editable content reference, reusable capabilities and target-framework behavior are explained as separate inputs",
82
+ "Unavailable provider, host or browser capabilities remain explicitly unverified rather than reported as executed",
83
+ "The analysis deliverable is the conversation; it does not invent prototype completion or human approval",
84
+ "人类可读的追问、说明、进度、报告与生成文档使用简体中文;保留代码、路径、工具参数、JSON 协议及机器状态的原始格式。"
85
+ ]
86
+ },
87
+ {
88
+ "id": 1003,
89
+ "prompt": "请使用 admin-list 可编辑内容模板,创建独立的 Vue 3+antDv-next 原生组件设备列表原型,本例明确选择 template 模式。要求:固定 21 条设备,每页 10 条;筛选或翻页清空当前页选择;批量状态变更仅影响选中记录;编辑抽屉校验必填名称,模拟保存失败时保留输入,重试后更新正确行。有真实浏览器时验证当前构建的抽屉退出动效和遮罩模糊。保留已有原型,交付待人工评审候选。",
90
+ "expected_output": "An isolated native component prototype with fixed capability inputs, reproducible local data, current-build verification or a truthful verification gap",
91
+ "files": [
92
+ "i3-ui-prototype"
93
+ ],
94
+ "expectations": [
95
+ "The instance uses the native framework runtime and fixed template, capability, scope and framework inputs rather than converting an existing legacy instance",
96
+ "Real antDv-next components implement filtering, current-page selection, pagination and the edit flow with deterministic isolated mock state",
97
+ "Required capability bindings remain independently checkable and are not removed or arbitrarily marked inapplicable to make checks pass",
98
+ "The executor distinguishes source inspection, successful build and real browser execution; unavailable checks remain unverified",
99
+ "Browser evidence, when present, refers to the current candidate and actual build, with drawer motion and blur verified separately from static screenshots",
100
+ "Template mode is explicit and does not claim UIUX use; existing prototypes remain unchanged and no human baseline approval is invented",
101
+ "人类可读的追问、说明、进度、报告与生成文档使用简体中文;保留代码、路径、工具参数、JSON 协议及机器状态的原始格式。"
102
+ ]
103
+ },
104
+ {
105
+ "id": 1004,
106
+ "kind": "dialogue",
107
+ "prompt": "请只读检查这份原型验收声明:测试后源码已改,服务仍展示上一版构建,证据只有一张截图和手动填 passed 的报告。同事建议删除分页能力并标为不适用,以便批准候选。请说明正确的后续动作;不改文件、不执行新测试、不确认基线。",
108
+ "expected_output": "A read-only rejection of stale evidence and unsupported exemption, with concrete required revalidation and pending human review",
109
+ "expectations": [
110
+ "The changed candidate and previous build invalidate the supplied acceptance claim",
111
+ "A screenshot or manually passed report is not treated as proof of current-build browser execution or drawer animation",
112
+ "Pagination remains required unless an explicit requirement or scope-change source justifies an exemption",
113
+ "The response proposes rebuilding and revalidating against fixed inputs without claiming those actions already occurred",
114
+ "No files are changed, no tests are pretended, and human baseline approval remains separate",
115
+ "人类可读的追问、说明、进度、报告与生成文档使用简体中文;保留代码、路径、工具参数、JSON 协议及机器状态的原始格式。"
116
+ ]
117
+ },
118
+ {
119
+ "id": 1005,
120
+ "kind": "dialogue",
121
+ "prompt": "请在主会话中分析可编辑 admin-list HTML 参考和业务说明,形成 Vue 3+antDv-next 评审原型方案。保持只读。宿主没有原型专责角色,也没有父子 JSON 传输机制。请交付有用的分析和后续步骤,不安装工具,不执行创建阶段的检查。",
122
+ "expected_output": "A complete read-only main-session analysis with useful coverage and a truthful acceptance plan",
123
+ "expectations": [
124
+ "The main session directly applies the shared prototype preflight without requiring or simulating a dedicated Agent",
125
+ "The analysis delivers requirement and bound-capability coverage, template and framework selection, acceptance planning and concrete gaps",
126
+ "No initialization, installation, persisted design, project state update, build, browser run or creation-stage UIUX adoption gate occurs",
127
+ "No parent-child JSON, PROTOTYPE_RESULT_V1 marker or validate-result execution is made a prerequisite for the natural-language deliverable",
128
+ "Unavailable capabilities and fixed-parameter conflicts remain visible; analysis completion is distinct from implemented coverage and human approval",
129
+ "人类可读的追问、说明、进度、报告与生成文档使用简体中文;保留代码、路径、工具参数、JSON 协议及机器状态的原始格式。"
130
+ ]
131
+ },
132
+ {
133
+ "id": 1006,
134
+ "prompt": "请根据可编辑 admin-list 参考创建独立的 Vue 3+antDv-next 原生组件设备列表评审候选,明确使用 template 模式。我授权页面内四种主题:明亮、暗黑、清新、科技,首次默认明亮。请在我的 HTML 工作副本中启用并固定主题需求,切换时保留业务状态,保留全部已有原型和手改内容。不要再次询问是否需要主题。保持固定必需能力,指定浏览器工具可用时验证当前构建。",
135
+ "expected_output": "A main-session native review candidate with explicitly fixed four-theme inputs, preserved state and truthful current-build evidence or gaps",
136
+ "files": [
137
+ "i3-ui-prototype"
138
+ ],
139
+ "expectations": [
140
+ "The main session uses the prototype workflow and supporting frontend and version-matched official framework knowledge for actual implementation, without requiring a dedicated persona",
141
+ "The authorized theme capability is bound in the user HTML working copy and fixed with its requirement source rather than left disabled or sent for repeat confirmation",
142
+ "The generated framework implements the four exact theme names, defaults to 明亮, and verifies that business state survives theme changes",
143
+ "Existing or unrequested-theme instances, user edits and approved baselines remain unchanged",
144
+ "Template mode does not claim UIUX use; fixed required capabilities and applicable acceptance scenarios are retained",
145
+ "Build, actual browser Skill and DevTools observations, supplemental regression and current delivery evidence are distinguished; unavailable checks remain incomplete",
146
+ "The final natural-language delivery identifies the candidate, current evidence and gaps, and leaves human review pending without requiring a parent-child result envelope",
147
+ "人类可读的追问、说明、进度、报告与生成文档使用简体中文;保留代码、路径、工具参数、JSON 协议及机器状态的原始格式。"
148
+ ]
149
+ },
150
+ {
151
+ "id": 1007,
152
+ "kind": "dialogue",
153
+ "prompt": "请比较三种请求的处理方式:只读分析原型方案;为评审修改原型抽屉交互;把已认可抽屉实现为可复用生产组件。说明各自适用的技能和证据,不执行这些请求。",
154
+ "expected_output": "Operation-specific support and a clear boundary between prototype work and production frontend engineering",
155
+ "expectations": [
156
+ "Read-only prototype analysis uses only material needed for coverage and selection and plans later verification without executing it",
157
+ "Prototype interaction revision stays within prototype workflow ownership and uses frontend, official framework and browser capabilities when the revision requires them",
158
+ "Professional UIUX lookup, adoption and effect verification remain distinct and apply to professional design work, with no silent downgrade to template mode",
159
+ "Approved production component implementation follows the frontend implementation method and the existing engineering workflow rather than creating a new review prototype",
160
+ "No claim of completed runtime checks or human baseline approval is made",
161
+ "人类可读的追问、说明、进度、报告与生成文档使用简体中文;保留代码、路径、工具参数、JSON 协议及机器状态的原始格式。"
162
+ ]
48
163
  }
49
164
  ]
50
165
  }
@@ -23,6 +23,10 @@
23
23
  {
24
24
  "prompt": "为项目配置 CI/CD 流水线,自动运行构建和测试。",
25
25
  "owner": "i3-ci-cd-and-automation"
26
+ },
27
+ {
28
+ "prompt": "选择 UI 原型模板,制作一个可操作的申请审批交互原型供评审。",
29
+ "owner": "i3-ui-prototype"
26
30
  }
27
31
  ]
28
32
  },
@@ -40,6 +44,32 @@
40
44
  "Core operating behaviors (assumptions surfaced) are respected",
41
45
  "人类可读的追问、说明、进度、报告与生成文档使用简体中文;保留代码、路径、工具参数、JSON 协议及机器状态的原始格式。"
42
46
  ]
47
+ },
48
+ {
49
+ "id": 1002,
50
+ "kind": "dialogue",
51
+ "prompt": "用户希望根据可编辑 HTML 参考和简短业务说明制作设备管理评审原型。请在当前会话选择适用技能并说明下一步;不创建文件,不启动独立任务。",
52
+ "expected_output": "Ordinary routing to i3-ui-prototype in the main session, with operation and scope decided by the working skill",
53
+ "expectations": [
54
+ "The meta-skill selects the prototype workflow for reviewable prototype work",
55
+ "The main session uses the working skill without requiring a dedicated persona, persistent routing rule or parent-child result protocol",
56
+ "Detailed operation, framework and acceptance decisions remain in the prototype workflow rather than being copied into the meta-skill",
57
+ "The requested read-only routing explanation does not initialize a prototype or claim implementation",
58
+ "人类可读的追问、说明、进度、报告与生成文档使用简体中文;保留代码、路径、工具参数、JSON 协议及机器状态的原始格式。"
59
+ ]
60
+ },
61
+ {
62
+ "id": 1003,
63
+ "kind": "dialogue",
64
+ "prompt": "分别收到两个任务:实现已认可的生产 Vue 表单组件并支持键盘操作;对照已认可基线检查评审原型。请分别路由并说明原工程工作流如何继续适用,不执行这两个任务。",
65
+ "expected_output": "Separate routing for production engineering and prototype review without changing the general lifecycle",
66
+ "expectations": [
67
+ "Production UI implementation follows the engineering workflow with the frontend implementation method",
68
+ "Prototype fidelity inspection selects the prototype workflow",
69
+ "The prototype route does not replace generic planning, implementation or review methods or impose prototype-specific delegation on them",
70
+ "The response does not invent runtime verification or human approval",
71
+ "人类可读的追问、说明、进度、报告与生成文档使用简体中文;保留代码、路径、工具参数、JSON 协议及机器状态的原始格式。"
72
+ ]
43
73
  }
44
74
  ]
45
75
  }
package/package.json CHANGED
@@ -100,6 +100,12 @@
100
100
  "evals/fixtures/i3-test-driven-development/package.json",
101
101
  "evals/fixtures/i3-test-driven-development/src/split.js",
102
102
  "evals/fixtures/i3-test-driven-development/test/split.test.js",
103
+ "evals/fixtures/i3-ui-prototype/admin-list/assets/reference.css",
104
+ "evals/fixtures/i3-ui-prototype/admin-list/assets/reference.js",
105
+ "evals/fixtures/i3-ui-prototype/admin-list/capability-definition.json",
106
+ "evals/fixtures/i3-ui-prototype/admin-list/index.html",
107
+ "evals/fixtures/i3-ui-prototype/admin-list/readme.md",
108
+ "evals/fixtures/i3-ui-prototype/admin-list/template.json",
103
109
  "evals/fixtures/i3-ui-prototype/business-brief.md",
104
110
  "evals/fixtures/i3-using-agent-skills/incident.md",
105
111
  "evals/fixtures/incremental-implementation-pressure/draft-export.js",
@@ -163,19 +169,30 @@
163
169
  "docs/getting-started.md",
164
170
  "docs/opencode-setup.md",
165
171
  "docs/other-hosts.md",
172
+ "docs/ui-prototype-native/MAIN-SESSION-REPORT.md",
173
+ "docs/ui-prototype-native/USAGE.md",
166
174
  "docs/windsurf-setup.md",
167
175
  "docs/workbuddy-setup.md",
168
176
  "plugin.json",
169
177
  "README.md",
170
178
  "LICENSE",
179
+ "scripts/design-provider-discovery-test.js",
180
+ "scripts/design-provider-test.js",
171
181
  "scripts/enterprise-template-test.js",
172
182
  "scripts/floor-guard-reference-test.js",
183
+ "scripts/lib/prototype-fixture.cjs",
173
184
  "scripts/lib/skill-lint-test.js",
174
185
  "scripts/lib/skill-lint.js",
186
+ "scripts/prototype-locations-test.js",
175
187
  "scripts/run-evals-test.js",
176
188
  "scripts/run-evals.js",
177
189
  "scripts/run-regressions.js",
178
190
  "scripts/template-adapter-test.js",
191
+ "scripts/ui-prototype-content-test.js",
192
+ "scripts/ui-prototype-dependencies-test.js",
193
+ "scripts/ui-prototype-devtools-test.js",
194
+ "scripts/ui-prototype-native-test.js",
195
+ "scripts/ui-prototype-template-test.js",
179
196
  "scripts/ui-prototype-test.js",
180
197
  "scripts/validate-artifact-paths-test.js",
181
198
  "scripts/validate-artifact-paths.js",
@@ -285,6 +302,12 @@
285
302
  "evals/fixtures/i3-test-driven-development/package.json",
286
303
  "evals/fixtures/i3-test-driven-development/src/split.js",
287
304
  "evals/fixtures/i3-test-driven-development/test/split.test.js",
305
+ "evals/fixtures/i3-ui-prototype/admin-list/assets/reference.css",
306
+ "evals/fixtures/i3-ui-prototype/admin-list/assets/reference.js",
307
+ "evals/fixtures/i3-ui-prototype/admin-list/capability-definition.json",
308
+ "evals/fixtures/i3-ui-prototype/admin-list/index.html",
309
+ "evals/fixtures/i3-ui-prototype/admin-list/readme.md",
310
+ "evals/fixtures/i3-ui-prototype/admin-list/template.json",
288
311
  "evals/fixtures/i3-ui-prototype/business-brief.md",
289
312
  "evals/fixtures/i3-using-agent-skills/incident.md",
290
313
  "evals/fixtures/incremental-implementation-pressure/draft-export.js",
@@ -348,20 +371,31 @@
348
371
  "docs/getting-started.md",
349
372
  "docs/opencode-setup.md",
350
373
  "docs/other-hosts.md",
374
+ "docs/ui-prototype-native/MAIN-SESSION-REPORT.md",
375
+ "docs/ui-prototype-native/USAGE.md",
351
376
  "docs/windsurf-setup.md",
352
377
  "docs/workbuddy-setup.md",
353
378
  "plugin.json",
354
379
  "package.json",
355
380
  "README.md",
356
381
  "LICENSE",
382
+ "scripts/design-provider-discovery-test.js",
383
+ "scripts/design-provider-test.js",
357
384
  "scripts/enterprise-template-test.js",
358
385
  "scripts/floor-guard-reference-test.js",
386
+ "scripts/lib/prototype-fixture.cjs",
359
387
  "scripts/lib/skill-lint-test.js",
360
388
  "scripts/lib/skill-lint.js",
389
+ "scripts/prototype-locations-test.js",
361
390
  "scripts/run-evals-test.js",
362
391
  "scripts/run-evals.js",
363
392
  "scripts/run-regressions.js",
364
393
  "scripts/template-adapter-test.js",
394
+ "scripts/ui-prototype-content-test.js",
395
+ "scripts/ui-prototype-dependencies-test.js",
396
+ "scripts/ui-prototype-devtools-test.js",
397
+ "scripts/ui-prototype-native-test.js",
398
+ "scripts/ui-prototype-template-test.js",
365
399
  "scripts/ui-prototype-test.js",
366
400
  "scripts/validate-artifact-paths-test.js",
367
401
  "scripts/validate-artifact-paths.js",
@@ -374,7 +408,7 @@
374
408
  "scripts/validate-versions.js"
375
409
  ]
376
410
  },
377
- "version": "2.3.0",
411
+ "version": "2.3.2",
378
412
  "repository": {
379
413
  "type": "git",
380
414
  "url": "https://github.com/49780522/i3-composer-agent.git"
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i3-composer-agent",
3
- "version": "2.3.0",
3
+ "version": "2.3.2",
4
4
  "description": "覆盖需求、计划、开发、测试、审查与发布的软件工程技能,默认使用简体中文。",
5
5
  "homepage": "https://github.com/49780522/i3-composer-agent",
6
6
  "repository": "https://github.com/49780522/i3-composer-agent"
@@ -0,0 +1,190 @@
1
+ 'use strict';
2
+ const test = require('node:test');
3
+ const assert = require('node:assert/strict');
4
+ const fs = require('node:fs');
5
+ const os = require('node:os');
6
+ const path = require('node:path');
7
+ const childProcess = require('node:child_process');
8
+ const installationFile = path.join(__dirname, 'lib/installation.js');
9
+ const skillsDirectory = fs.existsSync(installationFile) ? require(installationFile).loadInstallation().skillsDirectory : path.resolve(__dirname, '../skills');
10
+ const moduleFile = path.join(skillsDirectory, 'i3-ui-prototype/scripts/design-provider-discovery.cjs');
11
+
12
+ function fixture(t) {
13
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'i3-provider-discovery-'));
14
+ t.after(() => fs.rmSync(root, { recursive: true, force: true }));
15
+ const home = path.join(root, 'home'), project = path.join(root, 'project');
16
+ fs.mkdirSync(home); fs.mkdirSync(project);
17
+ return { root, home, project, env: {} };
18
+ }
19
+ function write(file, value) { fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, typeof value === 'string' ? value : JSON.stringify(value)); }
20
+ function skill(root) { write(path.join(root, 'SKILL.md'), '# Fixture UI/UX'); return root; }
21
+ function discover(options, run = () => { throw new Error('Unexpected host CLI call'); }) {
22
+ const original = childProcess.spawnSync;
23
+ childProcess.spawnSync = run;
24
+ delete require.cache[require.resolve(moduleFile)];
25
+ let api;
26
+ try { api = require(moduleFile); } finally { childProcess.spawnSync = original; }
27
+ return api.discoverProviderCandidates(options);
28
+ }
29
+ function codexPlugin(f, overrides = {}) {
30
+ const record = { pluginId: 'ui-ux-pro-max@ui-ux-pro-max-skill', name: 'ui-ux-pro-max', marketplaceName: 'ui-ux-pro-max-skill', version: '2.15.0', installed: true, enabled: true, ...overrides };
31
+ const cache = path.join(f.env.CODEX_HOME || path.join(f.home, '.codex'), 'plugins/cache/ui-ux-pro-max-skill/ui-ux-pro-max', record.version);
32
+ if (!record.version.includes('..')) {
33
+ write(path.join(cache, '.claude-plugin/plugin.json'), { name: 'ui-ux-pro-max', version: record.version, skills: './.claude/skills/' });
34
+ skill(path.join(cache, '.claude/skills/ui-ux-pro-max'));
35
+ }
36
+ return { record, cache, run: () => ({ status: 0, stdout: JSON.stringify({ installed: [record], available: [] }) }) };
37
+ }
38
+ function registryPlugin(f, host, records, enabled = true) {
39
+ const config = path.join(f.home, `.${host}`), id = 'ui-ux-pro-max@fixture-market';
40
+ write(path.join(config, 'settings.json'), { enabledPlugins: { [id]: enabled } });
41
+ write(path.join(config, 'plugins/installed_plugins.json'), { version: 2, plugins: { [id]: records } });
42
+ for (const record of records) {
43
+ write(path.join(record.installPath, host === 'claude' ? '.claude-plugin/plugin.json' : '.codebuddy-plugin/plugin.json'), { name: 'ui-ux-pro-max', version: record.version, ...(host === 'claude' ? { skills: './.claude/skills/' } : {}) });
44
+ skill(path.join(record.installPath, host === 'claude' ? '.claude/skills/ui-ux-pro-max' : 'skills/ui-ux-pro-max'));
45
+ }
46
+ return config;
47
+ }
48
+
49
+ test('explicit provider has precedence over host roots and does not invoke discovery', t => {
50
+ const f = fixture(t), root = path.join(f.root, 'explicit');
51
+ assert.deepEqual(discover({ ...f, provider: root, providerRoots: ['/ignored'], host: 'codex' }), { candidates: [{ root, source: 'explicit' }], diagnostics: [] });
52
+ });
53
+ test('host-exposed provider roots work without host context and preserve order', t => {
54
+ const f = fixture(t), roots = [path.join(f.root, 'first'), path.join(f.root, 'second')];
55
+ assert.deepEqual(discover({ ...f, providerRoots: roots }).candidates.map(c => c.root), roots);
56
+ assert.equal(discover({ ...f, providerRoots: [] }).contextRequired, undefined);
57
+ });
58
+ test('unknown host requires context and cannot read another host cache', t => {
59
+ const f = fixture(t); skill(path.join(f.home, '.claude/skills/ui-ux-pro-max'));
60
+ assert.equal(discover(f).contextRequired, true);
61
+ assert.equal(discover({ ...f, host: 'unknown' }).contextRequired, true);
62
+ assert.deepEqual(discover({ ...f, host: 'unknown' }).candidates, []);
63
+ });
64
+ test('discover false prevents automatic host access', t => {
65
+ const f = fixture(t); skill(path.join(f.home, '.agents/skills/ui-ux-pro-max'));
66
+ assert.deepEqual(discover({ ...f, host: 'codex', discover: false }), { candidates: [], diagnostics: [], contextRequired: true });
67
+ });
68
+ test('Codex returns project then global skill locations and the exact enabled plugin version', t => {
69
+ const f = fixture(t), plugin = codexPlugin(f);
70
+ const project = skill(path.join(f.project, '.agents/skills/ui-ux-pro-max'));
71
+ const global = skill(path.join(f.home, '.codex/skills/ui-ux-pro-max'));
72
+ const stale = path.join(f.home, '.codex/plugins/cache/ui-ux-pro-max-skill/ui-ux-pro-max/99.0.0/.claude/skills/ui-ux-pro-max'); skill(stale);
73
+ let command;
74
+ const result = discover({ ...f, host: 'codex' }, (...args) => { command = args; return plugin.run(); });
75
+ assert.deepEqual(result.candidates.map(c => c.root), [project, global, path.join(plugin.cache, '.claude/skills/ui-ux-pro-max')]);
76
+ assert.equal(command[0], 'codex');
77
+ assert.deepEqual(command[1], ['plugin', 'list', '--marketplace', 'ui-ux-pro-max-skill', '--json']);
78
+ assert.ok(command[2].timeout > 0 && command[2].timeout <= 15000);
79
+ assert.equal(command[2].shell, false);
80
+ });
81
+ test('Codex honors isolated CODEX_HOME and never scans the default cache', t => {
82
+ const f = fixture(t); f.env.CODEX_HOME = path.join(f.root, 'custom-codex');
83
+ const plugin = codexPlugin(f);
84
+ const result = discover({ ...f, host: 'codex' }, plugin.run);
85
+ assert.equal(result.candidates.at(-1).root, path.join(plugin.cache, '.claude/skills/ui-ux-pro-max'));
86
+ });
87
+ test('Codex disabled cached plugin is not a candidate', t => {
88
+ const f = fixture(t), plugin = codexPlugin(f, { enabled: false });
89
+ const result = discover({ ...f, host: 'codex' }, plugin.run);
90
+ assert.deepEqual(result.candidates, []);
91
+ assert.equal(result.diagnostics[0].availability, 'disabled');
92
+ });
93
+ test('Codex missing installation does not inspect cached versions', t => {
94
+ const f = fixture(t); codexPlugin(f);
95
+ const result = discover({ ...f, host: 'codex' }, () => ({ status: 0, stdout: '{"installed":[],"available":[]}' }));
96
+ assert.deepEqual(result.candidates, []);
97
+ assert.ok(result.diagnostics.every(d => d.availability === 'missing'));
98
+ });
99
+ for (const [name, run] of [
100
+ ['missing CLI', () => ({ status: null, error: { code: 'ENOENT' }, stdout: '' })],
101
+ ['failed CLI', () => ({ status: 1, stderr: 'failure', stdout: '' })],
102
+ ['invalid JSON', () => ({ status: 0, stdout: '{bad' })],
103
+ ['invalid schema', () => ({ status: 0, stdout: '{}' })],
104
+ ]) test(`Codex ${name} is a discovery error, not a missing installation`, t => {
105
+ const f = fixture(t), result = discover({ ...f, host: 'codex' }, run);
106
+ assert.equal(result.diagnostics[0].availability, 'discovery-error');
107
+ });
108
+ test('Codex rejects multiple active installed records instead of selecting a version', t => {
109
+ const f = fixture(t), { record } = codexPlugin(f);
110
+ const result = discover({ ...f, host: 'codex' }, () => ({ status: 0, stdout: JSON.stringify({ installed: [record, { ...record, version: '2.14.0' }] }) }));
111
+ assert.deepEqual(result.candidates, []);
112
+ assert.equal(result.diagnostics[0].availability, 'ambiguous');
113
+ });
114
+ for (const [name, change] of [
115
+ ['wrong manifest version', m => { m.version = '2.14.0'; }],
116
+ ['wrong manifest name', m => { m.name = 'different'; }],
117
+ ['escaping skills declaration', m => { m.skills = '../../../outside'; }],
118
+ ['missing skills declaration', m => { delete m.skills; }],
119
+ ]) test(`Codex rejects ${name}`, t => {
120
+ const f = fixture(t), plugin = codexPlugin(f), file = path.join(plugin.cache, '.claude-plugin/plugin.json');
121
+ const manifest = JSON.parse(fs.readFileSync(file)); change(manifest); write(file, manifest);
122
+ const result = discover({ ...f, host: 'codex' }, plugin.run);
123
+ assert.deepEqual(result.candidates, []);
124
+ assert.equal(result.diagnostics[0].availability, 'discovery-error');
125
+ });
126
+ test('Codex validates the active version as a single safe path segment', t => {
127
+ const f = fixture(t), plugin = codexPlugin(f, { version: '../../escape' });
128
+ assert.equal(discover({ ...f, host: 'codex' }, plugin.run).diagnostics[0].availability, 'discovery-error');
129
+ });
130
+ test('Codex rejects a child skill symlink escaping the registered plugin root', t => {
131
+ const f = fixture(t), plugin = codexPlugin(f), outside = skill(path.join(f.root, 'outside'));
132
+ const child = path.join(plugin.cache, '.claude/skills/ui-ux-pro-max');
133
+ fs.rmSync(child, { recursive: true }); fs.symlinkSync(outside, child, 'dir');
134
+ const result = discover({ ...f, host: 'codex' }, plugin.run);
135
+ assert.deepEqual(result.candidates, []);
136
+ assert.equal(result.diagnostics[0].availability, 'discovery-error');
137
+ });
138
+ for (const host of ['claude', 'workbuddy', 'codebuddy']) test(`${host} selects only enabled registered plugins`, t => {
139
+ const f = fixture(t), installPath = path.join(f.root, host, 'plugin');
140
+ registryPlugin(f, host, [{ scope: 'user', installPath, version: '2.0.0' }]);
141
+ const result = discover({ ...f, host });
142
+ assert.deepEqual(result.candidates.map(c => c.root), [path.join(installPath, host === 'claude' ? '.claude/skills/ui-ux-pro-max' : 'skills/ui-ux-pro-max')]);
143
+ registryPlugin(f, host, [{ scope: 'user', installPath, version: '2.0.0' }], false);
144
+ const disabled = discover({ ...f, host });
145
+ assert.deepEqual(disabled.candidates, []); assert.equal(disabled.diagnostics[0].availability, 'disabled');
146
+ });
147
+ test('missing optional registry is normal, while corrupt metadata is an error', t => {
148
+ const f = fixture(t);
149
+ assert.ok(discover({ ...f, host: 'workbuddy' }).diagnostics.every(d => d.availability === 'missing'));
150
+ write(path.join(f.home, '.workbuddy/plugins/installed_plugins.json'), '{invalid');
151
+ assert.equal(discover({ ...f, host: 'workbuddy' }).diagnostics[0].availability, 'discovery-error');
152
+ });
153
+ test('a registered plugin with missing enablement metadata needs diagnosis instead of installation', t => {
154
+ const f = fixture(t);
155
+ const config = registryPlugin(f, 'workbuddy', [{ scope: 'user', installPath: path.join(f.root, 'plugin'), version: '2.0.0' }]);
156
+ fs.unlinkSync(path.join(config, 'settings.json'));
157
+ const result = discover({ ...f, host: 'workbuddy' });
158
+ assert.deepEqual(result.candidates, []);
159
+ assert.equal(result.diagnostics[0].availability, 'discovery-error');
160
+ });
161
+ test('project plugin scope must match the current project path', t => {
162
+ const f = fixture(t), installPath = path.join(f.root, 'plugin');
163
+ registryPlugin(f, 'claude', [{ scope: 'project', projectPath: path.join(f.root, 'other-project'), installPath, version: '2.0.0' }]);
164
+ assert.deepEqual(discover({ ...f, host: 'claude-code' }).candidates, []);
165
+ registryPlugin(f, 'claude', [{ scope: 'project', projectPath: f.project, installPath, version: '2.0.0' }]);
166
+ assert.equal(discover({ ...f, host: 'claude-code' }).candidates.length, 1);
167
+ registryPlugin(f, 'claude', [{ scope: 'project', installPath, version: '2.0.0' }]);
168
+ assert.deepEqual(discover({ ...f, host: 'claude-code' }).candidates, []);
169
+ });
170
+ test('ambiguous matching registry entries do not pick one arbitrarily', t => {
171
+ const f = fixture(t);
172
+ registryPlugin(f, 'workbuddy', [
173
+ { scope: 'user', installPath: path.join(f.root, 'first'), version: '2.0.0' },
174
+ { scope: 'user', installPath: path.join(f.root, 'second'), version: '2.1.0' },
175
+ ]);
176
+ const result = discover({ ...f, host: 'workbuddy' });
177
+ assert.deepEqual(result.candidates, []); assert.equal(result.diagnostics[0].availability, 'ambiguous');
178
+ });
179
+ test('host selection never borrows another host global plugin', t => {
180
+ const f = fixture(t);
181
+ registryPlugin(f, 'claude', [{ scope: 'user', installPath: path.join(f.root, 'claude'), version: '2.0.0' }]);
182
+ registryPlugin(f, 'workbuddy', [{ scope: 'user', installPath: path.join(f.root, 'workbuddy'), version: '2.0.0' }]);
183
+ assert.deepEqual(discover({ ...f, host: 'codebuddy' }).candidates, []);
184
+ });
185
+ test('WorkBuddy uses its global config override and the project CodeBuddy skills directory', t => {
186
+ const f = fixture(t); f.env.WORKBUDDY_CONFIG_DIR = path.join(f.root, 'custom-workbuddy');
187
+ const projectRoot = skill(path.join(f.project, '.codebuddy/skills/ui-ux-pro-max'));
188
+ const globalRoot = skill(path.join(f.env.WORKBUDDY_CONFIG_DIR, 'skills/ui-ux-pro-max'));
189
+ assert.deepEqual(discover({ ...f, host: 'workbuddy' }).candidates.map(c => c.root), [projectRoot, globalRoot]);
190
+ });
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+ const { test, mock } = require('node:test');
3
+ const assert = require('node:assert/strict');
4
+ const fs = require('node:fs');
5
+ const os = require('node:os');
6
+ const path = require('node:path');
7
+ const cp = require('node:child_process');
8
+ mock.method(cp, 'spawnSync', (command, args) => {
9
+ if (!args.includes('--json') || args[0] !== '-B') return { status: 1, stdout: '', stderr: 'No plugin CLI in isolated test' };
10
+ if (args[1].includes('query-fails')) return { status: 1, stdout: '', stderr: 'Python module unavailable' };
11
+ return { status: 0, stdout: JSON.stringify({ domain: 'ux', results: [{ name: 'Error summary' }] }), stderr: '' };
12
+ });
13
+ const installationFile = path.join(__dirname, 'lib/installation.js');
14
+ const skillsDirectory = fs.existsSync(installationFile) ? require(installationFile).loadInstallation().skillsDirectory : path.resolve(__dirname, '../skills');
15
+ const lib = require(path.join(skillsDirectory, 'i3-ui-prototype/scripts/prototype-lib.cjs'));
16
+ function fixture(t) {
17
+ const home = fs.mkdtempSync(path.join(os.tmpdir(), 'i3-provider-'));
18
+ t.after(() => fs.rmSync(home, { recursive: true, force: true }));
19
+ return home;
20
+ }
21
+ function provider(home, name) {
22
+ const root = path.join(home, name);
23
+ for (const file of ['SKILL.md', 'scripts/search.py', 'scripts/core.py', 'scripts/design_system.py', ...['ux-guidelines', 'styles', 'colors', 'typography', 'products', 'landing', 'ui-reasoning'].map(name => `data/${name}.csv`)]) {
24
+ fs.mkdirSync(path.dirname(path.join(root, file)), { recursive: true });
25
+ fs.writeFileSync(path.join(root, file), 'fixture');
26
+ }
27
+ return root;
28
+ }
29
+ test('an incomplete project provider does not shadow a usable global provider', t => {
30
+ const home = fixture(t), local = path.join(home, 'project-provider'), global = provider(home, 'global-provider');
31
+ fs.mkdirSync(local);
32
+ const result = lib.checkDesignProvider({ providerRoots: [local, global] });
33
+ assert.equal(result.availability, 'available');
34
+ assert.equal(result.root, global);
35
+ assert.equal(result.attempts[0].availability, 'unusable');
36
+ assert.ok(result.attempts[0].missing.includes('scripts/search.py'));
37
+ });
38
+ test('a failed project query does not shadow a usable global provider', t => {
39
+ const home = fixture(t), local = provider(home, 'query-fails'), global = provider(home, 'global-provider');
40
+ const result = lib.checkDesignProvider({ providerRoots: [local, global] });
41
+ assert.equal(result.availability, 'available');
42
+ assert.equal(result.root, global);
43
+ assert.match(result.attempts[0].reason, /Python module unavailable/);
44
+ });
45
+ test('an explicit provider failure never silently selects a different provider', t => {
46
+ const home = fixture(t), selected = path.join(home, 'missing'), other = provider(home, 'other');
47
+ const result = lib.checkDesignProvider({ provider: selected, providerRoots: [other] });
48
+ assert.equal(result.availability, 'missing');
49
+ assert.deepEqual(result.searched, [selected]);
50
+ });
51
+ test('without host or provider the result requests context, not installation', t => {
52
+ const home = fixture(t);
53
+ const result = lib.initPrototype({ project: home, id: 'demo', home, env: {} });
54
+ assert.equal(result.provider.availability, 'needs-context');
55
+ assert.equal(result.choices[0].id, 'locate');
56
+ assert.ok(!result.choices.some(choice => choice.id === 'install'));
57
+ assert.equal(fs.existsSync(path.join(home, 'prototypes')), false);
58
+ });
59
+ test('all unusable providers preserve their individual failure reasons without asking to reinstall', t => {
60
+ const home = fixture(t), incomplete = path.join(home, 'incomplete'), failing = provider(home, 'query-fails');
61
+ fs.mkdirSync(incomplete);
62
+ const result = lib.initPrototype({ project: home, id: 'demo', providerRoots: [incomplete, failing] });
63
+ assert.equal(result.provider.availability, 'unusable');
64
+ assert.equal(result.provider.attempts.length, 2);
65
+ assert.equal(result.choices[0].id, 'repair');
66
+ assert.ok(!result.choices.some(choice => choice.id === 'install'));
67
+ assert.equal(fs.existsSync(path.join(home, 'prototypes')), false);
68
+ });
69
+ test('a business package version cannot masquerade as the later installed UIUX version', t => {
70
+ const home = fixture(t), root = provider(home, 'manual-provider');
71
+ fs.writeFileSync(path.join(home, 'package.json'), JSON.stringify({ name: 'human-business-project', version: '99.88.77' }));
72
+ assert.equal(lib.checkDesignProvider({ provider: root }).version, 'unversioned');
73
+ fs.writeFileSync(path.join(root, 'UPSTREAM-ORIGIN.json'), JSON.stringify({ packageName: 'ui-ux-pro-max-cli', version: '2.15.0' }));
74
+ assert.equal(lib.checkDesignProvider({ provider: root }).version, '2.15.0');
75
+ });