release-skill 0.9.36 → 0.9.38

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 (109) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codebuddy-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +2 -2
  4. package/.cursor-plugin/plugin.json +1 -1
  5. package/.kimi-plugin/plugin.json +1 -1
  6. package/.qoder-plugin/plugin.json +1 -1
  7. package/CHANGELOG.md +56 -0
  8. package/INSTALL.md +2 -2
  9. package/INSTALL.zh-CN.md +2 -2
  10. package/README.md +48 -14
  11. package/README.zh-CN.md +42 -14
  12. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  13. package/adapters/claude/bin/release-skill.bundle.mjs +2020 -1457
  14. package/adapters/claude/schemas/release-plan.schema.json +1 -0
  15. package/adapters/claude/schemas/test-intake.schema.json +1 -0
  16. package/adapters/claude/skills/release-config/SKILL.md +36 -41
  17. package/adapters/claude/skills/release-docs/SKILL.md +38 -35
  18. package/adapters/claude/skills/release-help/SKILL.md +59 -54
  19. package/adapters/claude/skills/release-marketplace/SKILL.md +38 -46
  20. package/adapters/claude/skills/release-prepare/SKILL.md +38 -27
  21. package/adapters/claude/skills/release-publish/SKILL.md +38 -44
  22. package/adapters/claude/skills/release-reconcile/SKILL.md +14 -0
  23. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  24. package/adapters/codex/bin/release-skill.bundle.mjs +2020 -1457
  25. package/adapters/codex/schemas/release-plan.schema.json +1 -0
  26. package/adapters/codex/schemas/test-intake.schema.json +1 -0
  27. package/adapters/codex/skills/release-config/SKILL.md +36 -41
  28. package/adapters/codex/skills/release-docs/SKILL.md +38 -35
  29. package/adapters/codex/skills/release-help/SKILL.md +59 -54
  30. package/adapters/codex/skills/release-marketplace/SKILL.md +38 -46
  31. package/adapters/codex/skills/release-prepare/SKILL.md +38 -27
  32. package/adapters/codex/skills/release-publish/SKILL.md +38 -44
  33. package/adapters/codex/skills/release-reconcile/SKILL.md +14 -0
  34. package/adapters/cursor/.cursor-plugin/plugin.json +1 -1
  35. package/adapters/cursor/bin/release-skill.bundle.mjs +2020 -1457
  36. package/adapters/cursor/schemas/release-plan.schema.json +1 -0
  37. package/adapters/cursor/schemas/test-intake.schema.json +1 -0
  38. package/adapters/cursor/skills/release-config/SKILL.md +36 -41
  39. package/adapters/cursor/skills/release-docs/SKILL.md +38 -35
  40. package/adapters/cursor/skills/release-help/SKILL.md +59 -54
  41. package/adapters/cursor/skills/release-marketplace/SKILL.md +38 -46
  42. package/adapters/cursor/skills/release-prepare/SKILL.md +38 -27
  43. package/adapters/cursor/skills/release-publish/SKILL.md +38 -44
  44. package/adapters/cursor/skills/release-reconcile/SKILL.md +14 -0
  45. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  46. package/adapters/kimi/bin/release-skill.bundle.mjs +2020 -1457
  47. package/adapters/kimi/schemas/release-plan.schema.json +1 -0
  48. package/adapters/kimi/schemas/test-intake.schema.json +1 -0
  49. package/adapters/kimi/skills/release-config/SKILL.md +36 -41
  50. package/adapters/kimi/skills/release-docs/SKILL.md +38 -35
  51. package/adapters/kimi/skills/release-help/SKILL.md +59 -54
  52. package/adapters/kimi/skills/release-marketplace/SKILL.md +38 -46
  53. package/adapters/kimi/skills/release-prepare/SKILL.md +38 -27
  54. package/adapters/kimi/skills/release-publish/SKILL.md +38 -44
  55. package/adapters/kimi/skills/release-reconcile/SKILL.md +14 -0
  56. package/adapters/qoder/.qoder-plugin/plugin.json +1 -1
  57. package/adapters/qoder/bin/release-skill.bundle.mjs +2020 -1457
  58. package/adapters/qoder/schemas/release-plan.schema.json +1 -0
  59. package/adapters/qoder/schemas/test-intake.schema.json +1 -0
  60. package/adapters/qoder/skills/release-config/SKILL.md +36 -41
  61. package/adapters/qoder/skills/release-docs/SKILL.md +38 -35
  62. package/adapters/qoder/skills/release-help/SKILL.md +59 -54
  63. package/adapters/qoder/skills/release-marketplace/SKILL.md +38 -46
  64. package/adapters/qoder/skills/release-prepare/SKILL.md +38 -27
  65. package/adapters/qoder/skills/release-publish/SKILL.md +38 -44
  66. package/adapters/qoder/skills/release-reconcile/SKILL.md +14 -0
  67. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  68. package/adapters/workbuddy/bin/release-skill.bundle.mjs +2020 -1457
  69. package/adapters/workbuddy/schemas/release-plan.schema.json +1 -0
  70. package/adapters/workbuddy/schemas/test-intake.schema.json +1 -0
  71. package/adapters/workbuddy/skills/release-config/SKILL.md +36 -41
  72. package/adapters/workbuddy/skills/release-docs/SKILL.md +38 -35
  73. package/adapters/workbuddy/skills/release-help/SKILL.md +59 -54
  74. package/adapters/workbuddy/skills/release-marketplace/SKILL.md +38 -46
  75. package/adapters/workbuddy/skills/release-prepare/SKILL.md +38 -27
  76. package/adapters/workbuddy/skills/release-publish/SKILL.md +38 -44
  77. package/adapters/workbuddy/skills/release-reconcile/SKILL.md +14 -0
  78. package/bin/release-skill-cli.mjs +5 -0
  79. package/bin/release-skill.bundle.mjs +2020 -1457
  80. package/package.json +1 -1
  81. package/platform-manifest.json +4 -4
  82. package/references/01-state-machine.md +26 -0
  83. package/references/02-project-config.md +108 -7
  84. package/schemas/release-plan.schema.json +1 -0
  85. package/schemas/test-intake.schema.json +1 -0
  86. package/skills/release-config/SKILL.md +36 -41
  87. package/skills/release-docs/SKILL.md +38 -35
  88. package/skills/release-help/SKILL.md +59 -54
  89. package/skills/release-marketplace/SKILL.md +38 -46
  90. package/skills/release-prepare/SKILL.md +38 -27
  91. package/skills/release-publish/SKILL.md +38 -44
  92. package/skills/release-reconcile/SKILL.md +14 -0
  93. package/skills-src/release-config/SKILL.md +36 -41
  94. package/skills-src/release-docs/SKILL.md +38 -35
  95. package/skills-src/release-help/SKILL.md +59 -54
  96. package/skills-src/release-marketplace/SKILL.md +38 -46
  97. package/skills-src/release-prepare/SKILL.md +38 -27
  98. package/skills-src/release-publish/SKILL.md +38 -44
  99. package/skills-src/release-reconcile/SKILL.md +14 -0
  100. package/src/commands/approve.mjs +33 -24
  101. package/src/commands/prepare.mjs +9 -2
  102. package/src/commands/publish.mjs +10 -2
  103. package/src/commands/reconcile.mjs +2 -0
  104. package/src/commands/route.mjs +2 -159
  105. package/src/commands/verify.mjs +123 -17
  106. package/src/core/plan-replacement.mjs +371 -0
  107. package/src/core/pre-authority-failure.mjs +241 -0
  108. package/src/core/skill-resource-closure.mjs +1 -1
  109. package/src/core/test-candidate.mjs +15 -67
