@givolt/i3-composer-agent 2.3.2 → 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.
Files changed (92) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +1 -1
  5. package/README.md +5 -1
  6. package/bin/agent-targets.json +4 -2
  7. package/bin/i3-composer-agent.cjs +44 -8
  8. package/bin/install.cjs +2 -1
  9. package/bin/native-layout.cjs +1 -1
  10. package/bin/tool-selector.cjs +24 -7
  11. package/bin/wizard.cjs +89 -15
  12. package/docs/codebuddy-setup.md +27 -2
  13. package/docs/codex-setup.md +3 -1
  14. package/docs/other-hosts.md +1 -1
  15. package/docs/ui-prototype-native/USAGE.md +4 -2
  16. package/docs/workbuddy-setup.md +3 -1
  17. package/hooks/codebuddy-upstream.cjs +93 -0
  18. package/hooks/i3-prototype.cjs +12 -0
  19. package/hooks/manage.cjs +162 -0
  20. package/package.json +31 -1
  21. package/plugin.json +1 -1
  22. package/scripts/prototype-cache-test.js +55 -0
  23. package/scripts/prototype-generation-entry-test.js +6 -0
  24. package/scripts/prototype-generation-evidence-test.js +10 -0
  25. package/scripts/prototype-generation-native-test.js +78 -0
  26. package/scripts/prototype-generation-test.js +99 -0
  27. package/scripts/prototype-inventory-test.js +43 -0
  28. package/scripts/prototype-locations-test.js +21 -0
  29. package/scripts/prototype-project-test.js +183 -0
  30. package/scripts/ui-prototype-codebuddy-hook-test.js +106 -0
  31. package/scripts/ui-prototype-execution-evidence-test.js +62 -0
  32. package/scripts/ui-prototype-execution-gates-test.js +66 -0
  33. package/scripts/ui-prototype-execution-policy-test.js +42 -0
  34. package/scripts/ui-prototype-execution-runtime-test.js +27 -0
  35. package/scripts/ui-prototype-ide-source-test.js +73 -0
  36. package/scripts/ui-prototype-registration-test.js +53 -0
  37. package/scripts/ui-prototype-transcript-auto-test.js +67 -0
  38. package/skills/i3-frontend-ui-engineering/SKILL.md +1 -1
  39. package/skills/i3-incremental-implementation/SKILL.md +1 -1
  40. package/skills/i3-ui-prototype/SKILL.md +27 -7
  41. package/skills/i3-ui-prototype/assets/capabilities/generation.json +833 -0
  42. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/generation-component-map.json +300 -0
  43. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/App.vue +14 -3
  44. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/PrototypeShell.vue +59 -0
  45. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/config.ts +6 -1
  46. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/domain/presentation.ts +29 -0
  47. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/style.css +13 -0
  48. package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/tests/presentation.test.ts +23 -0
  49. package/skills/i3-ui-prototype/assets/hosts/codebuddy/profile.json +13 -0
  50. package/skills/i3-ui-prototype/assets/optional-skills/host-targets.json +4 -2
  51. package/skills/i3-ui-prototype/assets/templates/catalog.json +2 -1
  52. package/skills/i3-ui-prototype/assets/templates/enterprise-admin/component-inventory.json +707 -0
  53. package/skills/i3-ui-prototype/assets/templates/enterprise-admin/generation.json +49 -0
  54. package/skills/i3-ui-prototype/assets/templates/enterprise-admin/template-inventory.json +28791 -0
  55. package/skills/i3-ui-prototype/assets/templates/gallery/index.html +1 -1
  56. package/skills/i3-ui-prototype/assets/templates/generation-options.js +42 -0
  57. package/skills/i3-ui-prototype/assets/templates/presentation.html +17 -0
  58. package/skills/i3-ui-prototype/references/artifact-contract.md +1 -1
  59. package/skills/i3-ui-prototype/references/content-template.md +9 -4
  60. package/skills/i3-ui-prototype/references/execution-evidence.md +66 -0
  61. package/skills/i3-ui-prototype/references/generation-contract.md +122 -0
  62. package/skills/i3-ui-prototype/references/interaction-acceptance.md +4 -0
  63. package/skills/i3-ui-prototype/references/optional-skills.md +4 -0
  64. package/skills/i3-ui-prototype/references/project-layout.md +92 -0
  65. package/skills/i3-ui-prototype/references/template-adapters.md +6 -0
  66. package/skills/i3-ui-prototype/references/workflow-contract.md +4 -0
  67. package/skills/i3-ui-prototype/scripts/browser-workspace.cjs +72 -0
  68. package/skills/i3-ui-prototype/scripts/codebuddy-context.cjs +75 -0
  69. package/skills/i3-ui-prototype/scripts/codebuddy-hook.cjs +157 -0
  70. package/skills/i3-ui-prototype/scripts/codebuddy-ide-source.cjs +69 -0
  71. package/skills/i3-ui-prototype/scripts/codebuddy-registration.cjs +123 -0
  72. package/skills/i3-ui-prototype/scripts/content-template.cjs +2 -2
  73. package/skills/i3-ui-prototype/scripts/execution-assertions.cjs +43 -0
  74. package/skills/i3-ui-prototype/scripts/execution-evidence.cjs +146 -0
  75. package/skills/i3-ui-prototype/scripts/execution-gates.cjs +147 -0
  76. package/skills/i3-ui-prototype/scripts/execution-policy.cjs +41 -0
  77. package/skills/i3-ui-prototype/scripts/execution-runtime.cjs +60 -0
  78. package/skills/i3-ui-prototype/scripts/generation-assets.cjs +18 -0
  79. package/skills/i3-ui-prototype/scripts/generation-contract.cjs +214 -0
  80. package/skills/i3-ui-prototype/scripts/generation-evidence.cjs +50 -0
  81. package/skills/i3-ui-prototype/scripts/generation-replan.cjs +34 -0
  82. package/skills/i3-ui-prototype/scripts/hosts/codebuddy.cjs +16 -0
  83. package/skills/i3-ui-prototype/scripts/native-delivery.cjs +17 -5
  84. package/skills/i3-ui-prototype/scripts/native-devtools.cjs +1 -0
  85. package/skills/i3-ui-prototype/scripts/native-pilot-lib.cjs +66 -18
  86. package/skills/i3-ui-prototype/scripts/native-pilot.cjs +18 -2
  87. package/skills/i3-ui-prototype/scripts/project-registry.cjs +126 -0
  88. package/skills/i3-ui-prototype/scripts/prototype-hooks.cjs +87 -0
  89. package/skills/i3-ui-prototype/scripts/prototype-lib.cjs +5 -7
  90. package/skills/i3-ui-prototype/scripts/prototype-locations.cjs +5 -1
  91. package/skills/i3-ui-prototype/scripts/template-contract.cjs +11 -2
  92. package/skills/i3-ui-prototype/scripts/template-inventory.cjs +78 -0
