@givolt/i3-composer-agent 2.3.1 → 2.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.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 +16 -7
- package/bin/agent-targets.json +4 -2
- package/bin/i3-composer-agent.cjs +74 -7
- package/bin/install.cjs +16 -1
- package/bin/native-layout.cjs +1 -1
- package/bin/native-migration.cjs +27 -3
- package/bin/tool-selector.cjs +24 -7
- package/bin/wizard.cjs +89 -15
- package/commands/i3-prototype.toml +5 -7
- package/docs/codebuddy-setup.md +91 -3
- package/docs/codex-setup.md +6 -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 +93 -0
- package/docs/workbuddy-setup.md +6 -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/hooks/codebuddy-upstream.cjs +93 -0
- package/hooks/i3-prototype.cjs +12 -0
- package/hooks/manage.cjs +162 -0
- package/package.json +61 -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-cache-test.js +55 -0
- package/scripts/prototype-generation-entry-test.js +6 -0
- package/scripts/prototype-generation-evidence-test.js +10 -0
- package/scripts/prototype-generation-native-test.js +78 -0
- package/scripts/prototype-generation-test.js +99 -0
- package/scripts/prototype-inventory-test.js +43 -0
- package/scripts/prototype-locations-test.js +79 -0
- package/scripts/prototype-project-test.js +183 -0
- package/scripts/ui-prototype-codebuddy-hook-test.js +106 -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-execution-evidence-test.js +62 -0
- package/scripts/ui-prototype-execution-gates-test.js +66 -0
- package/scripts/ui-prototype-execution-policy-test.js +42 -0
- package/scripts/ui-prototype-execution-runtime-test.js +27 -0
- package/scripts/ui-prototype-ide-source-test.js +73 -0
- package/scripts/ui-prototype-native-test.js +504 -0
- package/scripts/ui-prototype-registration-test.js +53 -0
- package/scripts/ui-prototype-template-test.js +261 -0
- package/scripts/ui-prototype-test.js +10 -0
- package/scripts/ui-prototype-transcript-auto-test.js +67 -0
- package/skills/i3-frontend-ui-engineering/SKILL.md +1 -1
- package/skills/i3-incremental-implementation/SKILL.md +1 -1
- package/skills/i3-ui-prototype/SKILL.md +158 -19
- package/skills/i3-ui-prototype/assets/capabilities/generation.json +833 -0
- 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/generation-component-map.json +300 -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 +62 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/PrototypeShell.vue +59 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/config.ts +23 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/domain/presentation.ts +29 -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 +26 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/tests/presentation.test.ts +23 -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/hosts/codebuddy/profile.json +13 -0
- package/skills/i3-ui-prototype/assets/optional-skills/host-targets.json +739 -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/catalog.json +2 -1
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/capability-definition.json +294 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/component-inventory.json +707 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/generation.json +49 -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-inventory.json +28791 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/template.json +9 -0
- package/skills/i3-ui-prototype/assets/templates/gallery/index.html +5 -5
- package/skills/i3-ui-prototype/assets/templates/generation-options.js +42 -0
- package/skills/i3-ui-prototype/assets/templates/index.html +3 -0
- package/skills/i3-ui-prototype/assets/templates/presentation.html +17 -0
- package/skills/i3-ui-prototype/references/artifact-contract.md +26 -1
- package/skills/i3-ui-prototype/references/content-template.md +81 -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/execution-evidence.md +66 -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/generation-contract.md +122 -0
- package/skills/i3-ui-prototype/references/interaction-acceptance.md +169 -0
- package/skills/i3-ui-prototype/references/legacy-native.md +58 -0
- package/skills/i3-ui-prototype/references/optional-skills.md +63 -0
- package/skills/i3-ui-prototype/references/project-layout.md +92 -0
- package/skills/i3-ui-prototype/references/template-adapters.md +7 -1
- package/skills/i3-ui-prototype/references/workflow-contract.md +62 -4
- package/skills/i3-ui-prototype/scripts/adapters/easel-html.cjs +1 -1
- package/skills/i3-ui-prototype/scripts/browser-workspace.cjs +72 -0
- package/skills/i3-ui-prototype/scripts/codebuddy-context.cjs +75 -0
- package/skills/i3-ui-prototype/scripts/codebuddy-hook.cjs +157 -0
- package/skills/i3-ui-prototype/scripts/codebuddy-ide-source.cjs +69 -0
- package/skills/i3-ui-prototype/scripts/codebuddy-registration.cjs +123 -0
- 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/execution-assertions.cjs +43 -0
- package/skills/i3-ui-prototype/scripts/execution-evidence.cjs +146 -0
- package/skills/i3-ui-prototype/scripts/execution-gates.cjs +147 -0
- package/skills/i3-ui-prototype/scripts/execution-policy.cjs +41 -0
- package/skills/i3-ui-prototype/scripts/execution-runtime.cjs +60 -0
- package/skills/i3-ui-prototype/scripts/generation-assets.cjs +18 -0
- package/skills/i3-ui-prototype/scripts/generation-contract.cjs +214 -0
- package/skills/i3-ui-prototype/scripts/generation-evidence.cjs +50 -0
- package/skills/i3-ui-prototype/scripts/generation-replan.cjs +34 -0
- package/skills/i3-ui-prototype/scripts/hosts/codebuddy.cjs +16 -0
- 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 +103 -0
- package/skills/i3-ui-prototype/scripts/native-devtools.cjs +92 -0
- package/skills/i3-ui-prototype/scripts/native-files.cjs +35 -0
- package/skills/i3-ui-prototype/scripts/native-pilot-lib.cjs +292 -0
- package/skills/i3-ui-prototype/scripts/native-pilot.cjs +50 -0
- package/skills/i3-ui-prototype/scripts/optional-skill-install.cjs +203 -0
- package/skills/i3-ui-prototype/scripts/project-registry.cjs +126 -0
- package/skills/i3-ui-prototype/scripts/prototype-dependencies.cjs +81 -0
- package/skills/i3-ui-prototype/scripts/prototype-hooks.cjs +87 -0
- package/skills/i3-ui-prototype/scripts/prototype-lib.cjs +31 -13
- package/skills/i3-ui-prototype/scripts/prototype-locations.cjs +69 -0
- package/skills/i3-ui-prototype/scripts/sync-template.cjs +19 -0
- package/skills/i3-ui-prototype/scripts/template-contract.cjs +407 -0
- package/skills/i3-ui-prototype/scripts/template-inventory.cjs +78 -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,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const prototypeScripts=(()=>{const fs=require('node:fs'),path=require('node:path'),file=path.join(__dirname,'lib/installation.js');return path.join(fs.existsSync(file)?require(file).loadInstallation().skillsDirectory:path.resolve(__dirname,'../skills'),'i3-ui-prototype/scripts');})();
|
|
3
|
+
const test=require('node:test'),assert=require('node:assert/strict');
|
|
4
|
+
const P=require(prototypeScripts+'/execution-policy.cjs');
|
|
5
|
+
const E=require(prototypeScripts+'/execution-evidence.cjs');
|
|
6
|
+
const G=require(prototypeScripts+'/execution-gates.cjs');
|
|
7
|
+
test('native UI writes resolve relative paths from the actual tool cwd',()=>{
|
|
8
|
+
assert.equal(G.uiWrite({name:'Write',cwd:'/work',input:{file_path:'prototypes/orders/app/src/Probe.vue'}},'/work/prototypes/orders'),true);
|
|
9
|
+
assert.equal(G.uiWrite({name:'Write',cwd:'/work/prototypes/orders/app',input:{file_path:'src/Probe.vue'}},'/work/prototypes/orders'),true);
|
|
10
|
+
assert.equal(G.uiWrite({name:'Write',cwd:'/work',input:{file_path:'other/app/src/Probe.vue'}},'/work/prototypes/orders'),false);
|
|
11
|
+
assert.equal(G.uiWrite({name:'Write',input:{filePath:'/work/prototypes/orders/app/src/Probe.vue'}},'/work/prototypes/orders'),true);
|
|
12
|
+
});
|
|
13
|
+
function fixture() {
|
|
14
|
+
const file='/work/prototypes/orders/app/src/Orders.vue',bytes='<Table />';
|
|
15
|
+
const call=(toolCallId,name,input,result,line,skill)=>({toolCallId,name,input,result,requestLine:line,resultLine:line+1,success:true,resultDigest:E.sha(result),sessionId:'s1',contextLine:0,skill});
|
|
16
|
+
const calls=[call('front','Skill',{},'frontend instructions',3,'frontend'),call('ant','Skill',{},'antdv instructions',5,'antdv'),call('query','Bash',{command:'antdv info Table'},'Table API: rowSelection',7),call('version','Read',{file_path:'/work/app/node_modules/antdv-next/package.json'},'{"version":"1.5.3"}',9),call('write','Write',{file_path:file},'saved',11)];
|
|
17
|
+
const policy=P.create({mode:'template'}),current={digest:'candidate',inputDigest:'input',revision:'r001'};
|
|
18
|
+
const binding={schemaVersion:1,runId:'r1',sessionId:'s1',projectKey:'p1',directory:'/work/prototypes/orders',instanceId:'instance',policyDigest:policy.digest,inputDigest:'input',entryLine:1};
|
|
19
|
+
const proof={schemaVersion:1,runId:'r1',sessionId:'s1',projectKey:'p1',policyDigest:policy.digest,candidateDigest:'candidate',inputDigest:'input',revision:'r001',frontend:{toolCallId:'front',files:[{path:'app/src/Orders.vue',sha256:E.sha(bytes)}],requirementIds:['R1']},official:{skill:{toolCallId:'ant'},version:{toolCallId:'version',quote:'"version":"1.5.3"'},queries:[{component:'Table',toolCallId:'query',quote:'Table API: rowSelection'}]},templateChoice:{quote:'template mode'}};
|
|
20
|
+
return {policy,current,binding,proof,source:{calls,contextLine:0,prompts:[{line:1,text:'generate prototype in template mode',digest:'prompt'}]},scope:{requirements:[{id:'R1'}]},files:{'app/src/Orders.vue':bytes},implementation:{bindings:[{status:'implemented',components:['Table'],files:[{path:'app/src/Orders.vue'}]}]},frameworkVersion:'1.5.3'};
|
|
21
|
+
}
|
|
22
|
+
test('a native frontend load is required before the first actual UI write in both modes',()=>{
|
|
23
|
+
const f=fixture();assert.equal(G.evaluate(f,'implementation').status,'passed');
|
|
24
|
+
f.source.calls[0].success=false;assert.equal(G.evaluate(f,'implementation').status,'failed');
|
|
25
|
+
f.source.calls[0].success=true;f.source.calls[0].resultLine=12;assert.match(G.evaluate(f,'implementation').errors.join(),/before/);
|
|
26
|
+
});
|
|
27
|
+
test('IDE relative write evidence uses its bound workspace while legacy evidence keeps its contract',()=>{
|
|
28
|
+
const f=fixture();f.source.format='codebuddy-ide-index-v1';f.binding.project='/work';f.source.calls.find(c=>c.name==='Write').input.file_path='prototypes/orders/app/src/Orders.vue';
|
|
29
|
+
assert.equal(G.evaluate(f,'implementation').status,'passed');
|
|
30
|
+
});
|
|
31
|
+
test('current context, task and artifact identities reject replay and changed inputs',()=>{
|
|
32
|
+
for(const mutate of [f=>f.proof.runId='other',f=>f.proof.inputDigest='old',f=>f.source.contextLine=10,f=>f.source.calls[0].sessionId='s2',f=>f.binding.entryLine=20]){const f=fixture();mutate(f);assert.equal(G.evaluate(f,'implementation').status,'failed');}
|
|
33
|
+
});
|
|
34
|
+
test('reading a skill, adapted metadata, or incomplete review mappings cannot pass',()=>{
|
|
35
|
+
const f=fixture();f.source.calls[0].skill=null;assert.equal(G.evaluate(f,'implementation').status,'failed');
|
|
36
|
+
const g=fixture();g.proof.frontend.files=[];assert.equal(G.evaluate(g,'implementation').status,'failed');
|
|
37
|
+
const h=fixture();h.proof.frontend.requirementIds=[];assert.equal(G.evaluate(h,'implementation').status,'failed');
|
|
38
|
+
});
|
|
39
|
+
test('official query references must be successful, relevant, and version checked',()=>{
|
|
40
|
+
const f=fixture();assert.equal(G.evaluate(f,'design').status,'passed');
|
|
41
|
+
f.proof.official.queries[0].quote='invented';assert.equal(G.evaluate(f,'design').status,'failed');
|
|
42
|
+
const g=fixture();g.source.calls[2].input.command='antdv probe';assert.equal(G.evaluate(g,'design').status,'failed');
|
|
43
|
+
const h=fixture();h.source.calls[3].result='{"version":"1.5.2"}';assert.equal(G.evaluate(h,'design').status,'failed');
|
|
44
|
+
});
|
|
45
|
+
test('professional requires actual UIUX execution; explicit template choice cannot be invented',()=>{
|
|
46
|
+
const f=fixture();f.policy=P.create({mode:'professional'});f.binding.policyDigest=f.proof.policyDigest=f.policy.digest;assert.match(G.evaluate(f,'design').errors.join(),/UIUX/);
|
|
47
|
+
const g=fixture();g.proof.templateChoice.quote='use template now';assert.equal(G.evaluate(g,'design').status,'failed');
|
|
48
|
+
const h=fixture();h.proof.templateChoice.quote='template';h.source.prompts[0].text='参考 template 制作专业增强原型';assert.equal(G.evaluate(h,'design').status,'failed');
|
|
49
|
+
});
|
|
50
|
+
test('missing current build, fake browser IDs, and self-written passed booleans cannot finish',()=>{
|
|
51
|
+
const f=fixture();f.proof.build={toolCallId:'query'};assert.equal(G.evaluate(f,'build').status,'failed');
|
|
52
|
+
f.proof.browser={passed:true};assert.equal(G.evaluate(f,'browser').status,'failed');assert.equal(G.evaluate(f,'delivery').status,'failed');
|
|
53
|
+
});
|
|
54
|
+
test('the shared gate can correlate a complete synthetic chain and rejects unrelated native facts',()=>{
|
|
55
|
+
const f=fixture(),call=(toolCallId,name,input,result,line,skill)=>({toolCallId,name,input,result,requestLine:line,resultLine:line+1,success:true,resultDigest:E.sha(result),sessionId:'s1',contextLine:0,skill});
|
|
56
|
+
f.build={buildId:'b1',buildDigest:'bytes',candidateDigest:'candidate'};
|
|
57
|
+
f.source.calls.push(call('build','Bash',{command:'node /skills/native-pilot.cjs build /work/prototypes/orders'},'{"buildId":"b1","buildDigest":"bytes"}',15),call('browser','Skill',{},'browser instructions',17,'browser'),call('preview','mcp__chrome_devtools__navigate_page',{url:'http://127.0.0.1:4173/'},'opened',19),call('fact','mcp__chrome_devtools__evaluate_script',{function:'() => document.title'},'{"candidate":"candidate","saved":true}',21));
|
|
58
|
+
f.proof.build={toolCallId:'build'};f.business={previewBaseURL:'http://127.0.0.1:4173/',journeys:[{id:'save'}]};
|
|
59
|
+
const methods={dom:'evaluate_script',screenshot:'take_screenshot',console:'list_console_messages',network:'list_network_requests',accessibility:'take_snapshot',performance:'performance_stop_trace'};
|
|
60
|
+
const refs=Object.fromEntries(Object.entries(methods).map(([kind,method],i)=>{const id=kind==='dom'?'fact':kind;if(kind!=='dom')f.source.calls.push(call(id,`mcp__chrome_devtools__${method}`,{},'synthetic observation',24+i*2));return [kind,{toolCallId:id,quote:kind==='dom'?'candidate':'synthetic observation'}];}));
|
|
61
|
+
f.devtools={skill:{toolCallId:'browser'},mcp:{calls:Object.entries(methods).map(([kind,method])=>({toolCallId:refs[kind].toolCallId,tool:`mcp__chrome_devtools__${method}`}))}};
|
|
62
|
+
f.proof.browser={skill:{toolCallId:'browser'},preview:{toolCallId:'preview'},identity:{toolCallId:'fact',quote:'candidate'},observations:refs,facts:[{scenarioId:'save',checkIndex:0,toolCallId:'fact',quote:'"saved":true'}]};
|
|
63
|
+
f.assertions={schemaVersion:1,scenarios:[{scenarioId:'save',checks:[{method:'evaluate_script',pointer:'/saved',operator:'equals',expected:true}]}]};
|
|
64
|
+
assert.equal(G.evaluate(f,'delivery').status,'passed');
|
|
65
|
+
f.source.calls.find(c=>c.toolCallId==='fact').result='{"candidate":"candidate","saved":false}';f.proof.browser.facts[0].quote='"saved":false';assert.equal(G.evaluate(f,'delivery').status,'failed');
|
|
66
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const prototypeScripts=(()=>{const fs=require('node:fs'),path=require('node:path'),file=path.join(__dirname,'lib/installation.js');return path.join(fs.existsSync(file)?require(file).loadInstallation().skillsDirectory:path.resolve(__dirname,'../skills'),'i3-ui-prototype/scripts');})();
|
|
3
|
+
const test = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const P = require(prototypeScripts+'/execution-policy.cjs');
|
|
6
|
+
test('CodeBuddy policy defaults never confuse a PRD scope file with project/global installation scope',()=>{
|
|
7
|
+
const options={host:'codebuddy',scope:'/work/prototypes/.preparation/orders/scope.json'};
|
|
8
|
+
const result=P.creationOptions(options,{name:'i3-composer-agent',scope:'global',agents:['codebuddy']});
|
|
9
|
+
assert.equal(result.scope,options.scope);assert.equal(result.executionPolicy,P.ID);
|
|
10
|
+
assert.equal(P.creationOptions({host:'codex'},{name:'i3-composer-agent'}).executionPolicy,undefined);
|
|
11
|
+
assert.equal(P.creationOptions({},null).executionPolicy,undefined);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('analysis and inspection never acquire implementation requirements', () => {
|
|
15
|
+
for (const operation of ['analyze', 'inspect', 'confirm-baseline']) {
|
|
16
|
+
const policy = P.create({ operation });
|
|
17
|
+
assert.deepEqual(P.requirements(policy, 'implementation-entry'), []);
|
|
18
|
+
assert.equal(policy.readOnly, true);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
test('both generation modes require frontend loading before implementation', () => {
|
|
22
|
+
for (const operation of ['create', 'revise']) for (const mode of ['professional', 'template']) {
|
|
23
|
+
const policy = P.create({ operation, mode });
|
|
24
|
+
assert.ok(P.requirements(policy, 'implementation-entry').includes('frontend'));
|
|
25
|
+
assert.ok(P.requirements(policy, 'browser-entry').includes('browser'));
|
|
26
|
+
assert.equal(P.requirements(policy, 'design').includes('uiux'), mode === 'professional');
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
test('official framework fallback is explicit and cannot invent a native invocation', () => {
|
|
30
|
+
assert.ok(P.requirements(P.create({ officialCallable: true }), 'design').includes('antdv'));
|
|
31
|
+
assert.ok(P.requirements(P.create({ officialCallable: false }), 'design').includes('official-source'));
|
|
32
|
+
assert.equal(P.skillKey('fake-i3-frontend-ui-engineering'), null);
|
|
33
|
+
assert.equal(P.skillKey('i3-frontend-ui-engineering'), 'frontend');
|
|
34
|
+
});
|
|
35
|
+
test('policy changes are detected and unsupported hosts cannot silently acquire CodeBuddy support', () => {
|
|
36
|
+
const policy = P.create({ mode: 'template' });
|
|
37
|
+
assert.equal(P.validate(policy).digest, policy.digest);
|
|
38
|
+
assert.throws(() => P.validate({ ...policy, mode: 'professional' }), /policy|Policy/);
|
|
39
|
+
assert.throws(() => P.create({ host: 'codex' }), /CodeBuddy/);
|
|
40
|
+
assert.throws(() => P.create({ operation: 'write-anything' }), /operation/);
|
|
41
|
+
assert.throws(() => P.create({ mode: 'basic' }), /mode/);
|
|
42
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const prototypeScripts=(()=>{const fs=require('node:fs'),path=require('node:path'),file=path.join(__dirname,'lib/installation.js');return path.join(fs.existsSync(file)?require(file).loadInstallation().skillsDirectory:path.resolve(__dirname,'../skills'),'i3-ui-prototype/scripts');})();
|
|
3
|
+
const test=require('node:test'),assert=require('node:assert/strict'),fs=require('node:fs'),path=require('node:path'),os=require('node:os');
|
|
4
|
+
const lib=require(prototypeScripts+'/native-pilot-lib.cjs'),X=require(prototypeScripts+'/execution-runtime.cjs'),A=require(prototypeScripts+'/execution-assertions.cjs');
|
|
5
|
+
function fixture(t){const project=fs.mkdtempSync(path.join(os.tmpdir(),'i3-execution-source-fixture-'));t.after(()=>fs.rmSync(project,{recursive:true,force:true}));const scope={schemaVersion:1,title:'Synthetic orders fixture',pages:[{id:'orders',title:'Orders',referencePage:'models-page',regions:[{id:'list',mode:'management-list'},{id:'editor',mode:'edit-form'}]}],requirements:[{id:'R1',text:'Synthetic source contract only',pages:['orders'],source:{type:'prd',reference:'fixture/R1',statement:'Synthetic fixture, not actual browser acceptance'}}],exemptions:[]};const scopeFile=path.join(project,'scope.json');fs.writeFileSync(scopeFile,JSON.stringify(scope));return {project,scope:scopeFile,expertMode:'template'};}
|
|
6
|
+
test('new execution inputs freeze complete scenario expectations without changing legacy candidates',t=>{
|
|
7
|
+
const options=fixture(t),legacy=lib.createPrototype({...options,id:'old'}),before=lib.manifest(legacy.directory);
|
|
8
|
+
assert.equal(X.policy(legacy.directory),null);assert.equal(X.check(legacy.directory,'delivery',lib).status,'not-required');
|
|
9
|
+
const assertions=A.template(options);assert.ok(assertions.scenarios.length>20);for(const scenario of assertions.scenarios)scenario.checks=[{method:'evaluate_script',pointer:'/synthetic',operator:'equals',expected:true}];
|
|
10
|
+
const file=path.join(options.project,'assertions.json');fs.writeFileSync(file,JSON.stringify(assertions));
|
|
11
|
+
const fresh=lib.createPrototype({...options,id:'strict',host:'codebuddy',executionPolicy:'codebuddy-ab-v1',executionAssertions:file});
|
|
12
|
+
assert.equal(X.policy(fresh.directory).id,'codebuddy-ab-v1');assert.equal(lib.inspect(fresh.directory).status,'passed');
|
|
13
|
+
assert.equal(X.check(fresh.directory,'delivery',lib).status,'failed');assert.throws(()=>X.requireStage(fresh.directory,'implementation',lib),/binding|execution-begin/);
|
|
14
|
+
assert.deepEqual(lib.manifest(legacy.directory),before);assert.deepEqual(fs.readdirSync(options.project).sort(),['assertions.json','prototypes','scope.json']);
|
|
15
|
+
});
|
|
16
|
+
test('missing or incomplete fixed facts cannot silently create a strict artifact or affect legacy state',t=>{
|
|
17
|
+
const options=fixture(t);assert.throws(()=>lib.createPrototype({...options,id:'missing',host:'codebuddy',executionPolicy:'codebuddy-ab-v1'}),/execution-assertions/);
|
|
18
|
+
assert.equal(fs.existsSync(path.join(options.project,'prototypes/missing')),false);
|
|
19
|
+
const assertions=A.template(options);assert.throws(()=>A.validate(assertions,assertions.scenarios.map(s=>s.scenarioId)),/actual result/);
|
|
20
|
+
});
|
|
21
|
+
test('native values are extracted and compared with fixed inputs instead of a self-written passed flag',()=>{
|
|
22
|
+
const check={method:'evaluate_script',pointer:'/form/errors',operator:'equals',expected:1};
|
|
23
|
+
A.compare(A.extract(A.resultData('### Result\n```json\n{"form":{"errors":1}}\n```'),check.pointer),check);
|
|
24
|
+
assert.throws(()=>A.compare(A.extract(A.resultData('{"passed":true,"form":{"errors":0}}'),check.pointer),check),/differs/);
|
|
25
|
+
assert.throws(()=>A.extract({passed:true},check.pointer),/absent/);
|
|
26
|
+
assert.throws(()=>A.resultData('passed'),/JSON/);
|
|
27
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const prototypeScripts=(()=>{const fs=require('node:fs'),path=require('node:path'),file=path.join(__dirname,'lib/installation.js');return path.join(fs.existsSync(file)?require(file).loadInstallation().skillsDirectory:path.resolve(__dirname,'../skills'),'i3-ui-prototype/scripts');})();
|
|
3
|
+
const test=require('node:test'),assert=require('node:assert/strict'),fs=require('node:fs'),path=require('node:path'),os=require('node:os');
|
|
4
|
+
const E=require(prototypeScripts+'/execution-evidence.cjs'),C=require(prototypeScripts+'/codebuddy-context.cjs');
|
|
5
|
+
function fixture(t){
|
|
6
|
+
const root=fs.mkdtempSync(path.join(os.tmpdir(),'i3-ide-index-'));t.after(()=>fs.rmSync(root,{recursive:true,force:true}));
|
|
7
|
+
const sessionId='a'.repeat(32),requestId='b'.repeat(32),directory=path.join(root,sessionId);fs.mkdirSync(path.join(directory,'messages'),{recursive:true});
|
|
8
|
+
const rows=[
|
|
9
|
+
{id:'1'.repeat(32),role:'user',message:JSON.stringify({role:'user',content:[{type:'text',text:'injected context'}]}),extra:JSON.stringify({requestId,isHelperMessage:false,sourceContentBlocks:[{type:'text',text:'请实现原型'}]})},
|
|
10
|
+
{id:'2'.repeat(32),role:'assistant',message:JSON.stringify({role:'assistant',content:[{type:'tool-call',toolCallId:'native-skill',toolName:'use_skill',args:{command:'i3-frontend-ui-engineering'}}]}),extra:JSON.stringify({requestId})},
|
|
11
|
+
{id:'3'.repeat(32),role:'tool',message:JSON.stringify({role:'tool',content:[{type:'tool-result',toolCallId:'native-skill',toolName:'use_skill',result:{status:'success',success:true,result:{message:'Actual Skill instructions'}},isError:false}]}),extra:JSON.stringify({requestId})},
|
|
12
|
+
{id:'4'.repeat(32),role:'assistant',message:JSON.stringify({role:'assistant',content:[{type:'text',text:'已完成'}]}),extra:JSON.stringify({requestId})},
|
|
13
|
+
];
|
|
14
|
+
const index={messages:rows.map(row=>({id:row.id,role:row.role,type:'text',isComplete:false})),requests:[{id:requestId,type:'craft',messages:rows.map(row=>row.id),state:'running'}]};
|
|
15
|
+
const file=path.join(directory,'index.json');
|
|
16
|
+
const save=()=>{fs.writeFileSync(file,JSON.stringify(index));for(const row of rows)fs.writeFileSync(path.join(directory,'messages',row.id+'.json'),JSON.stringify(row));};save();
|
|
17
|
+
return {file,root,sessionId,rows,index,save,read:()=>E.readSource(file,[root])};
|
|
18
|
+
}
|
|
19
|
+
test('observed IDE index resolves only referenced messages and pairs a real use_skill result',t=>{
|
|
20
|
+
const f=fixture(t),s=f.read();assert.equal(s.format,'codebuddy-ide-index-v1');
|
|
21
|
+
assert.equal(E.loaded(s.calls,'frontend').length,1);assert.equal(s.calls[0].sessionId,f.sessionId);
|
|
22
|
+
assert.equal(s.calls[0].name,'Skill');assert.deepEqual(s.calls[0].input,{command:'i3-frontend-ui-engineering'});
|
|
23
|
+
assert.equal(s.prompts[0].text,'请实现原型');assert.equal(s.latestAssistant,'已完成');
|
|
24
|
+
fs.writeFileSync(path.join(path.dirname(f.file),'messages','unreferenced.json'),'{broken');assert.equal(E.loaded(f.read().calls,'frontend').length,1);
|
|
25
|
+
});
|
|
26
|
+
test('IDE stable evidence cursor survives streaming text and state updates but detects changed facts',t=>{
|
|
27
|
+
const f=fixture(t),s=f.read();f.index.requests[0].state='completed';
|
|
28
|
+
f.rows[3].message=JSON.stringify({role:'assistant',content:[{type:'text',text:'已完成验收'}]});f.save();
|
|
29
|
+
C.prefix(f.file,s.bytes,s.digest);
|
|
30
|
+
const message=JSON.parse(f.rows[2].message);message.content[0].result.result.message='edited';f.rows[2].message=JSON.stringify(message);f.save();
|
|
31
|
+
assert.throws(()=>C.prefix(f.file,s.bytes,s.digest),/prefix changed|truncated/);
|
|
32
|
+
});
|
|
33
|
+
test('IDE failed use_skill and successful reads do not become frontend invocation evidence',t=>{
|
|
34
|
+
const f=fixture(t),message=JSON.parse(f.rows[2].message);message.content[0].result.success=false;f.rows[2].message=JSON.stringify(message);f.save();
|
|
35
|
+
assert.equal(E.loaded(f.read().calls,'frontend').length,0);
|
|
36
|
+
const g=fixture(t);for(const i of [1,2]){const row=JSON.parse(g.rows[i].message);row.content[0].toolName='read_file';g.rows[i].message=JSON.stringify(row);}g.save();
|
|
37
|
+
assert.equal(E.loaded(g.read().calls,'frontend').length,0);
|
|
38
|
+
});
|
|
39
|
+
test('IDE index traversal, missing files, duplicate references and wrong request identity fail closed',t=>{
|
|
40
|
+
const traversal=fixture(t);traversal.index.messages[0].id='../outside';traversal.save();assert.throws(()=>traversal.read(),/ID/);
|
|
41
|
+
const missing=fixture(t);fs.unlinkSync(path.join(path.dirname(missing.file),'messages',missing.rows[1].id+'.json'));assert.throws(()=>missing.read(),/ENOENT/);
|
|
42
|
+
const duplicate=fixture(t);duplicate.index.messages.push(duplicate.index.messages[0]);duplicate.save();assert.throws(()=>duplicate.read(),/Duplicate/);
|
|
43
|
+
const wrong=fixture(t);wrong.rows[1].extra=JSON.stringify({requestId:'f'.repeat(32)});wrong.save();assert.throws(()=>wrong.read(),/request identity/);
|
|
44
|
+
});
|
|
45
|
+
test('IDE symlinked message files and session mismatch cannot satisfy source verification',t=>{
|
|
46
|
+
const f=fixture(t),file=path.join(path.dirname(f.file),'messages',f.rows[1].id+'.json'),bytes=fs.readFileSync(file),other=path.join(f.root,'other.json');
|
|
47
|
+
fs.writeFileSync(other,bytes);fs.unlinkSync(file);fs.symlinkSync(other,file);assert.throws(()=>f.read(),/history file/);
|
|
48
|
+
const g=fixture(t);assert.throws(()=>C.source({transcript_path:g.file,session_id:'different'},{transcriptRoots:[g.root]},'/business'),/session identity/);
|
|
49
|
+
});
|
|
50
|
+
test('pending IDE argument serialization is excluded from the immutable completed prefix',t=>{
|
|
51
|
+
const f=fixture(t),result=f.rows.splice(2,1)[0];
|
|
52
|
+
const changeIndex=()=>{f.index.messages=f.rows.map(row=>({id:row.id,role:row.role,type:'text',isComplete:false}));f.index.requests[0].messages=f.rows.map(row=>row.id);f.save();};
|
|
53
|
+
f.rows[1].message=JSON.stringify({role:'assistant',content:[{type:'tool-call',toolCallId:'native-skill',toolName:'use_skill',args:{command:'i3-front'}}]});changeIndex();
|
|
54
|
+
const pending=f.read();assert.equal(pending.stableThroughLine,1);assert.equal(E.loaded(pending.calls,'frontend').length,0);
|
|
55
|
+
assert.equal(pending.calls[0].nativeRequestId,'b'.repeat(32));
|
|
56
|
+
f.rows[1].message=JSON.stringify({role:'assistant',content:[{type:'tool-call',toolCallId:'native-skill',toolName:'use_skill',args:{command:'i3-frontend-ui-engineering'}}]});f.rows.splice(2,0,result);changeIndex();
|
|
57
|
+
C.prefix(f.file,pending.bytes,pending.digest);assert.equal(E.loaded(f.read().calls,'frontend').length,1);
|
|
58
|
+
});
|
|
59
|
+
test('IDE request membership remains mandatory while duplicate extra metadata may still be streaming',t=>{
|
|
60
|
+
const f=fixture(t);delete f.rows[1].extra;delete f.rows[2].extra;f.save();
|
|
61
|
+
assert.equal(E.loaded(f.read().calls,'frontend').length,1);
|
|
62
|
+
f.index.requests=[];f.save();assert.throws(()=>f.read(),/Unbound/);
|
|
63
|
+
});
|
|
64
|
+
test('global IDE context uses the native host workspace prefix and remains session/project bound',t=>{
|
|
65
|
+
const f=fixture(t),project=path.join(f.root,'business');fs.mkdirSync(project);
|
|
66
|
+
f.rows[0].message=JSON.stringify({role:'user',content:[{type:'text',text:`<user_info>\nOS Version: darwin\nWorkspace Folder: ${project}\n</user_info>\n<user_query>请实现原型</user_query>`}]});f.save();
|
|
67
|
+
assert.equal(E.ideWorkspace(f.file,f.sessionId),project);
|
|
68
|
+
assert.equal(E.readSource(f.file,[f.root],{project}).calls[0].success,true);
|
|
69
|
+
assert.throws(()=>E.ideWorkspace(f.file,'other-session'),/session directory/);
|
|
70
|
+
assert.throws(()=>E.readSource(f.file,[f.root],{project:f.root}),/workspace differs/);
|
|
71
|
+
const g=fixture(t),extra=JSON.parse(g.rows[0].extra);extra.sourceContentBlocks=[{type:'text',text:'<user_info>\nWorkspace Folder: /forged\n</user_info>'}];g.rows[0].extra=JSON.stringify(extra);g.save();
|
|
72
|
+
assert.throws(()=>E.ideWorkspace(g.file,g.sessionId),/workspace context is missing/,'Raw user text is not host workspace metadata');
|
|
73
|
+
});
|