release-skill 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codebuddy-plugin/plugin.json +4 -2
  4. package/.codex-plugin/plugin.json +2 -2
  5. package/.kimi-plugin/plugin.json +1 -1
  6. package/CHANGELOG.md +40 -0
  7. package/INSTALL.md +183 -21
  8. package/INSTALL.zh-CN.md +160 -16
  9. package/README.md +112 -13
  10. package/README.zh-CN.md +72 -11
  11. package/adapters/claude/.claude-plugin/marketplace.json +1 -1
  12. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  13. package/adapters/claude/bin/release-skill.bundle.mjs +2240 -587
  14. package/adapters/claude/schemas/release-plan.schema.json +44 -2
  15. package/adapters/claude/schemas/release-project.schema.json +46 -4
  16. package/adapters/claude/schemas/release-run.schema.json +1 -0
  17. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  18. package/adapters/codex/bin/release-skill.bundle.mjs +2240 -587
  19. package/adapters/codex/schemas/release-plan.schema.json +44 -2
  20. package/adapters/codex/schemas/release-project.schema.json +46 -4
  21. package/adapters/codex/schemas/release-run.schema.json +1 -0
  22. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  23. package/adapters/kimi/bin/release-skill.bundle.mjs +2240 -587
  24. package/adapters/kimi/schemas/release-plan.schema.json +44 -2
  25. package/adapters/kimi/schemas/release-project.schema.json +46 -4
  26. package/adapters/kimi/schemas/release-run.schema.json +1 -0
  27. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +4 -2
  28. package/adapters/workbuddy/bin/release-skill.bundle.mjs +2240 -587
  29. package/adapters/workbuddy/schemas/release-plan.schema.json +44 -2
  30. package/adapters/workbuddy/schemas/release-project.schema.json +46 -4
  31. package/adapters/workbuddy/schemas/release-run.schema.json +1 -0
  32. package/bin/release-skill-cli.mjs +46 -4
  33. package/bin/release-skill.bundle.mjs +2240 -587
  34. package/package.json +1 -1
  35. package/references/02-project-config.md +7 -0
  36. package/references/06-adapter-contract.md +21 -2
  37. package/schemas/release-plan.schema.json +44 -2
  38. package/schemas/release-project.schema.json +46 -4
  39. package/schemas/release-run.schema.json +1 -0
  40. package/scripts/sync-public-files.mjs +8 -4
  41. package/src/adapters/contract.mjs +1 -0
  42. package/src/adapters/plugin-marketplace.mjs +796 -41
  43. package/src/commands/assess.mjs +50 -1
  44. package/src/commands/prepare.mjs +342 -9
  45. package/src/commands/publish.mjs +1 -0
  46. package/src/commands/reconcile.mjs +1 -0
  47. package/src/commands/setup.mjs +7 -3
  48. package/src/commands/verify.mjs +13 -5
  49. package/src/core/baseline.mjs +13 -0
  50. package/src/core/checkpoints.mjs +7 -2
  51. package/src/core/plan.mjs +275 -4
  52. package/src/core/verification-gates.mjs +1 -1
  53. package/src/platforms/codebuddy.mjs +658 -0
  54. package/src/platforms/registry.mjs +258 -5
  55. package/src/producers/build-adapters.mjs +30 -15
package/src/core/plan.mjs CHANGED
@@ -776,6 +776,19 @@ export function validatePlanActionCompleteness(plan, options = {}) {
776
776
  );
777
777
  }
778
778
 
779
+ // External independent marketplace form: the distribution declares
780
+ // marketplaceRepo, so the install targets the external marketplace
781
+ // repository instead of publicRepo. Only platforms with a marketplace
782
+ // add capability (marketplaceRefForm !== null: claude=name, codex=sha)
783
+ // may carry it; kimi/codebuddy fail closed. Inline form (no
784
+ // marketplaceRepo) keeps every assertion below byte-for-byte.
785
+ const externalMarketplace = dist.marketplaceRepo !== undefined && dist.marketplaceRepo !== null;
786
+ if (externalMarketplace && platform.marketplaceRefForm === null) {
787
+ failures.push(
788
+ `unit "${unitId}", action "${action.id}": ${platform.distributionType} distribution declares marketplaceRepo but the platform has no marketplace add capability`,
789
+ );
790
+ }
791
+
779
792
  // Parameter checks