@@ -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
+ });
@@ -0,0 +1,53 @@
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 R = require(prototypeScripts+'/codebuddy-registration.cjs');
6
+ const options = { action: 'enable', scope: 'project', product: 'ide', base: '/work/project/.codebuddy',
7
+ project: '/work/project', nodePath: '/runtime with spaces/node', scriptPath: '/work/project/.codebuddy/skills/i3-ui-prototype/scripts/hosts/codebuddy.cjs', scriptDigest: 'a'.repeat(64) };
8
+ test('registration planning preserves unrelated hooks and never grants permissions', () => {
9
+ const before = { theme: 'dark', hooks: { PreToolUse: [{ matcher: 'Write', hooks: [{ type: 'command', command: 'original-check' }] }] } };
10
+ const result = R.plan({ ...options, currentText: JSON.stringify(before) });
11
+ const after = JSON.parse(result.afterText);
12
+ assert.equal(after.theme, 'dark'); assert.deepEqual(after.hooks.PreToolUse[0], before.hooks.PreToolUse[0]);
13
+ assert.ok(result.summary.events.includes('PostToolUse'));
14
+ assert.match(after.hooks.PreToolUse[1].hooks[0].command, /'\/runtime with spaces\/node'/);
15
+ assert.equal(result.recordAfter.active, undefined);
16
+ assert.equal(result.summary.status, 'registered');
17
+ });
18
+ test('repeated enable is idempotent, disable removes only owned groups', () => {
19
+ const first = R.plan({ ...options, currentText: '{"userOption":42}' });
20
+ const again = R.plan({ ...options, currentText: first.afterText, record: first.recordAfter });
21
+ assert.equal(again.afterText, first.afterText);
22
+ const removed = R.plan({ ...options, action: 'disable', currentText: again.afterText, record: again.recordAfter });
23
+ assert.equal(JSON.parse(removed.afterText).userOption, 42);
24
+ assert.equal(Object.values(JSON.parse(removed.afterText).hooks).flat().length, 0);
25
+ });
26
+ test('missing ownership and modified owned hooks cannot be force adopted', () => {
27
+ const first = R.plan({ ...options, currentText: '{}' });
28
+ assert.throws(() => R.plan({ ...options, currentText: first.afterText }), /ownership|Ownership/);
29
+ const edited = JSON.parse(first.afterText); edited.hooks.Stop[0].hooks[0].timeout = 9;
30
+ assert.throws(() => R.plan({ ...options, currentText: JSON.stringify(edited), record: first.recordAfter }), /modified|changed/);
31
+ assert.throws(() => R.plan({ ...options, currentText: '{broken' }), /JSON/);
32
+ });
33
+ test('global CLI registration and defaults remain separate from actual activation', () => {
34
+ const keep = R.plan({ ...options, action: 'keep', currentText: null });
35
+ assert.equal(keep.afterText, null); assert.equal(keep.summary.status, 'not-enabled');
36
+ const global = R.plan({ ...options, scope: 'global', base: '/user/.codebuddy', product: 'cli', currentText: '{}' });
37
+ assert.equal(global.summary.status, 'pending-host-review');
38
+ assert.match(global.recordAfter.groups.Stop.hooks[0].command, /--scope' 'global/);
39
+ assert.throws(() => R.plan({ ...options, product: 'workbuddy' }), /product/);
40
+ });
41
+ test('configuration transaction refuses stale previews and restores only its own failed write in memory', () => {
42
+ const change = R.plan({ ...options, currentText: '{}' });
43
+ const files = new Map([[change.settingsPath, '{}']]); let fail = true;
44
+ const io = { read: file => files.get(file) ?? null, write(file, text) {
45
+ if (file === change.recordPath && fail) { fail = false; throw new Error('record write failed'); }
46
+ if (text === null) files.delete(file); else files.set(file, text);
47
+ } };
48
+ assert.throws(() => R.apply(change, io), /record write failed/);
49
+ assert.equal(files.get(change.settingsPath), '{}');
50
+ files.set(change.settingsPath, '{"userEdit":true}');
51
+ assert.throws(() => R.apply(change, io), /changed after preview/);
52
+ assert.equal(files.get(change.settingsPath), '{"userEdit":true}');
53
+ });
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+ // Synthetic files and in-memory configuration only; never installs or launches a host.
3
+ const fs=require('node:fs'),path=require('node:path'),os=require('node:os'),test=require('node:test'),assert=require('node:assert/strict');
4
+ const prototypeScripts=(()=>{const 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');})();
5
+ const E=require(prototypeScripts+'/execution-evidence.cjs'),C=require(prototypeScripts+'/codebuddy-context.cjs'),R=require(prototypeScripts+'/codebuddy-registration.cjs');
6
+ function fixture(t) {
7
+ const root=fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(),'i3-transcript-contract-'))),project=path.join(root,'business'),native=path.join(root,'native');
8
+ fs.mkdirSync(project);fs.mkdirSync(native);t.after(()=>fs.rmSync(root,{recursive:true,force:true}));
9
+ const rows=[{type:'user',sessionId:'session-a',message:{content:'生成当前原型'}},{type:'assistant',sessionId:'session-a',message:{content:[{type:'tool_use',id:'front-1',name:'Skill',input:{skill:'i3-frontend-ui-engineering'}}]}},{type:'user',sessionId:'session-a',message:{content:[{type:'tool_result',tool_use_id:'front-1',content:'Native fixture instructions'}]}}];
10
+ const file=path.join(native,'session.txt');fs.writeFileSync(file,rows.map(JSON.stringify).join('\n')+'\n');return {root,project,native,file,rows};
11
+ }
12
+ test('new IDE and CLI registrations default to automatic transcripts in both scopes',()=>{
13
+ for(const product of ['ide','cli'])for(const scope of ['project','global']) {
14
+ const base=scope==='project'?'/work/p/.codebuddy':'/users/u/.codebuddy';
15
+ const change=R.plan({action:'enable',product,scope,base,project:'/work/p',nodePath:'/node',scriptPath:base+'/hooks/i3-prototype.cjs',scriptDigest:'a'.repeat(64)});
16
+ assert.equal(change.recordAfter.transcriptMode,'auto');assert.deepEqual(change.recordAfter.transcriptRoots,[]);
17
+ }
18
+ });
19
+ test('manual and legacy root restrictions are retained; empty manual policy is rejected',()=>{
20
+ const options={action:'enable',product:'ide',scope:'project',base:'/work/p/.codebuddy',project:'/work/p',nodePath:'/node',scriptPath:'/fixed/hook.cjs',scriptDigest:'a'.repeat(64),transcriptRoots:['/native/logs']};
21
+ const first=R.plan(options);assert.equal(first.recordAfter.transcriptMode,'manual');
22
+ const legacy=structuredClone(first.recordAfter);delete legacy.transcriptMode;
23
+ const kept=R.plan({...options,action:'keep',transcriptRoots:undefined,currentText:first.afterText,record:legacy});
24
+ assert.equal(kept.recordAfter.transcriptMode,'manual');assert.deepEqual(kept.recordAfter.transcriptRoots,['/native/logs']);
25
+ assert.throws(()=>R.plan({...options,transcriptMode:'manual',transcriptRoots:[]}),/manual|roots/i);
26
+ });
27
+ test('automatic source uses the exact callback file regardless of filename suffix',t=>{
28
+ const f=fixture(t),payload={transcript_path:f.file,session_id:'session-a'};
29
+ const source=C.source(payload,{transcriptMode:'auto',transcriptRoots:[]},f.project);
30
+ assert.equal(source.path,f.file);assert.equal(E.loaded(source.calls,'frontend').length,1);assert.equal(source.tamperProof,false);
31
+ assert.equal(source.sourceVerification,'native-hook-transcript');
32
+ });
33
+ test('automatic discovery never admits business files, leaf symlinks or missing files',t=>{
34
+ const f=fixture(t),record={transcriptMode:'auto',transcriptRoots:[]},load=file=>C.source({transcript_path:file,session_id:'session-a'},record,f.project);
35
+ const fake=path.join(f.project,'qa.jsonl');fs.copyFileSync(f.file,fake);assert.throws(()=>load(fake),/business project/i);
36
+ const alias=path.join(f.native,'alias.jsonl');fs.symlinkSync(f.file,alias);assert.throws(()=>load(alias),/symlink|native transcript/i);
37
+ assert.throws(()=>load(path.join(f.native,'missing.jsonl')),/not.*available|ENOENT/i);
38
+ assert.throws(()=>load('relative.jsonl'),/absolute|native transcript/i);
39
+ });
40
+ test('automatic source still rejects malformed streams, forged declarations and mismatched sessions',t=>{
41
+ const f=fixture(t),record={transcriptMode:'auto',transcriptRoots:[]},payload={transcript_path:f.file,session_id:'different'};
42
+ assert.throws(()=>C.source(payload,record,f.project),/session/i);
43
+ payload.session_id='session-a';
44
+ for(const content of ['plain transcript text','{"skill":"frontend","passed":true}\n','{"type":"assistant","message":{"content":[]}}\n']) {
45
+ fs.writeFileSync(f.file,content);assert.throws(()=>C.source(payload,record,f.project),/JSONL|native|Native|user task/);
46
+ }
47
+ });
48
+ test('manual roots remain enforced even when a valid callback points elsewhere',t=>{
49
+ const f=fixture(t),record={transcriptMode:'manual',transcriptRoots:[path.join(f.root,'other')]};
50
+ assert.throws(()=>C.source({transcript_path:f.file,session_id:'session-a'},record,f.project),/outside.*roots/);
51
+ record.transcriptRoots=[f.native];assert.equal(C.source({transcript_path:f.file,session_id:'session-a'},record,f.project).path,f.file);
52
+ });
53
+ test('automatic reading refuses oversized native files without parsing their contents',t=>{
54
+ const f=fixture(t);fs.truncateSync(f.file,64*1024*1024+1);
55
+ assert.throws(()=>C.source({transcript_path:f.file,session_id:'session-a'},{transcriptMode:'auto',transcriptRoots:[]},f.project),/oversized|native transcript/i);
56
+ });
57
+ test('unreadable native transcripts cannot become valid evidence',t=>{
58
+ const f=fixture(t);fs.chmodSync(f.file,0);
59
+ try{assert.throws(()=>C.source({transcript_path:f.file,session_id:'session-a'},{transcriptMode:'auto',transcriptRoots:[]},f.project),/EACCES|EPERM/);}
60
+ finally{fs.chmodSync(f.file,0o600);}
61
+ });
62
+ test('missing callback identity and contradictory policy cannot fall back to another log',t=>{
63
+ const f=fixture(t),record={transcriptMode:'auto',transcriptRoots:[]};
64
+ assert.throws(()=>C.source({session_id:'session-a'},record,f.project),/fields required/);
65
+ assert.throws(()=>C.source({transcript_path:f.file},record,f.project),/fields required/);
66
+ assert.throws(()=>C.source({transcript_path:f.file,session_id:'session-a'},{...record,transcriptRoots:[f.native]},f.project),/manual roots/);
67
+ });
@@ -31,7 +31,7 @@ Build production-quality user interfaces that are accessible, performant, and vi
31
31
 
