From 199aef32f9992f84117c7de94a497419bdf59785 Mon Sep 17 00:00:00 2001 From: Miguel Angel Simon Sierra Date: Thu, 29 Oct 2026 00:26:00 -0400 Subject: [PATCH 1/2] fix(ci): skills sync steps past any taken ClawHub version, not one hard-coded skill --- scripts/clawhub-sync.mjs | 53 ++++++++++++++++++----------------- scripts/clawhub-sync.test.mjs | 43 +++++++++++++++++++--------- 2 files changed, 58 insertions(+), 38 deletions(-) diff --git a/scripts/clawhub-sync.mjs b/scripts/clawhub-sync.mjs index 09bbf26ce4e..d8695b32d58 100644 --- a/scripts/clawhub-sync.mjs +++ b/scripts/clawhub-sync.mjs @@ -1,12 +1,12 @@ #!/usr/bin/env node -// `clawhub sync` bumps from the registry's `latest` tag, so a version the registry has hidden -// blocks the skill for good; an acknowledged one is published one patch past, then checked live. +// `clawhub sync` bumps from the registry's `latest` tag, so a version the registry holds but does +// not list (hidden) blocks the skill for good; the next free patch past it is published instead, +// then checked live. import { spawnSync } from "node:child_process"; import { pathToFileURL } from "node:url"; const TAKEN_VERSION = /Version (\d+)\.(\d+)\.(\d+) already exists/; -// The registry hid this one version; any other taken version still fails the sync. -const SKIPPABLE_TAKEN = new Map([["hyperframes-creative", "1.0.13"]]); +const MAX_PUBLISH_ATTEMPTS = 5; const OWNER_HANDLE = "heygen-com"; const OWNER = ["--owner", OWNER_HANDLE]; @@ -36,29 +36,32 @@ const syncArgs = (provenance, dryRun) => dryRun ? ["--dry-run"] : [], ); -function versionPastSkippable(slug, message) { - const taken = takenVersion(message); - if (!taken || SKIPPABLE_TAKEN.get(slug) !== taken.join(".")) return null; - const [major, minor, patch] = taken; - return `${major}.${minor}.${patch + 1}`; -} - function republishTaken(run, provenance, { slug, message }) { - const version = versionPastSkippable(slug, message); - if (!version) return `${slug}: ${message}`; + let taken = takenVersion(message); + if (!taken) return `${slug}: ${message}`; const folder = `skills/${slug}`; - const published = run( - ["publish", folder, "--slug", slug, "--version", version, "--source-path", folder].concat( - OWNER, - provenance, - ), - ); - if (!published.ok) return `${slug}: ${published.message}`; - const latest = latestTag(run, slug); - if (latest !== version) - return `${slug}: published ${version}, but the registry's latest is ${latest}`; - console.log(`${slug}: ${SKIPPABLE_TAKEN.get(slug)} is taken, published ${version}`); - return null; + for (let attempt = 0; attempt < MAX_PUBLISH_ATTEMPTS; attempt++) { + const [major, minor, patch] = taken; + const version = `${major}.${minor}.${patch + 1}`; + const published = run( + ["publish", folder, "--slug", slug, "--version", version, "--source-path", folder].concat( + OWNER, + provenance, + ), + ); + const nextTaken = published.ok ? null : takenVersion(published.message); + if (nextTaken) { + taken = nextTaken; + continue; + } + if (!published.ok) return `${slug}: ${published.message}`; + const latest = latestTag(run, slug); + if (latest !== version) + return `${slug}: published ${version}, but the registry's latest is ${latest}`; + console.log(`${slug}: ${taken.join(".")} is taken, published ${version}`); + return null; + } + return `${slug}: no free version after ${taken.join(".")} in ${MAX_PUBLISH_ATTEMPTS} tries`; } export function syncSkills({ run, provenance, dryRun }) { diff --git a/scripts/clawhub-sync.test.mjs b/scripts/clawhub-sync.test.mjs index 04e3aad78eb..d80dd314410 100644 --- a/scripts/clawhub-sync.test.mjs +++ b/scripts/clawhub-sync.test.mjs @@ -79,11 +79,25 @@ describe("ClawHub skill sync", () => { ]); }); - it("fails rather than skipping further when the next patch is taken too", () => { - const clawhub = fakeClawhub({ syncFailed: [creativeTaken], takenVersions: ["1.0.14"] }); + it("steps past consecutive taken versions to the next free patch", () => { + const clawhub = fakeClawhub({ + syncFailed: [creativeTaken], + takenVersions: ["1.0.14", "1.0.15"], + }); - assert.deepEqual(sync(clawhub), [`hyperframes-creative: ${TAKEN("1.0.14")}`]); - assert.equal(clawhub.calls.filter((args) => args[0] === "publish").length, 1); + assert.deepEqual(sync(clawhub), []); + const versions = clawhub.calls + .filter((args) => args[0] === "publish") + .map((args) => args[args.indexOf("--version") + 1]); + assert.deepEqual(versions, ["1.0.14", "1.0.15", "1.0.16"]); + }); + + it("gives up after a bounded number of taken versions", () => { + const takenVersions = Array.from({ length: 9 }, (_, i) => `1.0.${14 + i}`); + const clawhub = fakeClawhub({ syncFailed: [creativeTaken], takenVersions }); + + assert.equal(sync(clawhub).length, 1); + assert.equal(clawhub.calls.filter((args) => args[0] === "publish").length, 5); }); it("still fails on any other refusal, without retrying it", () => { @@ -99,19 +113,22 @@ describe("ClawHub skill sync", () => { assert.deepEqual(sync(clawhub), ["hyperframes-creative: Rate limited"]); }); - it("fails on any taken version other than the one acknowledged, without publishing", () => { - const others = [ - { slug: "figma", message: TAKEN("1.0.17") }, - { slug: "figma", message: TAKEN("1.0.13") }, - { slug: "hyperframes-creative", message: TAKEN("1.0.15") }, + it("steps past a taken version for any skill, not one hard-coded", () => { + const failed = [ + { slug: "hyperframes", message: TAKEN("1.0.51") }, + { slug: "media-use", message: TAKEN("1.0.87") }, ]; - const clawhub = fakeClawhub({ syncFailed: others }); + const clawhub = fakeClawhub({ syncFailed: failed }); + assert.deepEqual(sync(clawhub), []); + const published = clawhub.calls.filter((args) => args[0] === "publish"); assert.deepEqual( - sync(clawhub), - others.map(({ slug, message }) => `${slug}: ${message}`), + published.map((args) => [args[3], args[args.indexOf("--version") + 1]]), + [ + ["hyperframes", "1.0.52"], + ["media-use", "1.0.88"], + ], ); - assert.equal(clawhub.calls.filter((args) => args[0] === "publish").length, 0); }); it("fails a sync that reports no failed skill or no readable result", () => { From b88777ef5de2add4a840f28e3314cc24f410542c Mon Sep 17 00:00:00 2001 From: Miguel Angel Simon Sierra Date: Thu, 29 Oct 2026 00:46:55 -0400 Subject: [PATCH 2/2] fix(ci): skills sync steps past the five hidden ClawHub versions blocking it --- scripts/clawhub-sync.mjs | 60 +++++++++++++++++++---------------- scripts/clawhub-sync.test.mjs | 51 +++++++++++++++-------------- 2 files changed, 59 insertions(+), 52 deletions(-) diff --git a/scripts/clawhub-sync.mjs b/scripts/clawhub-sync.mjs index d8695b32d58..3a13024a860 100644 --- a/scripts/clawhub-sync.mjs +++ b/scripts/clawhub-sync.mjs @@ -1,12 +1,19 @@ #!/usr/bin/env node -// `clawhub sync` bumps from the registry's `latest` tag, so a version the registry holds but does -// not list (hidden) blocks the skill for good; the next free patch past it is published instead, -// then checked live. +// `clawhub sync` bumps from the registry's `latest` tag, so a version the registry has hidden +// blocks the skill for good; an acknowledged one is published one patch past, then checked live. import { spawnSync } from "node:child_process"; import { pathToFileURL } from "node:url"; const TAKEN_VERSION = /Version (\d+)\.(\d+)\.(\d+) already exists/; -const MAX_PUBLISH_ATTEMPTS = 5; +// Versions the registry holds but does not list; any other taken version still fails the sync. +const SKIPPABLE_TAKEN = new Map([ + ["hyperframes-creative", "1.0.13"], + ["hyperframes", "1.0.51"], + ["hyperframes-cli", "1.0.73"], + ["hyperframes-registry", "1.0.15"], + ["media-use", "1.0.87"], + ["remotion-to-hyperframes", "1.0.17"], +]); const OWNER_HANDLE = "heygen-com"; const OWNER = ["--owner", OWNER_HANDLE]; @@ -36,32 +43,29 @@ const syncArgs = (provenance, dryRun) => dryRun ? ["--dry-run"] : [], ); +function versionPastSkippable(slug, message) { + const taken = takenVersion(message); + if (!taken || SKIPPABLE_TAKEN.get(slug) !== taken.join(".")) return null; + const [major, minor, patch] = taken; + return `${major}.${minor}.${patch + 1}`; +} + function republishTaken(run, provenance, { slug, message }) { - let taken = takenVersion(message); - if (!taken) return `${slug}: ${message}`; + const version = versionPastSkippable(slug, message); + if (!version) return `${slug}: ${message}`; const folder = `skills/${slug}`; - for (let attempt = 0; attempt < MAX_PUBLISH_ATTEMPTS; attempt++) { - const [major, minor, patch] = taken; - const version = `${major}.${minor}.${patch + 1}`; - const published = run( - ["publish", folder, "--slug", slug, "--version", version, "--source-path", folder].concat( - OWNER, - provenance, - ), - ); - const nextTaken = published.ok ? null : takenVersion(published.message); - if (nextTaken) { - taken = nextTaken; - continue; - } - if (!published.ok) return `${slug}: ${published.message}`; - const latest = latestTag(run, slug); - if (latest !== version) - return `${slug}: published ${version}, but the registry's latest is ${latest}`; - console.log(`${slug}: ${taken.join(".")} is taken, published ${version}`); - return null; - } - return `${slug}: no free version after ${taken.join(".")} in ${MAX_PUBLISH_ATTEMPTS} tries`; + const published = run( + ["publish", folder, "--slug", slug, "--version", version, "--source-path", folder].concat( + OWNER, + provenance, + ), + ); + if (!published.ok) return `${slug}: ${published.message}`; + const latest = latestTag(run, slug); + if (latest !== version) + return `${slug}: published ${version}, but the registry's latest is ${latest}`; + console.log(`${slug}: ${SKIPPABLE_TAKEN.get(slug)} is taken, published ${version}`); + return null; } export function syncSkills({ run, provenance, dryRun }) { diff --git a/scripts/clawhub-sync.test.mjs b/scripts/clawhub-sync.test.mjs index d80dd314410..31bb598bf62 100644 --- a/scripts/clawhub-sync.test.mjs +++ b/scripts/clawhub-sync.test.mjs @@ -79,25 +79,31 @@ describe("ClawHub skill sync", () => { ]); }); - it("steps past consecutive taken versions to the next free patch", () => { + it("publishes one patch past each acknowledged taken version", () => { + const acknowledged = [ + ["hyperframes", "1.0.51", "1.0.52"], + ["hyperframes-cli", "1.0.73", "1.0.74"], + ["hyperframes-registry", "1.0.15", "1.0.16"], + ["media-use", "1.0.87", "1.0.88"], + ["remotion-to-hyperframes", "1.0.17", "1.0.18"], + ]; const clawhub = fakeClawhub({ - syncFailed: [creativeTaken], - takenVersions: ["1.0.14", "1.0.15"], + syncFailed: acknowledged.map(([slug, taken]) => ({ slug, message: TAKEN(taken) })), }); assert.deepEqual(sync(clawhub), []); - const versions = clawhub.calls - .filter((args) => args[0] === "publish") - .map((args) => args[args.indexOf("--version") + 1]); - assert.deepEqual(versions, ["1.0.14", "1.0.15", "1.0.16"]); + const published = clawhub.calls.filter((args) => args[0] === "publish"); + assert.deepEqual( + published.map((args) => [args[3], args[args.indexOf("--version") + 1]]), + acknowledged.map(([slug, , next]) => [slug, next]), + ); }); - it("gives up after a bounded number of taken versions", () => { - const takenVersions = Array.from({ length: 9 }, (_, i) => `1.0.${14 + i}`); - const clawhub = fakeClawhub({ syncFailed: [creativeTaken], takenVersions }); + it("fails rather than skipping further when the next patch is taken too", () => { + const clawhub = fakeClawhub({ syncFailed: [creativeTaken], takenVersions: ["1.0.14"] }); - assert.equal(sync(clawhub).length, 1); - assert.equal(clawhub.calls.filter((args) => args[0] === "publish").length, 5); + assert.deepEqual(sync(clawhub), [`hyperframes-creative: ${TAKEN("1.0.14")}`]); + assert.equal(clawhub.calls.filter((args) => args[0] === "publish").length, 1); }); it("still fails on any other refusal, without retrying it", () => { @@ -113,22 +119,19 @@ describe("ClawHub skill sync", () => { assert.deepEqual(sync(clawhub), ["hyperframes-creative: Rate limited"]); }); - it("steps past a taken version for any skill, not one hard-coded", () => { - const failed = [ - { slug: "hyperframes", message: TAKEN("1.0.51") }, - { slug: "media-use", message: TAKEN("1.0.87") }, + it("fails on any taken version other than the one acknowledged, without publishing", () => { + const others = [ + { slug: "figma", message: TAKEN("1.0.17") }, + { slug: "figma", message: TAKEN("1.0.13") }, + { slug: "hyperframes-creative", message: TAKEN("1.0.15") }, ]; - const clawhub = fakeClawhub({ syncFailed: failed }); + const clawhub = fakeClawhub({ syncFailed: others }); - assert.deepEqual(sync(clawhub), []); - const published = clawhub.calls.filter((args) => args[0] === "publish"); assert.deepEqual( - published.map((args) => [args[3], args[args.indexOf("--version") + 1]]), - [ - ["hyperframes", "1.0.52"], - ["media-use", "1.0.88"], - ], + sync(clawhub), + others.map(({ slug, message }) => `${slug}: ${message}`), ); + assert.equal(clawhub.calls.filter((args) => args[0] === "publish").length, 0); }); it("fails a sync that reports no failed skill or no readable result", () => {