release-skill 0.9.34 → 0.9.36
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 +41 -0
- package/INSTALL.md +2 -2
- package/INSTALL.zh-CN.md +2 -2
- package/README.md +10 -17
- package/README.zh-CN.md +10 -17
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +190 -59
- package/adapters/claude/schemas/.render-manifest.json +6 -2
- package/adapters/claude/skills/release-finish/SKILL.md +5 -1
- package/adapters/claude/skills/release-help/SKILL.md +1 -1
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +190 -59
- package/adapters/codex/schemas/.render-manifest.json +6 -2
- package/adapters/codex/skills/release-finish/SKILL.md +5 -1
- package/adapters/codex/skills/release-help/SKILL.md +1 -1
- package/adapters/cursor/.cursor-plugin/plugin.json +1 -1
- package/adapters/cursor/bin/release-skill.bundle.mjs +190 -59
- package/adapters/cursor/schemas/.render-manifest.json +6 -2
- package/adapters/cursor/skills/release-finish/SKILL.md +5 -1
- package/adapters/cursor/skills/release-help/SKILL.md +1 -1
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +190 -59
- package/adapters/kimi/schemas/.render-manifest.json +6 -2
- package/adapters/kimi/skills/release-finish/SKILL.md +5 -1
- package/adapters/kimi/skills/release-help/SKILL.md +1 -1
- package/adapters/qoder/.qoder-plugin/plugin.json +1 -1
- package/adapters/qoder/bin/release-skill.bundle.mjs +190 -59
- package/adapters/qoder/schemas/.render-manifest.json +6 -2
- package/adapters/qoder/skills/release-finish/SKILL.md +5 -1
- package/adapters/qoder/skills/release-help/SKILL.md +1 -1
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +190 -59
- package/adapters/workbuddy/schemas/.render-manifest.json +6 -2
- package/adapters/workbuddy/skills/release-finish/SKILL.md +5 -1
- package/adapters/workbuddy/skills/release-help/SKILL.md +1 -1
- package/bin/release-skill.bundle.mjs +190 -59
- package/package.json +1 -1
- package/platform-manifest.json +4 -4
- package/references/.render-manifest.json +2 -2
- package/references/02-project-config.md +5 -1
- package/schemas/.render-manifest.json +6 -2
- package/skills/release-finish/SKILL.md +5 -1
- package/skills/release-help/SKILL.md +1 -1
- package/skills-src/release-finish/SKILL.md +5 -1
- package/skills-src/release-help/SKILL.md +1 -1
- package/src/adapters/plugin-marketplace.mjs +152 -43
- package/src/commands/post-release-local.mjs +71 -34
|
@@ -823,6 +823,88 @@ export function normalizeCodeBuddySkillsRel(skillsRaw) {
|
|
|
823
823
|
return rel;
|
|
824
824
|
}
|
|
825
825
|
|
|
826
|
+
/**
|
|
827
|
+
* Resolve a Codex entry skill using the plugin manifest at the actual plugin
|
|
828
|
+
* root. An omitted `skills` field keeps Codex's conventional `skills/` root;
|
|
829
|
+
* an explicit declaration is authoritative and must be valid and present.
|
|
830
|
+
*
|
|
831
|
+
* The short-name layout keeps priority over the plugin-prefixed layout, but
|
|
832
|
+
* fallback is allowed only when the short-name candidate is absent. Existing
|
|
833
|
+
* unsafe candidates are errors and cannot be hidden by a second layout.
|
|
834
|
+
*
|
|
835
|
+
* @param {string} pluginRoot - Real plugin root derived from source or install.
|
|
836
|
+
* @param {string} entrySkill - Frozen entry-skill id.
|
|
837
|
+
* @param {string} pluginName - Frozen plugin id used by the legacy prefix.
|
|
838
|
+
* @returns {Promise<string>} Absolute path to a regular, non-symlink SKILL.md.
|
|
839
|
+
*/
|
|
840
|
+
async function resolveCodexEntrySkillFile(pluginRoot, entrySkill, pluginName) {
|
|
841
|
+
const { normalizeGenericSkillsRel } = await import('../core/surface-host-bindings.mjs');
|
|
842
|
+
if (!entrySkill || typeof entrySkill !== 'string' || !SAFE_ID_RE.test(entrySkill)) {
|
|
843
|
+
throw new Error(`unsafe entrySkill: "${entrySkill}"`);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const platform = getPlatform('codex');
|
|
847
|
+
const manifestPath = await resolveContained(pluginRoot, platform.manifestPaths.plugin);
|
|
848
|
+
let manifest;
|
|
849
|
+
try {
|
|
850
|
+
manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
|
|
851
|
+
} catch (err) {
|
|
852
|
+
throw new Error(`Codex plugin manifest cannot be read: ${err.message}`);
|
|
853
|
+
}
|
|
854
|
+
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
|
|
855
|
+
throw new Error('Codex plugin manifest must be an object');
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
const skillsRel = Object.hasOwn(manifest, 'skills')
|
|
859
|
+
? normalizeGenericSkillsRel(manifest.skills)
|
|
860
|
+
: 'skills';
|
|
861
|
+
const skillsRoot = await resolveContained(pluginRoot, skillsRel);
|
|
862
|
+
let skillsRootStat;
|
|
863
|
+
try {
|
|
864
|
+
skillsRootStat = await lstat(skillsRoot);
|
|
865
|
+
} catch {
|
|
866
|
+
throw new Error(`Codex manifest skills root does not exist: ${manifest.skills ?? skillsRel}`);
|
|
867
|
+
}
|
|
868
|
+
if (skillsRootStat.isSymbolicLink() || !skillsRootStat.isDirectory()) {
|
|
869
|
+
throw new Error(`Codex manifest skills root must be a real directory: ${manifest.skills ?? skillsRel}`);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
const findCandidate = async (skillDirName) => {
|
|
873
|
+
const skillDir = await resolveContained(pluginRoot, `${skillsRel}/${skillDirName}`);
|
|
874
|
+
let skillDirStat;
|
|
875
|
+
try {
|
|
876
|
+
skillDirStat = await lstat(skillDir);
|
|
877
|
+
} catch (err) {
|
|
878
|
+
if (err?.code === 'ENOENT') return null;
|
|
879
|
+
throw err;
|
|
880
|
+
}
|
|
881
|
+
if (skillDirStat.isSymbolicLink() || !skillDirStat.isDirectory()) {
|
|
882
|
+
throw new Error(`Codex entry skill directory must be a real directory: ${skillsRel}/${skillDirName}`);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
const skillFile = await resolveContained(pluginRoot, `${skillsRel}/${skillDirName}/SKILL.md`);
|
|
886
|
+
let skillStat;
|
|
887
|
+
try {
|
|
888
|
+
skillStat = await lstat(skillFile);
|
|
889
|
+
} catch (err) {
|
|
890
|
+
if (err?.code === 'ENOENT') return null;
|
|
891
|
+
throw err;
|
|
892
|
+
}
|
|
893
|
+
if (skillStat.isSymbolicLink() || !skillStat.isFile()) {
|
|
894
|
+
throw new Error(`Codex entry skill must be a regular non-symlink file: ${skillsRel}/${skillDirName}/SKILL.md`);
|
|
895
|
+
}
|
|
896
|
+
return skillFile;
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
const shortNameEntry = await findCandidate(entrySkill);
|
|
900
|
+
if (shortNameEntry) return shortNameEntry;
|
|
901
|
+
if (pluginName && SAFE_ID_RE.test(pluginName)) {
|
|
902
|
+
const prefixedEntry = await findCandidate(`${pluginName}-${entrySkill}`);
|
|
903
|
+
if (prefixedEntry) return prefixedEntry;
|
|
904
|
+
}
|
|
905
|
+
throw new Error(`Codex entry skill not found: ${skillsRel}/${entrySkill}/SKILL.md`);
|
|
906
|
+
}
|
|
907
|
+
|
|
826
908
|
/**
|
|
827
909
|
* CodeBuddy equivalent of resolveKimiEntrySkillFile (parallel implementation,
|
|
828
910
|
* codebuddy wording; the kimi helper above is intentionally left untouched).
|
|
@@ -2110,8 +2192,8 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
2110
2192
|
// snapshot, so the snapshot marketplace-manifest section is skipped
|
|
2111
2193
|
// entirely. The plugin manifest is read directly from the snapshot
|
|
2112
2194
|
// root and the frozen external identity fields are validated. ref was
|
|
2113
|
-
// already injection-checked above (step 2);
|
|
2114
|
-
//
|
|
2195
|
+
// already injection-checked above (step 2); Codex entry lookup reads
|
|
2196
|
+
// the plugin manifest from this snapshot root.
|
|
2115
2197
|
if (
|
|
2116
2198
|
typeof action.marketplaceCommitSha !== 'string'
|
|
2117
2199
|
|| !/^[0-9a-f]{40}$/.test(action.marketplaceCommitSha)
|
|
@@ -2315,13 +2397,10 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
2315
2397
|
}
|
|
2316
2398
|
}
|
|
2317
2399
|
|
|
2318
|
-
// Verify the entry skill exists in the snapshot.
|
|
2319
|
-
//
|
|
2320
|
-
//
|
|
2321
|
-
//
|
|
2322
|
-
// entry skill via the manifest-declared skills root (MAJOR-4): the
|
|
2323
|
-
// root is validated + realpath-contained, and omitted `skills` means
|
|
2324
|
-
// the official single-skill root SKILL.md.
|
|
2400
|
+
// Verify the entry skill exists in the snapshot. Kimi/CodeBuddy use
|
|
2401
|
+
// their existing manifest-specific rules; Codex resolves its own
|
|
2402
|
+
// manifest-declared skills root below. Claude keeps its established
|
|
2403
|
+
// conventional skills/ lookup and prefixed fallback.
|
|
2325
2404
|
if (platformRoute.route === 'human-attestation') {
|
|
2326
2405
|
const resolveEntrySkillFile = consumer === 'codebuddy'
|
|
2327
2406
|
? resolveCodeBuddyEntrySkillFile
|
|
@@ -2335,6 +2414,17 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
2335
2414
|
error: `entry skill not resolvable in snapshot: ${entryErr.message}`,
|
|
2336
2415
|
});
|
|
2337
2416
|
}
|
|
2417
|
+
} else if (consumer === 'codex') {
|
|
2418
|
+
const pluginRootForSkill = pluginRootForEntrySkill || snapshotDirReal;
|
|
2419
|
+
try {
|
|
2420
|
+
await resolveCodexEntrySkillFile(pluginRootForSkill, action.entrySkill, action.plugin);
|
|
2421
|
+
} catch (entryErr) {
|
|
2422
|
+
return createResult({
|
|
2423
|
+
actionType,
|
|
2424
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
2425
|
+
error: `entry skill not resolvable in snapshot: ${entryErr.message}`,
|
|
2426
|
+
});
|
|
2427
|
+
}
|
|
2338
2428
|
} else {
|
|
2339
2429
|
// Entry skill 相对插件根解析(嵌套布局时 pluginRoot != snapshotRoot)。
|
|
2340
2430
|
// pluginRootForEntrySkill 在非 external 结构化 CLI 路径中已确定;
|
|
@@ -3735,48 +3825,67 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
3735
3825
|
}
|
|
3736
3826
|
|
|
3737
3827
|
// Verify entry skill exists as a regular file in install dir.
|
|
3738
|
-
//
|
|
3739
|
-
//
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3828
|
+
// Codex uses its actual installed plugin manifest for the skills
|
|
3829
|
+
// root, keeping observation aligned with frozen-source preflight.
|
|
3830
|
+
if (consumer === 'codex') {
|
|
3831
|
+
try {
|
|
3832
|
+
await resolveCodexEntrySkillFile(installPath, action.entrySkill, action.plugin);
|
|
3833
|
+
} catch (entryErr) {
|
|
3834
|
+
return createResult({
|
|
3835
|
+
actionType,
|
|
3836
|
+
status: ActionStatus.OBSERVED,
|
|
3837
|
+
observation: {
|
|
3838
|
+
installed: true,
|
|
3839
|
+
installPath,
|
|
3840
|
+
entrySkillFound: false,
|
|
3841
|
+
error: `entry skill not resolvable in installed plugin: ${entryErr.message}`,
|
|
3842
|
+
},
|
|
3843
|
+
});
|
|
3747
3844
|
}
|
|
3748
|
-
}
|
|
3749
|
-
//
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
'skills',
|
|
3755
|
-
`${action.plugin}-${action.entrySkill}`,
|
|
3756
|
-
'SKILL.md',
|
|
3757
|
-
);
|
|
3845
|
+
} else {
|
|
3846
|
+
// Prefixed skill-directory fallback (0.5.1 entry-skill-prefix fix):
|
|
3847
|
+
// mirror the preflight resolution — `skills/<entrySkill>` first,
|
|
3848
|
+
// then `skills/<plugin>-<entrySkill>` when the plugin is known.
|
|
3849
|
+
let entrySkillPath = resolve(installPath, 'skills', action.entrySkill, 'SKILL.md');
|
|
3850
|
+
let entrySkillFound = false;
|
|
3758
3851
|
try {
|
|
3759
|
-
const skillStat = await lstat(
|
|
3852
|
+
const skillStat = await lstat(entrySkillPath);
|
|
3760
3853
|
if (skillStat.isFile() && !skillStat.isSymbolicLink()) {
|
|
3761
|
-
entrySkillPath = prefixedPath;
|
|
3762
3854
|
entrySkillFound = true;
|
|
3763
3855
|
}
|
|
3764
3856
|
} catch {
|
|
3765
|
-
//
|
|
3857
|
+
// entry skill not found in unprefixed layout
|
|
3766
3858
|
}
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
if (!entrySkillFound) {
|
|
3770
|
-
return createResult({
|
|
3771
|
-
actionType,
|
|
3772
|
-
status: ActionStatus.OBSERVED,
|
|
3773
|
-
observation: {
|
|
3774
|
-
installed: true,
|
|
3859
|
+
if (!entrySkillFound && action.plugin) {
|
|
3860
|
+
const prefixedPath = resolve(
|
|
3775
3861
|
installPath,
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3862
|
+
'skills',
|
|
3863
|
+
`${action.plugin}-${action.entrySkill}`,
|
|
3864
|
+
'SKILL.md',
|
|
3865
|
+
);
|
|
3866
|
+
try {
|
|
3867
|
+
const skillStat = await lstat(prefixedPath);
|
|
3868
|
+
if (skillStat.isFile() && !skillStat.isSymbolicLink()) {
|
|
3869
|
+
entrySkillPath = prefixedPath;
|
|
3870
|
+
entrySkillFound = true;
|
|
3871
|
+
}
|
|
3872
|
+
} catch {
|
|
3873
|
+
// both layouts miss; report below
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
|
|
3877
|
+
if (!entrySkillFound) {
|
|
3878
|
+
return createResult({
|
|
3879
|
+
actionType,
|
|
3880
|
+
status: ActionStatus.OBSERVED,
|
|
3881
|
+
observation: {
|
|
3882
|
+
installed: true,
|
|
3883
|
+
installPath,
|
|
3884
|
+
entrySkillFound: false,
|
|
3885
|
+
error: `entry skill not found: skills/${action.entrySkill}/SKILL.md`,
|
|
3886
|
+
},
|
|
3887
|
+
});
|
|
3888
|
+
}
|
|
3780
3889
|
}
|
|
3781
3890
|
|
|
3782
3891
|
// Bind the installed payload back to the sealed authority while
|
|
@@ -846,15 +846,30 @@ async function observeQoderPlugin(target, command, env, run) {
|
|
|
846
846
|
};
|
|
847
847
|
}
|
|
848
848
|
|
|
849
|
-
async function readQoderManifest(root, label) {
|
|
850
|
-
const
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
849
|
+
async function readQoderManifest(root, label, target) {
|
|
850
|
+
const candidates = [];
|
|
851
|
+
for (const manifestPath of ['.qoder-plugin/plugin.json', '.claude-plugin/plugin.json']) {
|
|
852
|
+
let text;
|
|
853
|
+
try {
|
|
854
|
+
text = await readFileContained(root, manifestPath, { encoding: 'utf8' });
|
|
855
|
+
} catch (error) {
|
|
856
|
+
if (error?.code === 'SFC2004' && error?.details?.kind === 'missing-resource') continue;
|
|
857
|
+
throw error;
|
|
858
|
+
}
|
|
859
|
+
const manifest = parseJson(text, `${label} (${manifestPath})`);
|
|
860
|
+
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
|
|
861
|
+
throw new Error(`${label} (${manifestPath}) did not return an object`);
|
|
862
|
+
}
|
|
863
|
+
await assertQoderPluginManifest(target, manifest, `${label} (${manifestPath})`, root);
|
|
864
|
+
candidates.push({ manifestPath, manifest });
|
|
865
|
+
}
|
|
866
|
+
if (candidates.length === 0) throw new Error(`${label} has no native or compatible plugin manifest`);
|
|
867
|
+
if (candidates.length === 2 && ['name', 'version'].some((field) => (
|
|
868
|
+
candidates[0].manifest[field] !== candidates[1].manifest[field]
|
|
869
|
+
))) {
|
|
870
|
+
throw new Error(`${label} does not match the frozen Qoder plugin identity and projection path: conflicting native and compatible manifests`);
|
|
856
871
|
}
|
|
857
|
-
return
|
|
872
|
+
return candidates[0];
|
|
858
873
|
}
|
|
859
874
|
|
|
860
875
|
async function assertQoderPluginManifest(target, manifest, label, pluginRoot, frozenSkills) {
|
|
@@ -880,41 +895,63 @@ async function assertFrozenQoderManifest(target, root) {
|
|
|
880
895
|
snapshotPath: target.snapshotPath,
|
|
881
896
|
expectedDigest: target.manifestDigest,
|
|
882
897
|
});
|
|
883
|
-
const manifest = await readQoderManifest(snapshotRoot, 'frozen Qoder plugin manifest');
|
|
884
|
-
|
|
898
|
+
const { manifestPath, manifest } = await readQoderManifest(snapshotRoot, 'frozen Qoder plugin manifest', target);
|
|
899
|
+
const skills = await assertQoderPluginManifest(target, manifest, 'frozen Qoder plugin manifest', snapshotRoot);
|
|
900
|
+
return { manifestPath, skills };
|
|
885
901
|
}
|
|
886
902
|
|
|
887
903
|
async function assertQoderHubEntry(target, marketplaceRoot) {
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
904
|
+
let found = false;
|
|
905
|
+
for (const indexPath of ['marketplace.json', '.claude-plugin/marketplace.json']) {
|
|
906
|
+
let text;
|
|
907
|
+
try {
|
|
908
|
+
text = await readFileContained(marketplaceRoot, indexPath, { encoding: 'utf8' });
|
|
909
|
+
} catch (error) {
|
|
910
|
+
if (error?.code === 'SFC2004' && error?.details?.kind === 'missing-resource') continue;
|
|
911
|
+
throw error;
|
|
912
|
+
}
|
|
913
|
+
found = true;
|
|
914
|
+
const marketplace = parseJson(text, `Qoder Hub marketplace manifest (${indexPath})`);
|
|
915
|
+
if (!marketplace || typeof marketplace !== 'object' || Array.isArray(marketplace)
|
|
916
|
+
|| marketplace.name !== target.marketplace || !Array.isArray(marketplace.plugins)) {
|
|
917
|
+
throw new Error('Qoder Hub marketplace manifest does not match the frozen marketplace');
|
|
918
|
+
}
|
|
919
|
+
if (indexPath === '.claude-plugin/marketplace.json') {
|
|
920
|
+
if (target.hub.githubHost !== 'github.com' || typeof target.frozenTag !== 'string' || target.frozenTag.length === 0) {
|
|
921
|
+
throw new Error('Qoder compatible Hub entry requires a frozen GitHub tag identity');
|
|
922
|
+
}
|
|
923
|
+
await assertHubHostEntry({ ...target, host: 'claude', pluginTag: target.frozenTag, githubHost: target.hub.githubHost }, marketplaceRoot);
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
const matches = marketplace.plugins.filter((entry) => entry?.name === target.plugin);
|
|
927
|
+
if (matches.length !== 1) {
|
|
928
|
+
throw new Error(`Qoder Hub marketplace manifest must contain exactly one ${target.plugin} entry`);
|
|
929
|
+
}
|
|
930
|
+
const entry = matches[0];
|
|
931
|
+
const source = entry.source;
|
|
932
|
+
if (source?.source !== 'url'
|
|
933
|
+
|| normalizeHubGitSource(source.url, target.hub.githubHost) !== target.pluginRepo
|
|
934
|
+
|| source.sha !== target.pluginCommit
|
|
935
|
+
|| (entry.version !== undefined && entry.version !== target.version)
|
|
936
|
+
|| (source.ref !== undefined && (typeof target.frozenTag !== 'string' || source.ref !== target.frozenTag))) {
|
|
937
|
+
throw new Error('Qoder Hub entry does not match the frozen public source identity');
|
|
938
|
+
}
|
|
906
939
|
}
|
|
940
|
+
if (!found) throw new Error('Qoder Hub has no native or compatible marketplace manifest');
|
|
907
941
|
}
|
|
908
942
|
|
|
909
943
|
async function verifyQoderInstalledPayload({
|
|
910
944
|
target,
|
|
911
945
|
root,
|
|
912
946
|
installPath,
|
|
913
|
-
|
|
947
|
+
frozenManifest,
|
|
914
948
|
verifyInstalledPayload,
|
|
915
949
|
}) {
|
|
916
|
-
const manifest = await readQoderManifest(installPath, 'installed Qoder plugin manifest');
|
|
917
|
-
|
|
950
|
+
const { manifestPath, manifest } = await readQoderManifest(installPath, 'installed Qoder plugin manifest', target);
|
|
951
|
+
if (manifestPath !== frozenManifest.manifestPath) {
|
|
952
|
+
throw new Error('installed Qoder plugin manifest path differs from the frozen manifest selection');
|
|
953
|
+
}
|
|
954
|
+
await assertQoderPluginManifest(target, manifest, 'installed Qoder plugin manifest', installPath, frozenManifest.skills);
|
|
918
955
|
await verifyInstalledPayload({
|
|
919
956
|
snapshotPath: target.snapshotPath,
|
|
920
957
|
manifestDigest: target.manifestDigest,
|
|
@@ -928,7 +965,7 @@ async function runQoderUpdate(target, detected, run, {
|
|
|
928
965
|
verifyInstalledPayload,
|
|
929
966
|
}) {
|
|
930
967
|
const env = hostEnvironment('qoder');
|
|
931
|
-
const
|
|
968
|
+
const frozenManifest = await assertFrozenQoderManifest(target, root);
|
|
932
969
|
const marketplace = await observeQoderMarketplace(target, detected.command, env, run);
|
|
933
970
|
if (!marketplace.installed) {
|
|
934
971
|
return {
|
|
@@ -949,7 +986,7 @@ async function runQoderUpdate(target, detected, run, {
|
|
|
949
986
|
target,
|
|
950
987
|
root,
|
|
951
988
|
installPath: before.installPath,
|
|
952
|
-
|
|
989
|
+
frozenManifest,
|
|
953
990
|
verifyInstalledPayload,
|
|
954
991
|
});
|
|
955
992
|
return { status: 'ALREADY_CURRENT', version: target.version };
|
|
@@ -973,7 +1010,7 @@ async function runQoderUpdate(target, detected, run, {
|
|
|
973
1010
|
target,
|
|
974
1011
|
root,
|
|
975
1012
|
installPath: after.installPath,
|
|
976
|
-
|
|
1013
|
+
frozenManifest,
|
|
977
1014
|
verifyInstalledPayload,
|
|
978
1015
|
});
|
|
979
1016
|
return {
|