32
32
  ## Applicable Prototype Baselines
33
33
 
34
- When the project has `.i3-ui-prototypes.json`, read the prototype entry applicable to the UI module being changed, then its `baseline.json`, `design-system/MASTER.md`, tokens and page rules. Preserve approved design and interaction decisions; explain conflicts with new requirements instead of silently changing them. New pages reuse the selected template and component conventions. Changes to global tokens, navigation, component behavior or business states go through the `i3-ui-prototype` workflow and corresponding review. HTML review prototypes need explicit component mapping when implemented in another framework. Verify affected production pages in the browser; a prototype manifest check alone does not prove production fidelity. Projects without an applicable baseline retain this skill's existing workflow.
34
+ Read the project prototype index at `prototypes/index.json`, falling back to legacy `.i3-ui-prototypes.json` when no new index exists. Select the entry whose `appliesTo` contains the explicit business module ID; an empty array applies to no module, and conflicting indices or multiple matches require resolution before selecting a baseline. New associations are derived from fixed scope.moduleIds by `i3-ui-prototype`, not inferred from a PRD filename. Then read its `baseline.json`, `design-system/MASTER.md`, tokens and page rules. Preserve approved design and interaction decisions; explain conflicts with new requirements instead of silently changing them. New pages reuse the selected template and component conventions. Changes to global tokens, navigation, component behavior or business states go through the `i3-ui-prototype` workflow and corresponding review. HTML review prototypes need explicit component mapping when implemented in another framework. Verify affected production pages in the browser; a prototype manifest check alone does not prove production fidelity. Projects without an applicable baseline retain this skill's existing workflow.
35
35
 
