release-skill 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codebuddy-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +2 -2
  5. package/.kimi-plugin/plugin.json +1 -1
  6. package/CHANGELOG.md +15 -0
  7. package/INSTALL.md +2 -2
  8. package/INSTALL.zh-CN.md +2 -2
  9. package/README.md +16 -9
  10. package/README.zh-CN.md +12 -8
  11. package/adapters/claude/.claude-plugin/marketplace.json +1 -1
  12. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  13. package/adapters/claude/bin/release-skill.bundle.mjs +81 -18
  14. package/adapters/claude/schemas/release-plan.schema.json +5 -0
  15. package/adapters/claude/schemas/release-project.schema.json +5 -0
  16. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  17. package/adapters/codex/bin/release-skill.bundle.mjs +81 -18
  18. package/adapters/codex/schemas/release-plan.schema.json +5 -0
  19. package/adapters/codex/schemas/release-project.schema.json +5 -0
  20. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  21. package/adapters/kimi/bin/release-skill.bundle.mjs +81 -18
  22. package/adapters/kimi/schemas/release-plan.schema.json +5 -0
  23. package/adapters/kimi/schemas/release-project.schema.json +5 -0
  24. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  25. package/adapters/workbuddy/bin/release-skill.bundle.mjs +81 -18
  26. package/adapters/workbuddy/schemas/release-plan.schema.json +5 -0
  27. package/adapters/workbuddy/schemas/release-project.schema.json +5 -0
  28. package/bin/release-skill.bundle.mjs +81 -18
  29. package/package.json +1 -1
  30. package/schemas/release-plan.schema.json +5 -0
  31. package/schemas/release-project.schema.json +5 -0
  32. package/src/adapters/plugin-marketplace.mjs +21 -5
  33. package/src/commands/prepare.mjs +19 -13
  34. package/src/core/plan.mjs +4 -3
  35. package/src/platforms/codebuddy.mjs +92 -10
  36. package/src/platforms/registry.mjs +4 -3
@@ -1289,19 +1289,17 @@ export function buildExternalActions(unitResults, resolvedVersions, productionAs
1289
1289
  });
1290
1290
  // Consumer marketplace install actions (only when distribution
1291
1291
  // declared), driven by the platform registry (T2.2 step 3): one loop
1292
- // body for every platform; the registry declares the per-platform
1293
- // differences (actionType, distributionType, adapter, and via the
1294
- // schema required fields marketplace identity, which kimi does not
1295
- // carry: Kimi Code has no non-interactive install/marketplace API, so
1296
- // the kimi action carries no marketplace identity (MINOR-1); plugin +
1297
- // entrySkill are the meaningful identity fields there).
1292
+ // body for every platform. Marketplace identity follows the distribution
1293
+ // declaration: claude/codex require it via the registry schema fields;
1294
+ // kimi/codebuddy tolerate an optional declaration (codebuddy defaults to
1295
+ // its unified marketplace constant downstream when undeclared).
1296
+ // Undeclared stays absent so legacy frozen plans remain byte-identical.
1298
1297
  const frozenUnitDists = frozenDistributions?.get(unit.id) ?? null;
