release-skill 0.5.0 → 0.6.0

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 (121) 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 +31 -0
  7. package/INSTALL.md +2 -2
  8. package/INSTALL.zh-CN.md +2 -2
  9. package/README.md +13 -21
  10. package/README.zh-CN.md +14 -22
  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/error-codes.json +1 -1
  14. package/adapters/claude/bin/kernel-protocol.json +1 -1
  15. package/adapters/claude/bin/registry.json +15 -1
  16. package/adapters/claude/bin/release-skill.bundle.mjs +38147 -23284
  17. package/adapters/claude/bin/rules.json +1 -1
  18. package/adapters/claude/schemas/.render-manifest.json +6 -6
  19. package/adapters/claude/schemas/release-plan.schema.json +401 -2
  20. package/adapters/claude/schemas/release-project.schema.json +291 -0
  21. package/adapters/claude/schemas/release-run.schema.json +59 -6
  22. package/adapters/claude/skills/release-config/SKILL.md +140 -0
  23. package/adapters/claude/skills/release-docs/SKILL.md +106 -0
  24. package/adapters/claude/skills/release-help/SKILL.md +39 -0
  25. package/adapters/claude/skills/release-marketplace/SKILL.md +147 -0
  26. package/adapters/claude/skills/release-publish/SKILL.md +25 -6
  27. package/adapters/claude/skills/release-verify/SKILL.md +10 -0
  28. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  29. package/adapters/codex/bin/error-codes.json +1 -1
  30. package/adapters/codex/bin/kernel-protocol.json +1 -1
  31. package/adapters/codex/bin/registry.json +15 -1
  32. package/adapters/codex/bin/release-skill.bundle.mjs +38147 -23284
  33. package/adapters/codex/bin/rules.json +1 -1
  34. package/adapters/codex/schemas/.render-manifest.json +6 -6
  35. package/adapters/codex/schemas/release-plan.schema.json +401 -2
  36. package/adapters/codex/schemas/release-project.schema.json +291 -0
  37. package/adapters/codex/schemas/release-run.schema.json +59 -6
  38. package/adapters/codex/skills/release-config/SKILL.md +147 -0
  39. package/adapters/codex/skills/release-docs/SKILL.md +113 -0
  40. package/adapters/codex/skills/release-help/SKILL.md +39 -0
  41. package/adapters/codex/skills/release-marketplace/SKILL.md +154 -0
  42. package/adapters/codex/skills/release-publish/SKILL.md +25 -6
  43. package/adapters/codex/skills/release-verify/SKILL.md +10 -0
  44. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  45. package/adapters/kimi/bin/error-codes.json +1 -1
  46. package/adapters/kimi/bin/kernel-protocol.json +1 -1
  47. package/adapters/kimi/bin/registry.json +15 -1
  48. package/adapters/kimi/bin/release-skill.bundle.mjs +38147 -23284
  49. package/adapters/kimi/bin/rules.json +1 -1
  50. package/adapters/kimi/schemas/.render-manifest.json +6 -6
  51. package/adapters/kimi/schemas/release-plan.schema.json +401 -2
  52. package/adapters/kimi/schemas/release-project.schema.json +291 -0
  53. package/adapters/kimi/schemas/release-run.schema.json +59 -6
  54. package/adapters/kimi/skills/release-config/SKILL.md +147 -0
  55. package/adapters/kimi/skills/release-docs/SKILL.md +113 -0
  56. package/adapters/kimi/skills/release-help/SKILL.md +39 -0
  57. package/adapters/kimi/skills/release-marketplace/SKILL.md +154 -0
  58. package/adapters/kimi/skills/release-publish/SKILL.md +25 -6
  59. package/adapters/kimi/skills/release-verify/SKILL.md +10 -0
  60. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  61. package/adapters/workbuddy/bin/error-codes.json +1 -1
  62. package/adapters/workbuddy/bin/kernel-protocol.json +1 -1
  63. package/adapters/workbuddy/bin/registry.json +15 -1
  64. package/adapters/workbuddy/bin/release-skill.bundle.mjs +38147 -23284
  65. package/adapters/workbuddy/bin/rules.json +1 -1
  66. package/adapters/workbuddy/schemas/.render-manifest.json +6 -6
  67. package/adapters/workbuddy/schemas/release-plan.schema.json +401 -2
  68. package/adapters/workbuddy/schemas/release-project.schema.json +291 -0
  69. package/adapters/workbuddy/schemas/release-run.schema.json +59 -6
  70. package/adapters/workbuddy/skills/release-config/SKILL.md +140 -0
  71. package/adapters/workbuddy/skills/release-docs/SKILL.md +106 -0
  72. package/adapters/workbuddy/skills/release-help/SKILL.md +39 -0
  73. package/adapters/workbuddy/skills/release-marketplace/SKILL.md +147 -0
  74. package/adapters/workbuddy/skills/release-publish/SKILL.md +25 -6
  75. package/adapters/workbuddy/skills/release-verify/SKILL.md +10 -0
  76. package/bin/error-codes.json +1 -1
  77. package/bin/kernel-protocol.json +1 -1
  78. package/bin/registry.json +15 -1
  79. package/bin/release-skill-cli.mjs +235 -7
  80. package/bin/release-skill.bundle.mjs +38147 -23284
  81. package/bin/rules.json +1 -1
  82. package/package.json +5 -4
  83. package/platform-manifest.json +359 -0
  84. package/references/.render-manifest.json +9 -9
  85. package/references/02-project-config.md +35 -0
  86. package/references/05-evidence-and-errors.md +44 -0
  87. package/references/06-adapter-contract.md +13 -0
  88. package/schemas/.render-manifest.json +6 -6
  89. package/schemas/release-plan.schema.json +401 -2
  90. package/schemas/release-project.schema.json +291 -0
  91. package/schemas/release-run.schema.json +59 -6
  92. package/skills/release-config/SKILL.md +140 -0
  93. package/skills/release-docs/SKILL.md +106 -0
  94. package/skills/release-help/SKILL.md +39 -0
  95. package/skills/release-marketplace/SKILL.md +147 -0
  96. package/skills/release-publish/SKILL.md +25 -6
  97. package/skills/release-verify/SKILL.md +10 -0
  98. package/skills-src/release-config/SKILL.md +140 -0
  99. package/skills-src/release-docs/SKILL.md +106 -0
  100. package/skills-src/release-help/SKILL.md +39 -0
  101. package/skills-src/release-marketplace/SKILL.md +147 -0
  102. package/skills-src/release-publish/SKILL.md +25 -6
  103. package/skills-src/release-verify/SKILL.md +10 -0
  104. package/src/adapters/contract.mjs +5 -0
  105. package/src/adapters/distribute-git.mjs +625 -0
  106. package/src/adapters/plugin-marketplace.mjs +48 -4
  107. package/src/adapters/push-snapshot.mjs +12 -4
  108. package/src/commands/assess.mjs +171 -0
  109. package/src/commands/distribute.mjs +1078 -0
  110. package/src/commands/hooks.mjs +6 -1
  111. package/src/commands/lineage.mjs +616 -0
  112. package/src/commands/prepare.mjs +413 -72
  113. package/src/commands/route.mjs +686 -0
  114. package/src/commands/ship.mjs +45 -1
  115. package/src/commands/verify.mjs +176 -0
  116. package/src/core/baseline-advance.mjs +185 -0
  117. package/src/core/baseline.mjs +11 -1
  118. package/src/core/checkpoints.mjs +25 -0
  119. package/src/core/foundation-inflight.mjs +7 -0
  120. package/src/core/plan.mjs +12 -2
  121. package/src/core/postpublish.mjs +315 -0
