release-skill 0.9.20 → 0.9.21
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/plugin.json +1 -1
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.cursor-plugin/plugin.json +1 -1
- package/.kimi-plugin/plugin.json +1 -1
- package/.qoder-plugin/plugin.json +1 -1
- package/CHANGELOG.md +28 -0
- package/INSTALL.md +2 -2
- package/INSTALL.zh-CN.md +2 -2
- package/README.md +25 -27
- package/README.zh-CN.md +22 -25
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +200 -20
- package/adapters/claude/skills/release-finish/SKILL.md +5 -3
- package/adapters/claude/skills/release-help/SKILL.md +2 -2
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +200 -20
- package/adapters/codex/skills/release-finish/SKILL.md +5 -3
- package/adapters/codex/skills/release-help/SKILL.md +2 -2
- package/adapters/cursor/.cursor-plugin/plugin.json +1 -1
- package/adapters/cursor/bin/release-skill.bundle.mjs +200 -20
- package/adapters/cursor/skills/release-finish/SKILL.md +5 -3
- package/adapters/cursor/skills/release-help/SKILL.md +2 -2
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +200 -20
- package/adapters/kimi/skills/release-finish/SKILL.md +5 -3
- package/adapters/kimi/skills/release-help/SKILL.md +2 -2
- package/adapters/qoder/.qoder-plugin/plugin.json +1 -1
- package/adapters/qoder/bin/release-skill.bundle.mjs +200 -20
- package/adapters/qoder/skills/release-finish/SKILL.md +5 -3
- package/adapters/qoder/skills/release-help/SKILL.md +2 -2
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +200 -20
- package/adapters/workbuddy/skills/release-finish/SKILL.md +5 -3
- package/adapters/workbuddy/skills/release-help/SKILL.md +2 -2
- package/bin/release-skill.bundle.mjs +200 -20
- package/package.json +1 -1
- package/platform-manifest.json +4 -4
- package/references/02-project-config.md +2 -0
- package/skills/release-finish/SKILL.md +5 -3
- package/skills/release-help/SKILL.md +2 -2
- package/skills-src/release-finish/SKILL.md +5 -3
- package/skills-src/release-help/SKILL.md +2 -2
- package/src/commands/post-release-local.mjs +215 -20
|
@@ -41,7 +41,7 @@ const BRANCH_ACTION_INCLUDED = new Set(['advance-existing-branch', 'initialize-d
|
|
|
41
41
|
const CODEBUDDY_MACOS_PATH = '/Applications/WorkBuddy.app/Contents/Resources/app.asar.unpacked/cli/bin/codebuddy';
|
|
42
42
|
const SAFE_ENV_KEYS = Object.freeze([
|
|
43
43
|
'PATH', 'HOME', 'USER', 'LANG', 'LC_ALL', 'LC_CTYPE', 'TERM', 'TMPDIR',
|
|
44
|
-
'HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY', 'http_proxy', 'https_proxy', 'no_proxy',
|
|
44
|
+
'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY', 'http_proxy', 'https_proxy', 'all_proxy', 'no_proxy',
|
|
45
45
|
'SSL_CERT_FILE', 'SSL_CERT_DIR', 'NODE_EXTRA_CA_CERTS',
|
|
46
46
|
'CLAUDE_CONFIG_DIR', 'CODEX_HOME', 'KIMI_CODE_HOME',
|
|
47
47
|
'CODEBUDDY_CONFIG_DIR', 'WORKBUDDY_CONFIG_DIR',
|
|
@@ -176,14 +176,9 @@ function hubTargets(plan) {
|
|
|
176
176
|
if (!local) return [];
|
|
177
177
|
const hub = { ...local.hub, githubHost: local.hub.githubHost ?? 'github.com' };
|
|
178
178
|
const unit = (plan.units ?? []).find((candidate) => candidate.id === declaration.unitId);
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
codex: 'Use Codex’s existing marketplace management entry.',
|
|
183
|
-
kimi: `Use the frozen GitHub Release${tag ? ` (${tag})` : ''} and the existing manual confirmation path; Kimi has no marketplace index.`,
|
|
184
|
-
codebuddy: 'Handle manually; CodeBuddy cannot pin a Hub ref in this flow.',
|
|
185
|
-
workbuddy: 'Handle manually; WorkBuddy cannot pin a Hub ref in this flow (it follows the CodeBuddy manual boundary).',
|
|
186
|
-
};
|
|
179
|
+
const tagActions = (plan.externalActions ?? []).filter((action) => action.type === 'create-tag' && action.unitId === declaration.unitId);
|
|
180
|
+
const tagIdentity = tagActions[0]?.parameters;
|
|
181
|
+
const tag = tagIdentity?.tag;
|
|
187
182
|
return local.hosts.map((host) => {
|
|
188
183
|
if (host === 'cursor') {
|
|
189
184
|
return {
|
|
@@ -217,15 +212,30 @@ function hubTargets(plan) {
|
|
|
217
212
|
}
|
|
218
213
|
return {
|
|
219
214
|
targetKind: 'hub-backed',
|
|
220
|
-
executionMode: '
|
|
215
|
+
executionMode: 'executable',
|
|
221
216
|
unitId: declaration.unitId,
|
|
222
217
|
host,
|
|
223
218
|
plugin: local.plugin,
|
|
224
219
|
version: unit?.targetVersion,
|
|
225
220
|
hub,
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
221
|
+
marketplace: hub.name,
|
|
222
|
+
marketplaceRepo: hub.repo,
|
|
223
|
+
marketplaceRef: hub.ref,
|
|
224
|
+
githubHost: tagIdentity?.githubHost ?? 'github.com',
|
|
225
|
+
pluginRepo: unit?.publicRepo,
|
|
226
|
+
pluginCommit: unit?.frozenSnapshot?.commit,
|
|
227
|
+
pluginTag: tag,
|
|
228
|
+
frozenTag: tag,
|
|
229
|
+
snapshotPath: unit?.frozenSnapshot?.path,
|
|
230
|
+
manifestDigest: unit?.frozenSnapshot?.manifestDigest,
|
|
231
|
+
timeoutMs: 300_000,
|
|
232
|
+
message: host === 'kimi'
|
|
233
|
+
? `Install the frozen GitHub Release ${tag} through Kimi's controlled terminal interface.`
|
|
234
|
+
: `Update ${local.plugin} from the existing ${hub.name} marketplace and verify the frozen payload.`,
|
|
235
|
+
...(tagActions.length !== 1 || tagIdentity?.repo !== unit?.publicRepo
|
|
236
|
+
|| tagIdentity?.commit !== unit?.frozenSnapshot?.commit
|
|
237
|
+
|| tagIdentity?.version !== unit?.targetVersion
|
|
238
|
+
? { invalidFrozenIdentity: true } : {}),
|
|
229
239
|
};
|
|
230
240
|
});
|
|
231
241
|
});
|
|
@@ -238,23 +248,28 @@ function mergePostReleaseTargets(executableTargets, manualTargets) {
|
|
|
238
248
|
const existing = byIdentity.get(key);
|
|
239
249
|
if (existing && existing.targetKind === 'hub-backed' && target.targetKind === 'hub-backed') {
|
|
240
250
|
const sameHub = ['name', 'githubHost', 'repo', 'ref'].every((field) => existing.hub[field] === target.hub[field]);
|
|
241
|
-
|
|
251
|
+
const samePlugin = ['version', 'pluginRepo', 'pluginTag', 'pluginCommit', 'snapshotPath', 'manifestDigest']
|
|
252
|
+
.every((field) => existing[field] === target[field]);
|
|
253
|
+
if (!sameHub || !samePlugin) throw new Error(`post-release target identity conflict for ${target.unitId}/${target.host}/${target.plugin}`);
|
|
242
254
|
continue;
|
|
243
255
|
}
|
|
244
|
-
const hubMatchesExecutable = (hub, executable) => executable.marketplace === hub.name
|
|
256
|
+
const hubMatchesExecutable = (hub, executable, hubTarget) => executable.marketplace === hub.name
|
|
245
257
|
&& executable.marketplaceRepo === hub.repo
|
|
246
258
|
&& executable.marketplaceRef === hub.ref
|
|
247
|
-
&& executable.githubHost === hub.githubHost
|
|
259
|
+
&& executable.githubHost === hub.githubHost
|
|
260
|
+
&& executable.pluginRepo === hubTarget.pluginRepo
|
|
261
|
+
&& executable.pluginTag === hubTarget.pluginTag
|
|
262
|
+
&& executable.pluginCommit === hubTarget.pluginCommit;
|
|
248
263
|
// When the same frozen plugin is available through both sources, the
|
|
249
264
|
// executable action wins only when both frozen source identities agree.
|
|
250
265
|
if (existing && existing.targetKind !== 'hub-backed' && target.targetKind === 'hub-backed') {
|
|
251
|
-
if (!hubMatchesExecutable(target.hub, existing)) {
|
|
266
|
+
if (!hubMatchesExecutable(target.hub, existing, target)) {
|
|
252
267
|
throw new Error(`post-release target identity conflict for ${target.unitId}/${target.host}/${target.plugin}`);
|
|
253
268
|
}
|
|
254
269
|
continue;
|
|
255
270
|
}
|
|
256
271
|
if (existing && existing.targetKind === 'hub-backed' && target.targetKind !== 'hub-backed') {
|
|
257
|
-
if (!hubMatchesExecutable(existing.hub, target)) {
|
|
272
|
+
if (!hubMatchesExecutable(existing.hub, target, existing)) {
|
|
258
273
|
throw new Error(`post-release target identity conflict for ${target.unitId}/${target.host}/${target.plugin}`);
|
|
259
274
|
}
|
|
260
275
|
byIdentity.set(key, target);
|
|
@@ -271,6 +286,16 @@ function mergePostReleaseTargets(executableTargets, manualTargets) {
|
|
|
271
286
|
}
|
|
272
287
|
|
|
273
288
|
function assertExecutableTarget(target) {
|
|
289
|
+
if (target.targetKind === 'hub-backed') {
|
|
290
|
+
assertQoderExecutableTarget(target);
|
|
291
|
+
if (target.invalidFrozenIdentity || typeof target.pluginTag !== 'string' || !target.pluginTag
|
|
292
|
+
|| !/^[\w.-]+\/[\w.-]+$/u.test(target.pluginRepo)
|
|
293
|
+
|| !/^[\w.-]+\/[\w.-]+$/u.test(target.hub.repo)
|
|
294
|
+
|| !target.hub.ref?.startsWith('refs/heads/')) {
|
|
295
|
+
throw new Error(`local host update target has inconsistent frozen source identity for ${target.unitId}`);
|
|
296
|
+
}
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
274
299
|
for (const field of [
|
|
275
300
|
'actionId', 'unitId', 'plugin', 'version',
|
|
276
301
|
'pluginRepo', 'pluginTag', 'pluginCommit',
|
|
@@ -345,6 +370,9 @@ export function derivePostReleaseChecklist(plan, {
|
|
|
345
370
|
!BRANCH_ACTION_INCLUDED.has(unit.productionConfig?.branchStrategy)
|
|
346
371
|
));
|
|
347
372
|
const declaredHubTargets = hubTargets(plan);
|
|
373
|
+
for (const target of declaredHubTargets) {
|
|
374
|
+
if (target.targetKind === 'hub-backed' && target.host !== 'qoder') assertExecutableTarget(target);
|
|
375
|
+
}
|
|
348
376
|
const executableTargets = [
|
|
349
377
|
...pluginTargets(plan),
|
|
350
378
|
...declaredHubTargets.filter((target) => target.executionMode === 'executable'),
|
|
@@ -1076,10 +1104,16 @@ async function observeMarketplace(target, command, env, run) {
|
|
|
1076
1104
|
const parsed = parseJson(listed.stdout, `${target.host} marketplace list`);
|
|
1077
1105
|
const entries = target.host === 'claude' ? parsed : parsed?.marketplaces;
|
|
1078
1106
|
if (!Array.isArray(entries)) throw new Error(`${target.host} marketplace list has an invalid shape`);
|
|
1079
|
-
const
|
|
1107
|
+
const matches = entries.filter((entry) => entry?.name === target.marketplace);
|
|
1108
|
+
if (target.targetKind === 'hub-backed' && matches.length > 1) {
|
|
1109
|
+
throw new Error(`${target.host} marketplace list returned conflicting entries for ${target.marketplace}`);
|
|
1110
|
+
}
|
|
1111
|
+
const found = matches[0];
|
|
1080
1112
|
if (!found) return { installed: false };
|
|
1081
1113
|
const source = target.host === 'claude' ? found.repo : found.marketplaceSource?.source;
|
|
1082
|
-
|
|
1114
|
+
const observedSource = target.targetKind === 'hub-backed'
|
|
1115
|
+
? normalizeHubGitSource(source, target.hub.githubHost) : normalizeGitSource(source);
|
|
1116
|
+
if (observedSource !== normalizeGitSource(target.marketplaceRepo)) {
|
|
1083
1117
|
throw new Error(`${target.host} marketplace ${target.marketplace} does not point to ${target.marketplaceRepo}`);
|
|
1084
1118
|
}
|
|
1085
1119
|
const root = target.host === 'claude' ? found.installLocation : found.root;
|
|
@@ -1119,6 +1153,14 @@ async function bindStructuredMarketplace(target, command, env, run, observed) {
|
|
|
1119
1153
|
}
|
|
1120
1154
|
|
|
1121
1155
|
function actionParametersForTarget(plan, target) {
|
|
1156
|
+
if (target.targetKind === 'hub-backed') {
|
|
1157
|
+
return {
|
|
1158
|
+
snapshotPath: target.snapshotPath,
|
|
1159
|
+
manifestDigest: target.manifestDigest,
|
|
1160
|
+
payloadContract: 'external-marketplace-v1',
|
|
1161
|
+
marketplaceLocation: 'external',
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1122
1164
|
const action = (plan.externalActions ?? []).find((candidate) => candidate.id === target.actionId);
|
|
1123
1165
|
if (!action || action.type !== target.actionType || action.unitId !== target.unitId) {
|
|
1124
1166
|
throw new Error(`local host update target ${target.actionId} has no matching frozen action`);
|
|
@@ -1144,11 +1186,161 @@ async function verifyStructuredInstalledPayload({
|
|
|
1144
1186
|
);
|
|
1145
1187
|
}
|
|
1146
1188
|
|
|
1189
|
+
async function assertHubHostEntry(target, marketplaceRoot) {
|
|
1190
|
+
const codex = target.host === 'codex';
|
|
1191
|
+
const indexPath = codex ? '.agents/plugins/marketplace.json'
|
|
1192
|
+
: target.host === 'claude' ? '.claude-plugin/marketplace.json' : '.codebuddy-plugin/marketplace.json';
|
|
1193
|
+
const index = parseJson(await readFileContained(marketplaceRoot, indexPath, { encoding: 'utf8' }), `${target.host} Hub index`);
|
|
1194
|
+
const matches = index?.plugins?.filter((entry) => entry?.name === target.plugin);
|
|
1195
|
+
if (index?.name !== target.marketplace || !Array.isArray(matches) || matches.length !== 1) {
|
|
1196
|
+
throw new Error(`${target.host} Hub index must contain exactly one frozen plugin entry`);
|
|
1197
|
+
}
|
|
1198
|
+
const entry = matches[0];
|
|
1199
|
+
const source = entry.source;
|
|
1200
|
+
const repo = codex ? normalizeHubGitSource(source?.url, target.githubHost) : source?.repo;
|
|
1201
|
+
if (source?.source !== (codex ? 'url' : 'github') || repo !== target.pluginRepo
|
|
1202
|
+
|| source?.sha !== target.pluginCommit
|
|
1203
|
+
|| source?.ref !== (codex ? `refs/tags/${target.pluginTag}` : target.pluginTag)
|
|
1204
|
+
|| (!codex && entry.version !== target.version)
|
|
1205
|
+
|| (entry.version !== undefined && entry.version !== target.version)) {
|
|
1206
|
+
throw new Error(`${target.host} Hub entry does not match the frozen repository, tag, commit and version`);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
async function observeHubCheckout(target, checkout, env, run) {
|
|
1211
|
+
const remote = await run('git', ['-C', checkout, 'remote', 'get-url', 'origin'], { env, timeout: 30_000 });
|
|
1212
|
+
if (normalizeHubGitSource(remote.stdout.trim(), target.hub.githubHost) !== target.hub.repo) {
|
|
1213
|
+
throw new Error(`${target.host} marketplace checkout does not match the declared Hub repository`);
|
|
1214
|
+
}
|
|
1215
|
+
if (target.host !== 'codex') {
|
|
1216
|
+
const branch = await run('git', ['-C', checkout, 'symbolic-ref', 'HEAD'], { env, timeout: 30_000 });
|
|
1217
|
+
if (branch.stdout.trim() !== target.hub.ref) throw new Error(`${target.host} marketplace checkout does not match the declared Hub branch`);
|
|
1218
|
+
}
|
|
1219
|
+
const head = await run('git', ['-C', checkout, 'rev-parse', 'HEAD'], { env, timeout: 30_000 });
|
|
1220
|
+
const commit = head.stdout.trim();
|
|
1221
|
+
if (!/^[a-f0-9]{40}$/u.test(commit)) throw new Error(`${target.host} Hub checkout has no exact commit`);
|
|
1222
|
+
return commit;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
async function observeHubMarketplace(target, command, env, run) {
|
|
1226
|
+
if (target.host === 'claude' || target.host === 'codex') {
|
|
1227
|
+
const observed = await observeMarketplace(target, command, env, run);
|
|
1228
|
+
if (!observed.installed) return observed;
|
|
1229
|
+
return { ...observed, commit: await observeHubCheckout(target, observed.root, env, run) };
|
|
1230
|
+
}
|
|
1231
|
+
const listed = target.host === 'codebuddy'
|
|
1232
|
+
? (await runCodeBuddyRead(target, command, CODEBUDDY_MARKETPLACE_LIST_ARGS, 'codebuddy marketplace list', env, run)).output
|
|
1233
|
+
: await run(command, [...CODEBUDDY_MARKETPLACE_LIST_ARGS], { env });
|
|
1234
|
+
const entries = parseJson(listed.stdout, `${target.host} marketplace list`);
|
|
1235
|
+
if (!Array.isArray(entries)) throw new Error(`${target.host} marketplace list did not return an array`);
|
|
1236
|
+
if (!entries.some((entry) => entry?.name === target.marketplace)) return { installed: false };
|
|
1237
|
+
exactCodeBuddyMarketplaceObservation(target, listed.stdout);
|
|
1238
|
+
const root = await resolveContained(env.CODEBUDDY_CONFIG_DIR, `plugins/marketplaces/${target.marketplace}`);
|
|
1239
|
+
return { installed: true, root, commit: await observeHubCheckout(target, root, env, run) };
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
async function runHubStructuredUpdate(target, detected, run, { plan, root, verifyInstalledPayload }) {
|
|
1243
|
+
const env = hostEnvironment(target.host);
|
|
1244
|
+
const codeBuddy = target.host === 'codebuddy' || target.host === 'workbuddy';
|
|
1245
|
+
const readPlugin = async () => {
|
|
1246
|
+
const output = target.host === 'codebuddy'
|
|
1247
|
+
? (await runCodeBuddyRead(target, detected.command, CODEBUDDY_PLUGIN_LIST_ARGS, 'codebuddy plugin list', env, run)).output
|
|
1248
|
+
: await run(detected.command, ['plugin', 'list', '--json'], { env });
|
|
1249
|
+
const listed = parseJson(output.stdout, `${target.host} plugin list`);
|
|
1250
|
+
const entries = target.host === 'codex' ? listed?.installed : listed;
|
|
1251
|
+
if (Array.isArray(entries) && entries.filter((entry) => (
|
|
1252
|
+
(target.host === 'codex' ? entry?.pluginId : entry?.id) === `${target.plugin}@${target.marketplace}`
|
|
1253
|
+
)).length > 1) throw new Error(`${target.host} plugin list returned conflicting entries`);
|
|
1254
|
+
const observed = exactPluginObservation(target, output.stdout);
|
|
1255
|
+
if (observed.installed) {
|
|
1256
|
+
if (codeBuddy && observed.exact) {
|
|
1257
|
+
observed.installPath = await resolveContained(env.CODEBUDDY_CONFIG_DIR, `plugins/cache/${target.marketplace}/${target.plugin}/${target.version}`);
|
|
1258
|
+
} else if (target.host === 'codex') {
|
|
1259
|
+
observed.installPath = observed.found.installedPath;
|
|
1260
|
+
const source = observed.found.source;
|
|
1261
|
+
if (source?.source !== 'git' || normalizeHubGitSource(source.url, target.githubHost) !== target.pluginRepo) {
|
|
1262
|
+
throw new Error('codex installed plugin does not point to the frozen public repository');
|
|
1263
|
+
}
|
|
1264
|
+
if (source.sha !== target.pluginCommit || source.ref !== `refs/tags/${target.pluginTag}`) observed.exact = false;
|
|
1265
|
+
}
|
|
1266
|
+
if (observed.found.gitCommitSha !== undefined && observed.found.gitCommitSha !== target.pluginCommit) observed.exact = false;
|
|
1267
|
+
}
|
|
1268
|
+
return observed;
|
|
1269
|
+
};
|
|
1270
|
+
const beforeMarket = await observeHubMarketplace(target, detected.command, env, run);
|
|
1271
|
+
if (!beforeMarket.installed) return { status: 'MANUAL_REQUIRED', reason: `${target.host} declared Hub marketplace is not installed; no marketplace was added` };
|
|
1272
|
+
const before = await readPlugin();
|
|
1273
|
+
if (before.exact && before.installPath) {
|
|
1274
|
+
await assertHubHostEntry(target, beforeMarket.root);
|
|
1275
|
+
await verifyStructuredInstalledPayload({ plan, root, target, installPath: before.installPath, verifyInstalledPayload });
|
|
1276
|
+
return { status: 'ALREADY_CURRENT', version: target.version };
|
|
1277
|
+
}
|
|
1278
|
+
if (codeBuddy && !before.installed) return { status: 'MANUAL_REQUIRED', reason: `${target.host} target plugin is not installed; no initial installation was performed` };
|
|
1279
|
+
|
|
1280
|
+
// The observed Hub commit belongs to a different repository than pluginCommit.
|
|
1281
|
+
// Keep it local to this attempt; the frozen plan continues to bind plugin identity.
|
|
1282
|
+
const hubCommit = await withTemporaryWorkspace(async (workspace) => {
|
|
1283
|
+
const checkout = join(workspace.root, 'hub');
|
|
1284
|
+
await run('git', ['clone', '--depth', '1', '--branch', target.hub.ref.slice('refs/heads/'.length), '--single-branch',
|
|
1285
|
+
`https://${target.hub.githubHost}/${target.hub.repo}.git`, checkout], { env, timeout: target.timeoutMs });
|
|
1286
|
+
const commit = await observeHubCheckout(target, checkout, env, run);
|
|
1287
|
+
await assertHubHostEntry(target, checkout);
|
|
1288
|
+
return commit;
|
|
1289
|
+
}, { prefix: 'release-skill-hub-update-' });
|
|
1290
|
+
if (target.host === 'codex') {
|
|
1291
|
+
await bindStructuredMarketplace({ ...target, marketplaceCommit: hubCommit }, detected.command, env, run,
|
|
1292
|
+
{ marketplace: { ...beforeMarket, exact: beforeMarket.commit === hubCommit } });
|
|
1293
|
+
} else {
|
|
1294
|
+
try {
|
|
1295
|
+
await run(detected.command, ['plugin', 'marketplace', 'update', target.marketplace], { env, timeout: target.timeoutMs });
|
|
1296
|
+
} catch (error) {
|
|
1297
|
+
if (!isCodeBuddyWriteCommand(target, detected.command, ['plugin', 'marketplace', 'update', target.marketplace], 'marketplace-update')
|
|
1298
|
+
|| !isCodeBuddyResidualEnvelope(error)) throw error;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
const refreshed = await observeHubMarketplace(target, detected.command, env, run);
|
|
1302
|
+
if (!refreshed.installed || refreshed.commit !== hubCommit) throw new Error(`${target.host} Hub branch moved during marketplace refresh`);
|
|
1303
|
+
await assertHubHostEntry(target, refreshed.root);
|
|
1304
|
+
if (codeBuddy) {
|
|
1305
|
+
const remote = await run('git', ['ls-remote', '--exit-code', `https://${target.hub.githubHost}/${target.hub.repo}.git`, target.hub.ref], { env, timeout: 30_000 });
|
|
1306
|
+
if (!parseFrozenRemoteRef({ ...target, marketplaceCommit: hubCommit }, remote.stdout).exact) {
|
|
1307
|
+
throw new Error(`${target.host} Hub branch moved before plugin update`);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
const current = await readPlugin();
|
|
1311
|
+
const selector = `${target.plugin}@${target.marketplace}`;
|
|
1312
|
+
let installPath;
|
|
1313
|
+
if (target.host === 'codex') {
|
|
1314
|
+
if (current.installed) await run(detected.command, ['plugin', 'remove', selector, '--json'], { env });
|
|
1315
|
+
const installed = await run(detected.command, getPlatform('codex').cli.install(target.plugin, target.marketplace), { env, timeout: target.timeoutMs });
|
|
1316
|
+
const extracted = getPlatform('codex').strategy.extractInstallPath({ execEvidence: { installOutput: parseJson(installed.stdout, 'codex plugin install') } });
|
|
1317
|
+
if (!extracted.ok) throw new Error(extracted.error);
|
|
1318
|
+
installPath = extracted.installPath;
|
|
1319
|
+
} else {
|
|
1320
|
+
const args = codeBuddy || current.installed
|
|
1321
|
+
? ['plugin', 'update', selector, '--scope', 'user', ...(target.host === 'claude' ? ['--yes'] : [])]
|
|
1322
|
+
: getPlatform('claude').cli.install(target.plugin, target.marketplace);
|
|
1323
|
+
try {
|
|
1324
|
+
await run(detected.command, args, { env, timeout: target.timeoutMs });
|
|
1325
|
+
} catch (error) {
|
|
1326
|
+
if (!isCodeBuddyWriteCommand(target, detected.command, args, 'plugin-update') || !isCodeBuddyResidualEnvelope(error)) throw error;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
const after = await readPlugin();
|
|
1330
|
+
if (!after.exact) throw new Error(`${target.host} did not install the frozen plugin identity`);
|
|
1331
|
+
const finalMarket = await observeHubMarketplace(target, detected.command, env, run);
|
|
1332
|
+
if (!finalMarket.installed || finalMarket.commit !== hubCommit) throw new Error(`${target.host} Hub checkout changed during plugin update`);
|
|
1333
|
+
await assertHubHostEntry(target, finalMarket.root);
|
|
1334
|
+
await verifyStructuredInstalledPayload({ plan, root, target, installPath: installPath ?? after.installPath, verifyInstalledPayload });
|
|
1335
|
+
return { status: 'UPDATED', version: target.version, restartRequired: true };
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1147
1338
|
async function runStructuredUpdate(target, detected, run, {
|
|
1148
1339
|
plan,
|
|
1149
1340
|
root,
|
|
1150
1341
|
verifyInstalledPayload,
|
|
1151
1342
|
}) {
|
|
1343
|
+
if (target.targetKind === 'hub-backed') return runHubStructuredUpdate(target, detected, run, { plan, root, verifyInstalledPayload });
|
|
1152
1344
|
if (target.host === 'codebuddy' || target.host === 'workbuddy') {
|
|
1153
1345
|
const env = hostEnvironment(target.host);
|
|
1154
1346
|
const listedObservation = target.host === 'codebuddy'
|
|
@@ -2015,6 +2207,9 @@ async function updateLocalHostPluginsInternal({
|
|
|
2015
2207
|
results.push({ host: target.host, unitId: target.unitId, ...outcome });
|
|
2016
2208
|
continue;
|
|
2017
2209
|
}
|
|
2210
|
+
if (target.targetKind === 'hub-backed' && target.host !== 'qoder') {
|
|
2211
|
+
await verifyFrozenSnapshot({ root, snapshotPath: target.snapshotPath, expectedDigest: target.manifestDigest });
|
|
2212
|
+
}
|
|
2018
2213
|
const detected = await detect(target.host);
|
|
2019
2214
|
if (!detected?.available) {
|
|
2020
2215
|
results.push({
|