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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codebuddy-plugin/plugin.json +4 -2
- package/.codex-plugin/plugin.json +2 -2
- package/.kimi-plugin/plugin.json +1 -1
- package/CHANGELOG.md +40 -0
- package/INSTALL.md +183 -21
- package/INSTALL.zh-CN.md +160 -16
- package/README.md +112 -13
- package/README.zh-CN.md +72 -11
- package/adapters/claude/.claude-plugin/marketplace.json +1 -1
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +2240 -587
- package/adapters/claude/schemas/release-plan.schema.json +44 -2
- package/adapters/claude/schemas/release-project.schema.json +46 -4
- package/adapters/claude/schemas/release-run.schema.json +1 -0
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +2240 -587
- package/adapters/codex/schemas/release-plan.schema.json +44 -2
- package/adapters/codex/schemas/release-project.schema.json +46 -4
- package/adapters/codex/schemas/release-run.schema.json +1 -0
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +2240 -587
- package/adapters/kimi/schemas/release-plan.schema.json +44 -2
- package/adapters/kimi/schemas/release-project.schema.json +46 -4
- package/adapters/kimi/schemas/release-run.schema.json +1 -0
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +4 -2
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +2240 -587
- package/adapters/workbuddy/schemas/release-plan.schema.json +44 -2
- package/adapters/workbuddy/schemas/release-project.schema.json +46 -4
- package/adapters/workbuddy/schemas/release-run.schema.json +1 -0
- package/bin/release-skill-cli.mjs +46 -4
- package/bin/release-skill.bundle.mjs +2240 -587
- package/package.json +1 -1
- package/references/02-project-config.md +7 -0
- package/references/06-adapter-contract.md +21 -2
- package/schemas/release-plan.schema.json +44 -2
- package/schemas/release-project.schema.json +46 -4
- package/schemas/release-run.schema.json +1 -0
- package/scripts/sync-public-files.mjs +8 -4
- package/src/adapters/contract.mjs +1 -0
- package/src/adapters/plugin-marketplace.mjs +796 -41
- package/src/commands/assess.mjs +50 -1
- package/src/commands/prepare.mjs +342 -9
- package/src/commands/publish.mjs +1 -0
- package/src/commands/reconcile.mjs +1 -0
- package/src/commands/setup.mjs +7 -3
- package/src/commands/verify.mjs +13 -5
- package/src/core/baseline.mjs +13 -0
- package/src/core/checkpoints.mjs +7 -2
- package/src/core/plan.mjs +275 -4
- package/src/core/verification-gates.mjs +1 -1
- package/src/platforms/codebuddy.mjs +658 -0
- package/src/platforms/registry.mjs +258 -5
- 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
|
-
|
|
276
|
-
|
|
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
|
-
|
|
279
|
-
const
|
|
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
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* `${CODEBUDDY_PLUGIN_ROOT}` expanded inline
|
|
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
|
-
|
|
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';
|