1299
1298
  for (const platform of PLATFORMS) {
1300
1299
  const dist = frozenUnitDists
1301
1300
  ? frozenUnitDists.find((d) => d.type === platform.distributionType)
1302
1301
  : (unit.distributions ?? []).find((d) => d.type === platform.distributionType);
1303
1302
  if (!dist) continue;
1304
- const requiresMarketplace = platform.schemaRequiredFields.includes('marketplace');
1305
1303
  const timeoutMs = Number.isInteger(dist.timeoutMs) ? dist.timeoutMs : 300000;
1306
1304
  // External independent marketplace form: the distribution declares
1307
1305
  // marketplaceRepo, so the install targets the external marketplace repo
@@ -1350,7 +1348,8 @@ export function buildExternalActions(unitResults, resolvedVersions, productionAs
1350
1348
  parameters: {
1351
1349
  consumer: platform.id,
1352
1350
  plugin: dist.plugin,
1353
- ...(requiresMarketplace ? { marketplace: dist.marketplace } : {}),
1351
+ ...(dist.marketplace !== undefined ? { marketplace: dist.marketplace } : {}),
1352
+ ...(dist.marketplaceSource !== undefined ? { marketplaceSource: dist.marketplaceSource } : {}),
1354
1353
  repo: externalMarketplace ? dist.marketplaceRepo : unit.publicRepo,
1355
1354
  version,
1356
1355
  entrySkill: dist.entrySkill,
@@ -1377,7 +1376,8 @@ export function buildExternalActions(unitResults, resolvedVersions, productionAs
1377
1376
  expected: {
1378
1377
  installed: true,
1379
1378
  plugin: dist.plugin,
1380
- ...(requiresMarketplace ? { marketplace: dist.marketplace } : {}),
1379
+ ...(dist.marketplace !== undefined ? { marketplace: dist.marketplace } : {}),
1380
+ ...(dist.marketplaceSource !== undefined ? { marketplaceSource: dist.marketplaceSource } : {}),
1381
1381
  version,
1382
1382
  entrySkill: dist.entrySkill,
1383
1383
  ...(externalMarketplace ? { marketplaceLocation: 'external', repo: dist.marketplaceRepo } : {}),
@@ -1532,13 +1532,17 @@ export function buildExternalActions(unitResults, resolvedVersions, productionAs
1532
1532
  // non-production loop above plus the production-only bindings
1533
1533
  // (ref/snapshotPath/manifestDigest parameters; consumer/repo/ref/
1534
1534
  // entrySkillFound/manifestDigest expected). Marketplace identity follows
1535
- // the registry's schema required fields kimi carries none (MINOR-1).
1535
+ // the distribution declaration, same as the non-production loop.
1536
1536
  for (const platform of PLATFORMS) {
1537
1537
  const dist = frozenUnitDists
1538
1538
  ? frozenUnitDists.find((d) => d.type === platform.distributionType)
1539
1539
  : (unit.distributions ?? []).find((d) => d.type === platform.distributionType);
1540
1540
  if (!dist) continue;
1541
- const requiresMarketplace = platform.schemaRequiredFields.includes('marketplace');
1541
+ // Marketplace identity follows the distribution declaration: claude/codex
1542
+ // require it via the registry schema fields; kimi/codebuddy tolerate an
1543
+ // optional declaration (codebuddy defaults to its unified marketplace
1544
+ // constant downstream when undeclared). Undeclared stays absent so
1545
+ // legacy frozen plans remain byte-identical.
1542
1546
  const timeoutMs = Number.isInteger(dist.timeoutMs) ? dist.timeoutMs : 300000;
1543
1547
  // External independent marketplace form: the install targets the external
1544
1548
  // marketplace repo with the add-ref + marketplaceCommitSha frozen online
@@ -1583,7 +1587,8 @@ export function buildExternalActions(unitResults, resolvedVersions, productionAs
1583
1587
  parameters: {
1584
1588
  consumer: platform.id,
1585
1589
  plugin: dist.plugin,
1586
- ...(requiresMarketplace ? { marketplace: dist.marketplace } : {}),
1590
+ ...(dist.marketplace !== undefined ? { marketplace: dist.marketplace } : {}),
1591
+ ...(dist.marketplaceSource !== undefined ? { marketplaceSource: dist.marketplaceSource } : {}),
1587
1592
  repo: externalMarketplace ? dist.marketplaceRepo : unit.publicRepo,
1588
1593
  ref: externalMarketplace ? freeze.ref : resolvedTag,
1589
1594
  version: unitVersion,
@@ -1622,7 +1627,8 @@ export function buildExternalActions(unitResults, resolvedVersions, productionAs
1622
1627
  installed: true,
1623
1628
  consumer: platform.id,
1624
1629
  plugin: dist.plugin,
1625
- ...(requiresMarketplace ? { marketplace: dist.marketplace } : {}),
1630
+ ...(dist.marketplace !== undefined ? { marketplace: dist.marketplace } : {}),
1631
+ ...(dist.marketplaceSource !== undefined ? { marketplaceSource: dist.marketplaceSource } : {}),
1626
1632
  repo: externalMarketplace ? dist.marketplaceRepo : unit.publicRepo,
1627
1633
  version: unitVersion,
1628
1634
  ref: externalMarketplace ? freeze.ref : resolvedTag,
package/src/core/plan.mjs CHANGED
@@ -751,9 +751,10 @@ export function validatePlanActionCompleteness(plan, options = {}) {
751
751
  // consumer id, and — via the schema required fields — whether marketplace
752
752
  // identity is a required binding). Platforms without a required
753
753
  // marketplace (kimi, MINOR-1: Kimi Code has an interactive marketplace
754
- // but no non-interactive install API) tolerate a declared marketplace as
755
- // an optional legacy value but never bind it as a required condition, in
756
- // parameters or in expected.
754
+ // but no non-interactive install API; codebuddy, whose unified marketplace
755
+ // defaults to a constant but may be overridden by the distribution)
756
+ // tolerate a declared marketplace as an optional value but never bind it
757
+ // as a required condition, in parameters or in expected.
757
758
  for (const platform of PLATFORMS) {
758
759
  const dist = distributions.find((d) => d.type === platform.distributionType);
759
760
  if (!dist) continue;
@@ -88,15 +88,55 @@ export const CODEBUDDY_REQUIREMENT_FILE = 'release-skill-codebuddy-manual-instal
88
88
  export const CODEBUDDY_ATTESTATION_FILE = 'release-skill-codebuddy-attestation.json';
89
89
 
90
90
  /**
91
- * Unified marketplace the WorkBuddy desktop app and the codebuddy CLI install
92
- * release-skill from (verified fact). The attestation `marketplace` field must
93
- * equal this value; the install path is validated against this marketplace
94
- * segment in both channels.
91
+ * Default unified marketplace the WorkBuddy desktop app and the codebuddy CLI
92
+ * install from (verified fact). A distribution may override this by declaring
93
+ * `marketplace` (see resolveCodeBuddyMarketplace); the install path is
94
+ * validated against the resolved marketplace segment in both channels, and a
95
+ * declared attestation `marketplace` field must equal the resolved name.
95
96
  */
96
97
  export const CODEBUDDY_MARKETPLACE_NAME = 'artifact-skill-set';
97
- /** Marketplace source URL used by the isolated-CLI install path. */
98
+ /** Default marketplace source URL (used only when no marketplace override is declared). */
98
99
  export const CODEBUDDY_MARKETPLACE_SOURCE = 'https://github.com/ifoohoo/artifact-skill-set';
99
100
 
101
+ /**
102
+ * Resolve the unified marketplace name for a codebuddy action.
103
+ *
104
+ * Falls back to CODEBUDDY_MARKETPLACE_NAME when the action carries no
105
+ * `marketplace` (legacy frozen plans, byte-identical behavior). A declared
106
+ * marketplace must be a non-empty string; anything else fails closed.
107
+ *
108
+ * @param {object} [action] - expanded codebuddy action (may be undefined).
109
+ * @returns {string} the resolved marketplace name.
110
+ * @throws {Error} when a declared marketplace is not a non-empty string.
111
+ */
112
+ export function resolveCodeBuddyMarketplace(action) {
113
+ const marketplace = action?.marketplace ?? CODEBUDDY_MARKETPLACE_NAME;
114
+ if (typeof marketplace !== 'string' || marketplace.length === 0) {
115
+ throw new Error(`codebuddy marketplace must be a non-empty string when declared, got ${JSON.stringify(action?.marketplace)}`);
116
+ }
117
+ return marketplace;
118
+ }
119
+
120
+ /**
121
+ * Resolve the marketplace source URL for a codebuddy action, when knowable.
122
+ *
123
+ * The default marketplace has a verified source constant; a configured
124
+ * marketplace only has a source when the distribution declared
125
+ * `marketplaceSource`. When neither applies, null is returned and callers
126
+ * must NOT fabricate a URL.
127
+ *
128
+ * @param {object} [action] - expanded codebuddy action (may be undefined).
129
+ * @returns {string|null} the source URL, or null when unknown.
130
+ */
131
+ export function resolveCodeBuddyMarketplaceSource(action) {
132
+ if (action?.marketplace === undefined || action?.marketplace === null) {
133
+ return CODEBUDDY_MARKETPLACE_SOURCE;
134
+ }
135
+ return typeof action.marketplaceSource === 'string' && action.marketplaceSource.length > 0
136
+ ? action.marketplaceSource
137
+ : null;
138
+ }
139
+
100
140
  /** Authoritative codebuddy plugin manifest (single candidate, no precedence). */
101
141
  export const CODEBUDDY_PLUGIN_MANIFEST_RELATIVE = join('.codebuddy-plugin', 'plugin.json');
102
142
 
@@ -185,7 +225,7 @@ export function codebuddyAuthorityDir(context, planDigest, plugin) {
185
225
  /**
186
226
  * 统一人工安装说明:面向 CodeBuddy / WorkBuddy 的人工结果流程。
187
227
  *
188
- * @param {{plugin:string, version:string, ref:string, attestationDir:string, requiresInstalledClosure:boolean}} p
228
+ * @param {{plugin:string, version:string, ref:string, attestationDir:string, requiresInstalledClosure:boolean, marketplace:string, marketplaceSource:string|null}} p
189
229
  * @returns {string[]}
190
230
  */
191
231
  function buildCodeBuddyManualInstructions({
@@ -194,11 +234,13 @@ function buildCodeBuddyManualInstructions({
194
234
  ref,
195
235
  attestationDir,
196
236
  requiresInstalledClosure,
237
+ marketplace,
238
+ marketplaceSource,
197
239
  }) {
198
240
  return [
199
241
  `CodeBuddy/WorkBuddy 插件安装无法锁定冻结 ref(codebuddy CLI marketplace add/install 没有 ref 选项,跟踪默认分支),因此安装是需要人工结果证明的手动步骤。`,
200
242
  `1) publish 完成所有远端写入后进入 PUBLISHED 状态(自动化 Git 分支/标签、npm 和 GitHub Release 写入已完成)。此 codebuddy 检查点标记为需要人工安装。`,
201
- `2) 从统一市场 "${CODEBUDDY_MARKETPLACE_NAME}" (${CODEBUDDY_MARKETPLACE_SOURCE}) 安装 release-skill。确认安装的插件版本等于冻结版本 ${version}。`,
243
+ `2) 从统一市场 "${marketplace}"${marketplaceSource ? ` (${marketplaceSource})` : ''} 安装 ${plugin}。确认安装的插件版本等于冻结版本 ${version}。`,
202
244
  `3) 将人工结果 JSON 写入: ${attestationDir}/${CODEBUDDY_ATTESTATION_FILE}`,
203
245
  ` 必填字段: platform="codebuddy", version, planDigest(冻结计划摘要), result("passed" 或 "failed"), actor(确认人), confirmedAt(ISO 8601 时间戳)${requiresInstalledClosure ? ',installChannel("desktop" 或 "cli"),installPath(实际安装后的插件目录)' : ''}`,
204
246
  ` 可选字段: note(备注)`,
@@ -312,15 +354,35 @@ export function validateCodeBuddyAttestation(attestation, action, isoNow, boundP
312
354
  if (normalized.installPath.includes('..')) {
313
355
  return { valid: false, error: 'codebuddy attestation installPath must not contain path traversal ("..")', normalized: null };
314
356
  }
315
- // CLI 通道:installPath 必须以预期后缀结尾
357
+ // CLI 通道:installPath 必须以预期后缀结尾(市场名解析自冻结动作,
358
+ // 未声明时回落到默认统一市场常量)
316
359
  if (normalized.installChannel === 'cli') {
317
- const expectedSuffix = `.workbuddy/plugins/marketplaces/${CODEBUDDY_MARKETPLACE_NAME}/plugins/${action.plugin}`;
360
+ let resolvedMarketplace;
361
+ try {
362
+ resolvedMarketplace = resolveCodeBuddyMarketplace(action);
363
+ } catch (resolveErr) {
364
+ return { valid: false, error: resolveErr.message, normalized: null };
365
+ }
366
+ const expectedSuffix = `.workbuddy/plugins/marketplaces/${resolvedMarketplace}/plugins/${action.plugin}`;
318
367
  if (!normalized.installPath.endsWith(expectedSuffix)) {
319
368
  return { valid: false, error: `codebuddy CLI attestation installPath must end with "${expectedSuffix}", got "${normalized.installPath}"`, normalized: null };
320
369
  }
321
370
  }
322
371
  }
323
372
 
373
+ // 可选 marketplace 字段:一旦声明必须等于冻结动作解析出的市场名
374
+ if (normalized.marketplace !== undefined) {
375
+ let resolvedMarketplace;
376
+ try {
377
+ resolvedMarketplace = resolveCodeBuddyMarketplace(action);
378
+ } catch (resolveErr) {
379
+ return { valid: false, error: resolveErr.message, normalized: null };
380
+ }
381
+ if (normalized.marketplace !== resolvedMarketplace) {
382
+ return { valid: false, error: `codebuddy attestation marketplace "${normalized.marketplace}" does not match the resolved marketplace "${resolvedMarketplace}"`, normalized: null };
383
+ }
384
+ }
385
+
324
386
  // result 只接受 passed 或 failed
325
387
  if (normalized.result !== 'passed' && normalized.result !== 'failed') {
326
388
  return { valid: false, error: `codebuddy attestation result "${normalized.result}" must be "passed" or "failed"`, normalized: null };
@@ -454,6 +516,22 @@ export async function executeCodeBuddyManualRequirement(action, context) {
454
516
 
455
517
  const ref = action.ref ?? `v${action.version}`;
456
518
 
519
+ // Unified marketplace identity: resolved from the frozen action (declared
520
+ // `marketplace`/`marketplaceSource`), falling back to the default constants
521
+ // for legacy plans. A fabricated source URL is never emitted for a
522
+ // configured marketplace without a declared marketplaceSource.
523
+ let marketplace;
524
+ try {
525
+ marketplace = resolveCodeBuddyMarketplace(action);
526
+ } catch (marketplaceErr) {
527
+ return createResult({
528
+ actionType,
529
+ status: ActionStatus.EXECUTE_FAILED,
530
+ error: marketplaceErr.message,
531
+ });
532
+ }
533
+ const marketplaceSource = resolveCodeBuddyMarketplaceSource(action);
534
+
457
535
  // (B) Stable plugin-level authority dir, shared across
458
536
  // publish/reconcile/verify run dirs.
459
537
  let attestationDir;
@@ -473,6 +551,8 @@ export async function executeCodeBuddyManualRequirement(action, context) {
473
551
  ref,
474
552
  attestationDir,
475
553
  requiresInstalledClosure: Boolean(context.plan?.skillResourceClosure),
554
+ marketplace,
555
+ marketplaceSource,
476
556
  });
477
557
 
478
558
  // 统一 requirement 结构:不再包含隔离目录信息
@@ -484,6 +564,8 @@ export async function executeCodeBuddyManualRequirement(action, context) {
484
564
  repo: action.repo,
485
565
  ref,
486
566
  entrySkill: action.entrySkill,
567
+ marketplace,
568
+ ...(marketplaceSource ? { marketplaceSource } : {}),
487
569
  planDigest,
488
570
  attestationDir,
489
571
  attestationFile: CODEBUDDY_ATTESTATION_FILE,
@@ -497,7 +579,7 @@ export async function executeCodeBuddyManualRequirement(action, context) {
497
579
  ...(context.plan?.skillResourceClosure
498
580
  ? {
499
581
  installChannel: '<"desktop" or "cli">',
500
- installPath: `<actual .workbuddy/plugins/marketplaces/${CODEBUDDY_MARKETPLACE_NAME}/plugins/${action.plugin} directory>`,
582
+ installPath: `<actual .workbuddy/plugins/marketplaces/${marketplace}/plugins/${action.plugin} directory>`,
501
583
  }
502
584
  : {}),
503
585
  note: '<optional note>',
@@ -359,9 +359,10 @@ const CODEBUDDY = Object.freeze({
359
359
  marketplace: '.claude-plugin/marketplace.json',
360
360
  }),
361
361
  marketplaceSourceForm: null,
362
- // codebuddy installs from a unified marketplace but carries no marketplace
363
- // identity in the action (the marketplace is a fixed constant in
364
- // ./codebuddy.mjs); the entry-version binding does not apply (null = N/A).
362
+ // codebuddy installs from a unified marketplace whose identity is optional
363
+ // on the action: a distribution may declare `marketplace` to override the
364
+ // default constant in ./codebuddy.mjs (resolveCodeBuddyMarketplace); the
365
+ // entry-version binding does not apply either way (null = N/A).
365
366
  marketplaceEntryCarriesVersion: null,
366
367
  // codebuddy has no marketplace add that can pin a frozen ref (attestation
367
368
  // loop instead), so the marketplace ref form does not apply (null = N/A).