@@ -1,3 +1,4 @@
1
+ import { readFormalPlans, bindReplacement, canonicalFormalPlanPath } from './plan-replacement.mjs';
1
2
  /**
2
3
  * One test-intake candidate: exclusive save, strict read, and the no-git
3
4
  * handoff decision shared by prepare and ship.
@@ -281,68 +282,6 @@ function sameTargetVersions(left, right) {
281
282
  *
282
283
  * @param {string} root
283
284
  */
284
- async function readFormalPlans(root) {
285
- const plansPath = resolve(root, '.release-skill', 'plans');
286
- let names;
287
- try {
288
- names = await readdir(plansPath);
289
- } catch (err) {
290
- if (err?.code === 'ENOENT') {
291
- try {
292
- await lstat(plansPath);
293
- } catch (statError) {
294
- if (statError?.code === 'ENOENT') return [];
295
- throw new ReleaseError(CONFIG_INVALID, 'formal test acceptance plans could not be read', {
296
- reason: 'TEST_CANDIDATE_AUTHORITY_INVALID',
297
- field: 'plansPath',
298
- cause: statError?.code ?? 'unknown',
299
- });
300
- }
301
- }
302
- throw new ReleaseError(CONFIG_INVALID, 'formal test acceptance plans could not be read', {
303
- reason: 'TEST_CANDIDATE_AUTHORITY_INVALID',
304
- field: 'plansPath',
305
- cause: err?.code ?? 'unknown',
306
- });
307
- }
308
- const plans = [];
309
- for (const name of names) {
310
- if (!name.endsWith('.json')) continue;
311
- const rel = `.release-skill/plans/${name}`;
312
- let receipt;
313
- try {
314
- receipt = await readFileStrict(root, rel, { encoding: 'utf8' });
315
- } catch (err) {
316
- const cause = harnessKind(err) ?? 'unknown';
317
- throw new ReleaseError(CONFIG_INVALID, 'formal test acceptance plan could not be read', {
318
- reason: 'TEST_CANDIDATE_AUTHORITY_INVALID',
319
- field: 'planPath',
320
- path: rel,
321
- cause,
322
- });
323
- }
324
- let plan;
325
- try {
326
- plan = JSON.parse(receipt.content);
327
- } catch {
328
- throw new ReleaseError(CONFIG_INVALID, 'formal test acceptance plan is not valid JSON', {
329
- reason: 'TEST_CANDIDATE_AUTHORITY_INVALID',
330
- field: 'planPath',
331
- path: rel,
332
- });
333
- }
334
- plans.push({
335
- planPath: rel,
336
- planDigest: typeof plan?.digest === 'string' ? plan.digest : name.slice(0, -'.json'.length),
337
- status: plan?.status,
338
- subjectDigest: typeof plan?.testAcceptance?.subjectDigest === 'string'
339
- ? plan.testAcceptance.subjectDigest
340
- : null,
341
- targetVersions: targetVersionsOf(plan),
342
- });
343
- }
344
- return plans;
345
- }
346
285
 
