release-skill 0.2.1 → 0.2.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 (55) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codebuddy-plugin/plugin.json +4 -2
  4. package/.codex-plugin/plugin.json +2 -2
  5. package/.kimi-plugin/plugin.json +1 -1
  6. package/CHANGELOG.md +40 -0
  7. package/INSTALL.md +183 -21
  8. package/INSTALL.zh-CN.md +160 -16
  9. package/README.md +112 -13
  10. package/README.zh-CN.md +72 -11
  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 +2240 -587
  14. package/adapters/claude/schemas/release-plan.schema.json +44 -2
  15. package/adapters/claude/schemas/release-project.schema.json +46 -4
  16. package/adapters/claude/schemas/release-run.schema.json +1 -0
  17. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  18. package/adapters/codex/bin/release-skill.bundle.mjs +2240 -587
  19. package/adapters/codex/schemas/release-plan.schema.json +44 -2
  20. package/adapters/codex/schemas/release-project.schema.json +46 -4
  21. package/adapters/codex/schemas/release-run.schema.json +1 -0
  22. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  23. package/adapters/kimi/bin/release-skill.bundle.mjs +2240 -587
  24. package/adapters/kimi/schemas/release-plan.schema.json +44 -2
  25. package/adapters/kimi/schemas/release-project.schema.json +46 -4
  26. package/adapters/kimi/schemas/release-run.schema.json +1 -0
  27. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +4 -2
  28. package/adapters/workbuddy/bin/release-skill.bundle.mjs +2240 -587
  29. package/adapters/workbuddy/schemas/release-plan.schema.json +44 -2
  30. package/adapters/workbuddy/schemas/release-project.schema.json +46 -4
  31. package/adapters/workbuddy/schemas/release-run.schema.json +1 -0
  32. package/bin/release-skill-cli.mjs +46 -4
  33. package/bin/release-skill.bundle.mjs +2240 -587
  34. package/package.json +1 -1
  35. package/references/02-project-config.md +7 -0
  36. package/references/06-adapter-contract.md +21 -2
  37. package/schemas/release-plan.schema.json +44 -2
  38. package/schemas/release-project.schema.json +46 -4
  39. package/schemas/release-run.schema.json +1 -0
  40. package/scripts/sync-public-files.mjs +8 -4
  41. package/src/adapters/contract.mjs +1 -0
  42. package/src/adapters/plugin-marketplace.mjs +796 -41
  43. package/src/commands/assess.mjs +50 -1
  44. package/src/commands/prepare.mjs +342 -9
  45. package/src/commands/publish.mjs +1 -0
  46. package/src/commands/reconcile.mjs +1 -0
  47. package/src/commands/setup.mjs +7 -3
  48. package/src/commands/verify.mjs +13 -5
  49. package/src/core/baseline.mjs +13 -0
  50. package/src/core/checkpoints.mjs +7 -2
  51. package/src/core/plan.mjs +275 -4
  52. package/src/core/verification-gates.mjs +1 -1
  53. package/src/platforms/codebuddy.mjs +658 -0
  54. package/src/platforms/registry.mjs +258 -5
  55. package/src/producers/build-adapters.mjs +30 -15
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Platform registry — the single source of truth for consumer-platform
3
- * knowledge (claude / codex / kimi).
3
+ * knowledge (claude / codex / kimi / codebuddy).
4
4
  *
5
5
  * T2.2 converges platform facts that were scattered across plugin-marketplace.mjs,
6
6
  * build-adapters.mjs, prepare.mjs, plan.mjs and project.yaml into this module.
@@ -23,6 +23,8 @@
23
23
  * file; kimi's side-effecting manual-requirement and manifest-reading
24
24
  * strategies live in ./kimi.mjs (§4.2's per-platform-module option — the only
25
25
  * non-trivial strategies) and are referenced from the kimi descriptor below.
26
+ * codebuddy's analogous human-attestation strategies live in ./codebuddy.mjs
27
+ * and are referenced from the codebuddy descriptor below.
26
28
  * plugin-marketplace.mjs consumes every platform difference through this
27
29
  * registry; see the t2-2 deviation record for the step-2 wiring details.
28
30
  *
