@petercjl/taobao-ai-ops 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -101,3 +101,13 @@ Each component is an independent `@petercjl` npm package with its own canonical
101
101
  当前Agent通过 `dmp-product-report` 节点加载独立报告Skill,按需确认本店锚点。商品资料从橙色淘宝买家浏览器读取,DMP统计从蓝色商家电商浏览器读取;报告包含5张主图、店铺与页面指标,以及经营、推广、人群的四周期对比和8周趋势。子应用负责分析方法、独立模板、原始证据与最终校验。套件统一管理报告包和tbcli,保留独立包的升级边界。
102
102
 
103
103
  开发者入口:`taobao-ai-ops doctor --node dmp-product-report --json`、`taobao-ai-ops component run dmp-product-report ...`。
104
+
105
+ ## Skill 创建与维护
106
+
107
+ 独立组件 `@petercjl/portable-skill-creator` 提供Skill创建、更新、审查、测试与迁移方法,台账按用户业务流程图展示。当前Agent需要Node.js 22+、Python 3.10+及可动态加载的基础 `skill-creator`。套件管理独立包的安装和显式更新。
108
+
109
+ > 用 portable-skill-creator 创建一个Skill:【说明业务目标与流程】。
110
+
111
+ > 用 portable-skill-creator 优化这个Skill:【说明本次调整目标】。
112
+
113
+ 开发者入口:`taobao-ai-ops doctor --node portable-skill-creator --json`、`skill source --name portable-skill-creator --json` 与 `component run portable-skill-creator ...`。组件业务方法和资源由其独立包拥有;套件安装生成完整的独立运行副本。
package/UPDATE.md CHANGED
@@ -11,3 +11,5 @@
11
11
  宿主安装完整候选后,在任务和审批空闲时切换并刷新 Skill。下载失败、离线、候选验证失败、用户修改过受管 Skill 或宿主刷新失败时,保留上一套。更新只涉及选定宿主的订阅目录,配置和凭据留在包外。插件详情显示实际成员版本及上次检查结果。
12
12
 
13
13
  开发者:每次检查都以当前壳的注册表为准,与 previous 回执比较成员身份、版本和所需 Skill;`collection prepare` 返回独立候选,宿主验证后才激活。回执中的运行环境必须跟随 CLI 调用,当前任务内保持同一版本。新集合入口不接管历史全局 npm 安装;历史安装继续通过其已声明的维护入口操作。
14
+
15
+ 开发者:迁移已批准的跨兼容版本组件时,在collection check/prepare中显式传入 `--accept-component-upgrades ID,ID` 并保留previous回执。该参数仅批准列出的组件升级,不批准降级;其余不兼容组件继续保留原版本。DMP快照v2迁移选择 `tbcli,dmp-product-report`,候选doctor通过后再激活。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@petercjl/taobao-ai-ops",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "Independent Taobao AI operations SOP skills with a managed suite lifecycle",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,7 +9,8 @@ Input: an operator's goal, supplied workbooks or authorized data sources, option
9
9
 
10
10
  ## Main line
11
11
 
12
- 1. For a Taobao/Tmall item ID requiring operating, promotion and new/returning-customer analysis, select `dmp-product-report`; its method and standalone report contract are below. Resolve other requested business results with `taobao-ai-ops nodes list --json`. A category/monthly market question uses `category-research`; a search-result workbook and concrete research object use `product-research`. Existing product scope can enter product research directly. If no supported node covers the request, report the missing node.
12
+ 1. For creating, updating, reviewing, testing or porting an Agent Skill, select `portable-skill-creator` and follow its exact current main line. Resolve other business requests below.
13
+ For a Taobao/Tmall item ID requiring operating, promotion and new/returning-customer analysis, select `dmp-product-report`; its method and standalone report contract are below. Resolve other requested business results with `taobao-ai-ops nodes list --json`. A category/monthly market question uses `category-research`; a search-result workbook and concrete research object use `product-research`. Existing product scope can enter product research directly. If no supported node covers the request, report the missing node.
13
14
  An explicit request to use `procli` for project/task information, SOP progression, knowledge reuse or result publication uses `project-management`. Preserve this child's explicit-trigger contract; offering project integration does not authorize service reads or writes. For an authorized project-backed research request, resolve the project and read its task context and compiled Wiki before selecting the business research node. Preserve the target profile, exact project/task identities, observation windows, provenance and unresolved questions in the handoff.
