release-skill 0.9.3 → 0.9.5

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 (79) 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 +58 -0
  7. package/INSTALL.md +2 -2
  8. package/INSTALL.zh-CN.md +2 -2
  9. package/README.md +52 -16
  10. package/README.zh-CN.md +46 -16
  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/consumer-contract-vectors.json +7 -7
  14. package/adapters/claude/bin/foundation-resource-binding.json +1 -1
  15. package/adapters/claude/bin/release-skill.bundle.mjs +1513 -352
  16. package/adapters/claude/schemas/release-plan.schema.json +36 -0
  17. package/adapters/claude/schemas/release-project.schema.json +31 -0
  18. package/adapters/claude/skills/release-finish/SKILL.md +5 -5
  19. package/adapters/claude/skills/release-help/SKILL.md +11 -4
  20. package/adapters/claude/skills/release-prepare/SKILL.md +11 -3
  21. package/adapters/claude/skills/release-verify/SKILL.md +2 -1
  22. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  23. package/adapters/codex/bin/consumer-contract-vectors.json +7 -7
  24. package/adapters/codex/bin/foundation-resource-binding.json +1 -1
  25. package/adapters/codex/bin/release-skill.bundle.mjs +1513 -352
  26. package/adapters/codex/schemas/release-plan.schema.json +36 -0
  27. package/adapters/codex/schemas/release-project.schema.json +31 -0
  28. package/adapters/codex/skills/release-finish/SKILL.md +5 -5
  29. package/adapters/codex/skills/release-help/SKILL.md +11 -4
  30. package/adapters/codex/skills/release-prepare/SKILL.md +11 -3
  31. package/adapters/codex/skills/release-verify/SKILL.md +2 -1
  32. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  33. package/adapters/kimi/bin/consumer-contract-vectors.json +7 -7
  34. package/adapters/kimi/bin/foundation-resource-binding.json +1 -1
  35. package/adapters/kimi/bin/release-skill.bundle.mjs +1513 -352
  36. package/adapters/kimi/schemas/release-plan.schema.json +36 -0
  37. package/adapters/kimi/schemas/release-project.schema.json +31 -0
  38. package/adapters/kimi/skills/release-finish/SKILL.md +5 -5
  39. package/adapters/kimi/skills/release-help/SKILL.md +11 -4
  40. package/adapters/kimi/skills/release-prepare/SKILL.md +11 -3
  41. package/adapters/kimi/skills/release-verify/SKILL.md +2 -1
  42. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  43. package/adapters/workbuddy/bin/consumer-contract-vectors.json +7 -7
  44. package/adapters/workbuddy/bin/foundation-resource-binding.json +1 -1
  45. package/adapters/workbuddy/bin/release-skill.bundle.mjs +1513 -352
  46. package/adapters/workbuddy/schemas/release-plan.schema.json +36 -0
  47. package/adapters/workbuddy/schemas/release-project.schema.json +31 -0
  48. package/adapters/workbuddy/skills/release-finish/SKILL.md +5 -5
  49. package/adapters/workbuddy/skills/release-help/SKILL.md +11 -4
  50. package/adapters/workbuddy/skills/release-prepare/SKILL.md +11 -3
  51. package/adapters/workbuddy/skills/release-verify/SKILL.md +2 -1
  52. package/bin/consumer-contract-vectors.json +7 -7
  53. package/bin/foundation-resource-binding.json +1 -1
  54. package/bin/release-skill-cli.mjs +113 -22
  55. package/bin/release-skill.bundle.mjs +1513 -352
  56. package/package.json +4 -4
  57. package/platform-manifest.json +4 -4
  58. package/references/.render-manifest.json +4 -4
  59. package/references/01-state-machine.md +18 -0
  60. package/references/02-project-config.md +20 -0
  61. package/schemas/release-plan.schema.json +36 -0
  62. package/schemas/release-project.schema.json +31 -0
  63. package/skills/release-finish/SKILL.md +5 -5
  64. package/skills/release-help/SKILL.md +11 -4
  65. package/skills/release-prepare/SKILL.md +11 -3
  66. package/skills/release-verify/SKILL.md +2 -1
  67. package/skills-src/release-finish/SKILL.md +5 -5
  68. package/skills-src/release-help/SKILL.md +11 -4
  69. package/skills-src/release-prepare/SKILL.md +11 -3
  70. package/skills-src/release-verify/SKILL.md +2 -1
  71. package/src/commands/post-release-local.mjs +373 -26
  72. package/src/commands/prepare.mjs +361 -16
  73. package/src/commands/ship.mjs +190 -13
  74. package/src/commands/verify.mjs +175 -2
  75. package/src/core/foundation-plugin-verification.mjs +22 -2
  76. package/src/core/plan.mjs +45 -1
  77. package/src/platforms/registry.mjs +97 -0
  78. package/src/producers/foundation-resource-projection.mjs +3 -3
  79. package/src/readme/contract.mjs +23 -3