@@ -2179,10 +2179,34 @@ export function createPluginMarketplaceAdapter(deps = {}) {
2179
2179
  // pluginRootForEntrySkill 在非 external 结构化 CLI 路径中已确定;
2180
2180
  // external 路径或 human-attestation(null)时回退到 snapshotDirReal。
2181
2181
  const pluginRootForSkill = pluginRootForEntrySkill || snapshotDirReal;
2182
- const entrySkillFile = resolve(pluginRootForSkill, 'skills', action.entrySkill, 'SKILL.md');
2182
+ // Prefixed skill-directory fallback (0.5.1 entry-skill-prefix fix,
2183
+ // D-ACA-30 platform_physical_name): some candidate builds name
2184
+ // skill directories `skills/<plugin>-<entrySkill>/` instead of
2185
+ // the unprefixed `skills/<entrySkill>/`. Resolve the unprefixed
2186
+ // layout first; when it misses, retry with the plugin prefix.
2187
+ let entrySkillFile = resolve(pluginRootForSkill, 'skills', action.entrySkill, 'SKILL.md');
2188
+ let entrySkillExists = false;
2183
2189
  try {
2184
2190
  await stat(entrySkillFile);
2191
+ entrySkillExists = true;
2185
2192
  } catch {
2193
+ if (action.plugin) {
2194
+ const prefixed = resolve(
2195
+ pluginRootForSkill,
2196
+ 'skills',
2197
+ `${action.plugin}-${action.entrySkill}`,
2198
+ 'SKILL.md',
2199
+ );
2200
+ try {
2201
+ await stat(prefixed);
2202
+ entrySkillFile = prefixed;
2203
+ entrySkillExists = true;
2204
+ } catch {
2205
+ // both layouts miss; report below
2206
+ }
2207
+ }
2208
+ }
2209
+ if (!entrySkillExists) {
2186
2210
  const skillRel = relative(snapshotDirReal, entrySkillFile) || `skills/${action.entrySkill}/SKILL.md`;
2187
2211
  return createResult({
2188
2212
  actionType,
@@ -3476,8 +3500,11 @@ export function createPluginMarketplaceAdapter(deps = {}) {
3476
3500
  });
3477
3501
  }
3478
3502
 
3479
- // Verify entry skill exists as a regular file in install dir
3480
- const entrySkillPath = resolve(installPath, 'skills', action.entrySkill, 'SKILL.md');
3503
+ // Verify entry skill exists as a regular file in install dir.
3504
+ // Prefixed skill-directory fallback (0.5.1 entry-skill-prefix fix):
3505
+ // mirror the preflight resolution — `skills/<entrySkill>` first,
3506
+ // then `skills/<plugin>-<entrySkill>` when the plugin is known.
3507
+ let entrySkillPath = resolve(installPath, 'skills', action.entrySkill, 'SKILL.md');
3481
3508
  let entrySkillFound = false;
3482
3509
  try {
3483
3510
  const skillStat = await lstat(entrySkillPath);
@@ -3485,7 +3512,24 @@ export function createPluginMarketplaceAdapter(deps = {}) {
3485
3512
  entrySkillFound = true;
3486
3513
  }
3487
3514
  } catch {
3488
- // entry skill not found
3515
+ // entry skill not found in unprefixed layout
3516
+ }
3517
+ if (!entrySkillFound && action.plugin) {
3518
+ const prefixedPath = resolve(
3519
+ installPath,
3520
+ 'skills',
3521
+ `${action.plugin}-${action.entrySkill}`,
3522
+ 'SKILL.md',
3523
+ );
3524
+ try {
3525
+ const skillStat = await lstat(prefixedPath);
3526
+ if (skillStat.isFile() && !skillStat.isSymbolicLink()) {
3527
+ entrySkillPath = prefixedPath;
3528
+ entrySkillFound = true;
3529
+ }
3530
+ } catch {
3531
+ // both layouts miss; report below
3532
+ }
3489
3533
  }
3490
3534
 
3491
3535
  if (!entrySkillFound) {
@@ -58,10 +58,16 @@ function validateAction(action) {
58
58
  githubRepositoryUrl(action.repo, action.githubHost);
59
59
 
60
60
  const validStrategies = ['create-release-branch', 'advance-existing-branch', 'initialize-default-branch'];
61
- const strategy = action.branchStrategy ?? 'create-release-branch';
62
- if (typeof strategy !== 'string' || !validStrategies.includes(strategy)) {
61
+ // Explicit strategy is required (0.5.1 chain hardening): the historical
62
+ // `?? 'create-release-branch'` default silently produced orphan root
63
+ // commits whenever a plan omitted the strategy — the root cause of the
64
+ // synthetic-date root commits in the public repositories. The plan layer
65
+ // now guarantees the value; a missing value here fails closed instead of
66
+ // degrading to the orphan-root channel.
67
+ if (typeof action.branchStrategy !== 'string' || !validStrategies.includes(action.branchStrategy)) {
63
68
  throw new Error(`branchStrategy must be one of: ${validStrategies.join(', ')}`);
64
69
  }
70
+ const strategy = action.branchStrategy;
65
71
  if (strategy === 'advance-existing-branch' || strategy === 'initialize-default-branch') {
66
72
  if (!action.parentCommit || typeof action.parentCommit !== 'string') {
67
73
  throw new Error(`${strategy} requires parentCommit`);
@@ -126,7 +132,8 @@ export function createPushSnapshotAdapter(deps = {}) {
126
132
  { shell: false },
127
133
  );
128
134
  const remoteTip = stdout.trim().split(/\s+/)[0] ?? '';
129
- const strategy = action.branchStrategy ?? 'create-release-branch';
135
+ // Guaranteed by validateAction above (branchStrategy is required).
136
+ const strategy = action.branchStrategy;
130
137
 
131
138
  if (strategy === 'advance-existing-branch') {
132
139
  if (!remoteTip) {
@@ -173,7 +180,8 @@ export function createPushSnapshotAdapter(deps = {}) {
173
180
  });
174
181
  const gitDir = await inspectLocalObjects(action, context, exec);
175
182
  const remoteUrl = githubRepositoryUrl(action.repo, action.githubHost);
176
- const strategy = action.branchStrategy ?? 'create-release-branch';
183
+ // Guaranteed by validateAction above (branchStrategy is required).
184
+ const strategy = action.branchStrategy;
177
185
 
178
186
  if (strategy === 'advance-existing-branch') {
179
187
  await exec(
@@ -709,6 +709,171 @@ async function checkRemotePrerequisites(root, config, offline) {
709
709
  return gaps;
710
710
  }
711
711
 
712
+ /**
713
+ * Parse a semver-ish version string into comparable components.
714
+ *
715
+ * Accepts `major.minor.patch` with an optional `-prerelease` suffix (which
716
+ * sorts strictly below the same release triple). Returns null for anything
717
+ * that is not a version-shaped tag (candidate names like `0.1.27-candidate.2`
718
+ * parse fine; non-version tags are skipped).
719
+ *
720
+ * @param {string} version
721
+ * @returns {{ major: number, minor: number, patch: number, prerelease: string|null } | null}
722
+ */
723
+ export function parseSemverVersion(version) {
724
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(version);
725
+ if (!match) return null;
726
+ return {
727
+ major: Number(match[1]),
728
+ minor: Number(match[2]),
729
+ patch: Number(match[3]),
730
+ prerelease: match[4] ?? null,
731
+ };
732
+ }
733
+
734
+ /**
735
+ * Compare two parsed semver values. Returns a negative/zero/positive number.
736
+ *
737
+ * @param {{ major: number, minor: number, patch: number, prerelease: string|null }} a
738
+ * @param {{ major: number, minor: number, patch: number, prerelease: string|null }} b
739
+ * @returns {number}
740
+ */
741
+ export function compareSemverVersions(a, b) {
742
+ if (a.major !== b.major) return a.major - b.major;
743
+ if (a.minor !== b.minor) return a.minor - b.minor;
744
+ if (a.patch !== b.patch) return a.patch - b.patch;
745
+ if (a.prerelease && !b.prerelease) return -1;
746
+ if (!a.prerelease && b.prerelease) return 1;
747
+ return 0;
748
+ }
749
+
750
+ /**
751
+ * Describe a version-sequence gap between an immediate predecessor and the
752
+ * target version, or null when the step is contiguous.
753
+ *
754
+ * A gap means some version between the two was never released (e.g. the
755
+ * historical 0.1.1 -> 0.1.3 jump with no v0.1.2 tag). Pre-release steps
756
+ * never count as gaps by themselves.
757
+ *
758
+ * @param {{ major: number, minor: number, patch: number, prerelease: string|null }} prev
759
+ * @param {{ major: number, minor: number, patch: number, prerelease: string|null }} target
760
+ * @returns {string|null}
761
+ */
762
+ export function describeVersionSequenceGap(prev, target) {
763
+ if (prev.major !== target.major) {
764
+ return prev.major + 1 < target.major
765
+ ? `major ${prev.major}.x.x -> ${target.major}.x.x`
766
+ : null;
767
+ }
768
+ if (prev.minor !== target.minor) {
769
+ return prev.minor + 1 < target.minor
770
+ ? `minor ${prev.major}.${prev.minor}.x -> ${target.major}.${target.minor}.x`
771
+ : null;
772
+ }
773
+ if (prev.patch !== target.patch) {
774
+ return prev.patch + 1 < target.patch
775
+ ? `patch ${prev.major}.${prev.minor}.${prev.patch} -> ${target.major}.${target.minor}.${target.patch}`
776
+ : null;
777
+ }
778
+ return null;
779
+ }
780
+
781
+ /**
782
+ * Build a regex that extracts the version from a tag name following the
783
+ * unit's tagTemplate (`{version}` placeholder, regex-special characters
784
+ * escaped).
785
+ *
786
+ * @param {string} tagTemplate
787
+ * @returns {RegExp}
788
+ */
789
+ export function buildTagVersionRegex(tagTemplate) {
790
+ const escaped = tagTemplate
791
+ .split('{version}')
792
+ .map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
793
+ .join('(.+)');
794
+ return new RegExp(`^${escaped}$`);
795
+ }
796
+
797
+ /**
798
+ * Check the public repository's release tag sequence for version jumps
799
+ * (0.5.1 chain hardening): when the immediate predecessor release is more
800
+ * than one version step below the current target version, register a
801
+ * warning-level gap `VERSION_SEQUENCE_GAP` (e.g. 0.1.1 -> 0.1.3 with no
802
+ * v0.1.2 tag). This never blocks: it records the observation for the human
803
+ * release planner.
804
+ *
805
+ * @param {string} root - Project root.
806
+ * @param {Object} config - The loaded project config.
807
+ * @returns {Promise<Object[]>} Array of warning gap entries.
808
+ */
809
+ async function checkReleaseTagSequence(root, config) {
810
+ const gaps = [];
811
+ for (const unit of config.releaseUnits ?? []) {
812
+ const tagTemplate = unit.version?.tagTemplate;
813
+ if (!tagTemplate || !unit.publicRepo) continue;
814
+
815
+ const versionRegex = buildTagVersionRegex(tagTemplate);
816
+ const tagPattern = tagTemplate.replace('{version}', '*');
817
+
818
+ let stdout;
819
+ try {
820
+ ({ stdout } = await execFile(
821
+ 'git',
822
+ ['ls-remote', `https://github.com/${unit.publicRepo}.git`, `refs/tags/${tagPattern}`],
823
+ { cwd: root, shell: false, encoding: 'utf8', timeout: 15_000 },
824
+ ));
825
+ } catch {
826
+ // Tag enumeration failure (network/auth/repo absent) is not a version
827
+ // gap and must not block; the remote-prerequisites npm check already
828
+ // reports unreachable registries where relevant.
829
+ continue;
830
+ }
831
+
832
+ let targetVersion;
833
+ try {
834
+ const pkg = JSON.parse(await readFile(resolve(root, unit.source, 'package.json'), 'utf8'));
835
+ targetVersion = pkg.version;
836
+ } catch {
837
+ continue; // package metadata issues are reported elsewhere
838
+ }
839
+ const targetParsed = parseSemverVersion(targetVersion);
840
+ if (!targetParsed) continue;
841
+
842
+ let previousParsed = null;
843
+ let previousTag = null;
844
+ for (const line of stdout.trim().split('\n')) {
845
+ if (!line) continue;
846
+ const tagName = line.split('\t')[1]?.replace(/^refs\/tags\//, '');
847
+ if (!tagName) continue;
848
+ const versionMatch = versionRegex.exec(tagName);
849
+ if (!versionMatch) continue;
850
+ const parsed = parseSemverVersion(versionMatch[1]);
851
+ if (!parsed) continue;
852
+ // Prereleases never qualify as the "previous release" predecessor.
853
+ if (parsed.prerelease) continue;
854
+ if (compareSemverVersions(parsed, targetParsed) >= 0) continue;
855
+ if (previousParsed === null || compareSemverVersions(parsed, previousParsed) > 0) {
856
+ previousParsed = parsed;
857
+ previousTag = tagName;
858
+ }
859
+ }
860
+
861
+ if (!previousParsed) continue;
862
+ const gap = describeVersionSequenceGap(previousParsed, targetParsed);
863
+ if (gap) {
864
+ gaps.push(createGap({
865
+ scope: GapScope.PROJECT,
866
+ category: GapCategory.REMOTE,
867
+ severity: Severity.WARNING,
868
+ code: 'VERSION_SEQUENCE_GAP',
869
+ message: `发布单元 "${unit.id}" 的版本序列存在跳号(${gap},上一发布标签 ${previousTag},目标版本 ${targetVersion}):请确认中间版本未发布是否是有意为之;发布链仍会以链式历史推进,但跳过的版本不会补造标签`,
870
+ file: '.release-skill/project.yaml',
871
+ }));
872
+ }
873
+ }
874
+ return gaps;
875
+ }
876
+
712
877
  /**
713
878
  * Perform a basic README structural check.
714
879
  *
@@ -967,6 +1132,12 @@ export async function assessProject(options) {
967
1132
  const remoteGaps = await checkRemotePrerequisites(root, config, offline);
968
1133
  allGaps.push(...remoteGaps);
969
1134
 
1135
+ // --- 7b. Release tag sequence (online only; warning, never blocking) ---
1136
+ if (!offline) {
1137
+ const tagSequenceGaps = await checkReleaseTagSequence(root, config);
1138
+ allGaps.push(...tagSequenceGaps);
1139
+ }
1140
+
970
1141
  // --- 8. README structure check ---
971
1142
  const readmeGaps = await checkReadmeStructure(root, config);
972
1143
  allGaps.push(...readmeGaps);