From d0b93e96fe07aab103e83fee06ed8d69399829a2 Mon Sep 17 00:00:00 2001 From: Segun Olumbe <11536849+nugehs@users.noreply.github.com> Date: Mon, 28 Sep 2026 10:52:32 +0100 Subject: [PATCH] fix(docs): catch a stale site banner and missing What's New entry; publish v3.3.0 on the site --- CHANGELOG.md | 4 ++++ docs/index.md | 12 ++++++++++- scripts/check-version.js | 6 +++++- src/lib/version-docs.js | 39 ++++++++++++++++++++++++++++------ tests/version-docs.test.js | 43 +++++++++++++++++++++++++++++++++++++- 5 files changed, 95 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a25fc21..55435a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project follows SemVer. ## [Unreleased] +### Fixed + +- **`version:check` catches a stale docs site.** `docs/index.md` opens with a `**vX.Y.Z** is published` banner, which replaced the `**Status:** v` line the release sync and drift check looked for, so neither saw it: 3.3.0 was released on 2026-09-27 with `version:check` green while bashbop.github.io/otito still announced v3.2.0 and had no 3.3.0 entry under What's New. `syncPinnedDocVersion` now rewrites the banner, `findPinnedDocVersionDrift` reports it, and a new `findWhatsNewDrift` fails `version:check` when What's New has no `vX.Y.Z published` entry for the `package.json` version (it is written by hand, so the sync cannot add it). The site gets its v3.3.0 banner and entry. + ## [3.3.0] - 2026-09-27 The terminal output gets colour and shape it didn't have before. `otito` resolves one glyph set per run (plain Unicode by default, ASCII in CI, emoji opt-in) and builds tables, trees and coloured lists from shared string-building primitives instead of ad-hoc strings; every bullet and list item now dims its marker the way a box border is dimmed, matching the headers, boxes and closing line that already had colour. `otito install` prompts a human at a TTY for how to install, with a short personalized pitch first, while every non-interactive caller (`--yes`, `--json`, CI, agents) is unaffected. No command, field or schema was removed. diff --git a/docs/index.md b/docs/index.md index 3f1ed8a..0150b9b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,7 +4,7 @@ > For teams that want any coding agent to produce evidence a human can trust before merge. -**v3.2.0** is published to npm, GitHub Releases, and the official MCP Registry. Òtítọ́ is a Bashbop Ltd product, MIT licensed. +**v3.3.0** is published to npm, GitHub Releases, and the official MCP Registry. Òtítọ́ is a Bashbop Ltd product, MIT licensed. --- @@ -19,6 +19,16 @@ ## What's New +!!! tip "v3.3.0 published (2026-09-27)" + The terminal output gets colour and shape it didn't have before. No command, field or schema was removed. + + - The default terminal look is plain Unicode (`✓ ! ✗`, box drawing, arrows) with no emoji; CI, `NO_EMOJI`, `--no-emoji` and `TERM=dumb` keep ASCII, and `--emoji` opts back in. Tables, trees and lists come from shared primitives, and every command that prints for a person ends with one closing line such as `Verified.` or `Runs without errors.` + - `otito install` asks a person at a terminal how to install, while `--yes`, `--json`, CI and agents get the same output as before. + - `context_pack` and `change_impact` stop ranking translation catalogs, file extensions and test notes ahead of the code a request names; a path or symbol named in the request is pinned as a required owner. + - A version bump that reaches `main` is tagged and released by the new `Tag release` workflow, without a hand-pushed tag. + + [npm v3.3.0](https://www.npmjs.com/package/@bashbop/otito/v/3.3.0) · [GitHub Release](https://github.com/BASHBOP/otito/releases/tag/v3.3.0) · [MCP Registry](https://registry.modelcontextprotocol.io/?q=io.github.BASHBOP%2Fotito) + !!! tip "v3.2.0 published (2026-09-26)" The gates gate what they are given. No command, field or schema was removed. diff --git a/scripts/check-version.js b/scripts/check-version.js index c92cccf..c5af78b 100644 --- a/scripts/check-version.js +++ b/scripts/check-version.js @@ -1,7 +1,7 @@ import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; -import { findPinnedDocVersionDrift } from "../src/lib/version-docs.js"; +import { findPinnedDocVersionDrift, findWhatsNewDrift } from "../src/lib/version-docs.js"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const packageJson = readJson(path.join(root, "package.json")); @@ -52,6 +52,10 @@ function checkPinnedDocVersions(filePath, expectedVersion) { for (const issue of findPinnedDocVersionDrift(content, expectedVersion)) { fail(`${relativePath} ${issue} (run \`node scripts/sync-server-version.mjs\` to resync)`); } + + for (const issue of findWhatsNewDrift(content, expectedVersion)) { + fail(`${relativePath} ${issue} (add it by hand from CHANGELOG.md)`); + } } function readJson(filePath, { optional = false } = {}) { diff --git a/src/lib/version-docs.js b/src/lib/version-docs.js index 4a24f06..1b7f636 100644 --- a/src/lib/version-docs.js +++ b/src/lib/version-docs.js @@ -5,19 +5,25 @@ const VERSIONED_COMMAND = /(npm install -g @bashbop\/otito@|npx -y @bashbop\/otito@)(\d+\.\d+\.\d+)/g; const STATUS_LINE = /(\*\*Status:\*\* v)(\d+\.\d+\.\d+)/; +// The landing page's banner, "**v3.3.0** is published to npm, ...". It +// replaced the "**Status:** v" line, and neither the sync nor the drift +// check knew its shape, so the 3.3.0 release shipped with the site still +// announcing v3.2.0. +const PUBLISHED_BANNER = /(\*\*v)(\d+\.\d+\.\d+)(\*\* is published)/; +const WHATS_NEW_HEADING = /^## What's New\s*$/m; -// Rewrites pinned `@bashbop/otito@X.Y.Z` install/verify commands and the -// docs "Status" line to the current release. Deliberately does not touch -// docs/index.md's "What's New" section (a per-release changelog entry with -// its own historical npm/GitHub-release links) since neither pattern -// appears there. +// Rewrites pinned `@bashbop/otito@X.Y.Z` install/verify commands, the +// docs "Status" line and the landing page's published banner to the current +// release. Deliberately does not touch docs/index.md's "What's New" section +// (a per-release changelog entry with its own historical npm/GitHub-release +// links) since none of these patterns appears there. /** * @param {string} content * @param {string} version * @returns {{ content: string, changed: boolean }} */ export function syncPinnedDocVersion(content, version) { - const updated = content.replace(VERSIONED_COMMAND, `$1${version}`).replace(STATUS_LINE, `$1${version}`); + const updated = content.replace(VERSIONED_COMMAND, `$1${version}`).replace(STATUS_LINE, `$1${version}`).replace(PUBLISHED_BANNER, `$1${version}$3`); return { content: updated, changed: updated !== content }; } @@ -41,5 +47,26 @@ export function findPinnedDocVersionDrift(content, expectedVersion) { issues.push(`Status line says v${statusMatch[2]} but package.json version is ${expectedVersion}`); } + const bannerMatch = content.match(PUBLISHED_BANNER); + if (bannerMatch && bannerMatch[2] !== expectedVersion) { + issues.push(`published banner says v${bannerMatch[2]} but package.json version is ${expectedVersion}`); + } + return issues; } + +// The "What's New" section is written by hand per release, so the sync +// cannot fill it in; this only reports that the current version has no +// entry. Docs without the section (RELEASE.md) report nothing. +/** + * @param {string} content + * @param {string} expectedVersion + * @returns {string[]} + */ +export function findWhatsNewDrift(content, expectedVersion) { + if (!WHATS_NEW_HEADING.test(content)) return []; + + const escaped = expectedVersion.replace(/\./g, "\\."); + const entry = new RegExp(`^!!! \\w+ "v${escaped} published`, "m"); + return entry.test(content) ? [] : [`What's New has no "v${expectedVersion} published" entry for the current package.json version`]; +} diff --git a/tests/version-docs.test.js b/tests/version-docs.test.js index c282d3a..3719f21 100644 --- a/tests/version-docs.test.js +++ b/tests/version-docs.test.js @@ -1,6 +1,6 @@ import test from "node:test"; import assert from "node:assert/strict"; -import { syncPinnedDocVersion, findPinnedDocVersionDrift } from "../src/lib/version-docs.js"; +import { syncPinnedDocVersion, findPinnedDocVersionDrift, findWhatsNewDrift } from "../src/lib/version-docs.js"; const WHATS_NEW = ` ## What's New @@ -73,3 +73,44 @@ test("findPinnedDocVersionDrift ignores docs with no pinned version markers", () const issues = findPinnedDocVersionDrift("# Just prose, no pins here.", "1.9.2"); assert.deepEqual(issues, []); }); + +// docs/index.md replaced the "**Status:** v" line with this banner, and the +// 3.3.0 release shipped with the site still announcing v3.2.0. +function bannerIndexFixture(bannerVersion, whatsNewVersions) { + const entries = whatsNewVersions.map((v) => `!!! tip "v${v} published (2026-09-27)"\n - notes\n`).join("\n"); + return `**v${bannerVersion}** is published to npm, GitHub Releases, and the official MCP Registry. + +## What's New + +${entries}`; +} + +test("syncPinnedDocVersion rewrites the published banner", () => { + const { content, changed } = syncPinnedDocVersion(bannerIndexFixture("3.2.0", ["3.2.0"]), "3.3.0"); + assert.equal(changed, true); + assert.match(content, /^\*\*v3\.3\.0\*\* is published/); + assert.match(content, /v3\.2\.0 published \(2026-09-27\)/, "What's New history is untouched"); +}); + +test("findPinnedDocVersionDrift reports a stale published banner", () => { + assert.deepEqual(findPinnedDocVersionDrift(bannerIndexFixture("3.2.0", ["3.3.0"]), "3.3.0"), [ + "published banner says v3.2.0 but package.json version is 3.3.0", + ]); + assert.deepEqual(findPinnedDocVersionDrift(bannerIndexFixture("3.3.0", ["3.3.0"]), "3.3.0"), []); +}); + +test("findWhatsNewDrift reports a release with no What's New entry", () => { + assert.deepEqual(findWhatsNewDrift(bannerIndexFixture("3.3.0", ["3.2.0", "3.1.0"]), "3.3.0"), [ + 'What\'s New has no "v3.3.0 published" entry for the current package.json version', + ]); + assert.deepEqual(findWhatsNewDrift(bannerIndexFixture("3.3.0", ["3.3.0", "3.2.0"]), "3.3.0"), []); +}); + +test("findWhatsNewDrift does not accept a different version that shares a prefix", () => { + assert.equal(findWhatsNewDrift(bannerIndexFixture("3.3.0", ["3.3.01"]), "3.3.0").length, 1); + assert.equal(findWhatsNewDrift(bannerIndexFixture("3.3.0", ["3.3.0-rc.1"]), "3.3.0").length, 1); +}); + +test("findWhatsNewDrift ignores docs without a What's New section", () => { + assert.deepEqual(findWhatsNewDrift("npm install -g @bashbop/otito@3.3.0\n", "3.3.0"), []); +});