780
793
  _checkRequired(action, 'parameters.consumer', action.parameters?.consumer, platform.id, unitId, failures);
781
794
  _checkRequired(action, 'parameters.plugin', action.parameters?.plugin, plugin, unitId, failures);
@@ -792,12 +805,53 @@ export function validatePlanActionCompleteness(plan, options = {}) {
792
805
  `unit "${unitId}", action "${action.id}": parameters.marketplace is "${action.parameters.marketplace}", expected optional legacy value "${marketplace}"`,
793
806
  );
794
807
  }
795
- _checkRequired(action, 'parameters.repo', action.parameters?.repo, publicRepo, unitId, failures);
808
+ if (externalMarketplace) {
809
+ // External form: the install repo is the external marketplace repo,
810
+ // not publicRepo; marketplaceLocation is the action-level marker.
811
+ _checkRequired(action, 'parameters.repo', action.parameters?.repo, dist.marketplaceRepo, unitId, failures);
812
+ _checkRequired(action, 'parameters.marketplaceLocation', action.parameters?.marketplaceLocation, 'external', unitId, failures);
813
+ } else {
814
+ _checkRequired(action, 'parameters.repo', action.parameters?.repo, publicRepo, unitId, failures);
815
+ }
796
816
  _checkRequired(action, 'parameters.version', action.parameters?.version, targetVersion, unitId, failures);
797
817
  _checkRequired(action, 'parameters.entrySkill', action.parameters?.entrySkill, entrySkill, unitId, failures);
798
818
  if (production) {
819
+ // snapshotPath/manifestDigest always bind the unit's own frozen
820
+ // snapshot — the payload authority is unchanged for external form.
799
821
  _checkRequired(action, 'parameters.snapshotPath', action.parameters?.snapshotPath, frozen?.path, unitId, failures);
800
- _checkRequired(action, 'parameters.ref', action.parameters?.ref, expectedTag, unitId, failures);
822
+ if (externalMarketplace) {
823
+ // The add-ref is frozen online by prepare (codex=commit sha,
824
+ // claude=default branch name) and cannot be re-derived offline;
825
+ // validate structural safety plus self-consistency with expected.ref.
826
+ const ref = action.parameters?.ref;
827
+ if (ref === undefined || ref === null) {
828
+ failures.push(
829
+ `unit "${unitId}", action "${action.id}": parameters.ref is missing, expected the frozen external marketplace add-ref`,
830
+ );
831
+ } else if (!_isStructurallySafeExternalRef(ref)) {
832
+ failures.push(
833
+ `unit "${unitId}", action "${action.id}": parameters.ref is not a structurally safe ref: ${JSON.stringify(ref)}`,
834
+ );
835
+ } else if (ref !== action.expected?.ref) {
836
+ failures.push(
837
+ `unit "${unitId}", action "${action.id}": parameters.ref is "${ref}", expected self-consistent expected.ref "${action.expected?.ref}"`,
838
+ );
839
+ }
840
+ // marketplaceCommitSha is the frozen 40-hex marketplace HEAD
841
+ // (online freeze, offline completeness — same model as manifestDigest).
842
+ const sha = action.parameters?.marketplaceCommitSha;
843
+ if (sha === undefined || sha === null) {
844
+ failures.push(
845
+ `unit "${unitId}", action "${action.id}": parameters.marketplaceCommitSha is missing, expected a frozen 40-hex commit sha`,
846
+ );
847
+ } else if (typeof sha !== 'string' || !EXTERNAL_MARKETPLACE_SHA_RE.test(sha)) {
848
+ failures.push(
849
+ `unit "${unitId}", action "${action.id}": parameters.marketplaceCommitSha must be a 40-hex commit sha, got: ${JSON.stringify(sha)}`,
850
+ );
851
+ }
852
+ } else {
853
+ _checkRequired(action, 'parameters.ref', action.parameters?.ref, expectedTag, unitId, failures);
854
+ }
801
855
  _checkRequired(action, 'parameters.manifestDigest', action.parameters?.manifestDigest, frozen?.manifestDigest, unitId, failures);
802
856
  }
