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
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,46 @@ 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;
|
|
1261
|
+
// Normalized marketplace form: explicit mutually exclusive declaration.
|
|
1262
|
+
// bundled-family: marketplace and plugin live in the same repo.
|
|
1263
|
+
// standalone-index: external marketplace repo indexes a separate plugin repo.
|
|
1264
|
+
// Non-automatable platforms (kimi, codebuddy) use human-attestation and
|
|
1265
|
+
// carry no marketplace form.
|
|
1266
|
+
const marketplaceForm = requiresMarketplace
|
|
1267
|
+
? (externalMarketplace ? 'standalone-index' : 'bundled-family')
|
|
1268
|
+
: null;
|
|
1269
|
+
const sourceDescriptor = marketplaceForm === 'standalone-index'
|
|
1270
|
+
? Object.freeze({
|
|
1271
|
+
form: 'standalone-index',
|
|
1272
|
+
marketplaceRepo: dist.marketplaceRepo,
|
|
1273
|
+
marketplaceEntry: dist.plugin,
|
|
1274
|
+
// pluginRepo is the plugin's own public repo, NOT the external
|
|
1275
|
+
// marketplace repo. The marketplace repo contains the index; the
|
|
1276
|
+
// plugin repo contains the actual plugin code.
|
|
1277
|
+
pluginRepo: unit.publicRepo,
|
|
1278
|
+
sourceType: 'marketplace-entry',
|
|
1279
|
+
// Production-only fields: marketplaceCommitSha and ref are frozen
|
|
1280
|
+
// by resolveExternalMarketplaceFreezes in the production path.
|
|
1281
|
+
marketplaceCommitSha: null,
|
|
1282
|
+
ref: null,
|
|
1283
|
+
payloadDigest: null,
|
|
1284
|
+
})
|
|
1285
|
+
: marketplaceForm === 'bundled-family'
|
|
1286
|
+
? Object.freeze({
|
|
1287
|
+
form: 'bundled-family',
|
|
1288
|
+
repo: unit.publicRepo,
|
|
1289
|
+
marketplaceEntry: dist.plugin,
|
|
1290
|
+
pluginSubpath: '.',
|
|
1291
|
+
payloadDigest: null,
|
|
1292
|
+
})
|
|
1293
|
+
: null;
|
|
1029
1294
|
actions.push({
|
|
1030
1295
|
id: `${platform.actionType}-${unit.id}`,
|
|
1031
1296
|
type: platform.actionType,
|
|
@@ -1035,7 +1300,7 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
|
|
|
1035
1300
|
consumer: platform.id,
|
|
1036
1301
|
plugin: dist.plugin,
|
|
1037
1302
|
...(requiresMarketplace ? { marketplace: dist.marketplace } : {}),
|
|
1038
|
-
repo: unit.publicRepo,
|
|
1303
|
+
repo: externalMarketplace ? dist.marketplaceRepo : unit.publicRepo,
|
|
1039
1304
|
version,
|
|
1040
1305
|
entrySkill: dist.entrySkill,
|
|
1041
1306
|
timeoutMs,
|
|
@@ -1043,7 +1308,12 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
|
|
|
1043
1308
|
// payload is verified by declared-manifest containment; host-added
|
|
1044
1309
|
// files are recorded, not failed. Frozen plans without this marker
|
|
1045
1310
|
// keep the legacy full-tree equality semantics byte-for-byte.
|
|
1046
|
-
|
|
1311
|
+
// External marketplace form uses the external-marketplace-v1
|
|
1312
|
+
// contract (whole-tree '.' containment; see plugin-marketplace).
|
|
1313
|
+
payloadContract: externalMarketplace ? 'external-marketplace-v1' : 'declared-manifest-v1',
|
|
1314
|
+
...(externalMarketplace ? { marketplaceLocation: 'external' } : {}),
|
|
1315
|
+
...(marketplaceForm ? { marketplaceForm } : {}),
|
|
1316
|
+
...(sourceDescriptor ? { sourceDescriptor } : {}),
|
|
1047
1317
|
},
|
|
1048
1318
|
expected: {
|
|
1049
1319
|
installed: true,
|
|
@@ -1051,6 +1321,7 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
|
|
|
1051
1321
|
...(requiresMarketplace ? { marketplace: dist.marketplace } : {}),
|
|
1052
1322
|
version,
|
|
1053
1323
|
entrySkill: dist.entrySkill,
|
|
1324
|
+
...(externalMarketplace ? { marketplaceLocation: 'external', repo: dist.marketplaceRepo } : {}),
|
|
1054
1325
|
},
|
|
1055
1326
|
status: 'PENDING',
|
|
1056
1327
|
});
|
|
@@ -1207,6 +1478,41 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
|
|
|
1207
1478
|
if (!dist) continue;
|
|
1208
1479
|
const requiresMarketplace = platform.schemaRequiredFields.includes('marketplace');
|
|
1209
1480
|
const timeoutMs = Number.isInteger(dist.timeoutMs) ? dist.timeoutMs : 300000;
|
|
1481
|
+
// External independent marketplace form: the install targets the external
|
|
1482
|
+
// marketplace repo with the add-ref + marketplaceCommitSha frozen online
|
|
1483
|
+
// by resolveExternalMarketplaceFreezes. snapshotPath/manifestDigest still
|
|
1484
|
+
// bind this unit's own frozen snapshot — the payload authority is the unit
|
|
1485
|
+
// snapshot, unchanged. Inline form (no marketplaceRepo) is byte-identical.
|
|
1486
|
+
const externalMarketplace = dist.marketplaceRepo !== undefined && dist.marketplaceRepo !== null;
|
|
1487
|
+
const freeze = externalMarketplace ? externalFreezes.get(`${unit.id} ${dist.type}`) : null;
|
|
1488
|
+
// Normalized marketplace form: explicit mutually exclusive declaration.
|
|
1489
|
+
const marketplaceForm = requiresMarketplace
|
|
1490
|
+
? (externalMarketplace ? 'standalone-index' : 'bundled-family')
|
|
1491
|
+
: null;
|
|
1492
|
+
const sourceDescriptor = marketplaceForm === 'standalone-index'
|
|
1493
|
+
? Object.freeze({
|
|
1494
|
+
form: 'standalone-index',
|
|
1495
|
+
marketplaceRepo: dist.marketplaceRepo,
|
|
1496
|
+
marketplaceCommitSha: freeze?.marketplaceCommitSha ?? null,
|
|
1497
|
+
marketplaceEntry: dist.plugin,
|
|
1498
|
+
// pluginRepo is the plugin's own public repo, NOT the external
|
|
1499
|
+
// marketplace repo. The marketplace repo contains the index; the
|
|
1500
|
+
// plugin repo contains the actual plugin code.
|
|
1501
|
+
pluginRepo: unit.publicRepo,
|
|
1502
|
+
sourceType: 'marketplace-entry',
|
|
1503
|
+
ref: freeze?.ref ?? null,
|
|
1504
|
+
payloadDigest: asset.manifestDigest,
|
|
1505
|
+
})
|
|
1506
|
+
: marketplaceForm === 'bundled-family'
|
|
1507
|
+
? Object.freeze({
|
|
1508
|
+
form: 'bundled-family',
|
|
1509
|
+
repo: unit.publicRepo,
|
|
1510
|
+
commit: asset.commit,
|
|
1511
|
+
marketplaceEntry: dist.plugin,
|
|
1512
|
+
pluginSubpath: '.',
|
|
1513
|
+
payloadDigest: asset.manifestDigest,
|
|
1514
|
+
})
|
|
1515
|
+
: null;
|
|
1210
1516
|
actions.push({
|
|
1211
1517
|
id: `${platform.actionType}-${unit.id}`,
|
|
1212
1518
|
type: platform.actionType,
|
|
@@ -1216,8 +1522,8 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
|
|
|
1216
1522
|
consumer: platform.id,
|
|
1217
1523
|
plugin: dist.plugin,
|
|
1218
1524
|
...(requiresMarketplace ? { marketplace: dist.marketplace } : {}),
|
|
1219
|
-
repo: unit.publicRepo,
|
|
1220
|
-
ref: resolvedTag,
|
|
1525
|
+
repo: externalMarketplace ? dist.marketplaceRepo : unit.publicRepo,
|
|
1526
|
+
ref: externalMarketplace ? freeze.ref : resolvedTag,
|
|
1221
1527
|
version: unitVersion,
|
|
1222
1528
|
entrySkill: dist.entrySkill,
|
|
1223
1529
|
snapshotPath: asset.snapshotPath,
|
|
@@ -1227,19 +1533,29 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
|
|
|
1227
1533
|
// payload is verified by declared-manifest containment; host-added
|
|
1228
1534
|
// files are recorded, not failed. Frozen plans without this marker
|
|
1229
1535
|
// keep the legacy full-tree equality semantics byte-for-byte.
|
|
1230
|
-
|
|
1536
|
+
// External marketplace form uses the external-marketplace-v1
|
|
1537
|
+
// contract (whole-tree '.' containment; see plugin-marketplace).
|
|
1538
|
+
payloadContract: externalMarketplace ? 'external-marketplace-v1' : 'declared-manifest-v1',
|
|
1539
|
+
...(externalMarketplace ? { marketplaceLocation: 'external', marketplaceCommitSha: freeze.marketplaceCommitSha } : {}),
|
|
1540
|
+
...(marketplaceForm ? { marketplaceForm } : {}),
|
|
1541
|
+
...(sourceDescriptor ? { sourceDescriptor } : {}),
|
|
1542
|
+
// 冻结的插件来源提交,用于 sourceDescriptor.commit 交叉校验。
|
|
1543
|
+
// bundled-family: sourceDescriptor.commit 绑定到此值。
|
|
1544
|
+
// standalone-index: 通过此值绑定插件载荷来源。
|
|
1545
|
+
sourceCommit: asset.commit,
|
|
1231
1546
|
},
|
|
1232
1547
|
expected: {
|
|
1233
1548
|
installed: true,
|
|
1234
1549
|
consumer: platform.id,
|
|
1235
1550
|
plugin: dist.plugin,
|
|
1236
1551
|
...(requiresMarketplace ? { marketplace: dist.marketplace } : {}),
|
|
1237
|
-
repo: unit.publicRepo,
|
|
1552
|
+
repo: externalMarketplace ? dist.marketplaceRepo : unit.publicRepo,
|
|
1238
1553
|
version: unitVersion,
|
|
1239
|
-
ref: resolvedTag,
|
|
1554
|
+
ref: externalMarketplace ? freeze.ref : resolvedTag,
|
|
1240
1555
|
entrySkill: dist.entrySkill,
|
|
1241
1556
|
entrySkillFound: true,
|
|
1242
1557
|
manifestDigest: asset.manifestDigest,
|
|
1558
|
+
...(externalMarketplace ? { marketplaceLocation: 'external', marketplaceCommitSha: freeze.marketplaceCommitSha } : {}),
|
|
1243
1559
|
},
|
|
1244
1560
|
status: 'PENDING',
|
|
1245
1561
|
});
|
|
@@ -1891,7 +2207,24 @@ export async function prepareRelease(options) {
|
|
|
1891
2207
|
};
|
|
1892
2208
|
});
|
|
1893
2209
|
|
|
1894
|
-
|
|
2210
|
+
// Freeze external independent marketplace HEADs (production + online only):
|
|
2211
|
+
// for each claude/codex distribution declaring marketplaceRepo, resolve the
|
|
2212
|
+
// external repo's HEAD sha + default branch and validate the marketplace
|
|
2213
|
+
// index entry at that sha before freezing the add-ref. Offline production
|
|
2214
|
+
// with a declared marketplaceRepo fails closed inside the resolver. The
|
|
2215
|
+
// remote is only ever read (git ls-remote / gh api), never written.
|
|
2216
|
+
const externalMarketplaceFreezes = production
|
|
2217
|
+
? await resolveExternalMarketplaceFreezes({
|
|
2218
|
+
unitResults,
|
|
2219
|
+
resolvedVersions,
|
|
2220
|
+
offline,
|
|
2221
|
+
evidence,
|
|
2222
|
+
observeHeadFn: options.observeExternalMarketplaceHeadFn ?? defaultObserveExternalMarketplaceHead,
|
|
2223
|
+
fetchIndexFn: options.fetchExternalMarketplaceIndexFn ?? defaultFetchExternalMarketplaceIndex,
|
|
2224
|
+
})
|
|
2225
|
+
: new Map();
|
|
2226
|
+
|
|
2227
|
+
const externalActions = buildExternalActions(unitResults, resolvedVersions, productionAssets, externalMarketplaceFreezes);
|
|
1895
2228
|
|
|
1896
2229
|
// Compute overall snapshot digest
|
|
1897
2230
|
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) {
|
|
@@ -847,7 +849,9 @@ export async function verifyRelease(options) {
|
|
|
847
849
|
? 'claude-plugin'
|
|
848
850
|
: action.type === 'codex-marketplace-install'
|
|
849
851
|
? 'codex-plugin'
|
|
850
|
-
: '
|
|
852
|
+
: action.type === 'codebuddy-marketplace-install'
|
|
853
|
+
? 'codebuddy-plugin'
|
|
854
|
+
: 'kimi-plugin';
|
|
851
855
|
const installPath = verifyResult.observation?.installPath;
|
|
852
856
|
consumerGateResults.push(...await runConsumerVerificationGates({
|
|
853
857
|
plan,
|
|
@@ -866,10 +870,14 @@ export async function verifyRelease(options) {
|
|
|
866
870
|
HOME: resolve(runDir, 'consumers', `codex-${action.parameters.plugin}`),
|
|
867
871
|
CODEX_HOME: resolve(runDir, 'consumers', `codex-${action.parameters.plugin}`),
|
|
868
872
|
}
|
|
869
|
-
:
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
+
: action.type === 'codebuddy-marketplace-install'
|
|
874
|
+
? {
|
|
875
|
+
HOME: resolve(runDir, 'consumers', `codebuddy-${action.parameters.plugin}`),
|
|
876
|
+
}
|
|
877
|
+
: {
|
|
878
|
+
HOME: resolve(runDir, 'consumers', `kimi-${action.parameters.plugin}`),
|
|
879
|
+
KIMI_CODE_HOME: resolve(runDir, 'consumers', `kimi-${action.parameters.plugin}`),
|
|
880
|
+
},
|
|
873
881
|
}));
|
|
874
882
|
} else {
|
|
875
883
|
// --- Non-marketplace: read-only adapter.verify() ---
|
package/src/core/baseline.mjs
CHANGED
|
@@ -38,11 +38,24 @@ const CONTROL_PLANE_PREFIXES = [
|
|
|
38
38
|
'.release-skill/runs',
|
|
39
39
|
'.release-skill/transactions',
|
|
40
40
|
'.release-skill/kimi-attestations',
|
|
41
|
+
// codebuddy-attestations: same rationale as kimi-attestations above.
|
|
42
|
+
// Holds CodeBuddy's closure-protocol lifecycle artifacts: manual install
|
|
43
|
+
// requirements and human attestations bound to planDigest, payloadDigest,
|
|
44
|
+
// version, install path, responsible person, and expiry. Neither is
|
|
45
|
+
// publishable source or project configuration — they never enter the frozen
|
|
46
|
+
// snapshot — so excluding them keeps reconcile's own requirement output and
|
|
47
|
+
// the flow-required attestation from invalidating the baseline.
|
|
48
|
+
'.release-skill/codebuddy-attestations',
|
|
41
49
|
// T3.2 incremental hook cache: a pure local optimisation written by prepare.
|
|
42
50
|
// Excluding it keeps cache records from destabilising workspaceDigest on
|
|
43
51
|
// every prepare (and hook-cache.mjs also skips this prefix when fingerprinting
|
|
44
52
|
// inputs, so records never hash themselves).
|
|
45
53
|
'.release-skill/cache',
|
|
54
|
+
// waivers: exception records with reason, responsible person, and expiry.
|
|
55
|
+
// Like attestations, they are runtime closure artifacts — not publishable
|
|
56
|
+
// source or project configuration — so excluding them keeps waiver writes
|
|
57
|
+
// from invalidating the workspace baseline.
|
|
58
|
+
'.release-skill/waivers',
|
|
46
59
|
];
|
|
47
60
|
const RESERVED_CONTROL_PREFIXES = [
|
|
48
61
|
...CONTROL_PLANE_PREFIXES,
|
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). */
|