36
36
  ## Component Architecture
37
37
 
@@ -194,7 +194,7 @@ Each increment should be independently revertable:
194
194
 
195
195
  ## UI Slices with Prototype Baselines
196
196
 
197
- For a UI slice, check `.i3-ui-prototypes.json` for an applicable approved prototype. Read its design contract, business mapping and baseline before implementation, and include affected interaction/visual checks in the slice acceptance. Preserve prior snapshots; coordinate contract changes through `i3-ui-prototype`. Backend-only slices and projects without a baseline continue the existing increment workflow.
197
+ For a UI slice, check `prototypes/index.json` (legacy `.i3-ui-prototypes.json` fallback) for an applicable approved prototype. Match explicit module IDs against appliesTo; an empty array is unbound. Resolve conflicting indices or multiple matches instead of applying a prototype globally. New appliesTo values come from fixed scope.moduleIds through `i3-ui-prototype`. Read its design contract, business mapping and baseline before implementation, and include affected interaction/visual checks in the slice acceptance. Preserve prior snapshots; coordinate contract changes through `i3-ui-prototype`. Backend-only slices and projects without a baseline continue the existing increment workflow.
198
198
 
199
199
  ## Working with Agents
200
200
 
@@ -37,7 +37,7 @@ Skill/Command/Agent 标识、文件路径、命令参数、代码/API 名称、
37
37
 
