release-skill 0.2.2 → 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 +19 -0
- package/INSTALL.md +7 -7
- package/INSTALL.zh-CN.md +7 -7
- package/README.md +11 -13
- package/README.zh-CN.md +10 -12
- 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 +556 -21
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +556 -21
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +556 -21
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +556 -21
- package/bin/release-skill.bundle.mjs +556 -21
- package/package.json +1 -1
- package/src/adapters/plugin-marketplace.mjs +272 -30
- package/src/commands/prepare.mjs +69 -0
- package/src/commands/verify.mjs +11 -5
- package/src/core/baseline.mjs +13 -0
- package/src/core/plan.mjs +178 -0
- package/src/platforms/codebuddy.mjs +40 -0
- package/src/platforms/registry.mjs +158 -0
- package/src/producers/build-adapters.mjs +9 -2
package/package.json
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
|
|
32
32
|
import { createHash } from 'node:crypto';
|
|
33
33
|
import { computeFrozenSnapshot, resolveFrozenPath } from '../snapshot/frozen.mjs';
|
|
34
|
-
import { PLATFORMS, getPlatform } from '../platforms/registry.mjs';
|
|
34
|
+
import { PLATFORMS, getPlatform, resolvePlatformRoute, resolveCapabilityConflicts } from '../platforms/registry.mjs';
|
|
35
35
|
import {
|
|
36
36
|
KIMI_REQUIREMENT_FILE,
|
|
37
37
|
KIMI_ATTESTATION_FILE,
|
|
@@ -448,21 +448,37 @@ async function resolveKimiEntrySkillFile(pluginRootReal, manifest, entrySkill) {
|
|
|
448
448
|
* @returns {string} normalized relative path ('' for the plugin root itself)
|
|
449
449
|
*/
|
|
450
450
|
function normalizeCodeBuddySkillsRel(skillsRaw) {
|
|
451
|
-
|
|
451
|
+
// CodeBuddy real validator accepts array form; extract first element for
|
|
452
|
+
// backward compatibility with string form.
|
|
453
|
+
let skillsPath;
|
|
454
|
+
if (Array.isArray(skillsRaw)) {
|
|
455
|
+
if (skillsRaw.length !== 1) {
|
|
456
|
+
throw new Error(
|
|
457
|
+
`codebuddy manifest skills array must have exactly one element, got ${skillsRaw.length}`,
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
skillsPath = skillsRaw[0];
|
|
461
|
+
} else if (typeof skillsRaw === 'string') {
|
|
462
|
+
skillsPath = skillsRaw;
|
|
463
|
+
} else {
|
|
464
|
+
throw new Error('codebuddy manifest skills must be a string or single-element array when present');
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (typeof skillsPath !== 'string' || skillsPath.length === 0) {
|
|
452
468
|
throw new Error('codebuddy manifest skills must be a non-empty relative path when present');
|
|
453
469
|
}
|
|
454
470
|
if (
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
/^https?:\/\//i.test(
|
|
471
|
+
skillsPath.startsWith('/') ||
|
|
472
|
+
skillsPath.includes('..') ||
|
|
473
|
+
skillsPath.includes('\\') ||
|
|
474
|
+
/^https?:\/\//i.test(skillsPath)
|
|
459
475
|
) {
|
|
460
|
-
throw new Error(`codebuddy manifest skills "${
|
|
476
|
+
throw new Error(`codebuddy manifest skills "${skillsPath}" is not a safe relative path`);
|
|
461
477
|
}
|
|
462
|
-
let rel =
|
|
478
|
+
let rel = skillsPath.replace(/^\.\//, '');
|
|
463
479
|
rel = rel.replace(/\/+$/, '');
|
|
464
480
|
if (rel.split('/').some((segment) => segment === '' || segment === '.' || segment === '..')) {
|
|
465
|
-
throw new Error(`codebuddy manifest skills "${
|
|
481
|
+
throw new Error(`codebuddy manifest skills "${skillsPath}" is not a safe relative path`);
|
|
466
482
|
}
|
|
467
483
|
return rel;
|
|
468
484
|
}
|
|
@@ -551,6 +567,9 @@ const SUPPORTED_TYPES = [
|
|
|
551
567
|
/** Safe repo pattern: owner/repo with alphanumeric, hyphens, dots, underscores. */
|
|
552
568
|
const SAFE_REPO_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*\/[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
553
569
|
|
|
570
|
+
/** Safe digest pattern: 64-hex SHA-256. */
|
|
571
|
+
const SAFE_DIGEST_RE = /^[0-9a-f]{64}$/;
|
|
572
|
+
|
|
554
573
|
/** Valid consumer ids, derived from the platform registry (single source). */
|
|
555
574
|
const CONSUMER_IDS = new Set(PLATFORMS.map((p) => p.id));
|
|
556
575
|
|
|
@@ -634,7 +653,9 @@ function validateMarketplaceParams(params) {
|
|
|
634
653
|
// marketplace but NO non-interactive install API, so `marketplace` carries no
|
|
635
654
|
// executable meaning for kimi and is optional (validated only if present); it
|
|
636
655
|
// must not become a required identity condition for kimi execution/observe.
|
|
637
|
-
|
|
656
|
+
const consumerPlatform = getPlatform(consumer);
|
|
657
|
+
const consumerRoute = resolvePlatformRoute(consumerPlatform);
|
|
658
|
+
if (consumerRoute.route === 'human-attestation') {
|
|
638
659
|
if (marketplace !== undefined && marketplace !== null && !SAFE_ID_RE.test(marketplace)) {
|
|
639
660
|
return { valid: false, error: `unsafe marketplace identifier: "${marketplace}"` };
|
|
640
661
|
}
|
|
@@ -881,9 +902,224 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
881
902
|
});
|
|
882
903
|
}
|
|
883
904
|
|
|
884
|
-
//
|
|
905
|
+
// Capability conflict check (before sourceDescriptor validation).
|
|
906
|
+
// If the platform definition is internally inconsistent, fail closed.
|
|
885
907
|
const consumer = action.consumer;
|
|
908
|
+
const consumerPlatform = getPlatform(consumer);
|
|
909
|
+
const capabilityConflicts = resolveCapabilityConflicts(consumerPlatform);
|
|
910
|
+
if (capabilityConflicts.length > 0) {
|
|
911
|
+
return createResult({
|
|
912
|
+
actionType,
|
|
913
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
914
|
+
error: `platform capability conflict: ${capabilityConflicts.join('; ')}`,
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// 6. sourceDescriptor validation (structured-cli route only).
|
|
919
|
+
// Human-attestation platforms (kimi, codebuddy) are exempt: they have no
|
|
920
|
+
// scriptable install CLI, so the source descriptor's conflict
|
|
921
|
+
// detection is irrelevant to their manual-requirement workflow.
|
|
922
|
+
const route = resolvePlatformRoute(consumerPlatform);
|
|
923
|
+
if (route.route === 'structured-cli') {
|
|
924
|
+
const sd = action.sourceDescriptor;
|
|
925
|
+
if (!sd || typeof sd !== 'object') {
|
|
926
|
+
return createResult({
|
|
927
|
+
actionType,
|
|
928
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
929
|
+
error: 'sourceDescriptor is required for marketplace install',
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// Form consistency: marketplaceForm must agree with sourceDescriptor.form.
|
|
934
|
+
if (sd.form !== action.marketplaceForm) {
|
|
935
|
+
return createResult({
|
|
936
|
+
actionType,
|
|
937
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
938
|
+
error: `sourceDescriptor.form "${sd.form}" does not match marketplaceForm "${action.marketplaceForm}"`,
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// payloadDigest: must be a valid 64-char lowercase hex string and
|
|
943
|
+
// must not be the null hash (all zeros).
|
|
944
|
+
if (typeof sd.payloadDigest !== 'string' || sd.payloadDigest.length === 0) {
|
|
945
|
+
return createResult({
|
|
946
|
+
actionType,
|
|
947
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
948
|
+
error: 'sourceDescriptor.payloadDigest is required',
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
if (!/^[a-f0-9]{64}$/.test(sd.payloadDigest)) {
|
|
952
|
+
return createResult({
|
|
953
|
+
actionType,
|
|
954
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
955
|
+
error: 'sourceDescriptor.payloadDigest must be a 64-char lowercase hex string',
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
if (sd.payloadDigest === '0'.repeat(64)) {
|
|
959
|
+
return createResult({
|
|
960
|
+
actionType,
|
|
961
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
962
|
+
error: 'sourceDescriptor.payloadDigest must not be the null hash',
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// marketplaceEntry: must match the action plugin name.
|
|
967
|
+
if (typeof sd.marketplaceEntry !== 'string' || sd.marketplaceEntry.length === 0) {
|
|
968
|
+
return createResult({
|
|
969
|
+
actionType,
|
|
970
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
971
|
+
error: 'sourceDescriptor.marketplaceEntry is required',
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
if (sd.marketplaceEntry !== action.plugin) {
|
|
975
|
+
return createResult({
|
|
976
|
+
actionType,
|
|
977
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
978
|
+
error: `sourceDescriptor.marketplaceEntry "${sd.marketplaceEntry}" does not match action plugin "${action.plugin}"`,
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Form-specific field validation.
|
|
983
|
+
if (sd.form === 'bundled-family') {
|
|
984
|
+
if (!sd.repo || !SAFE_REPO_RE.test(sd.repo)) {
|
|
985
|
+
return createResult({
|
|
986
|
+
actionType,
|
|
987
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
988
|
+
error: `sourceDescriptor.repo is required and must be a safe repo pattern`,
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
if (sd.repo !== action.repo) {
|
|
992
|
+
return createResult({
|
|
993
|
+
actionType,
|
|
994
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
995
|
+
error: `sourceDescriptor.repo "${sd.repo}" does not match action.repo "${action.repo}"`,
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
// commit 可选,但如果存在则必须是 40-hex 格式
|
|
999
|
+
if (sd.commit !== undefined && (typeof sd.commit !== 'string' || !/^[0-9a-f]{40}$/.test(sd.commit))) {
|
|
1000
|
+
return createResult({
|
|
1001
|
+
actionType,
|
|
1002
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1003
|
+
error: 'sourceDescriptor.commit must be a 40-hex commit sha for bundled-family form',
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
// commit 交叉校验:bundled sourceDescriptor.commit 必须与
|
|
1007
|
+
// action.sourceCommit(冻结的插件来源提交)一致
|
|
1008
|
+
if (sd.commit !== undefined && action.sourceCommit && sd.commit !== action.sourceCommit) {
|
|
1009
|
+
return createResult({
|
|
1010
|
+
actionType,
|
|
1011
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1012
|
+
error: `sourceDescriptor.commit "${sd.commit}" does not match action.sourceCommit "${action.sourceCommit}"`,
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
// payloadDigest 格式校验(可选,但如果存在则必须是 64-hex)
|
|
1016
|
+
if (sd.payloadDigest !== undefined && (!sd.payloadDigest || !SAFE_DIGEST_RE.test(sd.payloadDigest))) {
|
|
1017
|
+
return createResult({
|
|
1018
|
+
actionType,
|
|
1019
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1020
|
+
error: 'sourceDescriptor.payloadDigest is required for bundled-family form',
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
// payloadDigest 交叉校验:必须与 action.manifestDigest(冻结的载荷摘要)一致
|
|
1024
|
+
if (sd.payloadDigest !== undefined && action.manifestDigest && sd.payloadDigest !== action.manifestDigest) {
|
|
1025
|
+
return createResult({
|
|
1026
|
+
actionType,
|
|
1027
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1028
|
+
error: `sourceDescriptor.payloadDigest does not match action.manifestDigest`,
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
if (typeof sd.pluginSubpath !== 'string' || sd.pluginSubpath.length === 0) {
|
|
1032
|
+
return createResult({
|
|
1033
|
+
actionType,
|
|
1034
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1035
|
+
error: 'sourceDescriptor.pluginSubpath is required for bundled-family form',
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
} else if (sd.form === 'standalone-index') {
|
|
1039
|
+
if (!sd.pluginRepo || !SAFE_REPO_RE.test(sd.pluginRepo)) {
|
|
1040
|
+
return createResult({
|
|
1041
|
+
actionType,
|
|
1042
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1043
|
+
error: `sourceDescriptor.pluginRepo is required for standalone-index form`,
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
if (!sd.marketplaceRepo || !SAFE_REPO_RE.test(sd.marketplaceRepo)) {
|
|
1047
|
+
return createResult({
|
|
1048
|
+
actionType,
|
|
1049
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1050
|
+
error: 'sourceDescriptor.marketplaceRepo is required for standalone-index form',
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
// 独立市场身份交叉校验:
|
|
1054
|
+
// 1. marketplaceRepo 必须等于 action.repo(市场仓库身份)
|
|
1055
|
+
if (sd.marketplaceRepo !== action.repo) {
|
|
1056
|
+
return createResult({
|
|
1057
|
+
actionType,
|
|
1058
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1059
|
+
error: `sourceDescriptor.marketplaceRepo "${sd.marketplaceRepo}" does not match action.repo "${action.repo}"`,
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
// 2. pluginRepo 必须与 marketplaceRepo 不同(发布单元仓库 ≠ 市场仓库)
|
|
1063
|
+
if (sd.pluginRepo === sd.marketplaceRepo) {
|
|
1064
|
+
return createResult({
|
|
1065
|
+
actionType,
|
|
1066
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1067
|
+
error: `sourceDescriptor.pluginRepo "${sd.pluginRepo}" must differ from marketplaceRepo "${sd.marketplaceRepo}"`,
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
if (
|
|
1071
|
+
typeof sd.marketplaceCommitSha !== 'string'
|
|
1072
|
+
|| !/^[0-9a-f]{40}$/.test(sd.marketplaceCommitSha)
|
|
1073
|
+
) {
|
|
1074
|
+
return createResult({
|
|
1075
|
+
actionType,
|
|
1076
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1077
|
+
error: 'sourceDescriptor.marketplaceCommitSha must be a 40-hex commit sha for standalone-index form',
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
// 3. marketplaceCommitSha 交叉校验(与 action 层一致)
|
|
1081
|
+
if (action.marketplaceCommitSha && sd.marketplaceCommitSha !== action.marketplaceCommitSha) {
|
|
1082
|
+
return createResult({
|
|
1083
|
+
actionType,
|
|
1084
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1085
|
+
error: `sourceDescriptor.marketplaceCommitSha "${sd.marketplaceCommitSha}" does not match action.marketplaceCommitSha "${action.marketplaceCommitSha}"`,
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
if (typeof sd.ref !== 'string' || sd.ref.length === 0) {
|
|
1089
|
+
return createResult({
|
|
1090
|
+
actionType,
|
|
1091
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1092
|
+
error: 'sourceDescriptor.ref is required for standalone-index form',
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
// 4. payloadDigest 格式校验(可选,但如果存在则必须是 64-hex)
|
|
1096
|
+
if (sd.payloadDigest !== undefined && (!sd.payloadDigest || !SAFE_DIGEST_RE.test(sd.payloadDigest))) {
|
|
1097
|
+
return createResult({
|
|
1098
|
+
actionType,
|
|
1099
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1100
|
+
error: 'sourceDescriptor.payloadDigest must be a 64-hex digest for standalone-index form',
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
// 5. payloadDigest 交叉校验:必须与 action.manifestDigest(冻结的载荷摘要)一致
|
|
1104
|
+
if (sd.payloadDigest !== undefined && action.manifestDigest && sd.payloadDigest !== action.manifestDigest) {
|
|
1105
|
+
return createResult({
|
|
1106
|
+
actionType,
|
|
1107
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1108
|
+
error: `sourceDescriptor.payloadDigest does not match action.manifestDigest`,
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
} else {
|
|
1112
|
+
return createResult({
|
|
1113
|
+
actionType,
|
|
1114
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1115
|
+
error: `sourceDescriptor.form "${sd.form}" is not a recognized form (expected "bundled-family" or "standalone-index")`,
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// 7. Verify frozen snapshot exists and contains required marketplace files
|
|
886
1121
|
const platform = getPlatform(consumer);
|
|
1122
|
+
const platformRoute = resolvePlatformRoute(platform);
|
|
887
1123
|
let snapshotDirReal;
|
|
888
1124
|
// Authoritative kimi manifest (from the frozen snapshot), used to
|
|
889
1125
|
// resolve the entry skill via the manifest-declared skills root.
|
|
@@ -898,12 +1134,12 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
898
1134
|
});
|
|
899
1135
|
}
|
|
900
1136
|
|
|
901
|
-
// A
|
|
1137
|
+
// A human-attestation platform (kimi, codebuddy) has no trustworthy
|
|
902
1138
|
// automated install: the whole repo is installed as one plugin. The
|
|
903
1139
|
// authoritative manifest is read from the verified snapshot root via
|
|
904
1140
|
// the platform strategy (kimi: kimi.plugin.json over
|
|
905
1141
|
// .kimi-plugin/plugin.json; codebuddy: .codebuddy-plugin/plugin.json).
|
|
906
|
-
if (
|
|
1142
|
+
if (platformRoute.route === 'human-attestation') {
|
|
907
1143
|
// Error-message label keeps the kimi wording byte-identical while
|
|
908
1144
|
// giving codebuddy its own wording.
|
|
909
1145
|
const manifestLabel = consumer === 'codebuddy' ? 'codebuddy' : 'kimi';
|
|
@@ -935,7 +1171,7 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
935
1171
|
kimiSnapshotManifest = kimiManifest;
|
|
936
1172
|
}
|
|
937
1173
|
|
|
938
|
-
if (
|
|
1174
|
+
if (platformRoute.route === 'structured-cli') {
|
|
939
1175
|
if (action.marketplaceLocation === 'external') {
|
|
940
1176
|
// External independent marketplace form: the marketplace index lives
|
|
941
1177
|
// in the external repository (frozen by prepare), NOT in the unit
|
|
@@ -1128,13 +1364,13 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1128
1364
|
}
|
|
1129
1365
|
|
|
1130
1366
|
// Verify the entry skill exists in the snapshot.
|
|
1131
|
-
//
|
|
1367
|
+
// Structured-cli platforms' manifests always declare ./skills/, so the
|
|
1132
1368
|
// fixed skills/<entrySkill>/SKILL.md layout is authoritative for
|
|
1133
|
-
// them. A
|
|
1369
|
+
// them. A human-attestation platform (kimi, codebuddy) resolves the
|
|
1134
1370
|
// entry skill via the manifest-declared skills root (MAJOR-4): the
|
|
1135
1371
|
// root is validated + realpath-contained, and omitted `skills` means
|
|
1136
1372
|
// the official single-skill root SKILL.md.
|
|
1137
|
-
if (
|
|
1373
|
+
if (platformRoute.route === 'human-attestation') {
|
|
1138
1374
|
const resolveEntrySkillFile = consumer === 'codebuddy'
|
|
1139
1375
|
? resolveCodeBuddyEntrySkillFile
|
|
1140
1376
|
: resolveKimiEntrySkillFile;
|
|
@@ -1341,10 +1577,11 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1341
1577
|
// It is handled entirely by its manual-requirement strategy, which
|
|
1342
1578
|
// uses a stable plan-digest-keyed home and deliberately SKIPS the
|
|
1343
1579
|
// per-run isolated consumer dir and its runDir containment check
|
|
1344
|
-
// (that model only fits
|
|
1580
|
+
// (that model only fits structured-cli platforms, which exec a real
|
|
1345
1581
|
// CLI into a per-run HOME).
|
|
1346
1582
|
const platform = getPlatform(action.consumer);
|
|
1347
|
-
|
|
1583
|
+
const executeRoute = resolvePlatformRoute(platform);
|
|
1584
|
+
if (executeRoute.route === 'human-attestation') {
|
|
1348
1585
|
return platform.strategy.buildManualRequirement(action, context);
|
|
1349
1586
|
}
|
|
1350
1587
|
|
|
@@ -1662,19 +1899,22 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1662
1899
|
});
|
|
1663
1900
|
}
|
|
1664
1901
|
const isolatedHome = resolve(runDir, 'consumers', `${consumer}-${action.plugin}`);
|
|
1665
|
-
// Registry-driven platform data.
|
|
1666
|
-
//
|
|
1667
|
-
//
|
|
1668
|
-
// kimi-shaped env
|
|
1669
|
-
// still fails closed on the missing execute evidence below.
|
|
1902
|
+
// Registry-driven platform data. An unregistered consumer is a hard
|
|
1903
|
+
// error: the platform registry is the single source of truth for
|
|
1904
|
+
// consumer-platform knowledge, and silently falling through to a
|
|
1905
|
+
// kimi-shaped env would mask configuration mistakes.
|
|
1670
1906
|
const platform = PLATFORMS.find((p) => p.id === consumer) ?? null;
|
|
1671
|
-
|
|
1907
|
+
if (!platform) {
|
|
1908
|
+
throw new Error(
|
|
1909
|
+
`Unknown consumer platform "${consumer}" for action "${actionType}". `
|
|
1910
|
+
+ `Registered platforms: ${PLATFORMS.map((p) => p.id).join(', ')}`,
|
|
1911
|
+
);
|
|
1912
|
+
}
|
|
1913
|
+
const cliCmd = platform.cli ? platform.cli.binary : null;
|
|
1672
1914
|
const baseEnv = { ...process.env, ...(context.env ?? {}) };
|
|
1673
1915
|
const env = {
|
|
1674
1916
|
...baseEnv,
|
|
1675
|
-
...(
|
|
1676
|
-
? platform.isolationEnv(isolatedHome)
|
|
1677
|
-
: { HOME: isolatedHome, KIMI_CODE_HOME: isolatedHome }),
|
|
1917
|
+
...platform.isolationEnv(isolatedHome),
|
|
1678
1918
|
};
|
|
1679
1919
|
|
|
1680
1920
|
// Resolve frozen timeoutMs from the expanded action (top-level).
|
|
@@ -1701,7 +1941,8 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1701
1941
|
// root (MAJOR-4), and manifest name/version.
|
|
1702
1942
|
// Missing/expired/mismatched/escaping proof fails closed so a kimi
|
|
1703
1943
|
// unit can never reach VERIFIED without it.
|
|
1704
|
-
|
|
1944
|
+
const kimiRoute = platform ? resolvePlatformRoute(platform) : null;
|
|
1945
|
+
if (platform && kimiRoute?.route === 'human-attestation' && actionType === ActionType.KIMI_MARKETPLACE_INSTALL) {
|
|
1705
1946
|
const expectedRef = action.ref ?? `v${action.version}`;
|
|
1706
1947
|
|
|
1707
1948
|
// Bind to the REAL frozen plan digest (A). Fail closed if the
|
|
@@ -1994,7 +2235,8 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1994
2235
|
// manifest skills root, and manifest name/version. Missing/expired/
|
|
1995
2236
|
// mismatched/escaping proof fails closed so a codebuddy unit can never
|
|
1996
2237
|
// reach VERIFIED without it.
|
|
1997
|
-
|
|
2238
|
+
const codebuddyRoute = platform ? resolvePlatformRoute(platform) : null;
|
|
2239
|
+
if (platform && codebuddyRoute?.route === 'human-attestation' && actionType === ActionType.CODEBUDDY_MARKETPLACE_INSTALL) {
|
|
1998
2240
|
const expectedRef = action.ref ?? `v${action.version}`;
|
|
1999
2241
|
|
|
2000
2242
|
// Bind to the REAL frozen plan digest (A). Fail closed if the
|
package/src/commands/prepare.mjs
CHANGED
|
@@ -1258,6 +1258,39 @@ export function buildExternalActions(unitResults, resolvedVersions, productionAs
|
|
|
1258
1258
|
// but no frozen ref/marketplaceCommitSha (production-only bindings),
|
|
1259
1259
|
// keeping the two loops' shapes aligned for plan completeness.
|
|
1260
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;
|
|
1261
1294
|
actions.push({
|
|
1262
1295
|
id: `${platform.actionType}-${unit.id}`,
|
|
1263
1296
|
type: platform.actionType,
|
|
@@ -1279,6 +1312,8 @@ export function buildExternalActions(unitResults, resolvedVersions, productionAs
|
|
|
1279
1312
|
// contract (whole-tree '.' containment; see plugin-marketplace).
|
|
1280
1313
|
payloadContract: externalMarketplace ? 'external-marketplace-v1' : 'declared-manifest-v1',
|
|
1281
1314
|
...(externalMarketplace ? { marketplaceLocation: 'external' } : {}),
|
|
1315
|
+
...(marketplaceForm ? { marketplaceForm } : {}),
|
|
1316
|
+
...(sourceDescriptor ? { sourceDescriptor } : {}),
|
|
1282
1317
|
},
|
|
1283
1318
|
expected: {
|
|
1284
1319
|
installed: true,
|
|
@@ -1450,6 +1485,34 @@ export function buildExternalActions(unitResults, resolvedVersions, productionAs
|
|
|
1450
1485
|
// snapshot, unchanged. Inline form (no marketplaceRepo) is byte-identical.
|
|
1451
1486
|
const externalMarketplace = dist.marketplaceRepo !== undefined && dist.marketplaceRepo !== null;
|
|
1452
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;
|
|
1453
1516
|
actions.push({
|
|
1454
1517
|
id: `${platform.actionType}-${unit.id}`,
|
|
1455
1518
|
type: platform.actionType,
|
|
@@ -1474,6 +1537,12 @@ export function buildExternalActions(unitResults, resolvedVersions, productionAs
|
|
|
1474
1537
|
// contract (whole-tree '.' containment; see plugin-marketplace).
|
|
1475
1538
|
payloadContract: externalMarketplace ? 'external-marketplace-v1' : 'declared-manifest-v1',
|
|
1476
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,
|
|
1477
1546
|
},
|
|
1478
1547
|
expected: {
|
|
1479
1548
|
installed: true,
|
package/src/commands/verify.mjs
CHANGED
|
@@ -849,7 +849,9 @@ export async function verifyRelease(options) {
|
|
|
849
849
|
? 'claude-plugin'
|
|
850
850
|
: action.type === 'codex-marketplace-install'
|
|
851
851
|
? 'codex-plugin'
|
|
852
|
-
: '
|
|
852
|
+
: action.type === 'codebuddy-marketplace-install'
|
|
853
|
+
? 'codebuddy-plugin'
|
|
854
|
+
: 'kimi-plugin';
|
|
853
855
|
const installPath = verifyResult.observation?.installPath;
|
|
854
856
|
consumerGateResults.push(...await runConsumerVerificationGates({
|
|
855
857
|
plan,
|
|
@@ -868,10 +870,14 @@ export async function verifyRelease(options) {
|
|
|
868
870
|
HOME: resolve(runDir, 'consumers', `codex-${action.parameters.plugin}`),
|
|
869
871
|
CODEX_HOME: resolve(runDir, 'consumers', `codex-${action.parameters.plugin}`),
|
|
870
872
|
}
|
|
871
|
-
:
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
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
|
+
},
|
|
875
881
|
}));
|
|
876
882
|
} else {
|
|
877
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,
|