803
857
  // timeoutMs is mandatory for all marketplace install actions.
@@ -828,6 +882,184 @@ export function validatePlanActionCompleteness(plan, options = {}) {
828
882
  }
829
883
  }
830
884
 
885
+ // sourceCommit: frozen plugin source commit, binding the marketplace
886
+ // install action to the unit's frozen snapshot commit. Both forms
887
+ // require it in production plans; non-production diagnostic plans may
888
+ // omit it (never冒充生产计划).
889
+ if (production) {
890
+ _checkRequired(action, 'parameters.sourceCommit', action.parameters?.sourceCommit, frozen?.commit, unitId, failures);
891
+ }
892
+
893
+ // --- sourceDescriptor validation (marketplace source identity contract) ---
894
+ // Validates that marketplaceForm and sourceDescriptor.form are consistent,
895
+ // all form-specific fields are present and non-null, pluginRepo !== marketplaceRepo
896
+ // for standalone-index, and no mixed/extra fields exist.
897
+ // Only applies to platforms that require marketplace (claude, codex).
898
+ // Non-automatable platforms (kimi, codebuddy) use human-attestation
899
+ // and carry no marketplace form or source descriptor.
900
+ // Legacy plans (pre-sourceDescriptor) with legacyCompatibility are tolerated.
901
+ if (requiresMarketplace) {
902
+ const sd = action.parameters?.sourceDescriptor;
903
+ const mf = action.parameters?.marketplaceForm;
904
+ if (sd === undefined || sd === null) {
905
+ if (!options.legacyCompatibility) {
906
+ failures.push(
907
+ `unit "${unitId}", action "${action.id}": parameters.sourceDescriptor is missing; every marketplace install action must carry a complete source descriptor`,
908
+ );
909
+ }
910
+ } else if (typeof sd === 'object') {
911
+ // 1. marketplaceForm must be present when sourceDescriptor is present
912
+ if (mf === undefined || mf === null) {
913
+ failures.push(
914
+ `unit "${unitId}", action "${action.id}": parameters.marketplaceForm is missing but sourceDescriptor is present; form must be explicitly declared`,
915
+ );
916
+ }
917
+ // 2. sourceDescriptor.form must match marketplaceForm
918
+ if (sd.form !== mf) {
919
+ failures.push(
920
+ `unit "${unitId}", action "${action.id}": sourceDescriptor.form "${sd.form}" does not match parameters.marketplaceForm "${mf}"`,
921
+ );
922
+ }
923
+ // 3. Form-specific field completeness and non-null checks
924
+ // payloadDigest is allowed to be null in non-production plans
925
+ // (the frozen manifestDigest is only available after production freeze)
926
+ const BUNDLED_REQUIRED = ['form', 'repo', 'marketplaceEntry', 'pluginSubpath'];
927
+ const STANDALONE_REQUIRED = ['form', 'marketplaceRepo', 'marketplaceEntry', 'pluginRepo', 'sourceType'];
928
+ // Production-only fields that may be null in non-production plans
929
+ const PROD_ONLY_FIELDS = ['marketplaceCommitSha', 'ref', 'payloadDigest'];
930
+
931
+ if (sd.form === 'bundled-family') {
932
+ // Check bundled-family required fields are present and non-null
933
+ for (const field of BUNDLED_REQUIRED) {
934
+ if (sd[field] === undefined || sd[field] === null) {
935
+ failures.push(
936
+ `unit "${unitId}", action "${action.id}": sourceDescriptor.${field} is missing or null; bundled-family form requires all identity fields to be non-null`,
937
+ );
938
+ }
939
+ }
940
+ // commit must be non-null in production plans (frozen from asset)
941
+ if (production && (sd.commit === undefined || sd.commit === null)) {
942
+ failures.push(
943
+ `unit "${unitId}", action "${action.id}": sourceDescriptor.commit is missing or null; production bundled-family plans require a frozen commit`,
944
+ );
945
+ }
946
+ // commit must match frozenSnapshot.commit (production binding)
947
+ if (production && sd.commit !== undefined && sd.commit !== null &&
948
+ frozen?.commit !== undefined && sd.commit !== frozen.commit) {
949
+ failures.push(
950
+ `unit "${unitId}", action "${action.id}": sourceDescriptor.commit does not match frozenSnapshot.commit`,
951
+ );
952
+ }
953
+ // payloadDigest must be non-null in production plans
954
+ if (production && (sd.payloadDigest === undefined || sd.payloadDigest === null)) {
955
+ failures.push(
956
+ `unit "${unitId}", action "${action.id}": sourceDescriptor.payloadDigest is missing or null; production plans require a frozen payload digest`,
957
+ );
958
+ }
959
+ // Prohibit standalone-index fields in bundled-family
960
+ const STANDALONE_ONLY = ['marketplaceRepo', 'pluginRepo', 'sourceType', 'marketplaceCommitSha', 'ref'];
961
+ for (const field of STANDALONE_ONLY) {
962
+ if (sd[field] !== undefined) {
963
+ failures.push(
964
+ `unit "${unitId}", action "${action.id}": sourceDescriptor.${field} is unexpected for bundled-family form; mixed fields are prohibited`,
965
+ );
966
+ }
967
+ }
968
+ // repo must match the action's repo (publicRepo for bundled-family)
969
+ if (sd.repo !== undefined && sd.repo !== null && sd.repo !== publicRepo) {
970
+ failures.push(
971
+ `unit "${unitId}", action "${action.id}": sourceDescriptor.repo "${sd.repo}" does not match unit publicRepo "${publicRepo}"`,
972
+ );
973
+ }
974
+ // marketplaceEntry must match action.plugin
975
+ if (sd.marketplaceEntry !== undefined && sd.marketplaceEntry !== null && sd.marketplaceEntry !== plugin) {
976
+ failures.push(
977
+ `unit "${unitId}", action "${action.id}": sourceDescriptor.marketplaceEntry "${sd.marketplaceEntry}" does not match action plugin "${plugin}"`,
978
+ );
979
+ }
980
+ // payloadDigest must match manifestDigest (when both are present)
981
+ if (production && sd.payloadDigest !== undefined && sd.payloadDigest !== null &&
982
+ frozen?.manifestDigest !== undefined && sd.payloadDigest !== frozen.manifestDigest) {
983
+ failures.push(
984
+ `unit "${unitId}", action "${action.id}": sourceDescriptor.payloadDigest does not match frozen manifestDigest`,
985
+ );
986
+ }
987
+ } else if (sd.form === 'standalone-index') {
988
+ // Check standalone-index required fields are present and non-null
989
+ for (const field of STANDALONE_REQUIRED) {
990
+ if (sd[field] === undefined || sd[field] === null) {
991
+ failures.push(
992
+ `unit "${unitId}", action "${action.id}": sourceDescriptor.${field} is missing or null; standalone-index form requires all identity fields to be non-null`,
993
+ );
994
+ }
995
+ }
996
+ // Production-only fields: must be non-null in production plans
997
+ if (production) {
998
+ for (const field of PROD_ONLY_FIELDS) {
999
+ if (sd[field] === undefined || sd[field] === null) {
1000
+ failures.push(
1001
+ `unit "${unitId}", action "${action.id}": sourceDescriptor.${field} is missing or null; production standalone-index requires all fields to be non-null`,
1002
+ );
1003
+ }
1004
+ }
1005
+ }
1006
+ // Prohibit bundled-family fields in standalone-index
1007
+ const BUNDLED_ONLY = ['repo', 'commit', 'pluginSubpath'];
1008
+ for (const field of BUNDLED_ONLY) {
1009
+ if (sd[field] !== undefined) {
1010
+ failures.push(
1011
+ `unit "${unitId}", action "${action.id}": sourceDescriptor.${field} is unexpected for standalone-index form; mixed fields are prohibited`,
1012
+ );
1013
+ }
1014
+ }
1015
+ // pluginRepo must NOT equal marketplaceRepo
1016
+ if (sd.pluginRepo !== undefined && sd.pluginRepo !== null &&
1017
+ sd.marketplaceRepo !== undefined && sd.marketplaceRepo !== null &&
1018
+ sd.pluginRepo === sd.marketplaceRepo) {
1019
+ failures.push(
1020
+ `unit "${unitId}", action "${action.id}": sourceDescriptor.pluginRepo must not equal marketplaceRepo; the plugin repo and marketplace repo are distinct repositories`,
1021
+ );
1022
+ }
1023
+ // marketplaceRepo must match action.repo (for standalone-index, repo is the external marketplace)
1024
+ if (externalMarketplace && sd.marketplaceRepo !== undefined && sd.marketplaceRepo !== null &&
1025
+ sd.marketplaceRepo !== dist.marketplaceRepo) {
1026
+ failures.push(
1027
+ `unit "${unitId}", action "${action.id}": sourceDescriptor.marketplaceRepo "${sd.marketplaceRepo}" does not match distribution marketplaceRepo "${dist.marketplaceRepo}"`,
1028
+ );
1029
+ }
1030
+ // pluginRepo must match the unit's publicRepo
1031
+ if (sd.pluginRepo !== undefined && sd.pluginRepo !== null && sd.pluginRepo !== publicRepo) {
1032
+ failures.push(
1033
+ `unit "${unitId}", action "${action.id}": sourceDescriptor.pluginRepo "${sd.pluginRepo}" does not match unit publicRepo "${publicRepo}"`,
1034
+ );
1035
+ }
1036
+ // marketplaceEntry must match action.plugin
1037
+ if (sd.marketplaceEntry !== undefined && sd.marketplaceEntry !== null && sd.marketplaceEntry !== plugin) {
1038
+ failures.push(
1039
+ `unit "${unitId}", action "${action.id}": sourceDescriptor.marketplaceEntry "${sd.marketplaceEntry}" does not match action plugin "${plugin}"`,
1040
+ );
1041
+ }
1042
+ // sourceType must be 'marketplace-entry'
1043
+ if (sd.sourceType !== undefined && sd.sourceType !== null && sd.sourceType !== 'marketplace-entry') {
1044
+ failures.push(
1045
+ `unit "${unitId}", action "${action.id}": sourceDescriptor.sourceType "${sd.sourceType}" is unexpected; standalone-index sourceType must be "marketplace-entry"`,
1046
+ );
1047
+ }
1048
+ // payloadDigest must match manifestDigest (when both are present)
1049
+ if (production && sd.payloadDigest !== undefined && sd.payloadDigest !== null &&
1050
+ frozen?.manifestDigest !== undefined && sd.payloadDigest !== frozen.manifestDigest) {
1051
+ failures.push(
1052
+ `unit "${unitId}", action "${action.id}": sourceDescriptor.payloadDigest does not match frozen manifestDigest`,
1053
+ );
1054
+ }
1055
+ } else {
1056
+ failures.push(
1057
+ `unit "${unitId}", action "${action.id}": sourceDescriptor.form "${sd.form}" is unknown; expected "bundled-family" or "standalone-index"`,
1058
+ );
1059
+ }
1060
+ }
1061
+ }
1062
+
831
1063
  // Expected checks. Marketplace identity is bound only on platforms