38
38
  ### 1. 公共前置:上下文、操作与副作用
39
39
 
40
- 所有入口,包括直接 Skill 调用,先读取用户原始要求与最新限制、项目规则、实际文件、技术栈、`.i3-ui-prototypes.json` 指向的原型和交接。没有正式规格时整理最小范围和待确认项,不补造审批权限或把演示规则写成已确认需求。
40
+ 所有入口,包括直接 Skill 调用,先读取用户原始要求与最新限制、项目规则、实际文件、技术栈、`prototypes/index.json` 指向的原型和交接;仅有旧 `.i3-ui-prototypes.json` 时兼容读取,不创建第二份活动索引。没有正式规格时整理最小范围和待确认项,不补造审批权限或把演示规则写成已确认需求。共享资料与宿主缓存按 [project-layout.md](references/project-layout.md) 管理。
41
41
 
42
42
  选择 `analyze`、`create`、`revise`、`inspect` 或 `confirm-baseline`;无参数默认 create,明确分析或只读限制优先。analyze 不初始化、不安装升级、不持久化设计、不更新项目/执行状态,也不为记录分析而写文件。允许经过核对的只读查询;用户明确要求保存分析文档时只执行该项写入授权。inspect 是否落盘服从本次范围。规则见 [workflow-contract.md](references/workflow-contract.md)。
43
43
 
@@ -57,6 +57,14 @@ analyze 的交付是需求/能力覆盖、模板与框架选型、交互验收
57
57
 
58
58
  已有 v2 实例先运行一次 `node <skill-root>/scripts/native-pilot.cjs state <prototype-root>`,从聚合结果恢复身份、固定需求、设计/构建/实际QA、负责人和下一步的精确命令。不要为寻找参数反复遍历全仓库、node_modules或阅读控制器内部实现。这里只读检查,不安装、启动浏览器或更新状态。
59
59
 