@@ -36,6 +38,10 @@ import {
36
38
  readKimiManifest,
37
39
  KIMI_MANIFEST_CANDIDATES,
38
40
  } from './kimi.mjs';
41
+ import {
42
+ executeCodeBuddyManualRequirement,
43
+ readCodeBuddyManifest,
44
+ } from './codebuddy.mjs';
39
45
 
40
46
  // --- claude strategy -------------------------------------------------------
41
47
 
@@ -131,6 +137,12 @@ const CLAUDE = Object.freeze({
131
137
  // actionType -> adapter map from it (T2.2 step 3).
132
138
  adapter: 'plugin-marketplace',
133
139
  automatable: true,
140
+ // --- capability contract (平台能力契约) ------------------------------------
141
+ installMethod: 'structured-cli',
142
+ refStrength: 'name-ref',
143
+ outputProtocol: 'text',
144
+ identityEvidence: 'list-record',
145
+ degradationPolicy: 'block',
134
146
  cli: Object.freeze({
135
147
  binary: 'claude',
136
148
  marketplaceAdd: (repo, ref) => ['plugin', 'marketplace', 'add', `${repo}@${ref}`],
@@ -155,6 +167,10 @@ const CLAUDE = Object.freeze({
155
167
  // Claude carries the authoritative version in the marketplace entry: the
156
168
  // preflight binds entry.version to the action version.
157
169
  marketplaceEntryCarriesVersion: true,
170
+ // External marketplace form: `claude plugin marketplace add repo@<ref>` can
171
+ // only pin a branch/tag NAME (a bare commit sha fails to clone and the
172
+ // resolved sha is not recorded locally) — name-form weak freeze.
173
+ marketplaceRefForm: 'name',
158
174
  knownHostArtifacts: Object.freeze(['.in_use']),
159
175
  schemaRequiredFields: Object.freeze(['plugin', 'marketplace', 'entrySkill']),
160
176
  skillRendering: Object.freeze({ mode: 'verbatim', preamble: null, placeholder: '${CLAUDE_PLUGIN_ROOT}' }),
@@ -180,6 +196,12 @@ const CODEX = Object.freeze({
180
196
  actionType: 'codex-marketplace-install',
181
197
  adapter: 'plugin-marketplace',
182
198
  automatable: true,
199
+ // --- capability contract (平台能力契约) ------------------------------------
200
+ installMethod: 'structured-cli',
201
+ refStrength: 'commit-sha',
202
+ outputProtocol: 'structured',
203
+ identityEvidence: 'install-output',
204
+ degradationPolicy: 'block',
183
205
  cli: Object.freeze({
184
206
  binary: 'codex',
185
207
  marketplaceAdd: (repo, ref) => ['plugin', 'marketplace', 'add', repo, '--ref', ref, '--json'],
@@ -204,6 +226,9 @@ const CODEX = Object.freeze({
204
226
  // Codex keeps the authoritative version in .codex-plugin/plugin.json; the
205
227
  // marketplace entry version is not bound to the action version.
206
228
  marketplaceEntryCarriesVersion: false,
229
+ // External marketplace form: `codex plugin marketplace add repo --ref <ref>`
230
+ // accepts a bare commit sha — sha-form strong freeze.
231
+ marketplaceRefForm: 'sha',
207
232
  knownHostArtifacts: Object.freeze(['.git', '.codex-plugin/migrated-command-skills']),
208
233
  schemaRequiredFields: Object.freeze(['plugin', 'marketplace', 'entrySkill']),
209
234
  skillRendering: Object.freeze({ mode: 'substitute', preamble: 'codex', placeholder: '${CLAUDE_PLUGIN_ROOT}' }),
@@ -229,6 +254,12 @@ const KIMI = Object.freeze({
229
254
  actionType: 'kimi-marketplace-install',
230
255
  adapter: 'plugin-marketplace',
231
256
  automatable: false,
257
+ // --- capability contract (平台能力契约) ------------------------------------
258
+ installMethod: 'interactive-only',
259
+ refStrength: 'unfixable',
260
+ outputProtocol: 'none',
261
+ identityEvidence: 'filesystem-payload',
262
+ degradationPolicy: 'human-attestation',
232
263
  cli: null,
233
264
  jsonProtocol: Object.freeze({
234
265
  listOutput: null,
@@ -251,6 +282,9 @@ const KIMI = Object.freeze({
251
282
  // apply (null = N/A, never consulted: kimi preflight reads its manifest via
252
283
  // strategy.readManifest).
253
284
  marketplaceEntryCarriesVersion: null,
285
+ // kimi has no marketplace add at all, so the marketplace ref form does not
286
+ // apply (null = N/A, never consulted).
287
+ marketplaceRefForm: null,
254
288
  knownHostArtifacts: Object.freeze(['.git']),
255
289
  schemaRequiredFields: Object.freeze(['plugin', 'entrySkill']),
256
290
  skillRendering: Object.freeze({ mode: 'substitute', preamble: 'kimi', placeholder: '${CLAUDE_PLUGIN_ROOT}' }),
@@ -272,15 +306,102 @@ const KIMI = Object.freeze({
272
306
  }),
273
307
  });
274
308
 
275
- /** Ordered platform registry. Order is significant (claude, codex, kimi). */
276
- export const PLATFORMS = Object.freeze([CLAUDE, CODEX, KIMI]);
309
+ // CodeBuddy (desktop product WorkBuddy) is a NON-automatable human-attestation
310
+ // platform like kimi: the codebuddy CLI's marketplace add/install cannot pin a
311
+ // frozen ref (no ref option; the install tracks the default branch / latest),
312
+ // so an automated install checkpoint cannot guarantee frozen-artifact identity.
313
+ // Install state has a stable on-disk layout, so the closed loop is proven by a
314
+ // human attestation + read-only verification (./codebuddy.mjs), fail-closed at
315
+ // the same severity as kimi.
316
+ //
317
+ // NAMING MAPPING: the platform id is `codebuddy`, but its BUILD adapter keeps
318
+ // the historical directory name `workbuddy` (`adapters/workbuddy/`, manifest
319
+ // `.codebuddy-plugin/plugin.json`). `buildAdapter.name = 'workbuddy'` drives
320
+ // the build-adapters producer + public-file collection; the pipeline identity
321
+ // (distributionType/actionType/consumer) stays `codebuddy`.
322
+ const CODEBUDDY = Object.freeze({
323
+ id: 'codebuddy',
324
+ distributionType: 'codebuddy-plugin',
325
+ actionType: 'codebuddy-marketplace-install',
326
+ adapter: 'plugin-marketplace',
327
+ automatable: false,
328
+ // --- capability contract (平台能力契约) ------------------------------------
329
+ installMethod: 'human-attestation',
330
+ refStrength: 'unfixable',
331
+ outputProtocol: 'none',
332
+ identityEvidence: 'human-attestation',
333
+ degradationPolicy: 'human-attestation',
334
+ cli: null,
335
+ jsonProtocol: Object.freeze({
336
+ listOutput: null,
337
+ installPathSource: null,
338
+ marketplaceAddOutput: null,
339
+ pluginInstallOutput: null,
340
+ }),
341
+ // The closed loop never execs the codebuddy CLI; HOME is the only isolation
342
+ // input and exists solely so the attestation's isolated cli-channel home has
343
+ // a base. No unverified host env vars are fabricated.
344
+ isolationEnv: (home) => ({ HOME: home }),
345
+ // codebuddy never execs a CLI; its isolated cli home is created by the
346
+ // manual-requirement strategy under the attestation authority.
347
+ isolationSubdirs: Object.freeze([]),
348
+ manifestPaths: Object.freeze({
349
+ // Single authoritative manifest (no precedence chain, unlike kimi).
350
+ plugin: '.codebuddy-plugin/plugin.json',
351
+ marketplace: null,
352
+ }),
353
+ marketplaceSourceForm: null,
354
+ // codebuddy installs from a unified marketplace but carries no marketplace
355
+ // identity in the action (the marketplace is a fixed constant in
356
+ // ./codebuddy.mjs); the entry-version binding does not apply (null = N/A).
357
+ marketplaceEntryCarriesVersion: null,
358
+ // codebuddy has no marketplace add that can pin a frozen ref (attestation
359
+ // loop instead), so the marketplace ref form does not apply (null = N/A).
360
+ marketplaceRefForm: null,
361
+ knownHostArtifacts: Object.freeze(['.git']),
362
+ schemaRequiredFields: Object.freeze(['plugin', 'entrySkill']),
363
+ // Declarative rendering intent (the build-adapters producer keys on
364
+ // buildAdapter.name = 'workbuddy'): pure ${CLAUDE_PLUGIN_ROOT} ->
365
+ // ${CODEBUDDY_PLUGIN_ROOT} substitution, no entry-resolution preamble
366
+ // (CodeBuddy expands the variable inline in skill content).
367
+ skillRendering: Object.freeze({ mode: 'substitute', preamble: null, placeholder: '${CODEBUDDY_PLUGIN_ROOT}' }),
368
+ buildAdapter: Object.freeze({
369
+ // Build adapter keeps the historical `workbuddy` directory name; the fields
370
+ // are byte-for-byte the legacy BUILD_ONLY workbuddy entry so the generated
371
+ // adapters/workbuddy/ tree is unchanged.
372
+ name: 'workbuddy',
373
+ pluginDirName: '.codebuddy-plugin',
374
+ templateFileName: 'plugin.json',
375
+ marketplaceFileName: null,
376
+ hasMarketplace: false,
377
+ }),
378
+ strategy: Object.freeze({
379
+ parseListOutput: null,
380
+ extractInstallPath: null,
381
+ extractListIdentity: null,
382
+ crossValidateListEntry: null,
383
+ // codebuddy's side-effecting manual-requirement builder and manifest reader
384
+ // live in ./codebuddy.mjs (per-platform-module option, mirroring kimi).
385
+ buildManualRequirement: executeCodeBuddyManualRequirement,
386
+ readManifest: readCodeBuddyManifest,
387
+ }),
388
+ });
277
389
 
278
- const VALID_DISTRIBUTION_TYPES = new Set(['claude-plugin', 'codex-plugin', 'kimi-plugin']);
279
- const VALID_ACTION_TYPES = new Set(['claude-marketplace-install', 'codex-marketplace-install', 'kimi-marketplace-install']);
390
+ /** Ordered platform registry. Order is significant (claude, codex, kimi, codebuddy). */
391
+ export const PLATFORMS = Object.freeze([CLAUDE, CODEX, KIMI, CODEBUDDY]);
392
+
393
+ const VALID_DISTRIBUTION_TYPES = new Set(['claude-plugin', 'codex-plugin', 'kimi-plugin', 'codebuddy-plugin']);
394
+ const VALID_ACTION_TYPES = new Set(['claude-marketplace-install', 'codex-marketplace-install', 'kimi-marketplace-install', 'codebuddy-marketplace-install']);
280
395
  const VALID_SOURCE_FORMS = new Set(['string', 'local-path-object', null]);
396
+ const VALID_MARKETPLACE_REF_FORMS = new Set(['sha', 'name', null]);
281
397
  const VALID_LIST_OUTPUTS = new Set(['array', 'installed-object', null]);
282
398
  const VALID_CLI_OUTPUTS = new Set(['json', null]);
283
399
  const VALID_ENTRY_VERSION_BINDING = new Set([true, false, null]);
400
+ const VALID_INSTALL_METHODS = new Set(['structured-cli', 'interactive-only', 'human-attestation']);
401
+ const VALID_REF_STRENGTHS = new Set(['commit-sha', 'name-ref', 'unfixable']);
402
+ const VALID_OUTPUT_PROTOCOLS = new Set(['structured', 'text', 'none']);
403
+ const VALID_IDENTITY_EVIDENCE = new Set(['list-record', 'install-output', 'filesystem-payload', 'human-attestation']);
404
+ const VALID_DEGRADATION_POLICIES = new Set(['block', 'human-attestation']);
284
405
 
285
406
  /**
286
407
  * Look up a platform descriptor by id.
@@ -333,6 +454,9 @@ export function assertRegistry(registry = PLATFORMS) {
333
454
  if (!VALID_SOURCE_FORMS.has(platform.marketplaceSourceForm)) {
334
455
  throw new Error(`platform registry: ${label} has illegal marketplaceSourceForm "${platform.marketplaceSourceForm}"`);
335
456
  }
457
+ if (!VALID_MARKETPLACE_REF_FORMS.has(platform.marketplaceRefForm)) {
458
+ throw new Error(`platform registry: ${label} has illegal marketplaceRefForm "${platform.marketplaceRefForm}"`);
459
+ }
336
460
  if (!Array.isArray(platform.knownHostArtifacts)) {
337
461
  throw new Error(`platform registry: ${label} knownHostArtifacts must be an array`);
338
462
  }
@@ -358,6 +482,23 @@ export function assertRegistry(registry = PLATFORMS) {
358
482
  throw new Error(`platform registry: ${label} isolationSubdirs must be an array`);
359
483
  }
360
484
 
485
+ // --- capability contract validation (能力契约验证) -----------------------
486
+ if (!VALID_INSTALL_METHODS.has(platform.installMethod)) {
487
+ throw new Error(`platform registry: ${label} has illegal installMethod "${platform.installMethod}"`);
488
+ }
489
+ if (!VALID_REF_STRENGTHS.has(platform.refStrength)) {
490
+ throw new Error(`platform registry: ${label} has illegal refStrength "${platform.refStrength}"`);
491
+ }
492
+ if (!VALID_OUTPUT_PROTOCOLS.has(platform.outputProtocol)) {
493
+ throw new Error(`platform registry: ${label} has illegal outputProtocol "${platform.outputProtocol}"`);
494
+ }
495
+ if (!VALID_IDENTITY_EVIDENCE.has(platform.identityEvidence)) {
496
+ throw new Error(`platform registry: ${label} has illegal identityEvidence "${platform.identityEvidence}"`);
497
+ }
498
+ if (!VALID_DEGRADATION_POLICIES.has(platform.degradationPolicy)) {
499
+ throw new Error(`platform registry: ${label} has illegal degradationPolicy "${platform.degradationPolicy}"`);
500
+ }
501
+
361
502
  if (platform.automatable) {
362
503
  if (!platform.cli || typeof platform.cli.marketplaceAdd !== 'function'
363
504
  || typeof platform.cli.install !== 'function' || typeof platform.cli.list !== 'function') {
@@ -386,8 +527,120 @@ export function assertRegistry(registry = PLATFORMS) {
386
527
  throw new Error(`platform registry: non-automatable platform ${label} needs strategy.readManifest`);
387
528
  }
388
529
  }
530
+
531
+ // --- capability contract consistency rules (能力契约一致性规则) -----------
532
+ // automatable === true 必须有 installMethod === 'structured-cli'
533
+ if (platform.automatable === true && platform.installMethod !== 'structured-cli') {
534
+ throw new Error(`platform registry: ${label} is automatable but installMethod is "${platform.installMethod}" (expected "structured-cli")`);
535
+ }
536
+ // automatable === false 必须有 installMethod !== 'structured-cli'
537
+ if (platform.automatable === false && platform.installMethod === 'structured-cli') {
538
+ throw new Error(`platform registry: ${label} is not automatable but installMethod is "structured-cli"`);
539
+ }
540
+ // installMethod === 'structured-cli' 必须有 refStrength !== 'unfixable'
541
+ if (platform.installMethod === 'structured-cli' && platform.refStrength === 'unfixable') {
542
+ throw new Error(`platform registry: ${label} has structured-cli install but unfixable refStrength`);
543
+ }
544
+ // installMethod === 'human-attestation' 必须有 identityEvidence === 'human-attestation'
545
+ if (platform.installMethod === 'human-attestation' && platform.identityEvidence !== 'human-attestation') {
546
+ throw new Error(`platform registry: ${label} has human-attestation install but identityEvidence is "${platform.identityEvidence}"`);
547
+ }
389
548
  }
390
549
  }
391
550
 
392
551
  // Self-validate at import time: a malformed registry never reaches runtime.
393
552
  assertRegistry();
553
+
554
+ // --- capability-driven routing & conflict detection (能力驱动路由与矛盾检测) --
555
+
556
+ /**
557
+ * 根据平台描述符的 installMethod 决定执行路由。
558
+ *
559
+ * @param {object} platform - 平台描述符
560
+ * @returns {{ route: string, reason: string }}
561
+ * @throws {Error} 无法路由时抛出
562
+ */
563
+ export function resolvePlatformRoute(platform) {
564
+ const { installMethod } = platform;
565
+ if (installMethod === 'structured-cli') {
566
+ return { route: 'structured-cli', reason: `installMethod is structured-cli` };
567
+ }
568
+ if (installMethod === 'interactive-only' || installMethod === 'human-attestation') {
569
+ return { route: 'human-attestation', reason: `installMethod is ${installMethod}` };
570
+ }
571
+ throw new Error(
572
+ `platform "${platform?.id ?? '<unknown>'}" with installMethod="${installMethod}" cannot be routed`,
573
+ );
574
+ }
575
+
576
+ /**
577
+ * 检测平台能力描述符中的矛盾。
578
+ *
579
+ * @param {object} platform - 平台描述符
580
+ * @returns {{ hasConflict: boolean, conflicts: string[] }}
581
+ * @throws {Error} 发现矛盾时抛出
582
+ */
583
+ export function resolveCapabilityConflicts(platform) {
584
+ const conflicts = [];
585
+ const { id, automatable, installMethod, refStrength, identityEvidence, cli, strategy } = platform;
586
+
587
+ // automatable <-> installMethod 一致性
588
+ if (automatable === true && installMethod !== 'structured-cli') {
589
+ conflicts.push(`automatable=true but installMethod="${installMethod}"`);
590
+ }
591
+ if (automatable === false && installMethod === 'structured-cli') {
592
+ conflicts.push(`automatable=false but installMethod="structured-cli"`);
593
+ }
594
+
595
+ // installMethod <-> refStrength 一致性
596
+ if (installMethod === 'structured-cli' && refStrength === 'unfixable') {
597
+ conflicts.push(`structured-cli with unfixable refStrength`);
598
+ }
599
+
600
+ // installMethod <-> identityEvidence 一致性
601
+ if (installMethod === 'human-attestation' && identityEvidence !== 'human-attestation') {
602
+ conflicts.push(`human-attestation install but identityEvidence="${identityEvidence}"`);
603
+ }
604
+
605
+ // 自动化平台必须有 cli 和策略解析函数
606
+ if (automatable === true) {
607
+ if (!cli || typeof cli !== 'object') {
608
+ conflicts.push(`automatable=true but cli is missing`);
609
+ }
610
+ if (!strategy || typeof strategy.parseListOutput !== 'function') {
611
+ conflicts.push(`automatable=true but strategy.parseListOutput is missing`);
612
+ }
613
+ if (!strategy || typeof strategy.extractInstallPath !== 'function') {
614
+ conflicts.push(`automatable=true but strategy.extractInstallPath is missing`);
615
+ }
616
+ }
617
+
618
+ // 非自动化平台必须 cli === null,且有手动策略函数
619
+ if (automatable === false) {
620
+ if (cli !== null) {
621
+ conflicts.push(`automatable=false but cli is not null`);
622
+ }
623
+ if (!strategy || typeof strategy.buildManualRequirement !== 'function') {
624
+ conflicts.push(`automatable=false but strategy.buildManualRequirement is missing`);
625
+ }
626
+ if (!strategy || typeof strategy.readManifest !== 'function') {
627
+ conflicts.push(`automatable=false but strategy.readManifest is missing`);
628
+ }
629
+ }
630
+
631
+ if (conflicts.length > 0) {
632
+ throw new Error(`capability conflict in platform "${id}": ${conflicts.join('; ')}`);
633
+ }
634
+
635
+ return { hasConflict: false, conflicts: [] };
636
+ }
637
+
638
+ /**
639
+ * 从 installMethod 派生 automatable 标志。
640
+ *
641
+ * @param {object} platform - 平台描述符
642
+ * @returns {boolean}
643
+ */
644
+ export function deriveAutomatable(platform) {
645
+ return platform.installMethod === 'structured-cli';
646
+ }
@@ -197,8 +197,15 @@ export function computeBuildAdaptersDigest(sourceBytes) {
197
197
  // of platform knowledge). The build shape mirrors the legacy inline table
198
198
  // byte-for-byte: marketplaceFileName is present only when the platform
199
199
  // co-locates a marketplace manifest in its plugin dir (claude).
200
+ //
201
+ // The build adapter NAME defaults to the platform id, but a platform may keep a
202
+ // historical directory name distinct from its pipeline id via
203
+ // `buildAdapter.name`: codebuddy (pipeline id) builds the `workbuddy` adapter
204
+ // tree (adapters/workbuddy/, manifest .codebuddy-plugin/plugin.json). This is
205
+ // what lets codebuddy join the publish pipeline WITHOUT changing a single byte
206
+ // of the generated workbuddy adapter (the former BUILD_ONLY workbuddy entry).
200
207
  const REGISTRY_PLATFORMS = PLATFORM_REGISTRY.map((platform) => ({
201
- name: platform.id,
208
+ name: platform.buildAdapter?.name ?? platform.id,
202
209
  pluginDirName: platform.buildAdapter.pluginDirName,
203
210
  templateFileName: platform.buildAdapter.templateFileName,
204
211
  ...(platform.buildAdapter.hasMarketplace
@@ -216,18 +223,16 @@ const REGISTRY_PLATFORMS = PLATFORM_REGISTRY.map((platform) => ({
216
223
  * until then a build-only entry lets the build emit an installable adapter
217
224
  * tree without fabricating an unverified install protocol.
218
225
  *
219
- * workbuddy: CodeBuddy/WorkBuddy plugin (official plugin reference:
220
- * `.codebuddy-plugin/plugin.json`, components at the plugin root,
221
- * `${CODEBUDDY_PLUGIN_ROOT}` expanded inline in skill content).
226
+ * Currently EMPTY: every platform the build emits now has a full registry
227
+ * descriptor. workbuddy (the CodeBuddy/WorkBuddy plugin, manifest
228
+ * `.codebuddy-plugin/plugin.json`, `${CODEBUDDY_PLUGIN_ROOT}` expanded inline
229
+ * in skill content) moved into the registry as the `codebuddy` platform with
230
+ * `buildAdapter.name = 'workbuddy'`, so its adapter tree is still produced —
231
+ * byte-for-byte unchanged — via REGISTRY_PLATFORMS. This constant is retained
232
+ * (still exported and consumed below) for future build-only hosts that have an
233
+ * installable adapter tree but no verified install protocol yet.
222
234
  */
223
- export const BUILD_ONLY_ADAPTERS = Object.freeze([
224
- Object.freeze({
225
- name: 'workbuddy',
226
- pluginDirName: '.codebuddy-plugin',
227
- templateFileName: 'plugin.json',
228
- hasMarketplace: false,
229
- }),
230
- ]);
235
+ export const BUILD_ONLY_ADAPTERS = Object.freeze([]);
231
236
 
232
237
  export const PLATFORMS = Object.freeze([...REGISTRY_PLATFORMS, ...BUILD_ONLY_ADAPTERS]);
233
238
 
@@ -427,12 +432,22 @@ async function generateAdapterFiles(platform, skills, templateJson, root, market
427
432
  const adapted = JSON.parse(JSON.stringify(templateJson));
428
433
 
429
434
  // Preserve platform-supported directory auto-discovery in generated adapters.
435
+ // The CodeBuddy server-side validator (`codebuddy plugin validate`) requires
436
+ // `skills` to be an array of component paths, so the workbuddy adapter emits
437
+ // ["./skills/"]; claude/codex/kimi hosts keep the directory-string form.
430
438
  if (typeof adapted.skills === 'string') {
431
- adapted.skills = './skills/';
439
+ adapted.skills = platform.name === 'workbuddy' ? ['./skills/'] : './skills/';
432
440
  } else if (Array.isArray(adapted.skills)) {
433
- for (const skill of adapted.skills) {
441
+ // Handle both string array and object array forms
442
+ adapted.skills = adapted.skills.map((skill) => {
443
+ if (typeof skill === 'string') {
444
+ // String array: transform to relative path for the adapter
445
+ return platform.name === 'workbuddy' ? './skills/' : skill;
446
+ }
447
+ // Object array: preserve existing behavior
434
448
  skill.source = `../skills/${skill.name}/SKILL.md`;
435
- }
449
+ return skill;
450
+ });
436
451
  }
437
452
 
438
453
  const pluginJsonContent = JSON.stringify(adapted, null, 2) + '\n';