14
15
  A supplied analysis or compatible ViewModel requiring a reusable HTML report uses `html-report`, whose exact Skill is `compact-commerce-ui`. It supplies reporting methods, not business conclusions; existing category/product outputs keep their own declared contracts.
15
16
  A Cloud Hug brand conversion request for supplied detail pages, photography, main KV or SKU images uses `cloud-hug-style-transfer`; its brand-production contract is below.
@@ -104,3 +105,17 @@ For explicit Boardmix file, media or object operations, select `boardmix-tools`.
104
105
  输入为目标商品ID,可补充报告日与新输出目录;已有证据可按子合同离线build/finalize。接口需要的本店商品参数由tbcli自动从当前账户发现,报告仅采用目标商品侧数据。输出包括自包含HTML、商品资料、近7天/前7天、近30天/前30天及8个完整自然周证据、CSV、ViewModel、分析和校验。run产生draft后继续按子Skill完成分析和finalize;一句结论与编号证据、经营策略解读及最终概览均由子应用的当前知识方法承接。返回套件主线第4步保留成果或第6步交付。
105
106
 
106
107
  报告模板和分析知识继续由独立报告包维护,运行不依赖commerce-ui。套件负责注册、安装更新、精确Skill来源解析、依赖传递和预检。商品资料的页面展示口径与DMP分期统计分别解释;本次可用范围以平台实际返回为准。
108
+
109
+ ### DMP配套版本迁移
110
+
111
+ 用户授权更新DMP报告和取数链路时,检查当前集合的blocked成员。若报告包0.2与tbcli 0.12因跨兼容版本而保留旧版,使用旧回执和 `collection prepare --accept-component-upgrades tbcli,dmp-product-report` 明确迁移这两个组件;其他不兼容组件维持原选择。核对候选DMP doctor及真实版本后再切换宿主入口。该操作按用户已授权的组件范围执行,不扩大到其他Agent或未选组件。
112
+
113
+ ## Skill 创建与维护
114
+
115
+ 用户要求创建、更新、审查、测试或迁移Skill时,选择 `portable-skill-creator` 节点。独立拥有包为 `@petercjl/portable-skill-creator`(0.2.1+);创建方法、策略来源、可移植性、业务流程台账和验证规则由该包维护。
116
+
117
+ 通过 `taobao-ai-ops skill source --name portable-skill-creator --json` 解析准确来源,完整读取当前SKILL.md。运行 `taobao-ai-ops doctor --node portable-skill-creator --json` 核对Node、Python及组件版本;当前Agent另外解析并完整读取系统提供的准确逻辑名 `skill-creator`,其缺失返回DEPENDENCY_MISSING,不由套件复制替代。宿主基础Creator解析是运行前置能力,doctor的本地成功不证明它存在。
118
+
119
+ 规范来源在组件npm包内,可使用 `taobao-ai-ops component run portable-skill-creator ...` 运行其实际CLI;本机受管安装副本带有自身 `.runtime`。输入是本次创建/维护意图、可选自然语言流程或脑图、既有资源与知识;输出是子Skill约定的规划、完整资源和验证证据。尊重子Skill当前的规划确认、隐私、独立回归授权和失败恢复规则;来源或能力缺失时停止相应阶段并保留原错误。完成后回到套件主线第4步保留成果、第6步交付。
120
+
121
+ 套件只登记并管理独立组件,普通业务调用保持已安装版本;用户显式更新时获取正式latest。台账按用户熟悉的业务流程图展示。基础Skill创建器仍由当前宿主动态提供。
package/src/cli.mjs CHANGED
@@ -9,10 +9,10 @@ import {checkUpdate,installUpdate} from './update.mjs';
9
9
  import {collectionCheck,collectionPrepare} from './subscription.mjs';