60
+ CodeBuddy 新建原型采用 A+B 执行合同,实际接口见 [execution-evidence.md](references/execution-evidence.md)。先只读检查该安装范围的 `prototype-hooks.cjs check`;未启用或来源协议未核实则报告缺项,不能自行改宿主配置或声称严格完成。analyze 和纯读取不进入制作门禁。已有实例没有 `runtime.executionPolicy` 时保留原合同,不隐式追加输入或重建原型。
61
+
62
+ 项目/全局 IDE/CLI 新启用默认从真实 Hook 的 `transcript_path` 自动取得当前会话文件;不让模型搜索/填写日志、不选择最近文件。`transcript.status=awaiting-native-event` 表示尚未观察,不阻止启动已授权任务及 `execution-begin`;进入制作和交付仍必须有可解析的原生证据。已有手动目录限制保持,可由用户显式切回 auto;缺字段、不可读、格式不支持或会话不符时报告未核验,不能用手写记录补齐。
63
+
64
+ CodeBuddy create 前,用 `native-pilot.cjs execution-assertions-template --scope <scope-file>` 取得完整场景,按本次 PRD 与能力参数填充每项预期 JSON 事实,存于该原型 `.preparation` 目录。create 增加 `--host codebuddy --execution-policy codebuddy-ab-v1 --execution-assertions <assertions-file>`;官方 Skill 真实不可用时才显式 `--official-source-fallback`,保留其失败调用定位。随后在实际 CodeBuddy 主会话执行 `execution-begin <prototype-root>`,以原生 Skill 工具加载当前上下文需要的前端 Skill 后再写业务界面。其他宿主尚未适配此 Hook 合同,按原流程披露调用核验边界。
65
+
66
+ `execution-template <prototype-root>` 返回当前身份与已观察到的真实加载引用;以实际查询、实现复核及浏览器结果补齐 `qa/execution-evidence.json`,不能补造调用 ID。构建、record-run、record-devtools-review、finalize 与 `execution-check --stage <stage>` 共用 A;B 在可识别的写入前检查,在 Stop 对可观察的完成声明给一次缺项反馈。只读分析、进度、阻塞及用户停止可正常结束。未通过门禁时交付候选和缺项;新收据的 `native-source-correlated` 不代表不可篡改认证或用户认可。
67
+
60
68
  ### 2. 完整内容模板与需求能力
61
69
 
62
70
  正式内容模板位于 `assets/templates/enterprise-admin/`;双击其 `index.html` 或上层 `assets/templates/index.html` 可离线预览。完整模板是内容、通用布局和交互参考,不是最终框架效果。`reference/` 保留原始来源;可编辑页面片段内嵌在离线 `index.html` 的 `template[data-source]` 中,本地样式修改后刷新生效,不需要服务、依赖安装或重建。小列表 HTML 已退出正式资产和默认生成入口。
@@ -68,29 +76,41 @@ node <skill-root>/scripts/native-pilot.cjs template-page --page models-page
68
76
 
69
77
  默认只读使用本 Skill 随版本发布的模板及能力定义,在内存中计算当前内容索引,不在业务项目创建 `ui-templates`,也不修改安装目录。模板由维护者在 Skill 源码中修改、校验并随包发布;业务修改直接作用于 `prototypes/<id>/app/`,生成依据及索引固定在 inputs。只按本次页面读取内容,避免把整套模板加载到上下文。完整模板的索引是参考清单,不声称已自动理解全部原始交互。既有模板工作副本和 prepare-template/sync-template 命令仅保留兼容;只有用户明确要求复用某个已有副本时指定 --template-root,不自动选择、覆盖或清理它。
70
78
 
71
- 生成前依据 PRD 整理范围文件:已有需求文件只读引用;本次新文件放在允许写入的 `.prototype-preparation/<id>/scope.json`,不提前占用原型目标目录。`scope.json`:逐个声明实际业务页面、参考页面、区域类型、需求原文及来源。管理列表由固定定义要求查询、复选、多页分页、批量操作和状态反馈;编辑区域要求校验、失败保留与重试。参数按实际需求记录;默认列表分页可调为 10/20/50,默认 10。豁免必须指向明确需求及原因,不能通过漏报区域逃避本页真实能力。人工/浏览器复核区域与需求是否一致。
79
+ 生成前依据 PRD 整理范围文件:已有需求文件只读引用;本次新文件放在允许写入的 `prototypes/.preparation/<id>/scope.json`,不提前占用原型目标目录。新 scope 使用 schemaVersion=3,逐个声明实际业务页面、参考页面、区域类型、需求原文及来源;moduleIds 填实际 PRD/项目已明确的稳定业务模块 ID,不填文档名、需求全文或代码路径,未明确时填 []。多个 PRD 的模块取本原型范围内并集,需求逐条保留来源与版本、使用唯一 ID;不编造模块或把未关联解释为适用全项目。创建及明确接受输入的修订会从固定 scope.moduleIds 去重生成索引 appliesTo,不手填两份。旧 scope 按原合同保留。管理列表由固定定义要求查询、复选、多页分页、批量操作和状态反馈;编辑区域要求校验、失败保留与重试。参数按实际需求记录;默认列表分页可调为 10/20/50,默认 10。豁免必须指向明确需求及原因,不能通过漏报区域逃避本页真实能力。人工/浏览器复核区域与需求是否一致。
72
80
 
