diff --git a/index.html b/index.html index 79a8c80..f9d93eb 100644 --- a/index.html +++ b/index.html @@ -431,7 +431,7 @@

Hive flagship

  • -

    Crux v0.1.0

    +

    Crux v0.2.0

    Run AI coding agents from YAML, send them work, and recover from stalls and crashes. No Hive required. Early software.

    Get started → diff --git a/llms.txt b/llms.txt index f6b68ce..4772bc7 100644 --- a/llms.txt +++ b/llms.txt @@ -9,7 +9,7 @@ Hive Commons also maintains Spektacular (Spek), a spec-driven workflow tool; the ## Projects - [Hive](https://github.com/hivecommons/hive): control layer for governed, audited fleets of AI coding agents. Docs: https://docs.hivecommons.dev -- [Crux](https://github.com/hivecommons/crux): starts AI coding agent CLIs in tmux from a YAML definition, sends them work, and restarts or pauses agents that stall. First release v0.1.0 (early software). Docs: https://docs.hivecommons.dev/docs/crux/overview/introduction +- [Crux](https://github.com/hivecommons/crux): starts AI coding agent CLIs in tmux from a YAML definition, sends them work, and restarts or pauses agents that stall. Latest release v0.2.0 (early software). Docs: https://docs.hivecommons.dev/docs/crux/overview/introduction - [Spektacular (Spek)](https://spektacular.dev): spec-driven development for AI coding agents; a markdown spek becomes a reviewed plan and an agent-driven implementation. - [hotshot](https://github.com/hivecommons/hotshot): watches for new screenshots and injects them into the terminal session you last used, for AI coding assistants that accept image paths. - [pluk](https://github.com/hivecommons/pluk): lightweight pub-sub event streaming for tmux sessions running AI coding agents; classifies, subscribes to, and reacts to their JSONL event streams. diff --git a/scripts/llms-txt.test.mjs b/scripts/llms-txt.test.mjs index 9406621..0f3f027 100644 --- a/scripts/llms-txt.test.mjs +++ b/scripts/llms-txt.test.mjs @@ -127,6 +127,20 @@ export function projectCards(html) { return cards; } +// --- version gates ----------------------------------------------------------- + +// Extract the Crux version tag from the project carousel: "

    Crux ... vX.Y.Z

    " +export function cruxVersionFromHtml(html) { + const m = html.match(/

    Crux\s*]*>([^<]*)<\/span><\/h3>/); + return m ? m[1] : null; +} + +// Extract the Crux version from llms.txt: "First release vX.Y.Z" or "Latest release vX.Y.Z" +export function cruxVersionFromLlms(text) { + const m = text.match(/\[Crux\].*?(?:First|Latest) release (v\d+\.\d+\.\d+)/); + return m ? m[1] : null; +} + function setDiff(a, b) { const sb = new Set(b); return a.filter((x) => !sb.has(x)); @@ -265,6 +279,22 @@ test("projectCards reads each carousel card's badge-free name from anchor and di assert.deepEqual(projectCards("

    nothing

    "), []); }); +test("fixture: cruxVersionFromHtml extracts version from Crux carousel tag or returns null when absent", () => { + assert.equal(cruxVersionFromHtml('

    Crux v0.2.0

    '), "v0.2.0"); + assert.equal(cruxVersionFromHtml('

    Crux v1.0.0

    '), "v1.0.0"); + assert.equal(cruxVersionFromHtml("

    Other v1.0.0

    "), null); + assert.equal(cruxVersionFromHtml("

    Crux

    "), null); + assert.equal(cruxVersionFromHtml(""), null); +}); + +test("fixture: cruxVersionFromLlms extracts version from llms.txt or returns null when absent", () => { + assert.equal(cruxVersionFromLlms("- [Crux](https://github.com/hivecommons/crux): ... Latest release v0.2.0 (early software)."), "v0.2.0"); + assert.equal(cruxVersionFromLlms("- [Crux](https://github.com/hivecommons/crux): ... First release v0.1.0 (early software)."), "v0.1.0"); + assert.equal(cruxVersionFromLlms("- [Other](https://github.com/hivecommons/other): ... Latest release v1.0.0."), null); + assert.equal(cruxVersionFromLlms("- [Crux](https://github.com/hivecommons/crux): ... No version here."), null); + assert.equal(cruxVersionFromLlms(""), null); +}); + // --- live checks ------------------------------------------------------------- const live = parseLlmsTxt(LLMS_TXT); @@ -320,3 +350,16 @@ test("llms.txt Projects list names the project carousel cards on index.html, in "llms.txt '## Projects' must list the same projects as the index.html carousel, in carousel order", ); }); + +test("Crux version in llms.txt matches the carousel tag in index.html and both are valid semver", () => { + const htmlVersion = cruxVersionFromHtml(INDEX_HTML); + const llmsVersion = cruxVersionFromLlms(LLMS_TXT); + const semverPattern = /^v\d+\.\d+\.\d+$/; + + assert.ok(htmlVersion, "Crux version tag not found in index.html carousel"); + assert.ok(llmsVersion, "Crux version not found in llms.txt"); + assert.match(htmlVersion, semverPattern, `index.html Crux tag '${htmlVersion}' is not valid semver`); + assert.match(llmsVersion, semverPattern, `llms.txt Crux version '${llmsVersion}' is not valid semver`); + assert.equal(htmlVersion, llmsVersion, `Crux version mismatch: index.html has '${htmlVersion}' but llms.txt has '${llmsVersion}' — keep them in sync`); +}); +