347
286
  async function sameAuthorityFile(left, right) {
348
287
  const identity = async (value) => {
@@ -432,10 +371,11 @@ export async function decideTestCandidateHandoff({
432
371
  expectedSubjectDigest,
433
372
  configDigest,
434
373
  extraDrifts = [],
374
+ replaceUnpublishedPlan,
435
375
  }) {
436
376
  if (!config?.testIntake) {
437
- if (candidatePath) {
438
- throw new ReleaseError(CONFIG_INVALID, 'candidate path requires testIntake', {
377
+ if (candidatePath || replaceUnpublishedPlan !== undefined) {
378
+ throw new ReleaseError(CONFIG_INVALID, 'candidate or replacement path requires testIntake', {
439
379
  reason: 'TEST_CANDIDATE_WITHOUT_INTAKE',
440
380
  field: 'candidatePath',
441
381
  });
@@ -449,7 +389,8 @@ export async function decideTestCandidateHandoff({
449
389
  });
450
390
  }
451
391
  if (!candidatePath) {
452
- const saved = await writeTestCandidate({ root, runDir, prepared, requirements, prepareOptions });
392
+ const bound = await bindReplacement({ root, prepared, replaceUnpublishedPlan, resultFile: requirements.resultFile });
393
+ const saved = await writeTestCandidate({ root, runDir, prepared: bound, requirements, prepareOptions });
453
394
  const subjectDigest = computePlanDigest(saved.candidate.prepared);
454
395
  return {
455
396
  kind: 'save-candidate',
@@ -483,6 +424,9 @@ export async function decideTestCandidateHandoff({
483
424
  });
484
425
  }
485
426
 
427
+ if (replaceUnpublishedPlan !== undefined && canonicalFormalPlanPath(root, replaceUnpublishedPlan) !== `.release-skill/plans/${loaded.candidate.prepared.replacesPlanDigest}.json`) {
428
+ throw new ReleaseError(GATE_FAILED, 'replacement selection differs from frozen candidate');
429
+ }
486
430
  const options = loaded.candidate.prepareOptions;
487
431
  const drifts = [...extraDrifts];
488
432
  if (loaded.candidate.writerVersion !== WRITER_VERSION) drifts.push('writerVersion');
@@ -510,10 +454,13 @@ export async function decideTestCandidateHandoff({
510
454
  });
511
455
  }
512
456
 
513
- const formalPlans = await readFormalPlans(root);
457
+ const formalPlans = await readFormalPlans(root, loaded.candidate.prepared);
514
458
  const targetVersions = targetVersionsOf(loaded.candidate.prepared);
459
+ const predecessor = loaded.candidate.prepared.replacesPlanDigest;
460
+ const retired = new Set(formalPlans.map((entry) => entry.plan.replacesPlanDigest).filter(Boolean));
461
+ if (predecessor) retired.add(predecessor);
515
462
  const differentBasis = formalPlans.filter((plan) => (
516
- sameTargetVersions(plan.targetVersions, targetVersions) && plan.subjectDigest !== subjectDigest
463
+ !retired.has(plan.planDigest) && sameTargetVersions(plan.targetVersions, targetVersions) && plan.subjectDigest !== subjectDigest
517
464
  ));
518
465
  if (differentBasis.length > 0) {
519
466
  throw new ReleaseError(GATE_FAILED, 'a different formal acceptance basis already exists for this target', {
@@ -543,6 +490,7 @@ export async function decideTestCandidateHandoff({
543
490
  };
544
491
  }
545
492
 
493
+ await bindReplacement({ root, prepared: loaded.candidate.prepared, replaceUnpublishedPlan, resultFile: requirements.resultFile });
546
494
  const acceptance = await acceptTestResults({
547
495
  root,
548
496
  requirements,