release-skill 0.2.3 → 0.2.4
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 +22 -0
- package/CONTRIBUTING.md +27 -0
- package/INSTALL.md +95 -139
- package/INSTALL.zh-CN.md +70 -121
- package/README.md +264 -913
- package/README.zh-CN.md +222 -535
- 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 +19054 -17573
- package/adapters/claude/schemas/release-plan.schema.json +137 -0
- package/adapters/claude/schemas/release-project.schema.json +93 -0
- package/adapters/claude/schemas/release-run.schema.json +70 -2
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +19054 -17573
- package/adapters/codex/schemas/release-plan.schema.json +137 -0
- package/adapters/codex/schemas/release-project.schema.json +93 -0
- package/adapters/codex/schemas/release-run.schema.json +70 -2
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +19054 -17573
- package/adapters/kimi/schemas/release-plan.schema.json +137 -0
- package/adapters/kimi/schemas/release-project.schema.json +93 -0
- package/adapters/kimi/schemas/release-run.schema.json +70 -2
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +19054 -17573
- package/adapters/workbuddy/schemas/release-plan.schema.json +137 -0
- package/adapters/workbuddy/schemas/release-project.schema.json +93 -0
- package/adapters/workbuddy/schemas/release-run.schema.json +70 -2
- package/bin/release-skill.bundle.mjs +19054 -17573
- package/package.json +1 -1
- package/schemas/release-plan.schema.json +137 -0
- package/schemas/release-project.schema.json +93 -0
- package/schemas/release-run.schema.json +70 -2
- package/src/adapters/plugin-marketplace.mjs +1190 -435
- package/src/commands/prepare.mjs +380 -40
- package/src/commands/publish.mjs +107 -75
- package/src/commands/reconcile.mjs +92 -327
- package/src/commands/setup.mjs +148 -20
- package/src/commands/verify.mjs +304 -20
- package/src/core/baseline.mjs +8 -1
- package/src/core/checkpoints.mjs +50 -7
- package/src/core/config.mjs +15 -0
- package/src/core/errors.mjs +2 -0
- package/src/core/installation-contract.mjs +341 -0
- package/src/core/plan.mjs +129 -6
- package/src/platforms/codebuddy.mjs +193 -280
- package/src/platforms/codex.mjs +369 -0
- package/src/platforms/kimi.mjs +164 -119
- package/src/platforms/registry.mjs +24 -6
package/src/commands/prepare.mjs
CHANGED
|
@@ -50,6 +50,15 @@ import { assertPreviousPublicBaselineTarget, observePreviousPublicBaseline } fro
|
|
|
50
50
|
import { verifyFrozenNpmTarballIdentity } from '../adapters/npm.mjs';
|
|
51
51
|
import { createProductionPrepareRunDir } from '../core/run.mjs';
|
|
52
52
|
import { PLATFORMS } from '../platforms/registry.mjs';
|
|
53
|
+
import { validateMarketplaceSourceSelection, MARKETPLACE_SOURCE_TYPES, resolvePluginManifestFromMarketplaceEntrySource, resolveMarketplaceRoot } from '../adapters/plugin-marketplace.mjs';
|
|
54
|
+
import { buildInstallationContract, computeInstallationContractDigest, INSTALLATION_CONTRACT_ALGORITHM_VERSION } from '../core/installation-contract.mjs';
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// 安装契约常量
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
/** 消费端安装验证配方版本。算法变更时递增。 */
|
|
61
|
+
const CONSUMER_INSTALL_RECIPE_VERSION = 'consumer-install-v1';
|
|
53
62
|
|
|
54
63
|
// ---------------------------------------------------------------------------
|
|
55
64
|
// Version resolution
|
|
@@ -1085,12 +1094,15 @@ export async function resolveExternalMarketplaceFreezes({
|
|
|
1085
1094
|
const version = resolvedVersions[index];
|
|
1086
1095
|
const githubHost = unit.production?.githubHost ?? 'github.com';
|
|
1087
1096
|
for (const dist of unit.distributions ?? []) {
|
|
1088
|
-
|
|
1097
|
+
// 只处理 standalone-index 来源;bundled-family 不需要外部冻结。
|
|
1098
|
+
if (dist.marketplaceSourceType !== 'standalone-index') continue;
|
|
1099
|
+
// standalone-index 必须有 marketplaceRepo;没有则跳过(兼容旧配置)。
|
|
1100
|
+
if (!dist.marketplaceRepo) continue;
|
|
1089
1101
|
const platform = PLATFORMS.find((p) => p.distributionType === dist.type);
|
|
1090
|
-
if (!platform
|
|
1102
|
+
if (!platform) {
|
|
1091
1103
|
throw new ReleaseError(
|
|
1092
1104
|
GATE_FAILED,
|
|
1093
|
-
`unit "${unit.id}" ${dist.type} distribution declares
|
|
1105
|
+
`unit "${unit.id}" ${dist.type} distribution declares standalone-index but the platform is unknown`,
|
|
1094
1106
|
{ unitId: unit.id, distributionType: dist.type },
|
|
1095
1107
|
);
|
|
1096
1108
|
}
|
|
@@ -1098,7 +1110,7 @@ export async function resolveExternalMarketplaceFreezes({
|
|
|
1098
1110
|
throw new ReleaseError(
|
|
1099
1111
|
GATE_FAILED,
|
|
1100
1112
|
`unit "${unit.id}" ${dist.type} external marketplace form requires online production prepare to freeze the marketplace commit sha`,
|
|
1101
|
-
{ unitId: unit.id,
|
|
1113
|
+
{ unitId: unit.id, marketplaceSourceType: dist.marketplaceSourceType },
|
|
1102
1114
|
);
|
|
1103
1115
|
}
|
|
1104
1116
|
const observed = await observeHeadFn(dist.marketplaceRepo, { githubHost });
|
|
@@ -1110,7 +1122,16 @@ export async function resolveExternalMarketplaceFreezes({
|
|
|
1110
1122
|
);
|
|
1111
1123
|
}
|
|
1112
1124
|
const sha = observed.sha;
|
|
1113
|
-
|
|
1125
|
+
// 索引路径:distribution 显式声明优先,否则使用平台默认路径。
|
|
1126
|
+
// 平台注册表没有默认路径时(kimi、codebuddy),必须显式提供。
|
|
1127
|
+
const manifestPath = dist.marketplaceIndexPath ?? platform.manifestPaths.marketplace;
|
|
1128
|
+
if (!manifestPath) {
|
|
1129
|
+
throw new ReleaseError(
|
|
1130
|
+
GATE_FAILED,
|
|
1131
|
+
`unit "${unit.id}" ${dist.type} cannot determine marketplace index path: neither marketplaceIndexPath nor platform.manifestPaths.marketplace is set`,
|
|
1132
|
+
{ unitId: unit.id, distributionType: dist.type },
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1114
1135
|
const fetched = await fetchIndexFn(dist.marketplaceRepo, manifestPath, sha, { githubHost });
|
|
1115
1136
|
if (fetched.status !== 'fetched' || !fetched.index || typeof fetched.index !== 'object') {
|
|
1116
1137
|
throw new ReleaseError(
|
|
@@ -1120,7 +1141,8 @@ export async function resolveExternalMarketplaceFreezes({
|
|
|
1120
1141
|
);
|
|
1121
1142
|
}
|
|
1122
1143
|
const marketplaceIndex = fetched.index;
|
|
1123
|
-
|
|
1144
|
+
// 校验市场名称(仅当 distribution 显式声明 marketplace 时)
|
|
1145
|
+
if (dist.marketplace && marketplaceIndex.name !== dist.marketplace) {
|
|
1124
1146
|
throw new ReleaseError(
|
|
1125
1147
|
GATE_FAILED,
|
|
1126
1148
|
`unit "${unit.id}" external marketplace index name "${marketplaceIndex.name}" does not match distribution marketplace "${dist.marketplace}"`,
|
|
@@ -1144,12 +1166,21 @@ export async function resolveExternalMarketplaceFreezes({
|
|
|
1144
1166
|
{ unitId: unit.id, marketplaceRepo: dist.marketplaceRepo },
|
|
1145
1167
|
);
|
|
1146
1168
|
}
|
|
1147
|
-
|
|
1169
|
+
// marketplaceRef:Claude 使用默认分支名(name-ref),其余平台使用提交 SHA。
|
|
1170
|
+
// 无 CLI 的平台(kimi、codebuddy)也必须能冻结,ref 用 SHA。
|
|
1171
|
+
const marketplaceRef = platform.marketplaceRefForm === 'name' ? observed.defaultBranch : sha;
|
|
1148
1172
|
freezes.set(`${unit.id} ${dist.type}`, {
|
|
1173
|
+
// 向后兼容字段(buildExternalActions 使用 repo / ref / marketplace)
|
|
1149
1174
|
repo: dist.marketplaceRepo,
|
|
1150
|
-
ref,
|
|
1175
|
+
ref: marketplaceRef,
|
|
1151
1176
|
marketplaceCommitSha: sha,
|
|
1152
1177
|
marketplace: dist.marketplace,
|
|
1178
|
+
// B3B 完整冻结字段
|
|
1179
|
+
marketplaceRepo: dist.marketplaceRepo,
|
|
1180
|
+
marketplaceRef,
|
|
1181
|
+
marketplaceIndexPath: manifestPath,
|
|
1182
|
+
marketplaceName: marketplaceIndex.name,
|
|
1183
|
+
selectedEntry: pluginEntries[0],
|
|
1153
1184
|
});
|
|
1154
1185
|
await evidence.append({
|
|
1155
1186
|
phase: 'external-marketplace-freeze',
|
|
@@ -1158,8 +1189,11 @@ export async function resolveExternalMarketplaceFreezes({
|
|
|
1158
1189
|
status: 'completed',
|
|
1159
1190
|
marketplaceRepo: dist.marketplaceRepo,
|
|
1160
1191
|
marketplaceCommitSha: sha,
|
|
1192
|
+
marketplaceRef,
|
|
1193
|
+
marketplaceIndexPath: manifestPath,
|
|
1194
|
+
marketplaceName: marketplaceIndex.name,
|
|
1195
|
+
selectedEntry: pluginEntries[0],
|
|
1161
1196
|
defaultBranch: observed.defaultBranch,
|
|
1162
|
-
addRef: ref,
|
|
1163
1197
|
});
|
|
1164
1198
|
}
|
|
1165
1199
|
}
|
|
@@ -1181,7 +1215,7 @@ export async function resolveExternalMarketplaceFreezes({
|
|
|
1181
1215
|
* @param {string} realRoot - The project root for relative path calculation.
|
|
1182
1216
|
* @returns {object[]} Array of external action descriptors.
|
|
1183
1217
|
*/
|
|
1184
|
-
export function buildExternalActions(unitResults, resolvedVersions, productionAssets, externalFreezes = new Map()) {
|
|
1218
|
+
export function buildExternalActions(unitResults, resolvedVersions, productionAssets, externalFreezes = new Map(), frozenDistributions = null) {
|
|
1185
1219
|
const actions = [];
|
|
1186
1220
|
|
|
1187
1221
|
if (!productionAssets) {
|
|
@@ -1246,8 +1280,11 @@ export function buildExternalActions(unitResults, resolvedVersions, productionAs
|
|
|
1246
1280
|
// carry: Kimi Code has no non-interactive install/marketplace API, so
|
|
1247
1281
|
// the kimi action carries no marketplace identity (MINOR-1); plugin +
|
|
1248
1282
|
// entrySkill are the meaningful identity fields there).
|
|
1283
|
+
const frozenUnitDists = frozenDistributions?.get(unit.id) ?? null;
|
|
1249
1284
|
for (const platform of PLATFORMS) {
|
|
1250
|
-
const dist =
|
|
1285
|
+
const dist = frozenUnitDists
|
|
1286
|
+
? frozenUnitDists.find((d) => d.type === platform.distributionType)
|
|
1287
|
+
: (unit.distributions ?? []).find((d) => d.type === platform.distributionType);
|
|
1251
1288
|
if (!dist) continue;
|
|
1252
1289
|
const requiresMarketplace = platform.schemaRequiredFields.includes('marketplace');
|
|
1253
1290
|
const timeoutMs = Number.isInteger(dist.timeoutMs) ? dist.timeoutMs : 300000;
|
|
@@ -1261,11 +1298,10 @@ export function buildExternalActions(unitResults, resolvedVersions, productionAs
|
|
|
1261
1298
|
// Normalized marketplace form: explicit mutually exclusive declaration.
|
|
1262
1299
|
// bundled-family: marketplace and plugin live in the same repo.
|
|
1263
1300
|
// standalone-index: external marketplace repo indexes a separate plugin repo.
|
|
1264
|
-
//
|
|
1265
|
-
//
|
|
1266
|
-
const
|
|
1267
|
-
|
|
1268
|
-
: null;
|
|
1301
|
+
// All four platforms carry marketplaceForm and sourceDescriptor when a
|
|
1302
|
+
// marketplace source type is declared (bundled-family or standalone-index).
|
|
1303
|
+
const marketplaceSourceType = dist.marketplaceSourceType ?? (externalMarketplace ? 'standalone-index' : 'bundled-family');
|
|
1304
|
+
const marketplaceForm = marketplaceSourceType;
|
|
1269
1305
|
const sourceDescriptor = marketplaceForm === 'standalone-index'
|
|
1270
1306
|
? Object.freeze({
|
|
1271
1307
|
form: 'standalone-index',
|
|
@@ -1314,6 +1350,14 @@ export function buildExternalActions(unitResults, resolvedVersions, productionAs
|
|
|
1314
1350
|
...(externalMarketplace ? { marketplaceLocation: 'external' } : {}),
|
|
1315
1351
|
...(marketplaceForm ? { marketplaceForm } : {}),
|
|
1316
1352
|
...(sourceDescriptor ? { sourceDescriptor } : {}),
|
|
1353
|
+
// 安装契约摘要、算法版本和来源类型,用于完整性交叉校验
|
|
1354
|
+
...(dist.installationContractDigest ? {
|
|
1355
|
+
installationContractDigest: dist.installationContractDigest,
|
|
1356
|
+
algorithmVersion: INSTALLATION_CONTRACT_ALGORITHM_VERSION,
|
|
1357
|
+
marketplaceSourceType: dist.marketplaceSourceType,
|
|
1358
|
+
} : {}),
|
|
1359
|
+
// standalone-index 审计字段仅在生产在线冻结成功后出现;
|
|
1360
|
+
// 非生产 action 不携带这三个字段(未冻结时无真实值可用)。
|
|
1317
1361
|
},
|
|
1318
1362
|
expected: {
|
|
1319
1363
|
installed: true,
|
|
@@ -1336,6 +1380,7 @@ export function buildExternalActions(unitResults, resolvedVersions, productionAs
|
|
|
1336
1380
|
const asset = productionAssets[index];
|
|
1337
1381
|
const tagTemplate = unit.version?.tagTemplate ?? `${unit.id}-v{version}`;
|
|
1338
1382
|
const resolvedTag = asset.tag;
|
|
1383
|
+
const frozenUnitDists = frozenDistributions?.get(unit.id) ?? null;
|
|
1339
1384
|
|
|
1340
1385
|
// Push snapshot
|
|
1341
1386
|
actions.push({
|
|
@@ -1474,7 +1519,9 @@ export function buildExternalActions(unitResults, resolvedVersions, productionAs
|
|
|
1474
1519
|
// entrySkillFound/manifestDigest expected). Marketplace identity follows
|
|
1475
1520
|
// the registry's schema required fields — kimi carries none (MINOR-1).
|
|
1476
1521
|
for (const platform of PLATFORMS) {
|
|
1477
|
-
const dist =
|
|
1522
|
+
const dist = frozenUnitDists
|
|
1523
|
+
? frozenUnitDists.find((d) => d.type === platform.distributionType)
|
|
1524
|
+
: (unit.distributions ?? []).find((d) => d.type === platform.distributionType);
|
|
1478
1525
|
if (!dist) continue;
|
|
1479
1526
|
const requiresMarketplace = platform.schemaRequiredFields.includes('marketplace');
|
|
1480
1527
|
const timeoutMs = Number.isInteger(dist.timeoutMs) ? dist.timeoutMs : 300000;
|
|
@@ -1486,9 +1533,9 @@ export function buildExternalActions(unitResults, resolvedVersions, productionAs
|
|
|
1486
1533
|
const externalMarketplace = dist.marketplaceRepo !== undefined && dist.marketplaceRepo !== null;
|
|
1487
1534
|
const freeze = externalMarketplace ? externalFreezes.get(`${unit.id} ${dist.type}`) : null;
|
|
1488
1535
|
// Normalized marketplace form: explicit mutually exclusive declaration.
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1536
|
+
// All four platforms carry marketplaceForm and sourceDescriptor.
|
|
1537
|
+
const marketplaceSourceType = dist.marketplaceSourceType ?? (externalMarketplace ? 'standalone-index' : 'bundled-family');
|
|
1538
|
+
const marketplaceForm = marketplaceSourceType;
|
|
1492
1539
|
const sourceDescriptor = marketplaceForm === 'standalone-index'
|
|
1493
1540
|
? Object.freeze({
|
|
1494
1541
|
form: 'standalone-index',
|
|
@@ -1543,6 +1590,18 @@ export function buildExternalActions(unitResults, resolvedVersions, productionAs
|
|
|
1543
1590
|
// bundled-family: sourceDescriptor.commit 绑定到此值。
|
|
1544
1591
|
// standalone-index: 通过此值绑定插件载荷来源。
|
|
1545
1592
|
sourceCommit: asset.commit,
|
|
1593
|
+
// 安装契约摘要、算法版本和来源类型,用于完整性交叉校验
|
|
1594
|
+
...(dist.installationContractDigest ? {
|
|
1595
|
+
installationContractDigest: dist.installationContractDigest,
|
|
1596
|
+
algorithmVersion: INSTALLATION_CONTRACT_ALGORITHM_VERSION,
|
|
1597
|
+
marketplaceSourceType: dist.marketplaceSourceType,
|
|
1598
|
+
} : {}),
|
|
1599
|
+
// standalone-index 审计字段:供后续静态预检使用
|
|
1600
|
+
...(externalMarketplace && freeze ? {
|
|
1601
|
+
marketplaceIndexPath: freeze.marketplaceIndexPath,
|
|
1602
|
+
marketplaceName: freeze.marketplaceName,
|
|
1603
|
+
selectedEntry: freeze.selectedEntry,
|
|
1604
|
+
} : {}),
|
|
1546
1605
|
},
|
|
1547
1606
|
expected: {
|
|
1548
1607
|
installed: true,
|
|
@@ -2175,9 +2234,304 @@ export async function prepareRelease(options) {
|
|
|
2175
2234
|
)
|
|
2176
2235
|
: null;
|
|
2177
2236
|
|
|
2178
|
-
|
|
2237
|
+
// Freeze external independent marketplace HEADs (production + online only):
|
|
2238
|
+
// for each claude/codex/codebuddy distribution declaring marketplaceRepo,
|
|
2239
|
+
// resolve the external repo's HEAD sha + default branch and validate the
|
|
2240
|
+
// marketplace index entry at that sha before freezing the add-ref. This
|
|
2241
|
+
// MUST happen before installation contract construction so that standalone-index
|
|
2242
|
+
// contracts can use the frozen external entries. Offline production with a
|
|
2243
|
+
// declared marketplaceRepo fails closed inside the resolver. The remote is
|
|
2244
|
+
// only ever read (git ls-remote / gh api), never written.
|
|
2245
|
+
const externalMarketplaceFreezes = production
|
|
2246
|
+
? await resolveExternalMarketplaceFreezes({
|
|
2247
|
+
unitResults,
|
|
2248
|
+
resolvedVersions,
|
|
2249
|
+
offline,
|
|
2250
|
+
evidence,
|
|
2251
|
+
observeHeadFn: options.observeExternalMarketplaceHeadFn ?? defaultObserveExternalMarketplaceHead,
|
|
2252
|
+
fetchIndexFn: options.fetchExternalMarketplaceIndexFn ?? defaultFetchExternalMarketplaceIndex,
|
|
2253
|
+
})
|
|
2254
|
+
: new Map();
|
|
2255
|
+
|
|
2256
|
+
// 为每个分发渠道校验 marketplaceSourceType 并冻结安装契约。
|
|
2257
|
+
// 对每个插件 distribution:
|
|
2258
|
+
// 1. 从 unitResults[].manifest.outputDir 的真实快照读取 manifest
|
|
2259
|
+
// 2. manifest 读取:若平台 strategy.readManifest 存在则调用它,否则读 platform.manifestPaths.plugin
|
|
2260
|
+
// 3. 确定 includeMarketplaceEntry:
|
|
2261
|
+
// - bundled-family: Kimi=false, Claude/Codex/CodeBuddy=true(从快照读取)
|
|
2262
|
+
// - standalone-index: Kimi=false, Claude/Codex/CodeBuddy=true(使用外部冻结条目)
|
|
2263
|
+
// 4. 需要条目时:
|
|
2264
|
+
// - bundled-family: 从 dist.marketplaceIndexPath ?? platform.manifestPaths.marketplace 读取 bundled 索引
|
|
2265
|
+
// - standalone-index: 从 externalMarketplaceFreezes 获取冻结的 selectedEntry 和 marketplaceIndexPath
|
|
2266
|
+
// 5. 使用 buildInstallationContract 构建完整可审计契约对象
|
|
2267
|
+
// 6. 在 distribution 中冻结 marketplaceSourceType / installationContract / installationContractDigest
|
|
2268
|
+
// 以及独立市场审计字段
|
|
2269
|
+
const units = await Promise.all(unitResults.map(async ({ unit, manifest }, idx) => {
|
|
2179
2270
|
const unitVersion = resolvedVersions[idx];
|
|
2180
2271
|
const unitBaseline = unitBaselineResults.get(unit.id);
|
|
2272
|
+
const snapshotDir = manifest.outputDir;
|
|
2273
|
+
|
|
2274
|
+
const distributionsWithSource = await Promise.all((unit.distributions ?? []).map(async (dist) => {
|
|
2275
|
+
const platform = PLATFORMS.find((p) => p.distributionType === dist.type);
|
|
2276
|
+
if (!platform) return dist;
|
|
2277
|
+
|
|
2278
|
+
// 校验 marketplaceSourceType:从配置读取,不允许硬编码默认值
|
|
2279
|
+
const sourceTypeResult = validateMarketplaceSourceSelection(
|
|
2280
|
+
platform.id,
|
|
2281
|
+
dist, // config
|
|
2282
|
+
dist, // plan (same source at prepare time)
|
|
2283
|
+
);
|
|
2284
|
+
if (!sourceTypeResult.valid) {
|
|
2285
|
+
throw new ReleaseError(
|
|
2286
|
+
CONFIG_INVALID,
|
|
2287
|
+
`unit "${unit.id}" ${dist.type} marketplace source validation failed: ${sourceTypeResult.error}`,
|
|
2288
|
+
{ unitId: unit.id, distributionType: dist.type },
|
|
2289
|
+
);
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
// 仅插件 distribution 需要安装契约
|
|
2293
|
+
if (dist.type === 'npm') {
|
|
2294
|
+
return dist;
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
// 1. 确定 marketplaceSourceType(防御性归一化:旧配置可能仍缺少字段)
|
|
2298
|
+
let marketplaceSourceType = sourceTypeResult.selectedSource;
|
|
2299
|
+
if (!marketplaceSourceType) {
|
|
2300
|
+
marketplaceSourceType = dist.marketplaceRepo ? 'standalone-index' : 'bundled-family';
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
// 2. 确定 includeMarketplaceEntry 和 selectedMarketplaceEntry
|
|
2304
|
+
// bundled-family: 从快照中的 bundled 索引读取唯一选中条目。
|
|
2305
|
+
// standalone-index: 从 externalMarketplaceFreezes 获取冻结的 selectedEntry 和 marketplaceIndexPath。
|
|
2306
|
+
// Kimi 不纳入市场条目(platform.manifestPaths.marketplace === null 且无显式路径);
|
|
2307
|
+
// Claude/Codex/CodeBuddy 使用默认或显式路径。
|
|
2308
|
+
const hasDefaultMarketplace = platform.manifestPaths.marketplace !== null;
|
|
2309
|
+
const hasExplicitMarketplacePath = dist.marketplaceIndexPath != null;
|
|
2310
|
+
const isBundledFamily = marketplaceSourceType === 'bundled-family';
|
|
2311
|
+
const isStandaloneIndex = marketplaceSourceType === 'standalone-index';
|
|
2312
|
+
|
|
2313
|
+
// includeMarketplaceEntry 代表"契约实际包含一条市场条目",
|
|
2314
|
+
// 不能只代表平台理论上支持市场条目。
|
|
2315
|
+
// bundled-family: 平台支持市场时即包含(从快照读取)
|
|
2316
|
+
// standalone-index: 只有拿到冻结条目且非 Kimi 时才包含
|
|
2317
|
+
// Kimi 不纳入市场条目:Kimi 无市场 CLI,selectedEntry 仅供静态校验。
|
|
2318
|
+
let includeMarketplaceEntry;
|
|
2319
|
+
if (isBundledFamily) {
|
|
2320
|
+
includeMarketplaceEntry = hasDefaultMarketplace || hasExplicitMarketplacePath;
|
|
2321
|
+
} else if (isStandaloneIndex) {
|
|
2322
|
+
if (platform.id === 'kimi') {
|
|
2323
|
+
// Kimi standalone: 安装契约不纳入市场条目
|
|
2324
|
+
includeMarketplaceEntry = false;
|
|
2325
|
+
} else {
|
|
2326
|
+
const freezeKey = `${unit.id} ${dist.type}`;
|
|
2327
|
+
const freeze = externalMarketplaceFreezes.get(freezeKey);
|
|
2328
|
+
if (freeze) {
|
|
2329
|
+
includeMarketplaceEntry = true;
|
|
2330
|
+
} else {
|
|
2331
|
+
// 生产在线的独立市场缺冻结结果必须失败关闭
|
|
2332
|
+
if (production && !offline) {
|
|
2333
|
+
throw new ReleaseError(
|
|
2334
|
+
GATE_FAILED,
|
|
2335
|
+
`unit "${unit.id}" ${dist.type} standalone-index requires external marketplace freeze but no freeze result found`,
|
|
2336
|
+
{ unitId: unit.id, distributionType: dist.type },
|
|
2337
|
+
);
|
|
2338
|
+
}
|
|
2339
|
+
// 非生产/离线没有冻结结果时,契约仍记录来源形态,但不包含空条目
|
|
2340
|
+
includeMarketplaceEntry = false;
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
} else {
|
|
2344
|
+
includeMarketplaceEntry = false;
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
// 3. 需要条目时,根据来源类型获取
|
|
2348
|
+
let selectedMarketplaceEntry = null;
|
|
2349
|
+
let marketplaceIndexRelative = null;
|
|
2350
|
+
let bundledMarketIndex = null;
|
|
2351
|
+
if (includeMarketplaceEntry) {
|
|
2352
|
+
if (isStandaloneIndex) {
|
|
2353
|
+
// standalone-index: 从外部冻结结果获取条目和路径(freeze 已确认存在)
|
|
2354
|
+
const freezeKey = `${unit.id} ${dist.type}`;
|
|
2355
|
+
const freeze = externalMarketplaceFreezes.get(freezeKey);
|
|
2356
|
+
selectedMarketplaceEntry = freeze.selectedEntry;
|
|
2357
|
+
marketplaceIndexRelative = freeze.marketplaceIndexPath;
|
|
2358
|
+
} else {
|
|
2359
|
+
// bundled-family: 从快照中的 bundled 索引读取
|
|
2360
|
+
marketplaceIndexRelative = dist.marketplaceIndexPath ?? platform.manifestPaths.marketplace;
|
|
2361
|
+
if (!marketplaceIndexRelative) {
|
|
2362
|
+
throw new ReleaseError(
|
|
2363
|
+
GATE_FAILED,
|
|
2364
|
+
`unit "${unit.id}" ${dist.type} cannot determine marketplace index path: neither marketplaceIndexPath nor platform.manifestPaths.marketplace is set`,
|
|
2365
|
+
{ unitId: unit.id, distributionType: dist.type },
|
|
2366
|
+
);
|
|
2367
|
+
}
|
|
2368
|
+
const marketplaceIndexPath = resolve(snapshotDir, marketplaceIndexRelative);
|
|
2369
|
+
let marketplaceIndexRaw;
|
|
2370
|
+
try {
|
|
2371
|
+
marketplaceIndexRaw = await readFile(marketplaceIndexPath, 'utf8');
|
|
2372
|
+
} catch (err) {
|
|
2373
|
+
throw new ReleaseError(
|
|
2374
|
+
GATE_FAILED,
|
|
2375
|
+
`unit "${unit.id}" ${dist.type} cannot read bundled marketplace index "${marketplaceIndexRelative}": ${err.message}`,
|
|
2376
|
+
{ unitId: unit.id, distributionType: dist.type, cause: err.code },
|
|
2377
|
+
);
|
|
2378
|
+
}
|
|
2379
|
+
let marketplaceIndex;
|
|
2380
|
+
try {
|
|
2381
|
+
marketplaceIndex = JSON.parse(marketplaceIndexRaw);
|
|
2382
|
+
} catch (err) {
|
|
2383
|
+
throw new ReleaseError(
|
|
2384
|
+
GATE_FAILED,
|
|
2385
|
+
`unit "${unit.id}" ${dist.type} invalid JSON in bundled marketplace index "${marketplaceIndexRelative}": ${err.message}`,
|
|
2386
|
+
{ unitId: unit.id, distributionType: dist.type },
|
|
2387
|
+
);
|
|
2388
|
+
}
|
|
2389
|
+
if (!marketplaceIndex || typeof marketplaceIndex !== 'object' || !Array.isArray(marketplaceIndex.plugins)) {
|
|
2390
|
+
throw new ReleaseError(
|
|
2391
|
+
GATE_FAILED,
|
|
2392
|
+
`unit "${unit.id}" ${dist.type} bundled marketplace index "${marketplaceIndexRelative}" must be an object with a plugins array`,
|
|
2393
|
+
{ unitId: unit.id, distributionType: dist.type },
|
|
2394
|
+
);
|
|
2395
|
+
}
|
|
2396
|
+
const matchingEntries = marketplaceIndex.plugins.filter(
|
|
2397
|
+
(entry) => entry && entry.name === dist.plugin,
|
|
2398
|
+
);
|
|
2399
|
+
if (matchingEntries.length !== 1) {
|
|
2400
|
+
throw new ReleaseError(
|
|
2401
|
+
GATE_FAILED,
|
|
2402
|
+
`unit "${unit.id}" ${dist.type} bundled marketplace index must contain exactly one plugin entry named "${dist.plugin}", found ${matchingEntries.length}`,
|
|
2403
|
+
{ unitId: unit.id, distributionType: dist.type },
|
|
2404
|
+
);
|
|
2405
|
+
}
|
|
2406
|
+
// 传完整解析条目给 buildInstallationContract,不得只取名字
|
|
2407
|
+
selectedMarketplaceEntry = matchingEntries[0];
|
|
2408
|
+
bundledMarketIndex = marketplaceIndex;
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
// 4. 从真实快照读取插件 manifest
|
|
2413
|
+
// bundled-family + 有市场索引:使用 resolvePluginManifestFromMarketplaceEntrySource
|
|
2414
|
+
// 从条目 source 安全解析插件根并读取 manifest(支持子目录布局)。
|
|
2415
|
+
// 其他路径:保留原有策略。
|
|
2416
|
+
let pluginManifestRelative;
|
|
2417
|
+
let pluginManifestParsed;
|
|
2418
|
+
if (isBundledFamily && bundledMarketIndex && platform.marketplaceSourceForm !== null) {
|
|
2419
|
+
// bundled-family 有市场索引且平台支持市场来源解析(Claude/Codex):
|
|
2420
|
+
// 通过条目 source 路径解析 manifest。
|
|
2421
|
+
// 计算市场根:从 marketplaceIndexRelative 推断(精确后缀匹配)。
|
|
2422
|
+
const mktRoot = resolveMarketplaceRoot(platform, marketplaceIndexRelative);
|
|
2423
|
+
try {
|
|
2424
|
+
const resolved = await resolvePluginManifestFromMarketplaceEntrySource(
|
|
2425
|
+
bundledMarketIndex, dist.plugin, platform, snapshotDir, mktRoot,
|
|
2426
|
+
);
|
|
2427
|
+
pluginManifestParsed = resolved.manifest;
|
|
2428
|
+
pluginManifestRelative = resolved.manifestRelativePath;
|
|
2429
|
+
} catch (err) {
|
|
2430
|
+
throw new ReleaseError(
|
|
2431
|
+
GATE_FAILED,
|
|
2432
|
+
`unit "${unit.id}" ${dist.type} cannot resolve plugin manifest from marketplace entry source: ${err.message}`,
|
|
2433
|
+
{ unitId: unit.id, distributionType: dist.type },
|
|
2434
|
+
);
|
|
2435
|
+
}
|
|
2436
|
+
} else if (platform.strategy.readManifest) {
|
|
2437
|
+
// Kimi/Codex/CodeBuddy 有自定义 manifest 读取策略
|
|
2438
|
+
const readResult = await platform.strategy.readManifest(snapshotDir);
|
|
2439
|
+
pluginManifestParsed = readResult.manifest;
|
|
2440
|
+
pluginManifestRelative = readResult.manifestRelative ?? platform.manifestPaths.plugin;
|
|
2441
|
+
} else {
|
|
2442
|
+
// Claude fallback(standalone-index 或无市场索引时)
|
|
2443
|
+
pluginManifestRelative = platform.manifestPaths.plugin;
|
|
2444
|
+
const pluginManifestPath = resolve(snapshotDir, pluginManifestRelative);
|
|
2445
|
+
let raw;
|
|
2446
|
+
try {
|
|
2447
|
+
raw = await readFile(pluginManifestPath, 'utf8');
|
|
2448
|
+
} catch (err) {
|
|
2449
|
+
throw new ReleaseError(
|
|
2450
|
+
GATE_FAILED,
|
|
2451
|
+
`unit "${unit.id}" ${dist.type} cannot read plugin manifest "${pluginManifestRelative}": ${err.message}`,
|
|
2452
|
+
{ unitId: unit.id, distributionType: dist.type, cause: err.code },
|
|
2453
|
+
);
|
|
2454
|
+
}
|
|
2455
|
+
try {
|
|
2456
|
+
pluginManifestParsed = JSON.parse(raw);
|
|
2457
|
+
} catch (err) {
|
|
2458
|
+
throw new ReleaseError(
|
|
2459
|
+
GATE_FAILED,
|
|
2460
|
+
`unit "${unit.id}" ${dist.type} invalid JSON in plugin manifest "${pluginManifestRelative}": ${err.message}`,
|
|
2461
|
+
{ unitId: unit.id, distributionType: dist.type },
|
|
2462
|
+
);
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
// 静态校验 manifest 名称和版本(被摘要剔除不等于不校验)
|
|
2467
|
+
if (pluginManifestParsed.name !== dist.plugin) {
|
|
2468
|
+
throw new ReleaseError(
|
|
2469
|
+
GATE_FAILED,
|
|
2470
|
+
`unit "${unit.id}" ${dist.type} plugin manifest name "${pluginManifestParsed.name}" does not match distribution plugin "${dist.plugin}"`,
|
|
2471
|
+
{ unitId: unit.id, distributionType: dist.type },
|
|
2472
|
+
);
|
|
2473
|
+
}
|
|
2474
|
+
if (typeof pluginManifestParsed.version === 'string' && pluginManifestParsed.version !== unitVersion) {
|
|
2475
|
+
throw new ReleaseError(
|
|
2476
|
+
GATE_FAILED,
|
|
2477
|
+
`unit "${unit.id}" ${dist.type} plugin manifest version "${pluginManifestParsed.version}" does not match target version "${unitVersion}"`,
|
|
2478
|
+
{ unitId: unit.id, distributionType: dist.type },
|
|
2479
|
+
);
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
// 5. 使用 buildInstallationContract 构建完整可审计契约对象
|
|
2483
|
+
const installationContract = buildInstallationContract({
|
|
2484
|
+
distributionType: dist.type,
|
|
2485
|
+
manifestRelativePath: pluginManifestRelative,
|
|
2486
|
+
manifest: pluginManifestParsed,
|
|
2487
|
+
marketplaceSourceType,
|
|
2488
|
+
includeMarketplaceEntry,
|
|
2489
|
+
...(includeMarketplaceEntry ? {
|
|
2490
|
+
marketplaceIndexRelativePath: marketplaceIndexRelative,
|
|
2491
|
+
selectedMarketplaceEntry,
|
|
2492
|
+
} : {}),
|
|
2493
|
+
verificationRecipeVersion: CONSUMER_INSTALL_RECIPE_VERSION,
|
|
2494
|
+
});
|
|
2495
|
+
|
|
2496
|
+
// 6. 计算摘要(使用权威算法入口,保证契约对象与摘要一致)
|
|
2497
|
+
const installationContractDigest = computeInstallationContractDigest({
|
|
2498
|
+
distributionType: dist.type,
|
|
2499
|
+
manifestRelativePath: pluginManifestRelative,
|
|
2500
|
+
manifest: pluginManifestParsed,
|
|
2501
|
+
marketplaceSourceType,
|
|
2502
|
+
includeMarketplaceEntry,
|
|
2503
|
+
...(includeMarketplaceEntry ? {
|
|
2504
|
+
marketplaceIndexRelativePath: marketplaceIndexRelative,
|
|
2505
|
+
selectedMarketplaceEntry,
|
|
2506
|
+
} : {}),
|
|
2507
|
+
verificationRecipeVersion: CONSUMER_INSTALL_RECIPE_VERSION,
|
|
2508
|
+
});
|
|
2509
|
+
|
|
2510
|
+
// 7. 构建返回对象,包含冻结的审计字段
|
|
2511
|
+
const frozenDist = {
|
|
2512
|
+
...dist,
|
|
2513
|
+
marketplaceSourceType,
|
|
2514
|
+
installationContract,
|
|
2515
|
+
installationContractDigest,
|
|
2516
|
+
};
|
|
2517
|
+
|
|
2518
|
+
// standalone-index 审计字段:来自外部冻结结果
|
|
2519
|
+
if (isStandaloneIndex) {
|
|
2520
|
+
const freezeKey = `${unit.id} ${dist.type}`;
|
|
2521
|
+
const freeze = externalMarketplaceFreezes.get(freezeKey);
|
|
2522
|
+
if (freeze) {
|
|
2523
|
+
frozenDist.marketplaceRepo = freeze.marketplaceRepo;
|
|
2524
|
+
frozenDist.marketplaceCommitSha = freeze.marketplaceCommitSha;
|
|
2525
|
+
frozenDist.marketplaceRef = freeze.marketplaceRef;
|
|
2526
|
+
frozenDist.marketplaceIndexPath = freeze.marketplaceIndexPath;
|
|
2527
|
+
frozenDist.marketplaceName = freeze.marketplaceName;
|
|
2528
|
+
frozenDist.selectedEntry = freeze.selectedEntry;
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
return frozenDist;
|
|
2533
|
+
}));
|
|
2534
|
+
|
|
2181
2535
|
return {
|
|
2182
2536
|
id: unit.id,
|
|
2183
2537
|
targetVersion: unitVersion,
|
|
@@ -2202,29 +2556,15 @@ export async function prepareRelease(options) {
|
|
|
2202
2556
|
npm: productionAssets[idx].npm,
|
|
2203
2557
|
},
|
|
2204
2558
|
} : {}),
|
|
2205
|
-
distributions:
|
|
2559
|
+
distributions: distributionsWithSource,
|
|
2206
2560
|
...(unitBaseline ? { previousPublicBaseline: unitBaseline } : {}),
|
|
2207
2561
|
};
|
|
2208
|
-
});
|
|
2562
|
+
}));
|
|
2209
2563
|
|
|
2210
|
-
//
|
|
2211
|
-
|
|
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();
|
|
2564
|
+
// 构建冻结分发映射:unitId -> frozen distributions
|
|
2565
|
+
const frozenDistributionsMap = new Map(units.map((u) => [u.id, u.distributions]));
|
|
2226
2566
|
|
|
2227
|
-
const externalActions = buildExternalActions(unitResults, resolvedVersions, productionAssets, externalMarketplaceFreezes);
|
|
2567
|
+
const externalActions = buildExternalActions(unitResults, resolvedVersions, productionAssets, externalMarketplaceFreezes, frozenDistributionsMap);
|
|
2228
2568
|
|
|
2229
2569
|
// Compute overall snapshot digest
|
|
2230
2570
|
const overallSnapshotDigest = sha256Hex(snapshotDigests.join(':'));
|