832
1064
  // whose schema requires it (MINOR-1): a non-marketplace platform's
833
1065
  // observation never binds a marketplace.
@@ -840,8 +1072,15 @@ export function validatePlanActionCompleteness(plan, options = {}) {
840
1072
  _checkRequired(action, 'expected.entrySkill', action.expected?.entrySkill, entrySkill, unitId, failures);
841
1073
  if (production) {
842
1074
  _checkRequired(action, 'expected.consumer', action.expected?.consumer, platform.id, unitId, failures);
843
- _checkRequired(action, 'expected.repo', action.expected?.repo, publicRepo, unitId, failures);
844
- _checkRequired(action, 'expected.ref', action.expected?.ref, expectedTag, unitId, failures);
1075
+ if (externalMarketplace) {
1076
+ _checkRequired(action, 'expected.repo', action.expected?.repo, dist.marketplaceRepo, unitId, failures);
1077
+ _checkRequired(action, 'expected.ref', action.expected?.ref, action.parameters?.ref, unitId, failures);
1078
+ _checkRequired(action, 'expected.marketplaceLocation', action.expected?.marketplaceLocation, 'external', unitId, failures);
1079
+ _checkRequired(action, 'expected.marketplaceCommitSha', action.expected?.marketplaceCommitSha, action.parameters?.marketplaceCommitSha, unitId, failures);
1080
+ } else {
1081
+ _checkRequired(action, 'expected.repo', action.expected?.repo, publicRepo, unitId, failures);
1082
+ _checkRequired(action, 'expected.ref', action.expected?.ref, expectedTag, unitId, failures);
1083
+ }
845
1084
  _checkRequired(action, 'expected.entrySkillFound', action.expected?.entrySkillFound, true, unitId, failures);
846
1085
  _checkRequired(action, 'expected.manifestDigest', action.expected?.manifestDigest, frozen?.manifestDigest, unitId, failures);
847
1086
  }
@@ -946,3 +1185,35 @@ function _checkRequired(action, fieldPath, actual, expected, unitId, failures) {
946
1185
  );
947
1186
  }