@@ -91,6 +91,39 @@ async function exitAfterFlush(exitCode) {
91
91
  process.exit(exitCode);
92
92
  }
93
93
 
94
+ /**
95
+ * Keep command-line error JSON useful without exposing internal error objects,
96
+ * process envelopes, or filesystem details. Local-finish evidence errors
97
+ * intentionally expose only their stable cause and executable next steps.
98
+ *
99
+ * @param {unknown} error
100
+ * @returns {Record<string, unknown>}
101
+ */
102
+ function publicErrorDetails(error) {
103
+ const details = error?.details;
104
+ if (!details || typeof details !== 'object' || Array.isArray(details)) return {};
105
+ const output = {};
106
+ if (details.cause && typeof details.cause === 'object' && !Array.isArray(details.cause)) {
107
+ const cause = {};
108
+ if (typeof details.cause.code === 'string') cause.code = details.cause.code;
109
+ if (typeof details.cause.message === 'string') cause.message = details.cause.message;
110
+ if (Object.keys(cause).length > 0) output.cause = cause;
111
+ }
112
+ if (Array.isArray(details.nextSteps)) {
113
+ const nextSteps = details.nextSteps
114
+ .filter((step) => step && typeof step === 'object' && !Array.isArray(step))
115
+ .map((step) => ({
116
+ ...(typeof step.code === 'string' ? { code: step.code } : {}),
117
+ ...(typeof step.message === 'string' ? { message: step.message } : {}),
118
+ ...(Array.isArray(step.argv) && step.argv.every((arg) => typeof arg === 'string')
119
+ ? { argv: [...step.argv] } : {}),
120
+ }))
121
+ .filter((step) => Object.keys(step).length > 0);
122
+ if (nextSteps.length > 0) output.nextSteps = nextSteps;
123
+ }
124
+ return output;
125
+ }
126
+
94
127
  /**
95
128
  * Check if a command is available and get its version.
96
129
  *
@@ -351,7 +384,7 @@ Options:
351
384
  performs an append-only hooks edit of an existing config (create-once is untouched)
352
385
  --select-hooks <ids> Comma-separated proposal ids to adopt (propose-hooks mode)
353
386
  --foundation-profile <path> Explicit foundation postPublish profile JSON (proposal input only; never auto-applied)
354
- --unit <id> Release unit whose declared release documents are refreshed (docs refresh)
387
+ --unit <id> Select a release unit for prepare/ship (repeatable); docs refresh accepts one unit
355
388
  --confirm-refresh <sha256:...> Confirm the exact dry-run refreshDigest before any document write
356
389
  --ack-local-document-write Acknowledge the explicit local release-document write (docs refresh --write)
357
390
  --platform <id> Legacy attestation platform: kimi or codebuddy
@@ -559,6 +592,7 @@ if (command === 'ship' && (args.includes('--help') || args.includes('-h'))) {
559
592
  options: {
560
593
  targetVersion: '--target-version <version>',
561
594
  state: '--state <path>',
595
+ unit: '--unit <id> (repeatable before plan freeze)',
562
596
  approve: '--approve --actor <name>',
563
597
  },
564
598
  message: 'Repeat the same command to resume from the same state. For parallel or cross-session versions, use .release-skill/ships/<version>.json.',
@@ -572,6 +606,7 @@ Usage:
572
606
  Options:
573
607
  --target-version <version> Specify the target version
574
608
  --state <path> Specify the durable state file
609
+ --unit <id> Select a release unit before plan freeze (repeatable)
575
610
  --approve --actor <name> Approve the plan bound to the current state
576
611
 
577
612
  Repeat the same command to resume from the same state.
@@ -787,6 +822,7 @@ function printShipApprovalSummary(result) {
787
822
  const summary = result.approvalSummary;
788
823
  if (!summary) return;
789
824
  console.log('Approval summary:');
825
+ console.log(` This approval covers only these release units: ${(summary.units ?? []).map((unit) => unit.id).join(', ') || 'none'}`);
790
826
  for (const unit of summary.units ?? []) {
791
827
  console.log(` Unit ${unit.id} ${unit.targetVersion}`);
792
828
  console.log(` Public repo: ${unit.publicRepo}`);
@@ -817,6 +853,13 @@ if (command === 'ship') {
817
853
  return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
818
854
  };
819
855
  const root = resolve(value('--root') ?? process.cwd());
856
+ const hasUnitSelection = args.includes('--unit');
857
+ const unitIds = [];
858
+ for (let i = 0; i < args.length; i += 1) {
859
+ if (args[i] === '--unit') {
860
+ unitIds.push(args[i + 1] && !args[i + 1].startsWith('--') ? args[i + 1] : '');
861
+ }
862
+ }
820
863
  // Repeatable --hook-approval <path>: checkpoint approvals for
821
864
  // requiresApproval postPublish hooks (same convention as distribute;
822
865
  // review major-1 — without this the ship flow can never complete a
@@ -862,6 +905,7 @@ if (command === 'ship') {
862
905
  planApprovalDigest: value('--approve-plan'),
863
906
  actor: value('--actor'),
864
907
  adapterRegistry,
908
+ ...(hasUnitSelection ? { unitIds } : {}),
865
909
  ...(postpublishApprovalPaths.length > 0 ? { postpublishApprovalPaths } : {}),
866
910
  });
867
911
  if (hasJson) {
@@ -892,9 +936,15 @@ if (command === 'ship') {
892
936
  } else {
893
937
  console.log('Post-release: branch advancement was already included; skip the merge question.');
894
938
  }
895
- if (result.postRelease.localHostUpdate?.promptRequired) {
939
+ const localHostUpdate = result.postRelease.localHostUpdate;
940
+ if (localHostUpdate?.available === true && typeof localHostUpdate.runPath === 'string') {
896
941
  console.log(`Post-release: ask whether to update local host plugins (${result.postRelease.localHostUpdate.hosts.join(', ')}).`);
897
- console.log(`Post-release command: release-skill post-release --plan ${result.planPath} --run ${result.verifyRunPath}`);
942
+ console.log(`Post-release command: release-skill post-release --plan ${result.planPath} --run ${localHostUpdate.runPath}`);
943
+ console.log('Choose --hosts before adding --update-local-hosts to perform a local update.');
944
+ } else {
945
+ for (const step of localHostUpdate?.nextSteps ?? []) {
946
+ console.log(`Next [${step.code}] ${step.message} (${step.argv.join(' ')})`);
947
+ }
898
948
  }
899
949
  }
900
950
  for (const followUp of result.manualFollowUps ?? []) {
@@ -996,6 +1046,13 @@ if (command === 'prepare') {
996
1046
  const output = outputIdx !== -1 && args[outputIdx + 1] ? resolve(args[outputIdx + 1]) : undefined;
997
1047
  const runDirIdx = args.indexOf('--run-dir');
998
1048
  const runDir = runDirIdx !== -1 && args[runDirIdx + 1] ? resolve(args[runDirIdx + 1]) : undefined;
1049
+ const hasUnitSelection = args.includes('--unit');
1050
+ const unitIds = [];
1051
+ for (let i = 0; i < args.length; i += 1) {
1052
+ if (args[i] === '--unit') {
1053
+ unitIds.push(args[i + 1] && !args[i + 1].startsWith('--') ? args[i + 1] : '');
1054
+ }
1055
+ }
999
1056
 
1000
1057
  try {
1001
1058
  const { prepareRelease } = await import('../src/commands/prepare.mjs');
@@ -1012,6 +1069,7 @@ if (command === 'prepare') {
1012
1069
  testSelection,
1013
1070
  output,
1014
1071
  runDir,
1072
+ ...(hasUnitSelection ? { unitIds } : {}),
1015
1073
  });
1016
1074
 
1017
1075
  // Keep stdout compact and stable. The immutable plan remains the single
@@ -1041,6 +1099,7 @@ if (command === 'prepare') {
1041
1099
  })),
1042
1100
  warnings: result.warnings,
1043
1101
  nextSteps: result.nextSteps ?? [],
1102
+ ...(result.releaseScope ? { releaseScope: result.releaseScope } : {}),
1044
1103
  }, null, 2));
1045
1104
  } else {
1046
1105
  for (const warning of result.warnings) {
@@ -1050,7 +1109,11 @@ if (command === 'prepare') {
1050
1109
  console.log(`Plan digest: ${result.planDigest}`);
1051
1110
  console.log(`Evidence: ${result.evidenceDir}`);
1052
1111
  for (const step of result.nextSteps ?? []) {
1053
- console.log(`Next [${step.code}] ${step.message}`);
1112
+ console.log(`Next [${step.code}] ${step.message}${step.argv ? ` (${step.argv.join(' ')})` : ''}`);
1113
+ }
1114
+ if (result.releaseScope) {
1115
+ console.log(`Release scope: ${result.releaseScope.selectedUnitIds.join(', ')}`);
1116
+ console.log(`Deferred units: ${result.releaseScope.deferredUnitIds.join(', ') || 'none'}`);
1054
1117
  }
1055
1118
  if (plan.workflowKind && plan.workflowKind !== 'full') {
1056
1119
  console.log(`Workflow: ${plan.workflowKind}`);
@@ -1269,14 +1332,13 @@ if (command === 'post-release') {
1269
1332
  const planPath = value('--plan') ? resolve(value('--plan')) : undefined;
1270
1333
  const runPath = value('--run') ? resolve(value('--run')) : undefined;
1271
1334
  if (!planPath || !runPath) {
1272
- const message = 'post-release requires --plan <path> and --run <verified-run-path>';
1335
+ const message = 'post-release requires --plan <path> and --run <verify-or-postverify-run-path>';
1273
1336
  if (hasJson) console.log(JSON.stringify({ error: 'MISSING_PARAMETERS', message, exitCode: 1 }));
1274
1337
  else console.error(`Error: ${message}`);
1275
1338
  await exitAfterFlush(1);
1276
1339
  }
1277
1340
  try {
1278
1341
  const {
1279
- assertVerifiedReleaseRun,
1280
1342
  derivePostReleaseChecklist,
1281
1343
  updateLocalHostPlugins,
1282
1344
  } = await import('../src/commands/post-release-local.mjs');
@@ -1284,24 +1346,26 @@ if (command === 'post-release') {
1284
1346
  const {
1285
1347
  loadRun,
1286
1348
  resolveRunPath,
1287
- validateRunLineage,
1288
1349
  } = await import('../src/core/run.mjs');
1289
1350
  const plan = JSON.parse(await readFile(planPath, 'utf8'));
1290
1351
  validatePlan(plan);
1352
+ const updateRequested = args.includes('--update-local-hosts');
1291
1353
  const resolvedRunPath = await resolveRunPath(runPath);
1292
1354
  const runRecord = await loadRun(resolvedRunPath, {
1293
1355
  requireDigest: true,
1294
1356
  authorityPlanPath: planPath,
1295
1357
  });
1296
- await validateRunLineage(runRecord, {
1297
- plan,
1298
- planPath,
1299
- runPath: resolvedRunPath,
1300
- production: Boolean(plan.production),
1301
- });
1302
- assertVerifiedReleaseRun(plan, runRecord);
1303
-
1304
- const updateRequested = args.includes('--update-local-hosts');
1358
+ if (!updateRequested) {
1359
+ const { assertLocalFinishRun } = await import('../src/commands/post-release-local.mjs');
1360
+ await assertLocalFinishRun({
1361
+ plan,
1362
+ planPath,
1363
+ runPath: resolvedRunPath,
1364
+ runRecord,
1365
+ production: Boolean(plan.production),
1366
+ root: resolve(value('--root') ?? process.cwd()),
1367
+ });
1368
+ }
1305
1369
  const hostsIndex = args.indexOf('--hosts');
1306
1370
  const rawHosts = hostsIndex !== -1
1307
1371
  && args[hostsIndex + 1]
@@ -1314,12 +1378,19 @@ if (command === 'post-release') {
1314
1378
  .filter(Boolean);
1315
1379
  const result = updateRequested
1316
1380
  ? await updateLocalHostPlugins({
1317
- plan,
1381
+ planPath,
1382
+ runPath: resolvedRunPath,
1318
1383
  root: resolve(value('--root') ?? process.cwd()),
1319
1384
  confirmPlanDigest: value('--confirm-plan'),
1320
1385
  selectedHosts,
1321
1386
  })
1322
- : derivePostReleaseChecklist(plan);
1387
+ : derivePostReleaseChecklist(plan, {
1388
+ runPath: runRecord.command === 'postverify' && runRecord.status === 'DISTRIBUTED'
1389
+ ? resolvedRunPath
1390
+ : runRecord.command === 'verify' ? resolvedRunPath : undefined,
1391
+ root: resolve(value('--root') ?? process.cwd()),
1392
+ postVerifyComplete: runRecord.command === 'postverify' && runRecord.status === 'DISTRIBUTED',
1393
+ });
1323
1394
 
1324
1395
  if (hasJson) {
1325
1396
  console.log(JSON.stringify(result, null, 2));
@@ -1327,8 +1398,14 @@ if (command === 'post-release') {
1327
1398
  console.log(`Post-release status: ${result.status}`);
1328
1399
  if (result.merge.promptRequired) console.log('Ask whether the user wants to merge the remaining branch.');
1329
1400
  else console.log('Branch advancement was already included in the release workflow; skip the merge question.');
1330
- if (result.localHostUpdate.promptRequired) {
1401
+ if (result.localHostUpdate.available === true && typeof result.localHostUpdate.runPath === 'string') {
1331
1402
  console.log(`Ask whether to update local host plugins: ${result.localHostUpdate.hosts.join(', ')}`);
1403
+ console.log(`Post-release command: release-skill post-release --plan ${planPath} --run ${result.localHostUpdate.runPath}`);
1404
+ console.log('Choose --hosts before adding --update-local-hosts to perform a local update.');
1405
+ } else {
1406
+ for (const step of result.localHostUpdate.nextSteps ?? []) {
1407
+ console.log(`Next [${step.code}] ${step.message} (${step.argv.join(' ')})`);
1408
+ }
1332
1409
  }
1333
1410
  } else {
1334
1411
  console.log(`Local host update: ${result.status}`);
@@ -1344,9 +1421,15 @@ if (command === 'post-release') {
1344
1421
  console.log(JSON.stringify({
1345
1422
  error: err.code ?? 'POST_RELEASE_FAILED',
1346
1423
  message: err.message,
1424
+ details: publicErrorDetails(err),
1347
1425
  exitCode: err.exitCode ?? 1,
1348
1426
  }));
1349
- } else console.error(`Error: ${err.message}`);
1427
+ } else {
1428
+ console.error(`Error: ${err.message}`);
1429
+ for (const step of err.details?.nextSteps ?? []) {
1430
+ console.error(`Next [${step.code}] ${step.message} (${step.argv.join(' ')})`);
1431
+ }
1432
+ }
1350
1433
  await exitAfterFlush(err.exitCode ?? 1);
1351
1434
  }
1352
1435
  }
@@ -1401,7 +1484,7 @@ if (command === 'verify') {
1401
1484
  } = await import('../src/commands/post-release-local.mjs');
1402
1485
  const plan = JSON.parse(await readFile(planPath, 'utf8'));
1403
1486
  try {
1404
- result.postRelease = derivePostReleaseChecklist(plan);
1487
+ result.postRelease = derivePostReleaseChecklist(plan, { root, runPath: result.runPath });
1405
1488
  } catch (error) {
1406
1489
  result.postRelease = unavailablePostReleaseChecklist(plan, error);
1407
1490
  }
@@ -1427,8 +1510,15 @@ if (command === 'verify') {
1427
1510
  } else if (result.postRelease?.merge.promptRequired) {
1428
1511
  console.log('Post-release: ask whether to merge the remaining branch.');
1429
1512
  }
1430
- if (result.postRelease?.localHostUpdate.promptRequired) {
1513
+ if (result.postRelease?.localHostUpdate?.available === true
1514
+ && typeof result.postRelease.localHostUpdate.runPath === 'string') {
1431
1515
  console.log(`Post-release: ask whether to update local host plugins (${result.postRelease.localHostUpdate.hosts.join(', ')}).`);
1516
+ console.log(`Post-release command: release-skill post-release --plan ${planPath} --run ${result.postRelease.localHostUpdate.runPath}`);
1517
+ console.log('Choose --hosts before adding --update-local-hosts to perform a local update.');
1518
+ } else {
1519
+ for (const step of result.postRelease?.localHostUpdate?.nextSteps ?? []) {
1520
+ console.log(`Next [${step.code}] ${step.message} (${step.argv.join(' ')})`);
1521
+ }
1432
1522
  }
1433
1523
  }
1434
1524
 
@@ -1438,6 +1528,7 @@ if (command === 'verify') {
1438
1528
  const errOutput = {
1439
1529
  error: err.code ?? 'UNKNOWN_ERROR',
1440
1530
  message: err.message,
1531
+ details: err.details ?? {},
1441
1532
  exitCode: err.exitCode ?? 1,
1442
1533
  };
1443
1534
  console.log(JSON.stringify(errOutput));