release-skill 0.2.1 → 0.2.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.kimi-plugin/plugin.json +1 -1
- package/CHANGELOG.md +21 -0
- package/INSTALL.md +183 -21
- package/INSTALL.zh-CN.md +160 -16
- package/README.md +112 -11
- package/README.zh-CN.md +73 -10
- 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 +1718 -600
- 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 +1718 -600
- 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 +1718 -600
- 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 +1718 -600
- 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 +1718 -600
- 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 +536 -23
- package/src/commands/assess.mjs +50 -1
- package/src/commands/prepare.mjs +273 -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 +2 -0
- package/src/core/checkpoints.mjs +7 -2
- package/src/core/plan.mjs +97 -4
- package/src/core/verification-gates.mjs +1 -1
- package/src/platforms/codebuddy.mjs +618 -0
- package/src/platforms/registry.mjs +100 -5
- package/src/producers/build-adapters.mjs +21 -13
package/src/commands/assess.mjs
CHANGED
|
@@ -129,7 +129,8 @@ function identifyTopology(config) {
|
|
|
129
129
|
const hasPlugin =
|
|
130
130
|
uniqueDistTypes.includes('claude-plugin') ||
|
|
131
131
|
uniqueDistTypes.includes('codex-plugin') ||
|
|
132
|
-
uniqueDistTypes.includes('kimi-plugin')
|
|
132
|
+
uniqueDistTypes.includes('kimi-plugin') ||
|
|
133
|
+
uniqueDistTypes.includes('codebuddy-plugin');
|
|
133
134
|
|
|
134
135
|
if (units.length === 0) {
|
|
135
136
|
type = 'no-release-units';
|
|
@@ -428,6 +429,54 @@ async function checkPluginManifests(root, config) {
|
|
|
428
429
|
}
|
|
429
430
|
}
|
|
430
431
|
}
|
|
432
|
+
|
|
433
|
+
if (distributionTypes.has('codebuddy-plugin')) {
|
|
434
|
+
const manifestPath = resolve(unitRoot, '.codebuddy-plugin', 'plugin.json');
|
|
435
|
+
const displayPath = unitFile(unit, '.codebuddy-plugin/plugin.json');
|
|
436
|
+
const exists = await fileExists(manifestPath);
|
|
437
|
+
if (!exists) {
|
|
438
|
+
gaps.push(
|
|
439
|
+
createGap({
|
|
440
|
+
scope: GapScope.PROFILE,
|
|
441
|
+
category: GapCategory.MANIFEST,
|
|
442
|
+
severity: Severity.ERROR,
|
|
443
|
+
code: 'CODEBUDDY_MANIFEST_MISSING',
|
|
444
|
+
message: `发布单元 "${unit.id}" 缺少 .codebuddy-plugin/plugin.json 插件清单`,
|
|
445
|
+
file: displayPath,
|
|
446
|
+
}),
|
|
447
|
+
);
|
|
448
|
+
} else {
|
|
449
|
+
try {
|
|
450
|
+
const content = await readFile(manifestPath, 'utf8');
|
|
451
|
+
const manifest = JSON.parse(content);
|
|
452
|
+
const requiredFields = ['name', 'version', 'description'];
|
|
453
|
+
const missingFields = requiredFields.filter((f) => !(f in manifest));
|
|
454
|
+
if (missingFields.length > 0) {
|
|
455
|
+
gaps.push(
|
|
456
|
+
createGap({
|
|
457
|
+
scope: GapScope.PROFILE,
|
|
458
|
+
category: GapCategory.MANIFEST,
|
|
459
|
+
severity: Severity.ERROR,
|
|
460
|
+
code: 'CODEBUDDY_MANIFEST_INCOMPLETE',
|
|
461
|
+
message: `CodeBuddy 插件清单缺少必填字段: ${missingFields.join(', ')}`,
|
|
462
|
+
file: displayPath,
|
|
463
|
+
}),
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
} catch {
|
|
467
|
+
gaps.push(
|
|
468
|
+
createGap({
|
|
469
|
+
scope: GapScope.PROFILE,
|
|
470
|
+
category: GapCategory.MANIFEST,
|
|
471
|
+
severity: Severity.ERROR,
|
|
472
|
+
code: 'CODEBUDDY_MANIFEST_INVALID',
|
|
473
|
+
message: '.codebuddy-plugin/plugin.json 解析失败',
|
|
474
|
+
file: displayPath,
|
|
475
|
+
}),
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
431
480
|
}
|
|
432
481
|
|
|
433
482
|
return gaps;
|
package/src/commands/prepare.mjs
CHANGED
|
@@ -941,6 +941,231 @@ async function buildProductionAssets(
|
|
|
941
941
|
return assets;
|
|
942
942
|
}
|
|
943
943
|
|
|
944
|
+
// ---------------------------------------------------------------------------
|
|
945
|
+
// External independent marketplace freeze (production + online only)
|
|
946
|
+
// ---------------------------------------------------------------------------
|
|
947
|
+
|
|
948
|
+
const EXTERNAL_MARKETPLACE_SHA_RE = /^[0-9a-f]{40}$/;
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Parse `git ls-remote --symref <url> HEAD` output into the resolved HEAD
|
|
952
|
+
* commit sha and the default branch name. Pure: no I/O.
|
|
953
|
+
*
|
|
954
|
+
* Expected lines (tab-separated):
|
|
955
|
+
* ref: refs/heads/<branch>\tHEAD
|
|
956
|
+
* <40-hex sha>\tHEAD
|
|
957
|
+
*
|
|
958
|
+
* @param {string} stdout
|
|
959
|
+
* @returns {{sha:string, defaultBranch:string}|null} null when either is absent.
|
|
960
|
+
*/
|
|
961
|
+
export function parseExternalMarketplaceLsRemote(stdout) {
|
|
962
|
+
if (typeof stdout !== 'string') return null;
|
|
963
|
+
const lines = stdout.trim().split('\n').filter((line) => line.length > 0);
|
|
964
|
+
let defaultBranch = null;
|
|
965
|
+
let sha = null;
|
|
966
|
+
for (const line of lines) {
|
|
967
|
+
const tabIndex = line.indexOf('\t');
|
|
968
|
+
if (tabIndex < 0) continue;
|
|
969
|
+
const left = line.slice(0, tabIndex);
|
|
970
|
+
const right = line.slice(tabIndex + 1);
|
|
971
|
+
if (right !== 'HEAD') continue;
|
|
972
|
+
if (left.startsWith('ref: refs/heads/')) {
|
|
973
|
+
defaultBranch = left.slice('ref: refs/heads/'.length);
|
|
974
|
+
} else if (EXTERNAL_MARKETPLACE_SHA_RE.test(left)) {
|
|
975
|
+
sha = left;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
if (!sha || !defaultBranch) return null;
|
|
979
|
+
return { sha, defaultBranch };
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* Decode a GitHub contents-API base64 `.content` field and parse it as the
|
|
984
|
+
* marketplace index JSON. Pure: no I/O.
|
|
985
|
+
*
|
|
986
|
+
* @param {string} base64Content
|
|
987
|
+
* @returns {object|null} parsed index, or null on decode/parse failure.
|
|
988
|
+
*/
|
|
989
|
+
export function decodeExternalMarketplaceIndex(base64Content) {
|
|
990
|
+
if (typeof base64Content !== 'string') return null;
|
|
991
|
+
try {
|
|
992
|
+
const base64 = base64Content.replace(/\s/g, '');
|
|
993
|
+
const content = Buffer.from(base64, 'base64').toString('utf8');
|
|
994
|
+
const parsed = JSON.parse(content);
|
|
995
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
996
|
+
} catch {
|
|
997
|
+
return null;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/**
|
|
1002
|
+
* Resolve an external marketplace repository's current HEAD via
|
|
1003
|
+
* `git ls-remote --symref`, returning both the resolved commit sha and the
|
|
1004
|
+
* default branch name. Read-only: never writes to the remote.
|
|
1005
|
+
*
|
|
1006
|
+
* @param {string} repo - External marketplace repository (owner/name).
|
|
1007
|
+
* @param {object} [opts]
|
|
1008
|
+
* @param {string} [opts.githubHost]
|
|
1009
|
+
* @returns {Promise<{status:string, sha?:string, defaultBranch?:string, error?:string}>}
|
|
1010
|
+
*/
|
|
1011
|
+
async function defaultObserveExternalMarketplaceHead(repo, { githubHost = 'github.com' } = {}) {
|
|
1012
|
+
try {
|
|
1013
|
+
const { stdout } = await execFile(
|
|
1014
|
+
'git',
|
|
1015
|
+
['ls-remote', '--symref', `https://${githubHost}/${repo}.git`, 'HEAD'],
|
|
1016
|
+
{ shell: false, encoding: 'utf8', timeout: 30000 },
|
|
1017
|
+
);
|
|
1018
|
+
const parsed = parseExternalMarketplaceLsRemote(stdout);
|
|
1019
|
+
if (!parsed) {
|
|
1020
|
+
return { status: 'unknown', error: 'could not resolve HEAD commit sha and default branch from ls-remote --symref output' };
|
|
1021
|
+
}
|
|
1022
|
+
return { status: 'observed', sha: parsed.sha, defaultBranch: parsed.defaultBranch };
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
return { status: 'unknown', error: error.message };
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* Fetch and parse an external marketplace index manifest at a frozen ref via
|
|
1030
|
+
* the GitHub contents API. Read-only: never writes to the remote.
|
|
1031
|
+
*
|
|
1032
|
+
* @param {string} repo - External marketplace repository (owner/name).
|
|
1033
|
+
* @param {string} manifestPath - Platform marketplace manifest path.
|
|
1034
|
+
* @param {string} ref - Frozen commit sha to read the index at.
|
|
1035
|
+
* @param {object} [opts]
|
|
1036
|
+
* @param {string} [opts.githubHost]
|
|
1037
|
+
* @returns {Promise<{status:string, index?:object, error?:string}>}
|
|
1038
|
+
*/
|
|
1039
|
+
async function defaultFetchExternalMarketplaceIndex(repo, manifestPath, ref, { githubHost = 'github.com' } = {}) {
|
|
1040
|
+
try {
|
|
1041
|
+
const { stdout } = await execFile(
|
|
1042
|
+
'gh',
|
|
1043
|
+
['api', `repos/${repo}/contents/${manifestPath}?ref=${ref}`, '--jq', '.content'],
|
|
1044
|
+
{
|
|
1045
|
+
shell: false,
|
|
1046
|
+
encoding: 'utf8',
|
|
1047
|
+
timeout: 30000,
|
|
1048
|
+
env: { ...process.env, GH_HOST: githubHost },
|
|
1049
|
+
},
|
|
1050
|
+
);
|
|
1051
|
+
const index = decodeExternalMarketplaceIndex(stdout);
|
|
1052
|
+
if (!index) {
|
|
1053
|
+
return { status: 'unknown', error: 'could not decode external marketplace index content' };
|
|
1054
|
+
}
|
|
1055
|
+
return { status: 'fetched', index };
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
return { status: 'unknown', error: error.message };
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Freeze the external marketplace HEAD for every claude/codex distribution
|
|
1063
|
+
* that declares `marketplaceRepo` (production + online only). For each such
|
|
1064
|
+
* distribution: resolve the external repository's HEAD commit sha + default
|
|
1065
|
+
* branch name, validate the marketplace index entry at that sha (name match,
|
|
1066
|
+
* exactly one plugin entry, claude-form entry version equals the target
|
|
1067
|
+
* version), then record the add-ref (codex=sha, claude=default branch name)
|
|
1068
|
+
* and the frozen marketplaceCommitSha. Any failure fails closed. The remote is
|
|
1069
|
+
* only ever read (git ls-remote / gh api), never written.
|
|
1070
|
+
*
|
|
1071
|
+
* @returns {Promise<Map<string, {repo:string, ref:string, marketplaceCommitSha:string, marketplace:string}>>}
|
|
1072
|
+
* keyed by `${unitId} ${distributionType}`.
|
|
1073
|
+
*/
|
|
1074
|
+
export async function resolveExternalMarketplaceFreezes({
|
|
1075
|
+
unitResults,
|
|
1076
|
+
resolvedVersions,
|
|
1077
|
+
offline,
|
|
1078
|
+
evidence,
|
|
1079
|
+
observeHeadFn,
|
|
1080
|
+
fetchIndexFn,
|
|
1081
|
+
}) {
|
|
1082
|
+
const freezes = new Map();
|
|
1083
|
+
for (let index = 0; index < unitResults.length; index += 1) {
|
|
1084
|
+
const { unit } = unitResults[index];
|
|
1085
|
+
const version = resolvedVersions[index];
|
|
1086
|
+
const githubHost = unit.production?.githubHost ?? 'github.com';
|
|
1087
|
+
for (const dist of unit.distributions ?? []) {
|
|
1088
|
+
if (dist.marketplaceRepo === undefined || dist.marketplaceRepo === null) continue;
|
|
1089
|
+
const platform = PLATFORMS.find((p) => p.distributionType === dist.type);
|
|
1090
|
+
if (!platform || platform.marketplaceRefForm === null) {
|
|
1091
|
+
throw new ReleaseError(
|
|
1092
|
+
GATE_FAILED,
|
|
1093
|
+
`unit "${unit.id}" ${dist.type} distribution declares marketplaceRepo but the platform has no marketplace add capability`,
|
|
1094
|
+
{ unitId: unit.id, distributionType: dist.type },
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
if (offline) {
|
|
1098
|
+
throw new ReleaseError(
|
|
1099
|
+
GATE_FAILED,
|
|
1100
|
+
`unit "${unit.id}" ${dist.type} external marketplace form requires online production prepare to freeze the marketplace commit sha`,
|
|
1101
|
+
{ unitId: unit.id, marketplaceRepo: dist.marketplaceRepo },
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
const observed = await observeHeadFn(dist.marketplaceRepo, { githubHost });
|
|
1105
|
+
if (observed.status !== 'observed' || !EXTERNAL_MARKETPLACE_SHA_RE.test(observed.sha ?? '') || !observed.defaultBranch) {
|
|
1106
|
+
throw new ReleaseError(
|
|
1107
|
+
GATE_FAILED,
|
|
1108
|
+
`unit "${unit.id}" could not freeze external marketplace "${dist.marketplaceRepo}" HEAD: ${observed.error ?? 'unknown'}`,
|
|
1109
|
+
{ unitId: unit.id, marketplaceRepo: dist.marketplaceRepo },
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
const sha = observed.sha;
|
|
1113
|
+
const manifestPath = platform.manifestPaths.marketplace;
|
|
1114
|
+
const fetched = await fetchIndexFn(dist.marketplaceRepo, manifestPath, sha, { githubHost });
|
|
1115
|
+
if (fetched.status !== 'fetched' || !fetched.index || typeof fetched.index !== 'object') {
|
|
1116
|
+
throw new ReleaseError(
|
|
1117
|
+
GATE_FAILED,
|
|
1118
|
+
`unit "${unit.id}" could not read external marketplace index for "${dist.marketplaceRepo}" at ${sha}: ${fetched.error ?? 'unknown'}`,
|
|
1119
|
+
{ unitId: unit.id, marketplaceRepo: dist.marketplaceRepo },
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
const marketplaceIndex = fetched.index;
|
|
1123
|
+
if (marketplaceIndex.name !== dist.marketplace) {
|
|
1124
|
+
throw new ReleaseError(
|
|
1125
|
+
GATE_FAILED,
|
|
1126
|
+
`unit "${unit.id}" external marketplace index name "${marketplaceIndex.name}" does not match distribution marketplace "${dist.marketplace}"`,
|
|
1127
|
+
{ unitId: unit.id, marketplaceRepo: dist.marketplaceRepo },
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
const pluginEntries = Array.isArray(marketplaceIndex.plugins)
|
|
1131
|
+
? marketplaceIndex.plugins.filter((entry) => entry && entry.name === dist.plugin)
|
|
1132
|
+
: [];
|
|
1133
|
+
if (pluginEntries.length !== 1) {
|
|
1134
|
+
throw new ReleaseError(
|
|
1135
|
+
GATE_FAILED,
|
|
1136
|
+
`unit "${unit.id}" external marketplace index must contain exactly one plugin entry named "${dist.plugin}", found ${pluginEntries.length}`,
|
|
1137
|
+
{ unitId: unit.id, marketplaceRepo: dist.marketplaceRepo },
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
if (platform.marketplaceEntryCarriesVersion && pluginEntries[0].version !== version) {
|
|
1141
|
+
throw new ReleaseError(
|
|
1142
|
+
GATE_FAILED,
|
|
1143
|
+
`unit "${unit.id}" external marketplace index entry version "${pluginEntries[0].version}" does not match target version "${version}"`,
|
|
1144
|
+
{ unitId: unit.id, marketplaceRepo: dist.marketplaceRepo },
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
const ref = platform.marketplaceRefForm === 'sha' ? sha : observed.defaultBranch;
|
|
1148
|
+
freezes.set(`${unit.id} ${dist.type}`, {
|
|
1149
|
+
repo: dist.marketplaceRepo,
|
|
1150
|
+
ref,
|
|
1151
|
+
marketplaceCommitSha: sha,
|
|
1152
|
+
marketplace: dist.marketplace,
|
|
1153
|
+
});
|
|
1154
|
+
await evidence.append({
|
|
1155
|
+
phase: 'external-marketplace-freeze',
|
|
1156
|
+
unitId: unit.id,
|
|
1157
|
+
distributionType: dist.type,
|
|
1158
|
+
status: 'completed',
|
|
1159
|
+
marketplaceRepo: dist.marketplaceRepo,
|
|
1160
|
+
marketplaceCommitSha: sha,
|
|
1161
|
+
defaultBranch: observed.defaultBranch,
|
|
1162
|
+
addRef: ref,
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
return freezes;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
944
1169
|
// ---------------------------------------------------------------------------
|
|
945
1170
|
// External actions generation
|
|
946
1171
|
// ---------------------------------------------------------------------------
|
|
@@ -956,7 +1181,7 @@ async function buildProductionAssets(
|
|
|
956
1181
|
* @param {string} realRoot - The project root for relative path calculation.
|
|
957
1182
|
* @returns {object[]} Array of external action descriptors.
|
|
958
1183
|
*/
|
|
959
|
-
function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
|
|
1184
|
+
export function buildExternalActions(unitResults, resolvedVersions, productionAssets, externalFreezes = new Map()) {
|
|
960
1185
|
const actions = [];
|
|
961
1186
|
|
|
962
1187
|
if (!productionAssets) {
|
|
@@ -1026,6 +1251,13 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
|
|
|
1026
1251
|
if (!dist) continue;
|
|
1027
1252
|
const requiresMarketplace = platform.schemaRequiredFields.includes('marketplace');
|
|
1028
1253
|
const timeoutMs = Number.isInteger(dist.timeoutMs) ? dist.timeoutMs : 300000;
|
|
1254
|
+
// External independent marketplace form: the distribution declares
|
|
1255
|
+
// marketplaceRepo, so the install targets the external marketplace repo
|
|
1256
|
+
// and carries the external payload contract. Non-production prepare does
|
|
1257
|
+
// no online resolution, so it carries the external marker + repo shape
|
|
1258
|
+
// but no frozen ref/marketplaceCommitSha (production-only bindings),
|
|
1259
|
+
// keeping the two loops' shapes aligned for plan completeness.
|
|
1260
|
+
const externalMarketplace = dist.marketplaceRepo !== undefined && dist.marketplaceRepo !== null;
|
|
1029
1261
|
actions.push({
|
|
1030
1262
|
id: `${platform.actionType}-${unit.id}`,
|
|
1031
1263
|
type: platform.actionType,
|
|
@@ -1035,7 +1267,7 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
|
|
|
1035
1267
|
consumer: platform.id,
|
|
1036
1268
|
plugin: dist.plugin,
|
|
1037
1269
|
...(requiresMarketplace ? { marketplace: dist.marketplace } : {}),
|
|
1038
|
-
repo: unit.publicRepo,
|
|
1270
|
+
repo: externalMarketplace ? dist.marketplaceRepo : unit.publicRepo,
|
|
1039
1271
|
version,
|
|
1040
1272
|
entrySkill: dist.entrySkill,
|
|
1041
1273
|
timeoutMs,
|
|
@@ -1043,7 +1275,10 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
|
|
|
1043
1275
|
// payload is verified by declared-manifest containment; host-added
|
|
1044
1276
|
// files are recorded, not failed. Frozen plans without this marker
|
|
1045
1277
|
// keep the legacy full-tree equality semantics byte-for-byte.
|
|
1046
|
-
|
|
1278
|
+
// External marketplace form uses the external-marketplace-v1
|
|
1279
|
+
// contract (whole-tree '.' containment; see plugin-marketplace).
|
|
1280
|
+
payloadContract: externalMarketplace ? 'external-marketplace-v1' : 'declared-manifest-v1',
|
|
1281
|
+
...(externalMarketplace ? { marketplaceLocation: 'external' } : {}),
|
|
1047
1282
|
},
|
|
1048
1283
|
expected: {
|
|
1049
1284
|
installed: true,
|
|
@@ -1051,6 +1286,7 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
|
|
|
1051
1286
|
...(requiresMarketplace ? { marketplace: dist.marketplace } : {}),
|
|
1052
1287
|
version,
|
|
1053
1288
|
entrySkill: dist.entrySkill,
|
|
1289
|
+
...(externalMarketplace ? { marketplaceLocation: 'external', repo: dist.marketplaceRepo } : {}),
|
|
1054
1290
|
},
|
|
1055
1291
|
status: 'PENDING',
|
|
1056
1292
|
});
|
|
@@ -1207,6 +1443,13 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
|
|
|
1207
1443
|
if (!dist) continue;
|
|
1208
1444
|
const requiresMarketplace = platform.schemaRequiredFields.includes('marketplace');
|
|
1209
1445
|
const timeoutMs = Number.isInteger(dist.timeoutMs) ? dist.timeoutMs : 300000;
|
|
1446
|
+
// External independent marketplace form: the install targets the external
|
|
1447
|
+
// marketplace repo with the add-ref + marketplaceCommitSha frozen online
|
|
1448
|
+
// by resolveExternalMarketplaceFreezes. snapshotPath/manifestDigest still
|
|
1449
|
+
// bind this unit's own frozen snapshot — the payload authority is the unit
|
|
1450
|
+
// snapshot, unchanged. Inline form (no marketplaceRepo) is byte-identical.
|
|
1451
|
+
const externalMarketplace = dist.marketplaceRepo !== undefined && dist.marketplaceRepo !== null;
|
|
1452
|
+
const freeze = externalMarketplace ? externalFreezes.get(`${unit.id} ${dist.type}`) : null;
|
|
1210
1453
|
actions.push({
|
|
1211
1454
|
id: `${platform.actionType}-${unit.id}`,
|
|
1212
1455
|
type: platform.actionType,
|
|
@@ -1216,8 +1459,8 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
|
|
|
1216
1459
|
consumer: platform.id,
|
|
1217
1460
|
plugin: dist.plugin,
|
|
1218
1461
|
...(requiresMarketplace ? { marketplace: dist.marketplace } : {}),
|
|
1219
|
-
repo: unit.publicRepo,
|
|
1220
|
-
ref: resolvedTag,
|
|
1462
|
+
repo: externalMarketplace ? dist.marketplaceRepo : unit.publicRepo,
|
|
1463
|
+
ref: externalMarketplace ? freeze.ref : resolvedTag,
|
|
1221
1464
|
version: unitVersion,
|
|
1222
1465
|
entrySkill: dist.entrySkill,
|
|
1223
1466
|
snapshotPath: asset.snapshotPath,
|
|
@@ -1227,19 +1470,23 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
|
|
|
1227
1470
|
// payload is verified by declared-manifest containment; host-added
|
|
1228
1471
|
// files are recorded, not failed. Frozen plans without this marker
|
|
1229
1472
|
// keep the legacy full-tree equality semantics byte-for-byte.
|
|
1230
|
-
|
|
1473
|
+
// External marketplace form uses the external-marketplace-v1
|
|
1474
|
+
// contract (whole-tree '.' containment; see plugin-marketplace).
|
|
1475
|
+
payloadContract: externalMarketplace ? 'external-marketplace-v1' : 'declared-manifest-v1',
|
|
1476
|
+
...(externalMarketplace ? { marketplaceLocation: 'external', marketplaceCommitSha: freeze.marketplaceCommitSha } : {}),
|
|
1231
1477
|
},
|
|
1232
1478
|
expected: {
|
|
1233
1479
|
installed: true,
|
|
1234
1480
|
consumer: platform.id,
|
|
1235
1481
|
plugin: dist.plugin,
|
|
1236
1482
|
...(requiresMarketplace ? { marketplace: dist.marketplace } : {}),
|
|
1237
|
-
repo: unit.publicRepo,
|
|
1483
|
+
repo: externalMarketplace ? dist.marketplaceRepo : unit.publicRepo,
|
|
1238
1484
|
version: unitVersion,
|
|
1239
|
-
ref: resolvedTag,
|
|
1485
|
+
ref: externalMarketplace ? freeze.ref : resolvedTag,
|
|
1240
1486
|
entrySkill: dist.entrySkill,
|
|
1241
1487
|
entrySkillFound: true,
|
|
1242
1488
|
manifestDigest: asset.manifestDigest,
|
|
1489
|
+
...(externalMarketplace ? { marketplaceLocation: 'external', marketplaceCommitSha: freeze.marketplaceCommitSha } : {}),
|
|
1243
1490
|
},
|
|
1244
1491
|
status: 'PENDING',
|
|
1245
1492
|
});
|
|
@@ -1891,7 +2138,24 @@ export async function prepareRelease(options) {
|
|
|
1891
2138
|
};
|
|
1892
2139
|
});
|
|
1893
2140
|
|
|
1894
|
-
|
|
2141
|
+
// Freeze external independent marketplace HEADs (production + online only):
|
|
2142
|
+
// for each claude/codex distribution declaring marketplaceRepo, resolve the
|
|
2143
|
+
// external repo's HEAD sha + default branch and validate the marketplace
|
|
2144
|
+
// index entry at that sha before freezing the add-ref. Offline production
|
|
2145
|
+
// with a declared marketplaceRepo fails closed inside the resolver. The
|
|
2146
|
+
// remote is only ever read (git ls-remote / gh api), never written.
|
|
2147
|
+
const externalMarketplaceFreezes = production
|
|
2148
|
+
? await resolveExternalMarketplaceFreezes({
|
|
2149
|
+
unitResults,
|
|
2150
|
+
resolvedVersions,
|
|
2151
|
+
offline,
|
|
2152
|
+
evidence,
|
|
2153
|
+
observeHeadFn: options.observeExternalMarketplaceHeadFn ?? defaultObserveExternalMarketplaceHead,
|
|
2154
|
+
fetchIndexFn: options.fetchExternalMarketplaceIndexFn ?? defaultFetchExternalMarketplaceIndex,
|
|
2155
|
+
})
|
|
2156
|
+
: new Map();
|
|
2157
|
+
|
|
2158
|
+
const externalActions = buildExternalActions(unitResults, resolvedVersions, productionAssets, externalMarketplaceFreezes);
|
|
1895
2159
|
|
|
1896
2160
|
// Compute overall snapshot digest
|
|
1897
2161
|
const overallSnapshotDigest = sha256Hex(snapshotDigests.join(':'));
|
package/src/commands/publish.mjs
CHANGED
package/src/commands/setup.mjs
CHANGED
|
@@ -89,6 +89,7 @@ async function walkDiscoveryFiles(root, maxDepth = 8) {
|
|
|
89
89
|
absolute.endsWith('/.claude-plugin/plugin.json') ||
|
|
90
90
|
absolute.endsWith('/.codex-plugin/plugin.json') ||
|
|
91
91
|
absolute.endsWith('/.kimi-plugin/plugin.json') ||
|
|
92
|
+
absolute.endsWith('/.codebuddy-plugin/plugin.json') ||
|
|
92
93
|
absolute.endsWith('/.claude-plugin/marketplace.json') ||
|
|
93
94
|
absolute.endsWith('/.codex-plugin/marketplace.json'))
|
|
94
95
|
) {
|
|
@@ -461,7 +462,8 @@ async function discoverFacts(root) {
|
|
|
461
462
|
path: safeRelative(root, path),
|
|
462
463
|
host: path.includes('/.claude-plugin/') ? 'claude'
|
|
463
464
|
: path.includes('/.kimi-plugin/') ? 'kimi'
|
|
464
|
-
: '
|
|
465
|
+
: path.includes('/.codebuddy-plugin/') ? 'codebuddy'
|
|
466
|
+
: 'codex',
|
|
465
467
|
kind: path.endsWith('/marketplace.json') ? 'marketplace' : 'plugin',
|
|
466
468
|
name: typeof value.name === 'string' ? value.name : null,
|
|
467
469
|
version: typeof value.version === 'string' ? value.version : null,
|
|
@@ -487,7 +489,8 @@ async function discoverFacts(root) {
|
|
|
487
489
|
host: relPath.includes('/adapters/claude/') ? 'claude'
|
|
488
490
|
: relPath.includes('/adapters/codex/') ? 'codex'
|
|
489
491
|
: relPath.includes('/adapters/kimi/') ? 'kimi'
|
|
490
|
-
: '
|
|
492
|
+
: relPath.includes('/adapters/workbuddy/') ? 'codebuddy'
|
|
493
|
+
: 'shared',
|
|
491
494
|
};
|
|
492
495
|
})
|
|
493
496
|
.filter((item) => item.name)
|
|
@@ -655,7 +658,7 @@ function buildCandidates(facts) {
|
|
|
655
658
|
const ids = new Set();
|
|
656
659
|
const knownFiles = new Set(facts.fileDigests.map((file) => file.path));
|
|
657
660
|
const manifestRoots = facts.manifests.map((manifest) => {
|
|
658
|
-
const match = manifest.path.match(/^(.*?)(?:\/)?(?:\.claude-plugin|\.codex-plugin|\.kimi-plugin)\/(?:plugin|marketplace)\.json$/);
|
|
661
|
+
const match = manifest.path.match(/^(.*?)(?:\/)?(?:\.claude-plugin|\.codex-plugin|\.kimi-plugin|\.codebuddy-plugin)\/(?:plugin|marketplace)\.json$/);
|
|
659
662
|
return { ...manifest, root: match?.[1] || '.' };
|
|
660
663
|
});
|
|
661
664
|
const manifestOwners = new Map();
|
|
@@ -703,6 +706,7 @@ function buildCandidates(facts) {
|
|
|
703
706
|
if (pluginHosts.includes('claude')) distributions.push('claude-plugin');
|
|
704
707
|
if (pluginHosts.includes('codex')) distributions.push('codex-plugin');
|
|
705
708
|
if (pluginHosts.includes('kimi')) distributions.push('kimi-plugin');
|
|
709
|
+
if (pluginHosts.includes('codebuddy')) distributions.push('codebuddy-plugin');
|
|
706
710
|
if (pkg.private && matchingLegacyUnits.length === 0 && facts.legacyReleaseConfigs.length > 0) continue;
|
|
707
711
|
if (pkg.private && distributions.length === 0) continue;
|
|
708
712
|
|
package/src/commands/verify.mjs
CHANGED
|
@@ -69,6 +69,7 @@ const ADAPTER_ACTION_TYPE_MAP = {
|
|
|
69
69
|
'claude-marketplace-install': 'claude-marketplace-install',
|
|
70
70
|
'codex-marketplace-install': 'codex-marketplace-install',
|
|
71
71
|
'kimi-marketplace-install': 'kimi-marketplace-install',
|
|
72
|
+
'codebuddy-marketplace-install': 'codebuddy-marketplace-install',
|
|
72
73
|
};
|
|
73
74
|
|
|
74
75
|
// ---------------------------------------------------------------------------
|
|
@@ -735,6 +736,7 @@ export async function verifyRelease(options) {
|
|
|
735
736
|
'claude-marketplace-install',
|
|
736
737
|
'codex-marketplace-install',
|
|
737
738
|
'kimi-marketplace-install',
|
|
739
|
+
'codebuddy-marketplace-install',
|
|
738
740
|
]);
|
|
739
741
|
|
|
740
742
|
for (const action of actions) {
|
package/src/core/checkpoints.mjs
CHANGED
|
@@ -33,6 +33,7 @@ export const CHECKPOINT_ORDER = [
|
|
|
33
33
|
'claude-marketplace-install',
|
|
34
34
|
'codex-marketplace-install',
|
|
35
35
|
'kimi-marketplace-install',
|
|
36
|
+
'codebuddy-marketplace-install',
|
|
36
37
|
];
|
|
37
38
|
|
|
38
39
|
/**
|
|
@@ -52,6 +53,7 @@ export const ADAPTER_ACTION_TYPE_MAP = {
|
|
|
52
53
|
'claude-marketplace-install': 'claude-marketplace-install',
|
|
53
54
|
'codex-marketplace-install': 'codex-marketplace-install',
|
|
54
55
|
'kimi-marketplace-install': 'kimi-marketplace-install',
|
|
56
|
+
'codebuddy-marketplace-install': 'codebuddy-marketplace-install',
|
|
55
57
|
};
|
|
56
58
|
|
|
57
59
|
/**
|
|
@@ -67,7 +69,10 @@ export const ADAPTER_ACTION_TYPE_MAP = {
|
|
|
67
69
|
* - Tier 2 `github-release` and the claude/codex marketplace installs depend
|
|
68
70
|
* on Tier 1 `create-tag` (release `--verify-tag`; install ref is the tag).
|
|
69
71
|
* - Tier 3 `kimi-marketplace-install` depends on Tier 2 `github-release`
|
|
70
|
-
* (its install URL points at the Release page).
|
|
72
|
+
* (its install URL points at the Release page). `codebuddy-marketplace-install`
|
|
73
|
+
* is also a non-automatable human-attestation closure and runs in Tier 3 after
|
|
74
|
+
* the automated writes (its install is from a unified marketplace, proven by a
|
|
75
|
+
* human attestation rather than an automated install checkpoint).
|
|
71
76
|
*
|
|
72
77
|
* Action types not listed in any tier are unknown to the scheduler and fail
|
|
73
78
|
* closed (see groupActionsByTier); they are never silently scheduled.
|
|
@@ -76,7 +81,7 @@ export const TIER_TABLE = [
|
|
|
76
81
|
['push-commit', 'push-snapshot'], // Tier 0
|
|
77
82
|
['set-default-branch', 'create-tag', 'npm-publish'], // Tier 1
|
|
78
83
|
['github-release', 'claude-marketplace-install', 'codex-marketplace-install'], // Tier 2
|
|
79
|
-
['kimi-marketplace-install'],
|
|
84
|
+
['kimi-marketplace-install', 'codebuddy-marketplace-install'], // Tier 3
|
|
80
85
|
];
|
|
81
86
|
|
|
82
87
|
/** Fast reverse lookup: action type -> tier index (-1 when unknown). */
|