948
1187
  }
1188
+
1189
+ // External independent marketplace form (external-marketplace): a frozen
1190
+ // marketplace HEAD commit sha is a 40-char lowercase hex string.
1191
+ const EXTERNAL_MARKETPLACE_SHA_RE = /^[0-9a-f]{40}$/;
1192
+
1193
+ /**
1194
+ * Structural safety check for an externally frozen marketplace add-ref.
1195
+ *
1196
+ * The add-ref (codex=commit sha, claude=default branch name) is frozen online
1197
+ * by prepare and cannot be re-derived offline; full injection-safety validation
1198
+ * already happened at freeze time (plugin-marketplace `validateSafeRef`). The
1199
+ * offline completeness gate re-checks structural well-formedness so a tampered
1200
+ * or corrupted frozen ref fails closed here too. Mirrors that validator's rules.
1201
+ *
1202
+ * @param {unknown} ref
1203
+ * @returns {boolean}
1204
+ */
1205
+ function _isStructurallySafeExternalRef(ref) {
1206
+ if (typeof ref !== 'string' || ref.length === 0) return false;
1207
+ if (/[\x00-\x1f]/.test(ref)) return false;
1208
+ if (ref.startsWith('-')) return false;
1209
+ if (ref.includes('\\')) return false;
1210
+ if (ref.includes('//')) return false;
1211
+ if (ref.startsWith('/') || ref.endsWith('/')) return false;
1212
+ if (ref.endsWith('.')) return false;
1213
+ if (ref.endsWith('.lock')) return false;
1214
+ if (ref.includes('@{')) return false;
1215
+ if (ref === '@') return false;
1216
+ if (ref.includes('..')) return false;
1217
+ if (/[;|&`$(){}]/.test(ref)) return false;
1218
+ return /^[a-zA-Z0-9][a-zA-Z0-9._/-]*$/.test(ref);
1219
+ }
@@ -57,7 +57,7 @@ function validateGate(gate) {
57
57
  if (!gate.scope || typeof gate.scope.unit !== 'string') {
58
58
  throw gateError(gate, 'must declare scope.unit');
59
59
  }
60
- if (gate.phase === 'consumer-verify' && !['npm', 'claude-plugin', 'codex-plugin', 'kimi-plugin'].includes(gate.scope.distribution)) {
60
+ if (gate.phase === 'consumer-verify' && !['npm', 'claude-plugin', 'codex-plugin', 'kimi-plugin', 'codebuddy-plugin'].includes(gate.scope.distribution)) {
61
61
  throw gateError(gate, 'consumer-verify must declare a supported scope.distribution');
62
62
  }
63
63
  if (!Array.isArray(gate.command) || gate.command.length === 0 || gate.command.some((value) => typeof value !== 'string')) {