73
81
  新生成原型默认包含“明亮、暗黑、清新、科技”,默认“明亮”,菜单不加编号。由 application-shell 的固定主题能力和原生 ConfigProvider 实现,主题偏好按实例隔离;不把 HTML 参考样式当成最终主题。旧实例的固定分页、可选主题与 20/23 场景合同仍按原输入检查,不追溯修改。完整字段和示例见 [content-template.md](references/content-template.md)。
74
82
 
83
+ 先运行 `generation-options` 查看本模板真实支持的风格/布局/密度和页面模式,向客户展示中文名称;已有明确选择直接沿用,未指定时说明模板默认值。风格与四主题分开:风格/布局/密度在生成前固定,四主题在成品上切换。离线选择页的“选择风格、布局与密度”只预览结构,不替代框架效果验收。
84
+
85
+ ```sh
86
+ node <skill-root>/scripts/native-pilot.cjs inventory --template enterprise-admin
87
+ node <skill-root>/scripts/native-pilot.cjs generation-options --template enterprise-admin
88
+ node <skill-root>/scripts/native-pilot.cjs generation-plan --template enterprise-admin --scope <scope.json>
89
+ ```
90
+
91
+ 逐页读 `template-page` 返回的真实 HTML 和 `generation-plan` 的参考清单;依据本次业务选择 management-list/tree-list/tree-table/edit-form/form-page/detail/wizard/workflow/permissions/content。模式选定后,其能力不能通过删绑定、关开关或 N/A 删除。普通列表不强加树;分类筛选树和树表须分别声明,导航分组也单独声明。列设置、删除、未保存保护、向导、画布、角色状态等均按合同处理,不能只补树。
92
+
93
+ 每个真实字段和操作填入区域的 fields/actions,保留唯一 ID、类型、需求来源及操作预期;页面上的新按钮不能只给反馈文字冒充业务结果。`pendingDecisions` 中的参考能力须明确采用或附需求依据排除;`pendingReferenceElements` 中每个功能节点(包括已被通用能力涵盖的按钮/字段/事件)须用 referenceDecisions 显式关联生成绑定或附来源排除,允许同类节点以明确 nodeIds 分组。不得用“已采用 interaction”批量视为全部节点已处理。检查真实页面与 PRD,工具只验证已声明内容的一致性,不能自动发现 PRD 中从未声明的遗漏。使用 replan 显式统一更新固定输入,避免手改多份合同不一致。完整格式与取舍、重新固定方法见 [generation-contract.md](references/generation-contract.md)。
94
+
75
95
  ### 3. 原生组件首期:设计与实现
76
96
 
77
97
  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
98
 
79
99
  按下文专业接入规则检测 UIUX,professional 不静默降级,template 必须明确选择。只读约束贯穿 provider;查询、采用和效果分别有依据。详细接入见 [expert-adapter.md](references/expert-adapter.md)。
80
100
 
81
- 初始化前的 probe/查询不得先占用 `prototypes/<id>`。先 probe,再用下列 create 命令原子初始化,然后在已创建实例的 qa 下保存查询、取舍与验证。如果必须在初始化前保存查询文件,使用独立准备目录(如项目内 `.prototype-preparation/<id>`),初始化后再归入本实例 QA;不要预先创建目标的 qa 目录。
101
+ 初始化前的 probe/查询不得先占用 `prototypes/<id>`。先 probe,再用下列 create 命令原子初始化,然后在已创建实例的 qa 下保存查询、取舍与验证。如果必须在初始化前保存查询文件,使用独立准备目录(如项目内 `prototypes/.preparation/<id>`),初始化后再归入本实例 QA;不要预先创建目标的 qa 目录。
82
102
 
83
103
  加载框架官方 Skill 或对应版本官方文档、类型和源码,先查 API、读示例、再实现。先完成上文工具预检,使用已准备的固定本地 CLI;缺项不因官方 Skill 的安装建议而扩大权限。固定项目依赖锁,记录请求与实际知识版本。映射与官方来源见 [framework-contract.md](references/framework-contract.md)。
84
104
 
