release-skill 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codebuddy-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +2 -2
  5. package/.kimi-plugin/plugin.json +1 -1
  6. package/CHANGELOG.md +22 -0
  7. package/CONTRIBUTING.md +27 -0
  8. package/INSTALL.md +95 -139
  9. package/INSTALL.zh-CN.md +70 -121
  10. package/README.md +264 -913
  11. package/README.zh-CN.md +222 -535
  12. package/adapters/claude/.claude-plugin/marketplace.json +1 -1
  13. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  14. package/adapters/claude/bin/release-skill.bundle.mjs +19054 -17573
  15. package/adapters/claude/schemas/release-plan.schema.json +137 -0
  16. package/adapters/claude/schemas/release-project.schema.json +93 -0
  17. package/adapters/claude/schemas/release-run.schema.json +70 -2
  18. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  19. package/adapters/codex/bin/release-skill.bundle.mjs +19054 -17573
  20. package/adapters/codex/schemas/release-plan.schema.json +137 -0
  21. package/adapters/codex/schemas/release-project.schema.json +93 -0
  22. package/adapters/codex/schemas/release-run.schema.json +70 -2
  23. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  24. package/adapters/kimi/bin/release-skill.bundle.mjs +19054 -17573
  25. package/adapters/kimi/schemas/release-plan.schema.json +137 -0
  26. package/adapters/kimi/schemas/release-project.schema.json +93 -0
  27. package/adapters/kimi/schemas/release-run.schema.json +70 -2
  28. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  29. package/adapters/workbuddy/bin/release-skill.bundle.mjs +19054 -17573
  30. package/adapters/workbuddy/schemas/release-plan.schema.json +137 -0
  31. package/adapters/workbuddy/schemas/release-project.schema.json +93 -0
  32. package/adapters/workbuddy/schemas/release-run.schema.json +70 -2
  33. package/bin/release-skill.bundle.mjs +19054 -17573
  34. package/package.json +1 -1
  35. package/schemas/release-plan.schema.json +137 -0
  36. package/schemas/release-project.schema.json +93 -0
  37. package/schemas/release-run.schema.json +70 -2
  38. package/src/adapters/plugin-marketplace.mjs +1190 -435
  39. package/src/commands/prepare.mjs +380 -40
  40. package/src/commands/publish.mjs +107 -75
  41. package/src/commands/reconcile.mjs +92 -327
  42. package/src/commands/setup.mjs +148 -20
  43. package/src/commands/verify.mjs +304 -20
  44. package/src/core/baseline.mjs +8 -1
  45. package/src/core/checkpoints.mjs +50 -7
  46. package/src/core/config.mjs +15 -0
  47. package/src/core/errors.mjs +2 -0
  48. package/src/core/installation-contract.mjs +341 -0
  49. package/src/core/plan.mjs +129 -6
  50. package/src/platforms/codebuddy.mjs +193 -280
  51. package/src/platforms/codex.mjs +369 -0
  52. package/src/platforms/kimi.mjs +164 -119
  53. package/src/platforms/registry.mjs +24 -6
@@ -46,6 +46,8 @@ import {
46
46
  TIER_TABLE,
47
47
  groupActionsByTier,
48
48
  sortActionsByCheckpointOrder,
49
+ isRemoteWriteAction,
50
+ isMarketplaceAction,
49
51
  } from '../core/checkpoints.mjs';
50
52
  import { appendRunState, createProductionRunDir, writeRunAtomic, resolveDefaultRunDir } from '../core/run.mjs';
