Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ <h3>Hive <span class="tag">flagship</span></h3>
<li class="slide" data-slide>
<div class="card">
<svg class="card-icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 9l3 3-3 3M12 15h5"/></svg>
<h3>Crux <span class="tag tag-new">v0.1.0</span></h3>
<h3>Crux <span class="tag tag-new">v0.2.0</span></h3>
<p>Run AI coding agents from YAML, send them work, and recover from stalls and crashes. No Hive required. Early software.</p>
<div class="card-actions">
<a class="btn btn-primary btn-sm" href="https://docs.hivecommons.dev/docs/crux/overview/introduction">Get started →</a>
Expand Down
2 changes: 1 addition & 1 deletion llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
43 changes: 43 additions & 0 deletions scripts/llms-txt.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,20 @@ export function projectCards(html) {
return cards;
}

// --- version gates -----------------------------------------------------------

// Extract the Crux version tag from the project carousel: "<h3>Crux ... <span...>vX.Y.Z</span></h3>"
export function cruxVersionFromHtml(html) {
const m = html.match(/<h3>Crux\s*<span[^>]*>([^<]*)<\/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));
Expand Down Expand Up @@ -265,6 +279,22 @@ test("projectCards reads each carousel card's badge-free name from anchor and di
assert.deepEqual(projectCards("<p>nothing</p>"), []);
});

test("fixture: cruxVersionFromHtml extracts version from Crux carousel tag or returns null when absent", () => {
assert.equal(cruxVersionFromHtml('<h3>Crux <span class="tag tag-new">v0.2.0</span></h3>'), "v0.2.0");
assert.equal(cruxVersionFromHtml('<h3>Crux <span class="tag">v1.0.0</span></h3>'), "v1.0.0");
assert.equal(cruxVersionFromHtml("<h3>Other <span>v1.0.0</span></h3>"), null);
assert.equal(cruxVersionFromHtml("<h3>Crux</h3>"), 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);
Expand Down Expand Up @@ -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`);
});

Loading