10
10
  import fs from 'node:fs/promises';
11
11
  const print=v=>console.log(JSON.stringify(v,null,2));
12
- function options(args){const out={};for(let i=0;i<args.length;i++){const k=args[i];if(['--json','--adopt','--yes'].includes(k))out[k.slice(2)]=true;else if(['--directory','--previous','--path','--agent','--target-dir','--name','--node','--profile','--mode','--run-dir','--input','--out','--candidate','--query','--tag','--context','--run-id'].includes(k)){if(!args[i+1]||args[i+1].startsWith('--'))throw fail('USAGE',`${k} needs a value`);out[k.slice(2).replace(/-([a-z])/g,(_,c)=>c.toUpperCase())]=args[++i];}else throw fail('USAGE',`Unknown option ${k}`);}return out;}
12
+ function options(args){const out={};for(let i=0;i<args.length;i++){const k=args[i];if(['--json','--adopt','--yes'].includes(k))out[k.slice(2)]=true;else if(['--accept-component-upgrades','--directory','--previous','--path','--agent','--target-dir','--name','--node','--profile','--mode','--run-dir','--input','--out','--candidate','--query','--tag','--context','--run-id'].includes(k)){if(!args[i+1]||args[i+1].startsWith('--'))throw fail('USAGE',`${k} needs a value`);out[k.slice(2).replace(/-([a-z])/g,(_,c)=>c.toUpperCase())]=args[++i];}else throw fail('USAGE',`Unknown option ${k}`);}return out;}
13
13
  export async function main(argv){
14
14
  const [command,action,...rest]=argv;
15
- if(!command||['help','--help','-h'].includes(command))return console.log(`taobao-ai-ops ${PACKAGE.version}\nversion | nodes list | node show ID | capabilities\ncollection check [--previous RECEIPT] --json | collection prepare --directory NEW --yes --json [--previous RECEIPT]\ndoctor --node ID [--mode excel|nas]\nskill source [--name NAME] | skill status|install|update --agent AGENT [--profile research] [--adopt]\ncomponents status|check|install [--agent AGENT --yes]\ncomponents register --name ID --path CHECKOUT --yes [--mode npm]\ncomponent run ID [arguments...] | script NODE SCRIPT [arguments...] | tool run TOOL [arguments...]\nworkflow plan|status --run-dir DIR [--node ID] [--input FILE]\nhandoff create --node ID --input FILE --out FILE [--candidate PATH --query QUERY]\nupdate check|install [--tag latest] [--agent AGENT --yes]`);
15
+ if(!command||['help','--help','-h'].includes(command))return console.log(`taobao-ai-ops ${PACKAGE.version}\nversion | nodes list | node show ID | capabilities\ncollection check [--previous RECEIPT] --json | collection prepare --directory NEW --yes --json [--previous RECEIPT] [--accept-component-upgrades ID,ID]\ndoctor --node ID [--mode excel|nas]\nskill source [--name NAME] | skill status|install|update --agent AGENT [--profile research] [--adopt]\ncomponents status|check|install [--agent AGENT --yes]\ncomponents register --name ID --path CHECKOUT --yes [--mode npm]\ncomponent run ID [arguments...] | script NODE SCRIPT [arguments...] | tool run TOOL [arguments...]\nworkflow plan|status --run-dir DIR [--node ID] [--input FILE]\nhandoff create --node ID --input FILE --out FILE [--candidate PATH --query QUERY]\nupdate check|install [--tag latest] [--agent AGENT --yes]`);
16
16
  if(['version','--version'].includes(command))return action==='--json'?print({ok:true,package:PACKAGE.name,version:PACKAGE.version}):console.log(PACKAGE.version);
17
17
  if(command==='script')return runScript(action,rest[0],rest.slice(1));
18
18
  if(command==='component'&&action==='run'){
@@ -25,11 +25,11 @@ export async function main(argv){
25
25
  }
26
26
  if(command==='nodes'&&action==='list')return print(MANIFEST.nodes);
27
27
  if(command==='node'&&action==='show')return print(nodeFor(rest[0]));
28
- if(command==='capabilities')return print({package:PACKAGE.name,version:PACKAGE.version,...MANIFEST,collectionContract:{schemaVersion:1,manifest:'sealwork.collection.json',check:['collection','check','--json'],prepare:['collection','prepare','--directory','{newDirectory}','--yes','--json'],previousOption:'--previous',installationMode:'host-owned-generation',hostDiscoveryAuthority:'host'}});
28
+ if(command==='capabilities')return print({package:PACKAGE.name,version:PACKAGE.version,...MANIFEST,collectionContract:{schemaVersion:1,manifest:'sealwork.collection.json',check:['collection','check','--json'],prepare:['collection','prepare','--directory','{newDirectory}','--yes','--json'],previousOption:'--previous',migrationOption:'--accept-component-upgrades',migrationPolicy:'Explicitly approved component IDs may cross compatibility boundaries; unselected incompatible versions stay pinned; downgrades stay blocked',installationMode:'host-owned-generation',hostDiscoveryAuthority:'host'}});
29
29
  if(command==='doctor'){const opts=options(argv.slice(1));const r=await doctor(opts.node,opts.mode);print(r);if(!r.ok)process.exitCode=1;return;}
30
30
  const opts=options(rest);
31
31
  if(command==='collection'){
32
- if(action==='check')return print(await collectionCheck(opts.previous?JSON.parse(await fs.readFile(opts.previous,'utf8')):undefined));
32
+ if(action==='check')return print(await collectionCheck(opts.previous?JSON.parse(await fs.readFile(opts.previous,'utf8')):undefined,undefined,opts.acceptComponentUpgrades));
33
33
  if(action==='prepare')return print(await collectionPrepare(opts));
34
34
  }
35
35
 
@@ -0,0 +1,16 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import {spawnSync} from 'node:child_process';
4
+ import {fail} from './core.mjs';
5
+ // Only explicitly registered components delegate complete Skill/runtime materialization.
6
+ // The suite owns selection/transaction recovery; the component owns its installation contract.
7
+ export async function materializeComponentSkill(record,name,destination){
8
+ if(name!=='portable-skill-creator'||record.package!=='@petercjl/portable-skill-creator')throw fail('UNSUPPORTED_COMPONENT_INSTALLER',name);
9
+ destination=path.join(await fs.realpath(path.dirname(destination)),path.basename(destination));
10
+ try{const existing=await fs.readdir(destination);if(existing.length)throw fail('UNMANAGED_TARGET','Component destination must be absent or an empty suite stage');await fs.rmdir(destination);}catch(e){if(e.code!=='ENOENT')throw e;}
11
+ const result=spawnSync(process.execPath,[record.entry,'skill','install','--agent','suite-managed','--target',destination,'--json'],{encoding:'utf8',timeout:60000,env:{...process.env,PORTABLE_SKILL_UPDATE_CHECK:'0'}});
12
+ let data;try{data=JSON.parse(result.stdout);}catch{}
13
+ if(result.status!==0||data?.ok!==true||data.package!==record.package||data.name!==name||data.version!==record.version||data.current!==true||data.verification?.verified!==true)throw fail('COMPONENT_SKILL_INSTALL_FAILED',name,{child:data??result.stderr});
14
+ await fs.access(path.join(destination,'.runtime','bin','portable-skill-creator.mjs'));
15
+ return data;
16
+ }
package/src/runtime.mjs CHANGED
@@ -9,7 +9,7 @@ export function componentEnvironment(id,env=process.env,records=componentRecords
9
9
  let reportEntries={};
10
10
  if(id==='dmp-product-report'){if(!records.tbcli?.entry)throw fail('COMPONENT_NOT_INSTALLED','DMP report requires the registered tbcli component');reportEntries={DMP_REPORT_TBCLI_ENTRY:records.tbcli.entry,AGENT_PACKAGE_UPDATE_CHECK:'0'};}
11
11
  const canvasEntries=id==='competitor-visual-canvas'?Object.fromEntries([['tbcli','TBCLI_ENTRY'],['sealseek-media','SEALSEEK_MEDIA_ENTRY'],['boardmixcli','BOARDMIXCLI_ENTRY']].filter(([key])=>records[key]?.entry).map(([key,variable])=>[variable,records[key].entry])):{};
12
- return {...env,...canvasEntries,...reportEntries,BOARDMIXCLI_UPDATE_CHECK:'0',TBCLI_UPDATE_CHECK:'0',SYCMCLI_DISABLE_AUTO_UPDATE:'1',SEEDAUDIO_AUTO_UPDATE:'0',SEALSEEK_MEDIA_AUTO_UPDATE:'0',COMPETITOR_CANVAS_AUTO_UPDATE:'0',...(id==='cloud-hug-style-transfer'&&media&&!env.CLOUD_HUG_MEDIA_ROOT?{CLOUD_HUG_MEDIA_ROOT:media.root}:{})};
12
+ return {...env,...(id==='portable-skill-creator'?{PORTABLE_SKILL_UPDATE_CHECK:'0'}:{}),...canvasEntries,...reportEntries,BOARDMIXCLI_UPDATE_CHECK:'0',TBCLI_UPDATE_CHECK:'0',SYCMCLI_DISABLE_AUTO_UPDATE:'1',SEEDAUDIO_AUTO_UPDATE:'0',SEALSEEK_MEDIA_AUTO_UPDATE:'0',COMPETITOR_CANVAS_AUTO_UPDATE:'0',...(id==='cloud-hug-style-transfer'&&media&&!env.CLOUD_HUG_MEDIA_ROOT?{CLOUD_HUG_MEDIA_ROOT:media.root}:{})};
13
13
  }
14
14
  export function pythonEnvironment(env=process.env){
15
15
  return {...env,PYTHONUTF8:'1',PYTHONIOENCODING:'utf-8',PYTHONDONTWRITEBYTECODE:'1'};
@@ -108,6 +108,14 @@ export async function doctor(id,mode='excel'){
108
108
  checks.push({id:'dmp-report-capabilities',ok:result.status===0&&detail.ok===true&&detail.tbcliSnapshot===2&&detail.subjectMode==='target-only'&&detail.tbcliItemSummary===1,value:detail});
109
109
  }catch(e){checks.push({id:'dmp-report-dependencies',ok:false,code:e.code??'CAPABILITY_UNAVAILABLE',message:e.message});}
110
110
  }
111
+ if(n.component==='portable-skill-creator'){
112
+ const record=componentRecord(n.component);
113
+ const result=spawnSync(process.execPath,[record.entry,'doctor','--json'],{encoding:'utf8',timeout:30000,env:componentEnvironment(n.component)});
114
+ let detail;try{detail=JSON.parse(result.stdout||result.stderr);}catch{detail={ok:false,code:'COMPONENT_DOCTOR_FAILED'};}
115
+ const version=record.version?.split('.').map(Number);const supported=version?.length===3&&version.every(Number.isInteger)&&(version[0]>0||version[1]>2||version[1]===2&&version[2]>=1);
116
+ checks.push({id:'creator-version',ok:Boolean(supported),value:record.version,minimum:'0.2.1'});
117
+ checks.push({id:'creator-runtime',ok:result.status===0&&detail.ok===true&&detail.package===MANIFEST.components[n.component].package&&detail.version===record.version&&detail.python?.version?.length>=2,value:detail});
118
+ }
111
119
  if(n.component==='competitor-visual-canvas'){
112
120
  const record=componentRecord(n.component);
113
121
  const result=spawnSync(process.execPath,[record.entry,'doctor','--json'],{encoding:'utf8',timeout:30000,env:componentEnvironment(n.component)});
@@ -2,7 +2,8 @@ import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import os from 'node:os';
4
4
  import {randomUUID,createHash} from 'node:crypto';
5
- import {PACKAGE,source,nodeVersion,closure,json,stat,fileHashes,digest,fail,stateRoot} from './core.mjs';
5
+ import {materializeComponentSkill} from './component-skill-install.mjs';
6
+ import {MANIFEST,componentRecord,PACKAGE,source,nodeVersion,closure,json,stat,fileHashes,digest,fail,stateRoot} from './core.mjs';
6
7
 
7
8
  const OWNER='.taobao-ai-ops-managed.json';
8
9
  export function targetRoot(opts){
@@ -39,9 +40,11 @@ export async function status(root,n){
39
40
  return {...base,mode:'copy',managed:true,state:!same?'modified':meta.sourceDigest===base.sourceDigest?'current':'stale'};
40
41
  }
41
42
  async function makeCopy(dest,n){
42
- const files=await fileHashes(source(n));await fs.mkdir(dest,{recursive:true});
43
- for(const relative of Object.keys(files)){const d=path.join(dest,relative);await fs.mkdir(path.dirname(d),{recursive:true});await fs.copyFile(path.join(source(n),relative),d);}
44
- await fs.writeFile(path.join(dest,'.suite-install.json'),JSON.stringify({package:PACKAGE.name,skill:n.skill,version:nodeVersion(n),sourceDigest:digest(files),files}),{flag:'wx'});
43
+ const canonical=await fileHashes(source(n));
44
+ if(MANIFEST.components[n.component]?.skill_installation==='component-managed')await materializeComponentSkill(componentRecord(n.component),n.skill,dest);
45
+ else{await fs.mkdir(dest,{recursive:true});for(const relative of Object.keys(canonical)){const d=path.join(dest,relative);await fs.mkdir(path.dirname(d),{recursive:true});await fs.copyFile(path.join(source(n),relative),d);}}
46
+ const files=await fileHashes(dest);
47
+ await fs.writeFile(path.join(dest,'.suite-install.json'),JSON.stringify({package:PACKAGE.name,skill:n.skill,version:nodeVersion(n),sourceDigest:digest(canonical),files}),{flag:'wx'});
45
48
  await fs.writeFile(path.join(dest,'.install-meta.json'),JSON.stringify({title:n.skill,description:`Taobao AI operations: ${n.id}`}),{flag:'wx'});
46
49
  }
47
50
  async function restore(backup,dest,inPlace){
@@ -67,7 +70,7 @@ export async function install(opts,options={}){
67
70
  if(inPlace)await fs.cp(dest,backup,{recursive:true,errorOnExist:true,force:false});
68
71
  }
69
72
  const record={dest,backup:await stat(backup)?backup:null,inPlace};changed.push(record);
70
- const mode=item.mode==='copy'&&item.managed?'copy':(opts.mode||'auto')==='auto'?(process.platform==='win32'?'copy':'link'):opts.mode;
73
+ const mode=MANIFEST.components[n.component]?.skill_installation==='component-managed'?'copy':item.mode==='copy'&&item.managed?'copy':(opts.mode||'auto')==='auto'?(process.platform==='win32'?'copy':'link'):opts.mode;
71
74
  if(!['link','copy'].includes(mode))throw fail('USAGE','Mode must be auto, link or copy');
72
75
  if(inPlace){for(const e of await fs.readdir(dest))await fs.rm(path.join(dest,e),{recursive:true,force:true});}
73
76
  if(mode==='link')await fs.symlink(source(n),dest,process.platform==='win32'?'junction':'dir');else await makeCopy(dest,n);
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import {spawn} from 'node:child_process';
4
+ import {materializeComponentSkill} from './component-skill-install.mjs';
4
5
  import {componentEnvironment} from './runtime.mjs';
5
6
  import {ROOT,PACKAGE,MANIFEST,fail,stat,fileHashes} from './core.mjs';
6
7
 
@@ -10,7 +11,9 @@ export function compatible(current,next){
10
11
  const a=current.split('.').map(Number),b=next.split('.').map(Number);
11
12
  return a[0]===b[0]&&(a[0]!==0||a[1]===b[1])&&next.localeCompare(current,undefined,{numeric:true})>=0;
12
13
  }
13
- export async function collectionCheck(previous,registry=fetch){
14
+ export async function collectionCheck(previous,registry=fetch,approved=[]){
15
+ const selected=new Set(typeof approved==='string'?approved.split(',').map(x=>x.trim()).filter(Boolean):approved);
16
+ for(const id of selected)if(!Object.hasOwn(MANIFEST.components,id))throw fail('UNKNOWN_COMPONENT',id);
14
17
  if(previous&&(previous.package!==PACKAGE.name||!Array.isArray(previous.members)||!stable(previous.version)))throw fail('INVALID_PREVIOUS_COLLECTION','Previous receipt must belong to this collection');
15
18
  const members=[];
16
19
  for(const [id,c] of Object.entries(MANIFEST.components)){
@@ -19,9 +22,10 @@ export async function collectionCheck(previous,registry=fetch){
19
22
  const metadata=await response.json();
20
23
  if(metadata.name!==c.package||!stable(metadata.version))throw fail('INVALID_REGISTRY_IDENTITY',c.package);
21
24
  const old=previous?.members?.find(m=>m.package===c.package);
22
- const accepted=!old||compatible(old.version,metadata.version);
25
+ const explicitMigration=Boolean(old&&selected.has(id)&&metadata.version.localeCompare(old.version,undefined,{numeric:true})>=0);
26
+ const accepted=!old||compatible(old.version,metadata.version)||explicitMigration;
23
27
  members.push({id,package:c.package,version:accepted?metadata.version:old.version,available:metadata.version,
24
- bin:c.bin,skills:c.skills,blocked:!accepted});
28
+ bin:c.bin,skills:c.skills,blocked:!accepted,explicitMigration});
25
29
  }
26
30
  const changed=previous?.version!==PACKAGE.version||JSON.stringify(members.map(m=>[m.id,m.package,m.version,m.skills]))!==JSON.stringify(previous?.members?.map(m=>[m.id,m.package,m.version,m.skills]));
27
31
  return {ok:true,schemaVersion:1,package:PACKAGE.name,version:PACKAGE.version,changed,members};
@@ -69,7 +73,7 @@ export async function collectionPrepare(opts,services={}){
69
73
  const directory=path.resolve(opts.directory);
70
74
  if(await stat(directory))throw fail('TARGET_EXISTS','Candidate directory must be new');
71
75
  const previous=opts.previous?JSON.parse(await fs.readFile(opts.previous,'utf8')):undefined;
72
- const plan=await collectionCheck(previous,services.registry);
76
+ const plan=await collectionCheck(previous,services.registry,opts.acceptComponentUpgrades);
73
77
  await fs.mkdir(directory,{recursive:true,mode:0o700});
74
78
  try{
75
79
  const shell=path.join(directory,'shell'),runtime=path.join(directory,'runtime'),skills=path.join(directory,'skills');
@@ -81,7 +85,7 @@ export async function collectionPrepare(opts,services={}){
81
85
  const installed=[],records={};
82
86
  async function copySkill(name,source,entry,member){
83
87
  if(installed.some(s=>s.name===name))throw fail('DUPLICATE_SKILL',name);
84
- const destination=path.join(skills,name);await fileHashes(source);await fs.cp(source,destination,{recursive:true,dereference:true});
88
+ const destination=path.join(skills,name);await fileHashes(source);if(MANIFEST.components[member.id]?.skill_installation==='component-managed')await materializeComponentSkill({entry,package:member.package,version:member.version},name,destination);else await fs.cp(source,destination,{recursive:true,dereference:true});
85
89
  installed.push({name,package:member.package,version:member.version,source,directory:destination,runtimeEntry:entry});
86
90
  }
87
91
  for(const member of plan.members){
@@ -18,7 +18,8 @@
18
18
  "competitor-visual-canvas",
19
19
  "cloud-hug-style-transfer",
20
20
  "boardmix-tools",
21
- "dmp-product-report"
21
+ "dmp-product-report",
22
+ "portable-skill-creator"
22
23
  ]
23
24
  },
24
25
  "nodes": [
@@ -50,7 +51,8 @@
50
51
  "1688-product-sourcing",
51
52
  "1688-opportunity-sourcing-research",
52
53
  "cloud-hug-style-transfer",
53
- "dmp-product-report"
54
+ "dmp-product-report",
55
+ "portable-skill-creator"
54
56
  ],
55
57
  "python_imports": [],
56
58
  "scripts": [],
@@ -307,6 +309,36 @@
307
309
  "tbcli": "0.12.0+; dmp snapshot version 2 target-only subject, automatic owned-item execution context and itemSummary version 1"
308
310
  },
309
311
  "contract_resolution": "Resolve current exact child Skill and CLI capabilities; preserve the selected merchant/buyer endpoints and returned data availability."
312
+ },
313
+ {
314
+ "id": "portable-skill-creator",
315
+ "skill": "portable-skill-creator",
316
+ "component": "portable-skill-creator",
317
+ "stage": "skill-authoring",
318
+ "method_version": "portable-skill-creator@0.2",
319
+ "requires_skills": [],
320
+ "python_imports": [],
321
+ "scripts": [],
322
+ "native_requirements": [
323
+ "agent.skill.base-creator.resolve"
324
+ ],
325
+ "input_contract": {
326
+ "required": [
327
+ "Skill creation, update, review or test intent"
328
+ ],
329
+ "optional": [
330
+ "business flow in natural language or diagram",
331
+ "existing Skill and knowledge sources"
332
+ ]
333
+ },
334
+ "output_contract": {
335
+ "result": "child-scoped design, complete Skill resources and verification evidence",
336
+ "state": "child owns business flow, strategies, privacy, run ledger and confirmation boundaries"
337
+ },
338
+ "required_component_contracts": {
339
+ "portable-skill-creator": "0.2.1+; business-flow ledger presentation; Node 22+ and Python 3.10+"
340
+ },
341
+ "contract_resolution": "Resolve the exact current child Skill and live CLI; dynamically load the host-provided base skill-creator, preserving the child confirmation and regression boundaries."
310
342
  }
311
343
  ],
312
344
  "tools": {
@@ -382,6 +414,11 @@
382
414
  "package": "@petercjl/dmp-product-report",
383
415
  "component": "dmp-product-report",
384
416
  "role": "DMP product performance, promotion and customer composition report with buyer-page product identity"
417
+ },
418
+ "portable-skill-creator": {
419
+ "package": "@petercjl/portable-skill-creator",
420
+ "component": "portable-skill-creator",
421
+ "role": "Skill creation, maintenance, portability QA and business-flow run ledger design"
385
422
  }
386
423
  },
387
424
  "targets": {
@@ -509,6 +546,14 @@
509
546
  "skills": [
510
547
  "dmp-product-report"
511
548
  ]
549
+ },
550
+ "portable-skill-creator": {
551
+ "package": "@petercjl/portable-skill-creator",
552
+ "bin": "portable-skill-creator",
553
+ "skills": [
554
+ "portable-skill-creator"
555
+ ],
556
+ "skill_installation": "component-managed"
512
557
  }
513
558
  }
514
559
  }