@givolt/i3-composer-agent 2.3.1 → 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.
- package/.agents/plugins/marketplace.json +1 -1
- package/.claude/commands/i3-prototype.md +5 -7
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.gemini/commands/i3-prototype.toml +5 -7
- package/README.md +12 -7
- package/bin/i3-composer-agent.cjs +33 -2
- package/bin/install.cjs +14 -0
- package/bin/native-migration.cjs +27 -3
- package/commands/i3-prototype.toml +5 -7
- package/docs/codebuddy-setup.md +65 -2
- package/docs/codex-setup.md +4 -0
- package/docs/other-hosts.md +1 -1
- package/docs/ui-prototype-native/MAIN-SESSION-REPORT.md +66 -0
- package/docs/ui-prototype-native/USAGE.md +91 -0
- package/docs/workbuddy-setup.md +4 -0
- package/evals/cases/i3-frontend-ui-engineering.json +16 -0
- package/evals/cases/i3-ui-prototype.json +109 -0
- package/evals/cases/i3-using-agent-skills.json +28 -0
- package/evals/fixtures/i3-ui-prototype/admin-list/assets/reference.css +3 -0
- package/evals/fixtures/i3-ui-prototype/admin-list/assets/reference.js +8 -0
- package/evals/fixtures/i3-ui-prototype/admin-list/capability-definition.json +67 -0
- package/evals/fixtures/i3-ui-prototype/admin-list/index.html +89 -0
- package/evals/fixtures/i3-ui-prototype/admin-list/readme.md +52 -0
- package/evals/fixtures/i3-ui-prototype/admin-list/template.json +8 -0
- package/evals/zh-CN/cases/i3-frontend-ui-engineering.json +17 -0
- package/evals/zh-CN/cases/i3-ui-prototype.json +115 -0
- package/evals/zh-CN/cases/i3-using-agent-skills.json +30 -0
- package/package.json +31 -1
- package/plugin.json +1 -1
- package/scripts/design-provider-test.js +7 -0
- package/scripts/enterprise-template-test.js +27 -1
- package/scripts/lib/prototype-fixture.cjs +17 -0
- package/scripts/prototype-locations-test.js +58 -0
- package/scripts/ui-prototype-content-test.js +123 -0
- package/scripts/ui-prototype-dependencies-test.js +213 -0
- package/scripts/ui-prototype-devtools-test.js +53 -0
- package/scripts/ui-prototype-native-test.js +504 -0
- package/scripts/ui-prototype-template-test.js +261 -0
- package/scripts/ui-prototype-test.js +10 -0
- package/skills/i3-ui-prototype/SKILL.md +138 -19
- package/skills/i3-ui-prototype/assets/capabilities/management.json +294 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/acceptance.json +51 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/component-map.json +13 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/index.html +11 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/package-lock.json +2298 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/package.json +22 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/public/prototype-config.json +6 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/App.vue +136 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/components/DeviceEditor.vue +111 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/config.ts +19 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/content.ts +28 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/domain/devices.ts +50 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/domain/theme.ts +25 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/main.ts +18 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/style.css +50 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/useDevices.ts +66 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/tests/devices.test.ts +44 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/tests/theme.test.ts +26 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/tsconfig.json +15 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/vite.config.ts +4 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/profile.json +174 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/readme.md +61 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/index.html +11 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/package-lock.json +2298 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/package.json +22 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/public/prototype-config.json +21 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/App.vue +51 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/config.ts +18 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/domain/theme.ts +25 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/main.ts +18 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/style.css +13 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/tests/theme.test.ts +26 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/tsconfig.json +15 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/vite.config.ts +4 -0
- package/skills/i3-ui-prototype/assets/optional-skills/host-targets.json +737 -0
- package/skills/i3-ui-prototype/assets/optional-skills/sources.json +27 -0
- package/skills/i3-ui-prototype/assets/templates/catalog.js +149 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/capability-definition.json +294 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/index.html +507 -2
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/offline-build.json +265 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/offline.js +8888 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/template.json +9 -0
- package/skills/i3-ui-prototype/assets/templates/gallery/index.html +4 -4
- package/skills/i3-ui-prototype/assets/templates/index.html +3 -0
- package/skills/i3-ui-prototype/references/artifact-contract.md +25 -0
- package/skills/i3-ui-prototype/references/content-template.md +76 -0
- package/skills/i3-ui-prototype/references/enterprise-admin.md +6 -0
- package/skills/i3-ui-prototype/references/entry-contract.md +71 -0
- package/skills/i3-ui-prototype/references/expert-adapter.md +33 -5
- package/skills/i3-ui-prototype/references/framework-contract.md +76 -0
- package/skills/i3-ui-prototype/references/interaction-acceptance.md +165 -0
- package/skills/i3-ui-prototype/references/legacy-native.md +58 -0
- package/skills/i3-ui-prototype/references/optional-skills.md +59 -0
- package/skills/i3-ui-prototype/references/template-adapters.md +1 -1
- package/skills/i3-ui-prototype/references/workflow-contract.md +58 -4
- package/skills/i3-ui-prototype/scripts/adapters/easel-html.cjs +1 -1
- package/skills/i3-ui-prototype/scripts/content-template.cjs +98 -0
- package/skills/i3-ui-prototype/scripts/enterprise-template.cjs +13 -6
- package/skills/i3-ui-prototype/scripts/manage-baseline.cjs +1 -1
- package/skills/i3-ui-prototype/scripts/native-browser.cjs +785 -0
- package/skills/i3-ui-prototype/scripts/native-build.cjs +91 -0
- package/skills/i3-ui-prototype/scripts/native-delivery.cjs +91 -0
- package/skills/i3-ui-prototype/scripts/native-devtools.cjs +91 -0
- package/skills/i3-ui-prototype/scripts/native-files.cjs +35 -0
- package/skills/i3-ui-prototype/scripts/native-pilot-lib.cjs +244 -0
- package/skills/i3-ui-prototype/scripts/native-pilot.cjs +34 -0
- package/skills/i3-ui-prototype/scripts/optional-skill-install.cjs +203 -0
- package/skills/i3-ui-prototype/scripts/prototype-dependencies.cjs +81 -0
- package/skills/i3-ui-prototype/scripts/prototype-lib.cjs +26 -6
- package/skills/i3-ui-prototype/scripts/prototype-locations.cjs +65 -0
- package/skills/i3-ui-prototype/scripts/sync-template.cjs +19 -0
- package/skills/i3-ui-prototype/scripts/template-contract.cjs +398 -0
- package/skills/i3-ui-prototype/scripts/validate-result.cjs +117 -0
- package/skills/i3-using-agent-skills/SKILL.md +2 -5
|
@@ -0,0 +1,261 @@
|
|
|
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 legacyFixture = require('./lib/prototype-fixture.cjs');
|
|
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 contract = require(path.join(skillsDirectory, 'i3-ui-prototype/scripts/template-contract.cjs'));
|
|
11
|
+
|
|
12
|
+
function fixture(t) {
|
|
13
|
+
const project = fs.mkdtempSync(path.join(os.tmpdir(), 'i3-ui-template-'));
|
|
14
|
+
t.after(() => fs.rmSync(project, { recursive: true, force: true }));
|
|
15
|
+
return legacyFixture.prepareTemplate(contract, { project });
|
|
16
|
+
}
|
|
17
|
+
function edit(directory, change) {
|
|
18
|
+
const file = path.join(directory, 'index.html');
|
|
19
|
+
fs.writeFileSync(file, change(fs.readFileSync(file, 'utf8')));
|
|
20
|
+
}
|
|
21
|
+
function parameters(index) {
|
|
22
|
+
return { definition: index.definition, bindings: index.bindings, regions: index.regions, scope: { requirements: [], exemptions: [] }, decisions: [] };
|
|
23
|
+
}
|
|
24
|
+
test('theme remains optional and its explicit preset binding rejects invalid defaults and identifiers', t => {
|
|
25
|
+
const { directory, index }=fixture(t);
|
|
26
|
+
assert.equal(index.bindings.some(binding=>binding.capabilityId==='theme'),false);
|
|
27
|
+
edit(directory,html=>html.replace(/<!-- OPTIONAL_FOUR_THEMES:[\s\S]*?(<aside[\s\S]*?<\/aside>)\s*-->/,'$1'));
|
|
28
|
+
const themed=contract.syncTemplate(directory);
|
|
29
|
+
assert.equal(themed.bindings.filter(binding=>binding.capabilityId==='theme').length,1,'Uncommenting the shipped opt-in snippet must create exactly one binding');
|
|
30
|
+
assert.equal(contract.projectAdminListContent(contract.loadTemplate(directory)).pageTitle,'设备管理','The opt-in preserves normal HTML content projection');
|
|
31
|
+
const source={type:'user',reference:'theme-request',statement:'明亮、暗黑、清新、科技,默认明亮'};
|
|
32
|
+
const inputs=parameters(themed);inputs.scope.requirements=[{id:'THEME-1',text:source.statement,source,capabilities:[{pageId:'devices',regionId:'device-list',capabilityId:'theme'}]}];
|
|
33
|
+
assert.equal(contract.checkCapabilities(inputs).status,'passed');
|
|
34
|
+
assert.equal(contract.checkCapabilities(inputs).requiredScenarios.filter(s=>s.id.includes('/theme/')).length,3);
|
|
35
|
+
for(const values of [{},{defaultTheme:'dark',presets:['light','dark','fresh','tech']},{defaultTheme:'light',presets:['light','dark','fresh',4]},{defaultTheme:'light',presets:['light','dark','fresh','fresh']}]){
|
|
36
|
+
const broken=structuredClone(inputs);broken.bindings.find(binding=>binding.capabilityId==='theme').parameters=values;
|
|
37
|
+
assert.equal(contract.checkCapabilities(broken).status,'failed',JSON.stringify(values));
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
test('an enabled theme cannot exempt its required acceptance while retaining the binding', t => {
|
|
41
|
+
const {directory}=fixture(t);
|
|
42
|
+
edit(directory,html=>html.replace('<nav class="pagination"',`<aside data-capability-id="theme" data-capability-parameters='{"defaultTheme":"light","presets":["light","dark","fresh","tech"]}'>可选主题</aside><nav class="pagination"`));
|
|
43
|
+
const index=contract.syncTemplate(directory),fixed=parameters(index),binding=fixed.bindings.find(item=>item.capabilityId==='theme');
|
|
44
|
+
const scope={requirements:[{id:'THEME-1',text:'四主题',source:{type:'user',reference:'theme-request',statement:'明亮、暗黑、清新、科技'},capabilities:[binding]}],exemptions:[]};
|
|
45
|
+
const inputRoot=path.join(directory,'fixed-inputs');fs.mkdirSync(inputRoot);
|
|
46
|
+
const save=()=>{fs.writeFileSync(path.join(inputRoot,'capabilities.json'),JSON.stringify(fixed));fs.writeFileSync(path.join(inputRoot,'scope.json'),JSON.stringify(scope));};
|
|
47
|
+
save();assert.equal(contract.checkInstanceCapabilities(inputRoot).status,'passed');
|
|
48
|
+
fixed.decisions=[{bindingId:binding.id,status:'not-applicable',requirementId:'THEME-1'}];
|
|
49
|
+
scope.exemptions=[{...binding,requirementId:'THEME-1',reason:'Trying to waive acceptance without removing the enabled theme'}];
|
|
50
|
+
save();const checked=contract.checkInstanceCapabilities(inputRoot);
|
|
51
|
+
assert.equal(checked.status,'failed');assert.match(checked.errors.join(';'),/enabled theme.*acceptance|主题.*验收/i);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('pagination supports an explicit adjustable contract without reinterpreting fixedPageSize', t => {
|
|
55
|
+
const { directory, index } = fixture(t);
|
|
56
|
+
const legacy = parameters(index);
|
|
57
|
+
assert.equal(contract.checkCapabilities(legacy).status, 'passed');
|
|
58
|
+
edit(directory, html => html.replace('"fixedPageSize":10,"minimumTestPages":3', '"mode":"adjustable","defaultPageSize":10,"pageSizeOptions":[10,20,50],"minimumTestPages":3'));
|
|
59
|
+
const synced = contract.syncTemplate(directory);
|
|
60
|
+
const binding = synced.bindings.find(entry => entry.capabilityId === 'pagination');
|
|
61
|
+
assert.deepEqual(binding.parameters, { mode: 'adjustable', defaultPageSize: 10, pageSizeOptions: [10,20,50], minimumTestPages: 3 });
|
|
62
|
+
assert.equal(contract.checkCapabilities(parameters(synced)).status, 'passed');
|
|
63
|
+
assert.equal(contract.checkCapabilities(legacy).status, 'passed', 'an existing fixed snapshot remains valid');
|
|
64
|
+
const oldSnapshot = structuredClone(legacy);
|
|
65
|
+
const oldPagination = oldSnapshot.definition.capabilities.find(item => item.id === 'pagination');
|
|
66
|
+
oldSnapshot.definition.version = '1.0.0';
|
|
67
|
+
oldPagination.parameters = { fixedPageSize: { type: 'integer', required: true, minimum: 1 }, minimumTestPages: { type: 'integer', required: true, minimum: 3 } };
|
|
68
|
+
assert.equal(contract.checkCapabilities(oldSnapshot).status, 'passed', 'the old fixed-only definition remains independently valid');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('pagination rejects ambiguous modes and malformed adjustable defaults or options', t => {
|
|
72
|
+
const { index } = fixture(t);
|
|
73
|
+
const cases = [
|
|
74
|
+
{ mode: 'automatic', fixedPageSize: 10 },
|
|
75
|
+
{ mode: 10, fixedPageSize: 10 },
|
|
76
|
+
{ mode: 'fixed' },
|
|
77
|
+
{ mode: 'fixed', fixedPageSize: 10, pageSizeOptions: [10,20,50] },
|
|
78
|
+
{ mode: 'fixed', fixedPageSize: 10, defaultPageSize: 10 },
|
|
79
|
+
{ mode: 'adjustable', fixedPageSize: 10, defaultPageSize: 10, pageSizeOptions: [10,20,50] },
|
|
80
|
+
{ mode: 'adjustable', pageSizeOptions: [10,20,50] },
|
|
81
|
+
{ mode: 'adjustable', defaultPageSize: 10 },
|
|
82
|
+
{ mode: 'adjustable', defaultPageSize: 10, pageSizeOptions: [20,50] },
|
|
83
|
+
{ mode: 'adjustable', defaultPageSize: 10, pageSizeOptions: [10,10,50] },
|
|
84
|
+
{ mode: 'adjustable', defaultPageSize: 10, pageSizeOptions: [10,'20',50] },
|
|
85
|
+
{ mode: 'adjustable', defaultPageSize: 10, pageSizeOptions: [10,0,50] },
|
|
86
|
+
{ mode: 'adjustable', defaultPageSize: 10, pageSizeOptions: [10,20.5,50] },
|
|
87
|
+
{ mode: 'adjustable', defaultPageSize: 0, pageSizeOptions: [0,20,50] },
|
|
88
|
+
{ mode: 'adjustable', defaultPageSize: '10', pageSizeOptions: [10,20,50] },
|
|
89
|
+
{ mode: 'adjustable', defaultPageSize: 10, pageSizeOptions: [10] },
|
|
90
|
+
];
|
|
91
|
+
for (const value of cases) {
|
|
92
|
+
const inputs = parameters(structuredClone(index));
|
|
93
|
+
inputs.bindings.find(binding => binding.capabilityId === 'pagination').parameters = { ...value, minimumTestPages: 3 };
|
|
94
|
+
const checked = contract.checkCapabilities(inputs);
|
|
95
|
+
assert.equal(checked.status, 'failed', JSON.stringify(value));
|
|
96
|
+
assert.ok(checked.errors.some(error => /pagination/.test(error)));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('user owns an offline editable copy and preparing again never overwrites it', t => {
|
|
101
|
+
const { directory, index, created } = fixture(t);
|
|
102
|
+
assert.equal(created, true);
|
|
103
|
+
assert.equal(index.templateId, 'admin-list');
|
|
104
|
+
assert.equal(contract.loadTemplate(directory).index.digest, index.digest);
|
|
105
|
+
edit(directory, html => html.replace('设备管理', '客户设备管理'));
|
|
106
|
+
contract.syncTemplate(directory);
|
|
107
|
+
const again = legacyFixture.prepareTemplate(contract, { project: path.resolve(directory, '../..') });
|
|
108
|
+
assert.equal(again.created, false);
|
|
109
|
+
assert.match(fs.readFileSync(path.join(directory, 'index.html'), 'utf8'), /客户设备管理/);
|
|
110
|
+
const html = fs.readFileSync(path.join(directory, 'index.html'), 'utf8');
|
|
111
|
+
assert.doesNotMatch(html, /<script[^>]+type=["']module|(?:src|href)=["'](?:https?:|\/\/)/i);
|
|
112
|
+
assert.doesNotMatch(fs.readFileSync(path.join(directory, 'assets/reference.js'), 'utf8'), /\bfetch\s*\(|\bimport\s/);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('template or asset edits invalidate index, and synchronization is deterministic', t => {
|
|
116
|
+
const { directory, index } = fixture(t);
|
|
117
|
+
edit(directory, html => html.replace('设备管理', '新设备清单'));
|
|
118
|
+
assert.throws(() => contract.loadTemplate(directory), /stale|不同步/);
|
|
119
|
+
const readonly = contract.syncTemplate(directory, { write: false });
|
|
120
|
+
assert.notEqual(readonly.digest, index.digest);
|
|
121
|
+
assert.throws(() => contract.loadTemplate(directory), /stale|不同步/);
|
|
122
|
+
const updated = contract.syncTemplate(directory);
|
|
123
|
+
assert.equal(updated.digest, contract.syncTemplate(directory, { write: false }).digest);
|
|
124
|
+
fs.appendFileSync(path.join(directory, 'assets/reference.css'), '\n/* user style */');
|
|
125
|
+
assert.throws(() => contract.loadTemplate(directory), /stale|不同步/);
|
|
126
|
+
contract.syncTemplate(directory);
|
|
127
|
+
assert.ok(contract.loadTemplate(directory).files.every(file => file.encoding === 'base64'));
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('deleted pagination is detected from region mode even when its binding is deleted', t => {
|
|
131
|
+
const { index } = fixture(t);
|
|
132
|
+
const inputs = parameters(index);
|
|
133
|
+
inputs.bindings = inputs.bindings.filter(binding => binding.capabilityId !== 'pagination');
|
|
134
|
+
const result = contract.checkCapabilities(inputs);
|
|
135
|
+
assert.equal(result.status, 'failed');
|
|
136
|
+
assert.ok(result.errors.some(error => error.includes('pagination')));
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('required capability cannot be waived by an unsupported N/A or pending decision', t => {
|
|
140
|
+
const { index } = fixture(t);
|
|
141
|
+
const inputs = parameters(index);
|
|
142
|
+
const binding = index.bindings.find(entry => entry.capabilityId === 'pagination');
|
|
143
|
+
inputs.decisions = [{ bindingId: binding.id, status: 'not-applicable', reason: 'Prototype only' }];
|
|
144
|
+
assert.equal(contract.checkCapabilities(inputs).status, 'failed');
|
|
145
|
+
inputs.decisions[0].status = 'pending';
|
|
146
|
+
assert.equal(contract.checkCapabilities(inputs).status, 'failed');
|
|
147
|
+
inputs.decisions[0].status = 'not-applicable';
|
|
148
|
+
inputs.scope = {
|
|
149
|
+
requirements: [{ id: 'SCOPE-1', text: 'The user explicitly excluded pagination for this area', source: { type: 'scope-change', reference: 'user-message-12', statement: 'This area contains exactly one fixed record; omit paging.' } }],
|
|
150
|
+
exemptions: [{ pageId: binding.pageId, regionId: binding.regionId, capabilityId: 'pagination', requirementId: 'SCOPE-1', reason: 'Explicit scope change' }],
|
|
151
|
+
};
|
|
152
|
+
inputs.decisions[0].requirementId = 'SCOPE-1';
|
|
153
|
+
const result = contract.checkCapabilities(inputs);
|
|
154
|
+
assert.equal(result.status, 'passed', result.errors.join('\n'));
|
|
155
|
+
assert.ok(!result.requiredBindings.includes(binding.id));
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('each region needs its own capabilities and scenarios', t => {
|
|
159
|
+
const { index } = fixture(t);
|
|
160
|
+
const inputs = parameters(index);
|
|
161
|
+
const original = inputs.regions.find(region => region.mode === 'management-list');
|
|
162
|
+
inputs.regions.push({ ...original, id: 'second-list' });
|
|
163
|
+
const result = contract.checkCapabilities(inputs);
|
|
164
|
+
assert.equal(result.status, 'failed');
|
|
165
|
+
assert.ok(result.errors.some(error => error.includes('second-list/pagination')));
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('duplicate identity, unknown capability and unbound controls block sync without changing old index', t => {
|
|
169
|
+
const { directory } = fixture(t);
|
|
170
|
+
const initial = fs.readFileSync(path.join(directory, 'index.html'), 'utf8');
|
|
171
|
+
const indexBefore = fs.readFileSync(path.join(directory, 'capabilities.generated.json'), 'utf8');
|
|
172
|
+
for (const invalid of [
|
|
173
|
+
initial.replace('</main>', '<section data-page-id="devices"></section></main>'),
|
|
174
|
+
initial.replace('data-capability-id="pagination"', 'data-capability-id="unknown-widget"'),
|
|
175
|
+
initial.replace('</main>', '<button>Unmapped action</button></main>'),
|
|
176
|
+
]) {
|
|
177
|
+
fs.writeFileSync(path.join(directory, 'index.html'), invalid);
|
|
178
|
+
assert.throws(() => contract.syncTemplate(directory), /identity|capability|interaction|身份|能力|交互/);
|
|
179
|
+
assert.equal(fs.readFileSync(path.join(directory, 'capabilities.generated.json'), 'utf8'), indexBefore);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('missing behavior parameters and manually edited index are rejected', t => {
|
|
184
|
+
const { directory } = fixture(t);
|
|
185
|
+
const inputs = parameters(contract.loadTemplate(directory).index);
|
|
186
|
+
inputs.bindings.find(binding => binding.capabilityId === 'selection').parameters = {};
|
|
187
|
+
assert.equal(contract.checkCapabilities(inputs).status, 'failed');
|
|
188
|
+
const file = path.join(directory, 'capabilities.generated.json');
|
|
189
|
+
const edited = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
190
|
+
edited.bindings = edited.bindings.filter(binding => binding.capabilityId !== 'pagination');
|
|
191
|
+
fs.writeFileSync(file, JSON.stringify(edited));
|
|
192
|
+
assert.throws(() => contract.loadTemplate(directory), /stale|不同步/);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('a frozen input snapshot is checked independently of current built-ins', t => {
|
|
196
|
+
const { directory, index } = fixture(t);
|
|
197
|
+
const inputRoot = path.join(directory, 'fixed-inputs');
|
|
198
|
+
fs.mkdirSync(inputRoot);
|
|
199
|
+
fs.writeFileSync(path.join(inputRoot, 'capabilities.json'), JSON.stringify(parameters(index)));
|
|
200
|
+
fs.writeFileSync(path.join(inputRoot, 'scope.json'), JSON.stringify({ requirements: [], exemptions: [] }));
|
|
201
|
+
assert.equal(contract.checkInstanceCapabilities(inputRoot).status, 'passed');
|
|
202
|
+
const frozen = JSON.parse(fs.readFileSync(path.join(inputRoot, 'capabilities.json'), 'utf8'));
|
|
203
|
+
frozen.bindings = frozen.bindings.filter(binding => binding.capabilityId !== 'pagination');
|
|
204
|
+
fs.writeFileSync(path.join(inputRoot, 'capabilities.json'), JSON.stringify(frozen));
|
|
205
|
+
assert.equal(contract.checkInstanceCapabilities(inputRoot).status, 'failed');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test('frozen template regions cannot disappear or change mode in the instance contract', t => {
|
|
209
|
+
const { directory } = fixture(t);
|
|
210
|
+
const snapshot = contract.loadTemplate(directory);
|
|
211
|
+
const inputRoot = path.join(directory, 'fixed-inputs');
|
|
212
|
+
fs.mkdirSync(inputRoot);
|
|
213
|
+
fs.writeFileSync(path.join(inputRoot, 'template-snapshot.json'), JSON.stringify(snapshot));
|
|
214
|
+
const fixed = parameters(snapshot.index);
|
|
215
|
+
fixed.regions = fixed.regions.filter(region => region.mode !== 'management-list');
|
|
216
|
+
fixed.bindings = fixed.bindings.filter(binding => binding.regionId !== 'device-list');
|
|
217
|
+
fs.writeFileSync(path.join(inputRoot, 'capabilities.json'), JSON.stringify(fixed));
|
|
218
|
+
assert.equal(contract.checkInstanceCapabilities(inputRoot).status, 'failed');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test('remote resources, module scripts and async loads cannot become offline templates', t => {
|
|
222
|
+
const { directory } = fixture(t);
|
|
223
|
+
const html = fs.readFileSync(path.join(directory, 'index.html'), 'utf8');
|
|
224
|
+
for (const invalid of [html.replace('src="assets/reference.js"', 'src="https://example.test/reference.js"'), html.replace('<script src=', '<script type="module" src=')]) {
|
|
225
|
+
fs.writeFileSync(path.join(directory, 'index.html'), invalid);
|
|
226
|
+
assert.throws(() => contract.syncTemplate(directory), /offline|离线/);
|
|
227
|
+
}
|
|
228
|
+
fs.writeFileSync(path.join(directory, 'index.html'), html);
|
|
229
|
+
fs.appendFileSync(path.join(directory, 'assets/reference.js'), '\nfetch("parts/table.html")');
|
|
230
|
+
assert.throws(() => contract.syncTemplate(directory), /offline|离线/);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test('unsafe identifiers and symlinked user template directories never write outside project', t => {
|
|
234
|
+
const project = fs.mkdtempSync(path.join(os.tmpdir(), 'i3-ui-template-path-'));
|
|
235
|
+
const other = fs.mkdtempSync(path.join(os.tmpdir(), 'i3-ui-template-other-'));
|
|
236
|
+
t.after(() => { fs.rmSync(project, { recursive: true, force: true }); fs.rmSync(other, { recursive: true, force: true }); });
|
|
237
|
+
assert.throws(() => legacyFixture.prepareTemplate(contract, { project, id: '../escape' }), /identifier|标识/);
|
|
238
|
+
fs.symlinkSync(other, path.join(project, 'ui-templates'), 'dir');
|
|
239
|
+
assert.throws(() => legacyFixture.prepareTemplate(contract, { project }), /symlink|符号链接/);
|
|
240
|
+
assert.deepEqual(fs.readdirSync(other), []);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test('content extraction reads visible headings, labels and button text with stable identities', t => {
|
|
244
|
+
const { directory }=fixture(t);
|
|
245
|
+
edit(directory,html=>html
|
|
246
|
+
.replace('<h1>设备管理</h1>','<h1>资产 & 设备</h1>')
|
|
247
|
+
.replace('data-field-id="device-name">设备名称','data-field-id="device-name">设备显示名称')
|
|
248
|
+
.replace('设备名称 <span aria-label="必填">*','资产名称 <span aria-label="必填">*')
|
|
249
|
+
.replace('data-action-id="query" type="button">查询','data-action-id="query" type="button"><strong>检索设备</strong>'));
|
|
250
|
+
const index=contract.syncTemplate(directory);
|
|
251
|
+
assert.equal(index.pages[0].title,'资产 & 设备');
|
|
252
|
+
assert.equal(index.fields.find(item=>item.localId==='device-name').label,'设备显示名称');
|
|
253
|
+
assert.equal(index.fields.find(item=>item.localId==='edit-name').label,'资产名称');
|
|
254
|
+
assert.equal(index.fields.find(item=>item.localId==='query-status').label,'设备状态');
|
|
255
|
+
assert.equal(index.actions.find(item=>item.localId==='query').label,'检索设备');
|
|
256
|
+
const projected=contract.projectAdminListContent(contract.loadTemplate(directory));
|
|
257
|
+
assert.equal(projected.pageTitle,'资产 & 设备');
|
|
258
|
+
assert.equal(projected.fields.name.label,'设备显示名称');
|
|
259
|
+
assert.equal(projected.fields.editName.label,'资产名称');
|
|
260
|
+
assert.equal(projected.actions.query,'检索设备');
|
|
261
|
+
});
|
|
@@ -112,3 +112,13 @@ test('unsafe identifiers and symlinked output parents are rejected before writes
|
|
|
112
112
|
assert.throws(() => lib.initPrototype({ project, id: 'safe', expertMode: 'template' }), /symlink|符号链接/);
|
|
113
113
|
assert.deepEqual(fs.readdirSync(elsewhere), []);
|
|
114
114
|
});
|
|
115
|
+
test('a real review can reject every recommendation without inventing adoption at freeze', t => {
|
|
116
|
+
const dir=fixture(t); const metadata=json(dir,'prototype.json');
|
|
117
|
+
metadata.expertMode='professional'; metadata.expert={status:'reviewed'}; write(dir,'prototype.json',metadata);
|
|
118
|
+
write(dir,'qa/design-advice.json',{schemaVersion:1,mode:'professional',provider:{digest:'a'.repeat(64)},records:[{decision:'rejected',query:'targeted UX review',resultIdentity:'unit-fixture-result',rationale:'Fixture is intentionally incompatible with locked design',verification:'Reviewed against fixture contract'}]});
|
|
119
|
+
browserEvidence(dir);
|
|
120
|
+
const current=lib.contentManifest(dir);
|
|
121
|
+
assert.equal(lib.checkPrototype(dir,{acceptance:true}).status,'passed');
|
|
122
|
+
lib.freezeBaseline(dir,{revision:current.revision,digest:current.digest,reviewer:'Fixture reviewer',statement:'Isolated guard test only',source:'unit-test-fixture'});
|
|
123
|
+
assert.equal(json(dir,'prototype.json').expert.status,'reviewed');
|
|
124
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: i3-ui-prototype
|
|
3
|
-
description: "
|
|
4
|
-
# i3-description-source: "description: Creates and iterates reviewable interactive UI prototypes from
|
|
3
|
+
description: "将 HTML 内容模板、业务需求与目标组件框架组合为可操作 UI 原型。适用于原型分析、原型候选生成、原型交互验证和已确认原型基线维护;生产页面实现使用工程流程。"
|
|
4
|
+
# i3-description-source: "description: Creates, analyzes, inspects and iterates reviewable interactive UI prototypes from content templates, business requirements and a selected component framework. Use when creating prototype review candidates, inspecting prototype fidelity, reviewing interactions or maintaining approved prototype baselines; production frontend implementation uses the engineering workflow."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- i3:zh-CN:start -->
|
|
@@ -12,32 +12,142 @@ description: "根据业务需求选择 UI 模板,生成、评审并迭代交
|
|
|
12
12
|
Skill/Command/Agent 标识、文件路径、命令参数、代码/API 名称、工具调用参数、JSON 键/类型/枚举、机器状态、测试期望字面量及引用原文保持原样。生成文档正文用中文,SPEC.md、tasks/plan.md、tasks/todo.md、CONSTRAINTS.md 等约定路径不改名。
|
|
13
13
|
严重程度按原规则判断,面向用户可写“严重(Critical)”“必须修复(Required)”“小问题(Nit)”“可选(Optional)”“建议考虑(Consider)”“仅供参考(FYI)”;High/Medium/Low、APPROVE/REQUEST CHANGES 和 GO/NO-GO 保留其原始语义与机器值。不得因翻译降低门槛,角色不能互相委派。
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
适用说明:将 HTML 内容模板、业务需求与目标组件框架组合为可操作 UI 原型。适用于原型分析、原型候选生成、原型交互验证和已确认原型基线维护;生产页面实现使用工程流程。
|
|
16
16
|
<!-- i3:zh-CN:end -->
|
|
17
17
|
|
|
18
18
|
# UI Prototype
|
|
19
19
|
|
|
20
20
|
## Overview
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
将业务需求、HTML 内容参考、通用能力合同和目标框架组合为可操作、可评审的原型,保留设计决策、业务映射、证据和已确认基线。主会话使用本 Skill 完成原型工作;自然语言、命令、元技能和直接 Skill 进入同一公共前置,不要求专责 Agent 或额外分派。入口与交付边界见 [entry-contract.md](references/entry-contract.md)。默认专业增强;明确选择基础生成时使用 template,两者功能验收一致。
|
|
23
|
+
|
|
24
|
+
默认使用完整 enterprise-admin HTML 内容模板,目标框架首期为 Vue 3/antDv-next。小列表 HTML 只留在回归测试夹具,不作为正式模板。既有 HTML 原型、来源快照和基线继续采用原运行合同,不隐式迁移。原型认可、正式规格、生产开发与发布分别处理。
|
|
23
25
|
|
|
24
26
|
## When to Use
|
|
25
27
|
|
|
26
|
-
-
|
|
28
|
+
- 分析需求、选择内容模板和技术框架,按业务需求生成交互原型。
|
|
27
29
|
- 根据评审意见修改原型,检查与既有基线的一致性。
|
|
28
30
|
- 确认具体原型版本,维护后续迭代使用的设计基线。
|
|
29
31
|
|
|
30
32
|
仅实现已有设计的生产组件时 use the `i3-frontend-ui-engineering` skill。普通排错、纯后端工作和不涉及原型的代码评审继续使用对应工程技能。
|
|
31
33
|
|
|
34
|
+
按当前操作复用能力:实现或修改界面时使用 `i3-frontend-ui-engineering`;框架适配时查对应版本的官方 Skill/文档;专业模式按 UI/UX Pro Max 接入合同查询、取舍和验证;真实浏览器验收时使用 `i3-browser-testing-with-devtools`。分析只加载影响结论的资料,不预跑制作或验收。原型 Skill 维护原型范围与证据,各工程 Skill 保留原有方法。
|
|
35
|
+
|
|
32
36
|
## Process
|
|
33
37
|
|
|
34
|
-
### 1.
|
|
38
|
+
### 1. 公共前置:上下文、操作与副作用
|
|
39
|
+
|
|
40
|
+
所有入口,包括直接 Skill 调用,先读取用户原始要求与最新限制、项目规则、实际文件、技术栈、`.i3-ui-prototypes.json` 指向的原型和交接。没有正式规格时整理最小范围和待确认项,不补造审批权限或把演示规则写成已确认需求。
|
|
41
|
+
|
|
42
|
+
选择 `analyze`、`create`、`revise`、`inspect` 或 `confirm-baseline`;无参数默认 create,明确分析或只读限制优先。analyze 不初始化、不安装升级、不持久化设计、不更新项目/执行状态,也不为记录分析而写文件。允许经过核对的只读查询;用户明确要求保存分析文档时只执行该项写入授权。inspect 是否落盘服从本次范围。规则见 [workflow-contract.md](references/workflow-contract.md)。
|
|
43
|
+
|
|
44
|
+
analyze 的交付是需求/能力覆盖、模板与框架选型、交互验收计划和缺口;信息足以形成这些结论后即回传。选型可使用已有来源的固定 profile、component-map、acceptance 与需求/内容模板,不等于逐组件逐 Prop 的实施核验。仅对影响选型的具体疑点定向查本地 API;完整目标版本包/源码验证留给 create,不遍历 starter、node_modules、全套 CLI 帮助或组件清单来证明分析。必要读取仍可进行;尚未核实的点留在 openItems,不执行 create 阶段的 provider 准入、逐项 UIUX 采纳门禁、构建或浏览器验收,不把计划写成已完成验收。
|
|
45
|
+
|
|
46
|
+
覆盖判断核对能力语义与当前绑定参数,不能只看组件名。PRD 与固定参数冲突时明确标为部分覆盖和需要适配,将需求原文、绑定值、适配与验收计划列入 openItems;完整交付了分析仍可 status=complete,资料不足导致分析未完成时才用 partial/blocked。分析完成不等于原型需求全部满足,也不因此额外要求用户批准。`fixedPageSize: 10` 是固定每页 10 条,不能改口解释为默认值来声称支持调整;只读分析不修改绑定或删除需求。
|
|
47
|
+
|
|
48
|
+
分析直接在会话交付结论、覆盖、选型、验收计划和缺口,不为结果格式写文件。制作或修订交付当前原型身份、范围、检查结果、证据位置和待评审状态;原生流程仍须核对当前 state/delivery。自然语言回复足以交付,不强制父子回传 JSON、标记或校验器;维护工具的结构校验不证明业务或 QA。严格只读期间不写传输文件、创建临时文件或探测可写目录。
|
|
49
|
+
|
|
50
|
+
查询前按实际 `--host` 和 `--scope` 只读检测,优先使用返回的 antDv 实际 Node 入口。新工具在对应宿主基址的 `tools/i3-prototype/`;兼容既有项目 `.prototype-tools/antdv-cli` 和 `.prototype-tools/node_modules` 布局或宿主明确提供的绝对路径及固定版本,不反复搜索 node_modules。只读分析禁止 npm/npx 自动安装,包括可能下载私有缓存的 npm exec;入口缺失或不能执行时报告未决项,使用已有只读资料,不自行补工具。可写模式也优先使用已准备的本地固定 CLI;只有已获工具准备授权才在指定工具目录安装固定版本。工具准备的写范围不包含业务项目文件或已安装 SDK,细则见框架合同。
|
|
51
|
+
|
|
52
|
+
主会话核对实际可用的技能与浏览器能力;Skill 文件存在、安装成功或词法排序不证明宿主实际调用。文件承担连续性,恢复写操作前核对 revision/digest。保留用户手改,只有一个源码与合同修改负责人;若任务另有授权的协作,记录各项实际执行与证据,不能冒认其他执行者的工作。
|
|
53
|
+
|
|
54
|
+
原型 create/revise 不包含用户级长期记忆、宿主 auto-memory 或其他项目的写入授权;只有用户明确要求记忆时才保存。文件续接使用本实例合同与交接。验证只读或纯文件冷启动时,另核对宿主的自动记忆/计划输出及写入权限,不能把“不保存会话”当作不写长期记忆;不要擅自修改全局配置。
|
|
55
|
+
|
|
56
|
+
先按实际 metadata 选择运行合同:schemaVersion=2、artifactKind=framework-prototype、runtime.kind=vue-antdv-next 使用下节原生流程;v1/旧 HTML 进入下文 HTML 合同流程并使用原摘要。未知形态拒绝,不回退其他模板。详见 [artifact-contract.md](references/artifact-contract.md)。
|
|
57
|
+
|
|
58
|
+
已有 v2 实例先运行一次 `node <skill-root>/scripts/native-pilot.cjs state <prototype-root>`,从聚合结果恢复身份、固定需求、设计/构建/实际QA、负责人和下一步的精确命令。不要为寻找参数反复遍历全仓库、node_modules或阅读控制器内部实现。这里只读检查,不安装、启动浏览器或更新状态。
|
|
59
|
+
|
|
60
|
+
### 2. 完整内容模板与需求能力
|
|
61
|
+
|
|
62
|
+
正式内容模板位于 `assets/templates/enterprise-admin/`;双击其 `index.html` 或上层 `assets/templates/index.html` 可离线预览。完整模板是内容、通用布局和交互参考,不是最终框架效果。`reference/` 保留原始来源;可编辑页面片段内嵌在离线 `index.html` 的 `template[data-source]` 中,本地样式修改后刷新生效,不需要服务、依赖安装或重建。小列表 HTML 已退出正式资产和默认生成入口。
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
node <skill-root>/scripts/native-pilot.cjs analyze --project <project-root> --execution inline
|
|
66
|
+
node <skill-root>/scripts/native-pilot.cjs template-page --page models-page
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
默认只读使用本 Skill 随版本发布的模板及能力定义,在内存中计算当前内容索引,不在业务项目创建 `ui-templates`,也不修改安装目录。模板由维护者在 Skill 源码中修改、校验并随包发布;业务修改直接作用于 `prototypes/<id>/app/`,生成依据及索引固定在 inputs。只按本次页面读取内容,避免把整套模板加载到上下文。完整模板的索引是参考清单,不声称已自动理解全部原始交互。既有模板工作副本和 prepare-template/sync-template 命令仅保留兼容;只有用户明确要求复用某个已有副本时指定 --template-root,不自动选择、覆盖或清理它。
|
|
70
|
+
|
|
71
|
+
生成前依据 PRD 整理范围文件:已有需求文件只读引用;本次新文件放在允许写入的 `.prototype-preparation/<id>/scope.json`,不提前占用原型目标目录。`scope.json`:逐个声明实际业务页面、参考页面、区域类型、需求原文及来源。管理列表由固定定义要求查询、复选、多页分页、批量操作和状态反馈;编辑区域要求校验、失败保留与重试。参数按实际需求记录;默认列表分页可调为 10/20/50,默认 10。豁免必须指向明确需求及原因,不能通过漏报区域逃避本页真实能力。人工/浏览器复核区域与需求是否一致。
|
|
72
|
+
|
|
73
|
+
新生成原型默认包含“明亮、暗黑、清新、科技”,默认“明亮”,菜单不加编号。由 application-shell 的固定主题能力和原生 ConfigProvider 实现,主题偏好按实例隔离;不把 HTML 参考样式当成最终主题。旧实例的固定分页、可选主题与 20/23 场景合同仍按原输入检查,不追溯修改。完整字段和示例见 [content-template.md](references/content-template.md)。
|
|
74
|
+
|
|
75
|
+
### 3. 原生组件首期:设计与实现
|
|
76
|
+
|
|
77
|
+
UIUX 和框架官方 Skill 独立可选安装,不随本技能默认复制。create/revise 需要这些能力前,运行 `node <skill-root>/scripts/prototype-dependencies.cjs check --host <host-id> --scope project --project <project-root>` 只读检测当前宿主项目及同宿主全局依赖;仅检查全局时用 `--scope global`;不缓存“跳过安装”,后续补装后重新检测,无需重装本技能。宿主未发现新 Skill 时刷新/新建会话。安装须明确选择,不在 analyze 自动下载安装;命令、固定版本和使用边界见 [optional-skills.md](references/optional-skills.md)。已有实例的模式、源码和验收不因安装依赖而自动变更。
|
|
78
|
+
|
|
79
|
+
按下文专业接入规则检测 UIUX,professional 不静默降级,template 必须明确选择。只读约束贯穿 provider;查询、采用和效果分别有依据。详细接入见 [expert-adapter.md](references/expert-adapter.md)。
|
|
80
|
+
|
|
81
|
+
初始化前的 probe/查询不得先占用 `prototypes/<id>`。先 probe,再用下列 create 命令原子初始化,然后在已创建实例的 qa 下保存查询、取舍与验证。如果必须在初始化前保存查询文件,使用独立准备目录(如项目内 `.prototype-preparation/<id>`),初始化后再归入本实例 QA;不要预先创建目标的 qa 目录。
|
|
82
|
+
|
|
83
|
+
加载框架官方 Skill 或对应版本官方文档、类型和源码,先查 API、读示例、再实现。先完成上文工具预检,使用已准备的固定本地 CLI;缺项不因官方 Skill 的安装建议而扩大权限。固定项目依赖锁,记录请求与实际知识版本。映射与官方来源见 [framework-contract.md](references/framework-contract.md)。
|
|
84
|
+
|
|
85
|
+
宿主已发现可调用的官方框架 Skill 时,先通过真实 Skill 工具加载它;读取 SKILL.md 或运行 CLI 查询不能冒充宿主 Skill 调用。只有该 Skill 在当前执行者中不可调用时,才使用版本匹配的官方文档/类型/源码等效渠道,并明确记录官方 Skill 未唤醒及实际资料来源。UIUX 的 Skill 调用、真实查询、建议采纳与效果验证也分别记录,查询成功不自动记 used。
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
node <skill-root>/scripts/native-pilot.cjs create --project <project-root> --id business-demo --scope <project-root>/.prototype-preparation/business-demo/scope.json --expert-mode professional --provider <uiux-root>
|
|
89
|
+
# 仅在用户已明确选择基础生成后:
|
|
90
|
+
node <skill-root>/scripts/native-pilot.cjs create --project <project-root> --id business-demo --scope <project-root>/.prototype-preparation/business-demo/scope.json --expert-mode template
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
目标存在则 revise,不重新初始化。Follow the `i3-frontend-ui-engineering` skill,使用真实 Table/Form/Drawer/反馈组件及 ConfigProvider。初始化只产生需求对应的框架骨架和四主题,business.status 为 needs-adaptation;这不是已完成业务。实现固定输入中的实际字段、列表和流程,再核对需求映射、模拟数据并更新 business.status 为 adapted。UIUX 采用规则落实到 design-system、主题和页面;模板样式不锁定生成外观。
|
|
94
|
+
|
|
95
|
+
目标存在但没有 prototype.json 时属于未识别的准备目录,不能当作有效实例执行 revise。先核对来源并保留文件;只有从本次工具记录确认整个目标目录均由当前任务的准备操作产生,才可将该整个目录移到独立准备位置,确保目标路径不存在后重试原子 create。仅移动查询文件仍会留下空目录并阻止 create。来源不明或包含用户已有文件时保留原目录并报告冲突,不得覆盖或清空。
|
|
96
|
+
|
|
97
|
+
新流程按实际业务定义模拟数据,默认容量下至少覆盖首、中、末页,不再固定为设备或 21 条。按 inputs 的分页参数验收容量切换和恢复。筛选、翻页和更换页大小清空选择,当前页多选与批量变更作用于正确记录。加载、空、失败、恢复可主动触发;校验和提交失败保留输入,重试保存更新列表。模拟数据按实例隔离,可重置。演示规则不得当作已确认权限。
|
|
98
|
+
|
|
99
|
+
### 4. 原生组件首期:构建、验收与迭代
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
node <skill-root>/scripts/native-pilot.cjs state <prototype-root>
|
|
103
|
+
# 首次且本次允许安装项目依赖时:
|
|
104
|
+
node <skill-root>/scripts/native-pilot.cjs build <prototype-root> --install
|
|
105
|
+
# 后续源码变化重新构建,已有依赖时不加 --install:
|
|
106
|
+
node <skill-root>/scripts/native-pilot.cjs build <prototype-root>
|
|
107
|
+
# 先实际加载 i3-browser-testing-with-devtools Skill,用 Chrome DevTools MCP 完成固定能力检查;
|
|
108
|
+
# 完整内容模板的新业务:按 inputs/acceptance.json 实测后,导入真实业务报告:
|
|
109
|
+
node <skill-root>/scripts/native-pilot.cjs record-run <prototype-root> --report <actual-browser-report.json>
|
|
110
|
+
# browser <root> 只服务已有的设备列表固定合同,不能用于新业务。
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
先完成真实官方/UIUX查询、业务适配与设计取舍,再构建;state.design为pending时由执行者复核并整理正式决定,工具不会代替设计判断。设计记录使用 expert-adapter 的原生 v2 格式;verificationScenario 直接取本实例 inputs/acceptance.json 的 requiredScenarios/frameworkScenarios,不从控制器或浏览器执行器反推。使用state返回的精确commands,首次构建按本次授权加--install。输入、候选和实际构建产物分别有摘要;进度、QA、依赖目录和缓存不进入候选身份。构建失败不能沿用旧产物声称通过。
|
|
114
|
+
|
|
115
|
+
本轮 CodeBuddy 原生验收由实际执行者通过宿主 Skill 工具加载 `i3-browser-testing-with-devtools`,再使用该 Skill 要求的 Chrome DevTools MCP 按固定能力与 PRD 验证。必须实际取得 DOM/计算样式、截图、console、network、可访问性和性能观测;只加载挂名、读 SKILL.md 或 Bash 调用脚本不算 Skill 执行。该 Skill 没有 Playwright 替代路径;缺 Skill 调用能力、MCP 连接或必需观测时,整链不报 complete。analyze 仅列验收计划,不启动浏览器或安装 MCP。执行与证据边界见 [interaction-acceptance.md](references/interaction-acceptance.md)。
|
|
116
|
+
|
|
117
|
+
安装内历史 browser 工具实际调用 Playwright,是同一候选/构建的专项补充回归,自动启停固定构建服务并记录场景、截图与 trace。该工具仅覆盖历史设备列表的 20/23 场景,不适用于完整内容模板的新业务;它不证明宿主调用了浏览器 Skill 或 DevTools。运行库优先发现所选宿主 `tools/i3-prototype/playwright/node_modules` 或 `tools/i3-prototype/node_modules`,保留项目 `.prototype-tools/node_modules/playwright`,也可用 app/项目模块或显式 `--playwright-module <已存在包>`;发现不等于启动成功。缺运行库或 Chrome 时报告缺项,不自动全局安装或手填报告。其他宿主的等效真实浏览器结果如实单列,不能冒充指定 Skill/MCP 链通过;旧 v1/已有 v2 继续核对原固定合同,不追溯追加新验收要求。
|
|
118
|
+
|
|
119
|
+
仅迭代历史设备列表且需要专项证明时,browser 传`--expected-categories '<JSON数组>'`、`--verify-blur-validation`、`--verify-atomic-status`或`--expected-description '<精确文案>'`;仅采用相应规则时启用,不能为了工具预设强迫采用UIUX建议。
|
|
120
|
+
|
|
121
|
+
覆盖筛选→多选→分页→抽屉→校验失败→提交失败→重试保存→列表同步,另查状态、窄屏、键盘、焦点和控制台。进入与退出使用正常动画;退出结束后再销毁;毛玻璃核对配置、计算样式及真实效果,静态截图不证明动效。
|
|
122
|
+
|
|
123
|
+
清空文本后要核对查询/校验实际结果。本轮固定 Chrome DevTools MCP 1.11.0 的空字符串 fill 可仅清 DOM 值而未发送输入事件;遇到此现象记录工具限制。实测替代操作是原生 fill 单个空格后,只读取得实际文本长度与光标位置,确认光标在末尾再按该长度逐次 press_key Backspace,最后核对空值及查询/校验结果。空格可能追加到恢复的旧文本;本轮 headless 的 Meta+A/Control+A 也未全选,不能假定快捷键成功。不要用 evaluate_script 补发事件或修改状态。输入事件问题与宿主参数回调兼容分别记录,fill 返回成功不证明页面状态已更新;版本不同按实际观测判断,详见交互验收合同。
|
|
124
|
+
|
|
125
|
+
DevTools 检查使用已核对构建的 preview URL,预览进程采用宿主支持的受管服务方式,不用持续前台进程阻塞后续工具;专项 browser 自行管理服务。preview 默认 last-build,可传 --build-id。实际检查后运行 `record-devtools-review <prototype-root> --report <actual-report.json>` 登记六类证据;新 create 的固定 browserReview policy 要求 state.devtoolsReview 为 current,缺失/过期或文件变化会阻止 finalize。旧 inputs 无 policy 时保留原合同。报告导入、字段与限制见交互验收合同。
|
|
126
|
+
|
|
127
|
+
浏览器两层证据保存后重新 state,按 commands.finalize 传当前 expected revision/digest;只有本次适用的实际验收全部通过才回传 complete。脚本核对报告结构、证据字节和身份,仍明确 invocationVerified=false;CodeBuddy 另核原始宿主 stream 的真实 Skill/MCP 调用及观测。state 的 current 和交付收据均不能代替这一层证明。
|
|
128
|
+
|
|
129
|
+
QA 引用当前candidateDigest/buildDigest,记录真实执行者和实际证据。测试前后候选一致,变化则本轮证据失效。未运行保持未验证,不能手填passed代替执行。state.nextAction=return-result时停止探索,最后工具之后用自然语言交付范围、revision、实际模式、检查结果、当前收据和HANDOFF;通过仍是pending-review。工具状态或进度文本不能代替最终交付。
|
|
130
|
+
|
|
131
|
+
原生迭代先state恢复真实源码、固定输入及手改差异。完成本次授权编辑后重新state,取得实际当前摘要,再执行其commands.revise;不要复用编辑前的摘要或命令:
|
|
132
|
+
|
|
133
|
+
```sh
|
|
134
|
+
node <skill-root>/scripts/native-pilot.cjs revise <prototype-root> --revision r002 --expected-revision r001 --expected-digest <candidate-digest>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
需要持续占用修改权时用 `claim <root> --owner <owner> --expected-revision <current> --expected-digest <digest>`,revise 传同一 --owner;编辑结束 `release <root> --owner <owner>`,再执行验收与确认。版本检查仍须匹配实际当前状态,不共享锁冒充新版本。短操作可以由脚本取得临时修改权。
|
|
138
|
+
|
|
139
|
+
中断或更换执行者从文件恢复,过期执行者不得覆盖。只重验受影响场景;实例改进不会自动回写模板。confirm-baseline 仅依据用户对明确 revision/digest 的认可和当前验收:
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
node <skill-root>/scripts/native-pilot.cjs compare-baseline <prototype-root>
|
|
143
|
+
node <skill-root>/scripts/native-pilot.cjs confirm-baseline <prototype-root> --confirmation <human-confirmation.json>
|
|
144
|
+
```
|
|
35
145
|
|
|
36
|
-
|
|
146
|
+
确认文件使用 revision、digest、reviewer、statement、source;source 指向真实人类确认。不要用测试夹具、代理自我认可或 legacy 写命令冻结 v2。
|
|
37
147
|
|
|
38
|
-
|
|
148
|
+
### HTML 审查与既有 v1:专业能力与模板预览
|
|
39
149
|
|
|
40
|
-
|
|
150
|
+
以下步骤适用于完整 HTML 模板审查及已有 HTML 合同;公共前置和只读限制同样适用。
|
|
41
151
|
|
|
42
152
|
新增原型默认 `professional`(专业增强模式)。优先使用当前宿主暴露的 UI/UX Pro Max 技能位置,运行 `scripts/check-design-provider.cjs --provider <实际技能根>`;也可传入明确的 `--host` 和 `--project` 自动检测项目、全局 Skill 和已启用插件。项目安装的本技能可直接复用同一宿主的全局 UI/UX Pro Max,无需再复制到项目。不能硬编码缓存版本、借用另一宿主安装,或仅凭目录存在判断可执行。明确指定的 provider 失败时报告该路径,不自动换成别的副本。
|
|
43
153
|
|
|
@@ -45,9 +155,9 @@ Skill/Command/Agent 标识、文件路径、命令参数、代码/API 名称、
|
|
|
45
155
|
|
|
46
156
|
模板模式使用 `template`,仍使用同一套业务和浏览器验收标准。不得静默切换,交付记录必须说明实际模式。专业接入的查询与建议适配见 [expert-adapter.md](references/expert-adapter.md)。
|
|
47
157
|
|
|
48
|
-
###
|
|
158
|
+
### HTML 审查与既有 v1:选择模板并固定版本
|
|
49
159
|
|
|
50
|
-
运行 `node <skill-root>/scripts/init-prototype.cjs templates` 查看四类:企业管理后台、AI 工作台、数字孪生大屏、移动端应用。按 availability 区分可操作模板与 planned
|
|
160
|
+
运行 `node <skill-root>/scripts/init-prototype.cjs templates` 查看四类:企业管理后台、AI 工作台、数字孪生大屏、移动端应用。按 availability 区分可操作模板与 planned;不能把待建设类型生成成其他类型。双击 `<skill-root>/assets/templates/index.html` 打开离线选择页;静态预览服务仅为可选方式。企业后台使用 i3-Composer 品牌的完整 HTML 审查版,当前可供人工测试,其余三类待后续实施。已有模板选择时沿用。
|
|
51
161
|
|
|
52
162
|
选择页只切换预览,不创建或确认业务实例。可生成模板使用 catalog/manifest 显式指定运行层;未知或缺失 adapter 不回退到 common。资源组织、独立模板扩展与旧实例兼容见 [template-adapters.md](references/template-adapters.md)。
|
|
53
163
|
|
|
@@ -63,19 +173,19 @@ node <skill-root>/scripts/init-prototype.cjs --project <project-root> --id enter
|
|
|
63
173
|
|
|
64
174
|
企业后台初始化保留完整 i3-Composer 示例。template-review 用于本次模板人工审查;business-prototype 初始为 needs-adaptation,未完成实际业务适配时验收守卫拒绝通过。复制的 app/qa 是历史来源材料,本次验收使用原型根 qa/。目标已存在时使用迭代流程,不覆盖。完整复用、来源核对与响应式规则见 [enterprise-admin.md](references/enterprise-admin.md)。
|
|
65
175
|
|
|
66
|
-
###
|
|
176
|
+
### HTML 审查与既有 v1:专业设计与业务实现
|
|
67
177
|
|
|
68
178
|
将需求编号映射到页面、字段、操作、角色、可见范围、状态与验收场景。专业增强模式加载已发现的 UI/UX Pro Max 指令,运行真实查询并核对匹配;建议不能覆盖选定模板、需求或已认可的规则。查询失配时缩小问题重试一次,仍失配则拒绝并记录依据。
|
|
69
179
|
|
|
70
180
|
专业建议的采用/拒绝写入 `qa/design-advice.json`,包括查询、结果身份、原因和验证条件。被采用规则写入 `design-system/MASTER.md`、token 或页面规则。基础工程与业务映射始终由 i3 维护。
|
|
71
181
|
|
|
72
|
-
|
|
182
|
+
真实查询、建议处理、实际采用和效果验证分别记录;采用后可记录 used,没有适用结果记录 no-match,有依据的零采用同样可完成复核。冻结不把状态自动改成 used,不修改已冻结旧快照。HANDOFF.md 同步实际情况,不能保留初始化待办描述冒充完成。
|
|
73
183
|
|
|
74
184
|
Follow the `i3-frontend-ui-engineering` skill 实现原型。固定模板版本和 token,新增页面复用组件约定。演示数据、状态切换和本地权限模拟必须可复现;普通按钮反馈不能代替完整的业务流程。修改 token 后同步 `app/tokens.css`。
|
|
75
185
|
|
|
76
|
-
###
|
|
186
|
+
### HTML 审查与既有 v1:验证并交付候选
|
|
77
187
|
|
|
78
|
-
运行 `node <skill-root>/scripts/check-prototype.cjs <prototype-root
|
|
188
|
+
运行 `node <skill-root>/scripts/check-prototype.cjs <prototype-root>`,新完整模板实例双击 `app/index.html` 即可预览;旧 v1 模块入口仍可使用 `node app/serve.cjs`。Follow the `i3-browser-testing-with-devtools` skill 或当前宿主可用的等效真实浏览器能力,检查需求映射中的主流程、校验、权限演示、失败反馈、窄屏、键盘焦点和控制台。
|
|
79
189
|
|
|
80
190
|
先取 `manage-baseline.cjs manifest <prototype-root>` 的版本与内容摘要,将实际浏览器结果、场景 ID、视口和截图保存到 `qa/browser.json`,绑定同一摘要。运行 `check-prototype.cjs <prototype-root> --acceptance` 核对证据与覆盖;有修改则旧证据失效。
|
|
81
191
|
|
|
@@ -83,7 +193,7 @@ Follow the `i3-frontend-ui-engineering` skill 实现原型。固定模板版本
|
|
|
83
193
|
|
|
84
194
|
实际验收通过后,将 prototype.json 的 verification 更新为 passed 并记录对应 digest;review 保持 pending-review。文件再变化时重新验证,不能只修改状态字段。
|
|
85
195
|
|
|
86
|
-
###
|
|
196
|
+
### HTML 审查与既有 v1:迭代与确认
|
|
87
197
|
|
|
88
198
|
`revise` 先运行 `manage-baseline.cjs revise <prototype-root> --revision r002`,读取当前设计契约和旧基线,按反馈产生新候选并重新验证受影响场景。保留旧基线和其确认记录;修改全局设计或业务状态规则必须说明差异。
|
|
89
199
|
|
|
@@ -95,6 +205,10 @@ Follow the `i3-frontend-ui-engineering` skill 实现原型。固定模板版本
|
|
|
95
205
|
|
|
96
206
|
| 想法 | 要求 |
|
|
97
207
|
| --- | --- |
|
|
208
|
+
| 原型任务必须先分派专责 Agent | 主会话使用工作 Skill,各入口共用前置与验收 |
|
|
209
|
+
| Skill 文件存在就是已经执行 | 宿主调用和实际行为分别出证据 |
|
|
210
|
+
| HTML 删除分页就可以任意不适用 | 固定规则重算,豁免须有需求来源 |
|
|
211
|
+
| 分析时顺手初始化和升级工具 | 只读限制贯穿下游技能和脚本 |
|
|
98
212
|
| 专业技能缺失就直接换成模板模式 | 先提示安装或修复,用户明确选择后才切换 |
|
|
99
213
|
| 搜索返回了配色和首页结构就全部采用 | 核对产品与页面匹配,业务界面不能被营销结构替换 |
|
|
100
214
|
| 通用演示生成成功就是完成用户业务 | 按实际需求实现并更新业务映射与验收 |
|
|
@@ -103,17 +217,22 @@ Follow the `i3-frontend-ui-engineering` skill 实现原型。固定模板版本
|
|
|
103
217
|
|
|
104
218
|
## Red Flags
|
|
105
219
|
|
|
106
|
-
-
|
|
220
|
+
- 未说明模式变化,声称使用了未查询的专业技能,或混淆查询、采用和验证。
|
|
221
|
+
- 将原型入口变成强制角色分派、绕回命令/元技能,或把结果 JSON 当作实际完成证据。
|
|
222
|
+
- 覆盖工作副本、手改或旧基线,检查时追随全局新模板。
|
|
107
223
|
- 保留通用演示数据却报告真实业务完成,或把演示授权当服务端权限。
|
|
108
224
|
- 文件变化后继续使用旧的浏览器报告或用户确认。
|
|
109
225
|
- 覆盖已认可的快照、随意更换模板,或为了检查通过降低原有门槛。
|
|
110
226
|
- 用结构校验、词法路由或截图文件存在冒充浏览器和宿主执行证据。
|
|
227
|
+
- 把 Playwright 专项回归或读取 Skill 文件写成浏览器 Skill/Chrome DevTools MCP 已实际执行。
|
|
111
228
|
|
|
112
229
|
## Verification
|
|
113
230
|
|
|
114
|
-
- [ ]
|
|
231
|
+
- [ ] 操作副作用、执行方式、模板、版本、运行形态及实际能力有记录。
|
|
232
|
+
- [ ] 固定能力与区域绑定完整,豁免有来源,各入口在主会话进入同一工作流程。
|
|
115
233
|
- [ ] 业务映射完整,原型行为与本次范围一致;待确认项可见。
|
|
116
|
-
- [ ]
|
|
234
|
+
- [ ] 专业查询、采用和验证分别记录;零采用有依据,template 明确未使用。
|
|
117
235
|
- [ ] 结构校验通过,实际浏览器覆盖必要场景并绑定当前内容摘要。
|
|
236
|
+
- [ ] 本次适用的 CodeBuddy 浏览器 Skill、DevTools 实际观测与专项回归分别有证据;旧固定合同未被隐式扩展。
|
|
118
237
|
- [ ] 候选交付待评审;冻结仅依据同一版本的人类确认。
|
|
119
238
|
- [ ] 迭代读取适用基线,设计变更和工程交接保持可追溯。
|