85
105
  宿主已发现可调用的官方框架 Skill 时,先通过真实 Skill 工具加载它;读取 SKILL.md 或运行 CLI 查询不能冒充宿主 Skill 调用。只有该 Skill 在当前执行者中不可调用时,才使用版本匹配的官方文档/类型/源码等效渠道,并明确记录官方 Skill 未唤醒及实际资料来源。UIUX 的 Skill 调用、真实查询、建议采纳与效果验证也分别记录,查询成功不自动记 used。
86
106
 
87
107
  ```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>
108
+ node <skill-root>/scripts/native-pilot.cjs create --project <project-root> --id business-demo --scope <project-root>/prototypes/.preparation/business-demo/scope.json --expert-mode professional --provider <uiux-root>
89
109
  # 仅在用户已明确选择基础生成后:
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
110
+ node <skill-root>/scripts/native-pilot.cjs create --project <project-root> --id business-demo --scope <project-root>/prototypes/.preparation/business-demo/scope.json --expert-mode template
91
111
  ```
92
112
 
93
- 目标存在则 revise,不重新初始化。Follow the `i3-frontend-ui-engineering` skill,使用真实 Table/Form/Drawer/反馈组件及 ConfigProvider。初始化只产生需求对应的框架骨架和四主题,business.status 为 needs-adaptation;这不是已完成业务。实现固定输入中的实际字段、列表和流程,再核对需求映射、模拟数据并更新 business.status 为 adapted。UIUX 采用规则落实到 design-system、主题和页面;模板样式不锁定生成外观。
113
+ 目标存在则 revise,不重新初始化。Follow the `i3-frontend-ui-engineering` skill,使用真实 Table/Form/Drawer/反馈组件及 ConfigProvider。初始化只产生需求对应的框架骨架和四主题,business.status 为 needs-adaptation;这不是已完成业务。实现固定输入中的实际字段、列表和流程,逐个填写 `design-system/implementation.json` 的实际组件、源文件/定位符和固定场景,再核对需求映射、模拟数据并更新 business.status 为 adapted。UIUX 采用规则落实到 design-system、主题和页面;模板样式不锁定生成外观。
94
114
 
95
115
  目标存在但没有 prototype.json 时属于未识别的准备目录,不能当作有效实例执行 revise。先核对来源并保留文件;只有从本次工具记录确认整个目标目录均由当前任务的准备操作产生,才可将该整个目录移到独立准备位置,确保目标路径不存在后重试原子 create。仅移动查询文件仍会留下空目录并阻止 create。来源不明或包含用户已有文件时保留原目录并报告冲突,不得覆盖或清空。
96
116
 
@@ -118,7 +138,7 @@ node <skill-root>/scripts/native-pilot.cjs record-run <prototype-root> --report
118
138
 
119
139
  仅迭代历史设备列表且需要专项证明时,browser 传`--expected-categories '<JSON数组>'`、`--verify-blur-validation`、`--verify-atomic-status`或`--expected-description '<精确文案>'`;仅采用相应规则时启用,不能为了工具预设强迫采用UIUX建议。
120
140
 
121
- 覆盖筛选→多选→分页→抽屉→校验失败→提交失败→重试保存→列表同步,另查状态、窄屏、键盘、焦点和控制台。进入与退出使用正常动画;退出结束后再销毁;毛玻璃核对配置、计算样式及真实效果,静态截图不证明动效。
141
+ 按选定模式覆盖实际流程;列表与编辑覆盖筛选→多选→分页→抽屉→校验失败→提交失败→重试保存→列表同步,树/树表、列设置、删除、向导、画布等逐项按固定场景验证。新 v3 报告还须逐场景填写 generationObservations,附实际动作/目标/结果、工具调用定位及证据摘要,不能只填写场景 passed。另查状态、窄屏、键盘、焦点和控制台。进入与退出使用正常动画;退出结束后再销毁;毛玻璃核对配置、计算样式及真实效果,静态截图不证明动效。
122
142
 
123
143
  清空文本后要核对查询/校验实际结果。本轮固定 Chrome DevTools MCP 1.11.0 的空字符串 fill 可仅清 DOM 值而未发送输入事件;遇到此现象记录工具限制。实测替代操作是原生 fill 单个空格后,只读取得实际文本长度与光标位置,确认光标在末尾再按该长度逐次 press_key Backspace,最后核对空值及查询/校验结果。空格可能追加到恢复的旧文本;本轮 headless 的 Meta+A/Control+A 也未全选,不能假定快捷键成功。不要用 evaluate_script 补发事件或修改状态。输入事件问题与宿主参数回调兼容分别记录,fill 返回成功不证明页面状态已更新;版本不同按实际观测判断,详见交互验收合同。
124
144