51
53
  import {
@@ -53,6 +55,7 @@ import {
53
55
  GATE_FAILED,
54
56
  BASELINE_CHANGED,
55
57
  PARTIAL_RELEASE,
58
+ CONSUMER_VERIFICATION_DEFERRED,
56
59
  } from '../core/errors.mjs';
57
60
  import { assertTransition, PUBLISHING, PUBLISHED, PARTIAL } from '../core/state-machine.mjs';
58
61
  import { matchObservation } from '../adapters/contract.mjs';
@@ -81,13 +84,6 @@ const ACTION_NOT_ALLOWED = 'ACTION_NOT_ALLOWED';
81
84
  // CHECKPOINT_ORDER, ADAPTER_ACTION_TYPE_MAP and TIER_TABLE live in
82
85
  // ../core/checkpoints.mjs (single source shared with reconcile.mjs; T3.1 §4.7).
83
86
 
84
- const MARKETPLACE_TYPES = new Set([
85
- 'claude-marketplace-install',
86
- 'codex-marketplace-install',
87
- 'kimi-marketplace-install',
88
- 'codebuddy-marketplace-install',
89
- ]);
90
-
91
87
  // ---------------------------------------------------------------------------
92
88
  // Helpers
93
89
  // ---------------------------------------------------------------------------
@@ -359,7 +355,7 @@ async function executeCheckpoint(action, adapterRegistry, context) {
359
355
  // (T1.1 PROPAGATING handling). A present-but-mismatched
360
356
  // observation (CONFLICTING) is never retried — it is an
361
357
  // authoritative conflict that must fail closed for human review.
362
- const observePolicy = MARKETPLACE_TYPES.has(planActionType)
358
+ const observePolicy = isMarketplaceAction(planActionType)
363
359
  ? clampPolicyToTimeout(DEFAULT_OBSERVE_RETRY_POLICY, action.parameters?.timeoutMs)
364
360
  : DEFAULT_OBSERVE_RETRY_POLICY;
365
361
  const retryOutcome = await observeWithRetry({
@@ -892,21 +888,27 @@ export async function publishRelease(options) {
892
888
  const orderedActions = sortActionsByCheckpointOrder(publishingPlan.externalActions);
893
889
 
894
890
  // =======================================================================
895
- // Safety Gate 10: Global preflight - validate all actions before any execute
891
+ // Partition: marketplace actions are deferred to verify phase.
892
+ // They get a stable CONSUMER_VERIFICATION_DEFERRED checkpoint immediately;
893
+ // they never call marketplace adapter preflight/execute/observe/verify.
894
+ // =======================================================================
895
+ const remoteWriteActions = orderedActions.filter((a) => !isMarketplaceAction(a.type));
896
+
897
+ // =======================================================================
898
+ // Safety Gate 10: Global preflight - validate all non-marketplace actions
899
+ // before any execute. Marketplace actions are deferred and skip preflight.
896
900
  // =======================================================================
897
901
  await evidence.append({ phase: 'safety-gate', gate: 'global-preflight', status: 'started' });
898
902
 
899
- for (const action of orderedActions) {
903
+ for (const action of remoteWriteActions) {
900
904
  if (action.type === 'write-remote-identifier') continue;
901
905
 
902
906
  const adapterActionType = ADAPTER_ACTION_TYPE_MAP[action.type];
903
907
  if (!adapterActionType) continue;
904
908
 
905
909
  const adapter = adapterRegistry.getAdapter(adapterActionType);
906
- const isMarketplace = MARKETPLACE_TYPES.has(action.type);
907
910
  const preflightContext = {
908
911
  externalWritesAuthorized: false,
909
- isolatedConsumerWritesAuthorized: isMarketplace,
910
912
  plan: publishingPlan,
911
913
  baseline: plan.baseline,
912
914
  root,
@@ -918,15 +920,11 @@ export async function publishRelease(options) {
918
920
  );
919
921
  if (preflightResult.status === 'PREFLIGHT_FAILED') {
920
922
  // T3.3 §4.6: an "occupied" preflight failure overlaps semantically with
921
- // "already consistent". For NON-marketplace action types, arbitrate with
922
- // a single read-only pre-observe using the SAME classifier as the
923
- // per-checkpoint path: CONSISTENT means the remote already satisfies the
924
- // frozen plan, so let the action through to its SKIPPED path instead of
925
- // failing the whole gate. Anything else (MISSING/CONFLICTING/UNOBSERVABLE)
926
- // keeps the fail-closed GATE_FAILED. Marketplace preflights are local
927
- // integrity checks, never remote occupation, so they are NEVER arbitrated
928
- // -- a CONSISTENT observation must not bypass their completeness gate.
929
- if (!isMarketplace && hasExpected(action.expected)) {
923
+ // "already consistent". Arbitrate with a single read-only pre-observe
924
+ // using the SAME classifier as the per-checkpoint path: CONSISTENT means
925
+ // the remote already satisfies the frozen plan, so let the action through
926
+ // to its SKIPPED path instead of failing the whole gate.
927
+ if (hasExpected(action.expected)) {
930
928
  let arbitration;
931
929
  try {
932
930
  arbitration = await adapter.observe(
@@ -961,15 +959,27 @@ export async function publishRelease(options) {
961
959
  await evidence.append({ phase: 'safety-gate', gate: 'global-preflight', status: 'passed' });
962
960
 
963
961
  // =======================================================================
964
- // Persist an append-only initial state before the first adapter execute.
962
+ // Build checkpoints: marketplace actions get a deferred result immediately;
963
+ // non-marketplace actions start as PENDING and are executed by tiers.
965
964
  // =======================================================================
966
965
  const runPath = join(runDir, 'release-run.json');
967
- const checkpoints = orderedActions.map((action) => ({
968
- actionId: action.id,
969
- actionType: action.type,
970
- status: 'PENDING',
971
- error: null,
972
- }));
966
+ const checkpoints = orderedActions.map((action) => {
967
+ if (isMarketplaceAction(action.type)) {
968
+ return {
969
+ actionId: action.id,
970
+ actionType: action.type,
971
+ status: 'DEFERRED',
972
+ phase: 'post-publish-verification',
973
+ reason: CONSUMER_VERIFICATION_DEFERRED,
974
+ };
975
+ }
976
+ return {
977
+ actionId: action.id,
978
+ actionType: action.type,
979
+ status: 'PENDING',
980
+ error: null,
981
+ };
982
+ });
973
983
 
974
984
  const startedAt = clockFn();
975
985
  const buildPersistedState = (status = PUBLISHING, finishedAt) => ({
@@ -987,10 +997,14 @@ export async function publishRelease(options) {
987
997
  : checkpoint.status === 'FAILED' ? 'failed'
988
998
  : checkpoint.status === 'UNCERTAIN' ? 'uncertain'
989
999
  : checkpoint.status === 'SKIPPED' ? 'skipped'
1000
+ : checkpoint.status === 'DEFERRED' ? 'deferred'
990
1001
  : 'pending',
991
1002
  ...(checkpoint.preObserve ? { preObserve: checkpoint.preObserve } : {}),
992
1003
  ...(checkpoint.postObserve ? { postObserve: checkpoint.postObserve } : {}),
993
1004
  ...(checkpoint.error ? { error: { code: 'GATE_FAILED', message: checkpoint.error } } : {}),
1005
+ ...(checkpoint.reason === CONSUMER_VERIFICATION_DEFERRED
1006
+ ? { reason: CONSUMER_VERIFICATION_DEFERRED, phase: checkpoint.phase ?? 'post-publish-verification' }
1007
+ : {}),
994
1008
  })),
995
1009
  startedAt,
996
1010
  ...(finishedAt ? { finishedAt } : {}),
@@ -1023,7 +1037,9 @@ export async function publishRelease(options) {
1023
1037
  // order). appendRunState's seq chain (run.mjs) is unchanged.
1024
1038
  // =======================================================================
1025
1039
  const checkpointByActionId = new Map(checkpoints.map((cp) => [cp.actionId, cp]));
1026
- const { tiers, unknown } = groupActionsByTier(orderedActions);
1040
+ // Only non-marketplace actions participate in tier execution.
1041
+ // Marketplace actions are already deferred and excluded from tiers.
1042
+ const { tiers, unknown } = groupActionsByTier(remoteWriteActions);
1027
1043
  let stopped = false;
1028
1044
 
1029
1045
  // Fail closed on any action type the tier table does not recognize. Such a
@@ -1075,7 +1091,7 @@ export async function publishRelease(options) {
1075
1091
 
1076
1092
  // --- Tier execute: concurrent, allSettled semantics (no fail-fast).
1077
1093
  const results = await Promise.all(tierActions.map(async (action) => {
1078
- const isMarketplace = MARKETPLACE_TYPES.has(action.type);
1094
+ const isMarketplace = isMarketplaceAction(action.type);
1079
1095
  const actionContext = {
1080
1096
  externalWritesAuthorized: !isMarketplace,
1081
1097
  isolatedConsumerWritesAuthorized: isMarketplace,
@@ -1136,60 +1152,76 @@ export async function publishRelease(options) {
1136
1152
  // consistency pass. Both the branch tip and default-branch name are bound
1137
1153
  // in the frozen action expectations. A late change keeps the saga PARTIAL
1138
1154
  // and must be resolved by reconcile/human review.
1139
- if (checkpoints.every((cp) => cp.status === 'SUCCEEDED' || cp.status === 'SKIPPED')) {
1140
- await evidence.append({ phase: 'safety-gate', gate: 'final-branch-consistency', status: 'started' });
1141
- for (let index = 0; index < orderedActions.length; index += 1) {
1142
- const action = orderedActions[index];
1143
- if (!['push-snapshot', 'set-default-branch'].includes(action.type)) continue;
1144
- const adapterActionType = ADAPTER_ACTION_TYPE_MAP[action.type];
1145
- const adapter = adapterRegistry.getAdapter(adapterActionType);
1146
- let observed;
1147
- try {
1148
- observed = await adapter.observe(
1149
- { actionType: adapterActionType, ...action.parameters, expected: action.expected },
1150
- { externalWritesAuthorized: false, plan: publishingPlan, baseline: plan.baseline, root, runDir },
1151
- );
1152
- } catch (error) {
1153
- observed = { observation: null, error: error.message };
1155
+ //
1156
+ // IMPORTANT: the final status determination must read the LATEST checkpoint
1157
+ // state AFTER this consistency check, not use any previously cached boolean.
1158
+ // A late consistency failure that changes a checkpoint from SUCCEEDED to
1159
+ // FAILED must cause PARTIAL, not stale PUBLISHED.
1160
+ {
1161
+ // Build fresh remote-write checkpoint view (reads latest state)
1162
+ const remoteWriteCps = checkpoints.filter((cp) => isRemoteWriteAction(cp.actionType));
1163
+ const allRemoteConsistent = remoteWriteCps.every(
1164
+ (cp) => cp.status === 'SUCCEEDED' || cp.status === 'SKIPPED',
1165
+ );
1166
+ if (allRemoteConsistent) {
1167
+ await evidence.append({ phase: 'safety-gate', gate: 'final-branch-consistency', status: 'started' });
1168
+ for (let index = 0; index < orderedActions.length; index += 1) {
1169
+ const action = orderedActions[index];
1170
+ if (!['push-snapshot', 'set-default-branch'].includes(action.type)) continue;
1171
+ const adapterActionType = ADAPTER_ACTION_TYPE_MAP[action.type];
1172
+ const adapter = adapterRegistry.getAdapter(adapterActionType);
1173
+ let observed;
1174
+ try {
1175
+ observed = await adapter.observe(
1176
+ { actionType: adapterActionType, ...action.parameters, expected: action.expected },
1177
+ { externalWritesAuthorized: false, plan: publishingPlan, baseline: plan.baseline, root, runDir },
1178
+ );
1179
+ } catch (error) {
1180
+ observed = { observation: null, error: error.message };
1181
+ }
1182
+ const observation = observed?.observation;
1183
+ const observable = observation && !(observed.error && Object.keys(observation).length === 0);
1184
+ const comparison = observable ? matchObservation(action.expected ?? {}, observation) : { matches: false, mismatches: [] };
1185
+ if (!comparison.matches) {
1186
+ checkpoints[index].status = observable ? 'FAILED' : 'UNCERTAIN';
1187
+ checkpoints[index].error = observable
1188
+ ? `final branch consistency mismatch: ${comparison.mismatches.join('; ')}`
1189
+ : `final branch consistency unobservable: ${observed?.error ?? 'empty observation'}`;
1190
+ await evidence.append({
1191
+ phase: 'safety-gate',
1192
+ gate: 'final-branch-consistency',
1193
+ status: 'failed',
1194
+ actionId: action.id,
1195
+ error: checkpoints[index].error,
1196
+ });
1197
+ break;
1198
+ }
1154
1199
  }
1155
- const observation = observed?.observation;
1156
- const observable = observation && !(observed.error && Object.keys(observation).length === 0);
1157
- const comparison = observable ? matchObservation(action.expected ?? {}, observation) : { matches: false, mismatches: [] };
1158
- if (!comparison.matches) {
1159
- checkpoints[index].status = observable ? 'FAILED' : 'UNCERTAIN';
1160
- checkpoints[index].error = observable
1161
- ? `final branch consistency mismatch: ${comparison.mismatches.join('; ')}`
1162
- : `final branch consistency unobservable: ${observed?.error ?? 'empty observation'}`;
1163
- await evidence.append({
1164
- phase: 'safety-gate',
1165
- gate: 'final-branch-consistency',
1166
- status: 'failed',
1167
- actionId: action.id,
1168
- error: checkpoints[index].error,
1169
- });
1170
- break;
1200
+ // Re-read fresh state after consistency check
1201
+ const freshRemoteCps = checkpoints.filter((cp) => isRemoteWriteAction(cp.actionType));
1202
+ if (freshRemoteCps.every((cp) => cp.status === 'SUCCEEDED' || cp.status === 'SKIPPED')) {
1203
+ await evidence.append({ phase: 'safety-gate', gate: 'final-branch-consistency', status: 'passed' });
1171
1204
  }
1172
1205
  }
1173
- if (checkpoints.every((cp) => cp.status === 'SUCCEEDED' || cp.status === 'SKIPPED')) {
1174
- await evidence.append({ phase: 'safety-gate', gate: 'final-branch-consistency', status: 'passed' });
1175
- }
1176
1206
  }
1177
1207
 
1178
- // Determine overall status
1179
- const hasFailure = checkpoints.some((cp) => cp.status === 'FAILED' || cp.status === 'UNCERTAIN');
1180
- // SKIPPED counts as success: the remote was already consistent with the
1181
- // frozen plan, so an all-SKIPPED/SUCCEEDED run is a clean PUBLISHED (the
1182
- // idempotent ideal -- zero writes were even needed).
1183
- const allSucceeded = checkpoints.every((cp) => cp.status === 'SUCCEEDED' || cp.status === 'SKIPPED');
1208
+ // Determine overall status from the LATEST checkpoint state.
1209
+ // Never use a cached boolean computed before the consistency check.
1210
+ // 远端写入动作的状态决定整体发布状态
1211
+ // 市场安装动作的结果记录在 run 中,但不阻止 PUBLISHED
1212
+ const remoteWriteCheckpointsFinal = checkpoints.filter((cp) => isRemoteWriteAction(cp.actionType));
1213
+ const remoteWriteAllSucceeded = remoteWriteCheckpointsFinal.every(
1214
+ (cp) => cp.status === 'SUCCEEDED' || cp.status === 'SKIPPED',
1215
+ );
1216
+ const remoteWriteHasFailure = remoteWriteCheckpointsFinal.some(
1217
+ (cp) => cp.status === 'FAILED' || cp.status === 'UNCERTAIN',
1218
+ );
1184
1219
 
1185
1220
  let overallStatus;
1186
- if (allSucceeded) {
1221
+ if (remoteWriteAllSucceeded) {
1187
1222
  overallStatus = PUBLISHED;
1188
1223
  publishingPlan.status = PUBLISHED;
1189
- } else if (hasFailure) {
1190
- // Once any execute was attempted, recovery must go through reconcile,
1191
- // even when no success was observed. Re-running publish could duplicate
1192
- // a write accepted just before a transport failure.
1224
+ } else if (remoteWriteHasFailure) {
1193
1225
  overallStatus = PARTIAL;
1194
1226
  publishingPlan.status = overallStatus;
1195
1227
  } else {