Skip to content

feat: recency-aware channel/version API for CI consumers (#3) - #4

Merged
mobileskyfi merged 3 commits into
mainfrom
feat/recency-channel-api
Jun 21, 2026
Merged

feat: recency-aware channel/version API for CI consumers (#3)#4
mobileskyfi merged 3 commits into
mainfrom
feat/recency-channel-api

Conversation

@mobileskyfi

@mobileskyfi mobileskyfi commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Closes #3.

What

Exposes the RouterOS version/channel facts CI consumers (centrs) need, so they can import them instead of scraping quickchr version stdout. centrs keeps the merge-gate policy; quickchr owns version facts + recency classification.

1. Public version exports (#3 ask 1)

src/index.ts now re-exports the version helpers, so import { resolveAllVersions } from "@tikoci/quickchr" works (no blocked deep src/lib/... paths): resolveVersion, resolveAllVersions, parseVersionParts, compareRouterOsVersion, isValidVersion, isProvisioningSupportedVersion, CHANNELS, and the Channel type.

2. version --json (#3 ask 2)

$ quickchr version --json
{"stable":"7.23.1","long-term":"7.21.4","testing":"7.23rc4","development":"7.24beta2"}

Offline emits {}. Human output unchanged.

3. Suffix-aware comparator (#3 ask 3 — bug)

compareRouterOsVersion now orders pre-release suffixes:
7.24beta2 < 7.24rc1 < 7.24 < 7.24.1 (previously the beta/rc suffix was stripped, so those compared equal).

⚠️ Behavior change for callers comparing pre-release versions. Verified the only in-repo consumers (cache-prune "older than long-term", doctor stale-image check) compare release versions against a release, so they're unaffected. The anchor test that encoded the old equality was updated.

4. Recency / active-channels API (#3 ask 4 — the smarts)

classifyChannels(versions): ChannelStatus[]          // { channel, version, maturity, aheadOfStable }
selectActiveChannels(versions, { aheadOf? }): Channel[]   // pure, network-free
resolveChannelStatuses(): Promise<ChannelStatus[]>   // fetch + classify
resolveActiveChannels({ aheadOf? }): Promise<Channel[]>

"Active" = every released channel (stable, long-term — always, even when long-term sits behind stable) plus any pre-release channel at or ahead of a reference channel (default stable). Answers "what's worth booting," never "what must pass."

Other --json gaps (per issue comment)

Audited the CLI: added doctor --json ({ ok, checks, staleImages }, exit still reflects ok) — it was already flagged centrs-facing in BACKLOG. networks/disk --json are tracked as a P3 follow-up in BACKLOG (out of scope here to keep the PR focused).

Acceptance / DoD

  • import { resolveAllVersions, compareRouterOsVersion, resolveActiveChannels, type Channel } from "@tikoci/quickchr" works (no deep path)
  • quickchr version --json emits a stable channel→version object
  • compareRouterOsVersion("7.24beta2","7.24rc1") < 0, ("7.24rc1","7.24") < 0, ("7.24beta1","7.24beta2") < 0 — unit-tested
  • resolveActiveChannels() returns released channels + pre-release at/ahead of stable — unit-tested with a fixture where testing is behind stable (excluded) and development is ahead (included)
  • CHANGELOG entry; existing version/cache behavior unaffected

Test plan

  • bun test test/unit/ — 566 pass / 0 fail (adds comparator suffix cases, recency fixtures — pure + mocked-fetch wrappers, CLI --json shape tests)
  • bun run check — biome + tsc + markdownlint + cspell clean
  • Live smoke: quickchr version --json, quickchr doctor --json, and a public-entry import smoke for selectActiveChannels/compareRouterOsVersion

Design boundary

Quickchr owns version facts + recency classification; the must-pass gate (which channels may red a merge) stays in the consumer (centrs).

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added --json output option to doctor and version CLI commands for CI/automation workflows
    • Introduced recency-aware channel classification and selection APIs for improved version management
  • Bug Fixes

    • Corrected RouterOS version comparison to properly order pre-release versions (beta < rc < final release)
  • Documentation

    • Enhanced CLI documentation with JSON output specifications and library API reference guide

Expose the RouterOS version facts CI consumers (centrs) need, so they can
import them instead of scraping `quickchr version` stdout.

- Re-export version helpers from the public entry (`resolveVersion`,
  `resolveAllVersions`, `parseVersionParts`, `compareRouterOsVersion`,
  `isValidVersion`, `isProvisioningSupportedVersion`, `CHANNELS`, `Channel`).
- Add recency API: `classifyChannels`/`resolveChannelStatuses` (maturity +
  aheadOfStable) and `selectActiveChannels`/`resolveActiveChannels` (released
  channels always + pre-release at/ahead of a reference, default stable).
  Pure variants take a `Record<Channel,string>` for network-free tests.
- Fix `compareRouterOsVersion`: order pre-release suffixes
  `betaN < rcN < release < patch` (was stripping suffix → all equal).
  Behavior change for pre-release comparisons; release-vs-release consumers
  (cache-prune, doctor stale-image) unaffected.
- Add `version --json` ({channel: version}) and `doctor --json`
  ({ok, checks, staleImages}); exit code still reflects doctor `ok`.
- Tests: comparator suffix ordering, recency fixtures (pure + mocked-fetch
  wrappers), CLI json shape. Docs: CHANGELOG, MANUAL, README, BACKLOG.

Quickchr owns version facts + recency; merge-gating policy stays in the
consumer. Out of scope, tracked in BACKLOG: networks/disk `--json`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 21, 2026 20:56
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@mobileskyfi, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 31 minutes and 8 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 835631fb-377c-4269-9564-551d049259d1

📥 Commits

Reviewing files that changed from the base of the PR and between 5b7e536 and 6a58f28.

📒 Files selected for processing (5)
  • MANUAL.md
  • src/cli/index.ts
  • src/lib/quickchr.ts
  • src/lib/types.ts
  • test/unit/versions.test.ts
📝 Walkthrough

Walkthrough

Adds suffix-aware RouterOS version comparison (beta < rc < release), a channel recency classification API (ChannelMaturity, ChannelStatus, classifyChannels, resolveActiveChannels), public barrel exports from src/index.ts, and --json output modes for the version and doctor CLI commands, with accompanying tests and documentation.

Changes

Recency-aware channel/version API for CI consumers

Layer / File(s) Summary
Stage-aware version parsing and comparison
src/lib/versions.ts
Introduces STAGE_RANK, ParsedVersion, and an internal parseVersion() to extract beta/rc stage and stage number; rewrites compareRouterOsVersion() to order beta < rc < release semantically; parseVersionParts() is derived from the new parser; resolveAllVersions() iterates CHANNELS instead of a hardcoded array.
Channel recency classification and active-channel selection
src/lib/versions.ts
Defines ChannelMaturity (released vs prerelease) and ChannelStatus (with aheadOfStable); exports channelMaturity(), classifyChannels(), selectActiveChannels(), and async wrappers resolveChannelStatuses()/resolveActiveChannels() that fetch via resolveAllVersions() and apply classification.
Public barrel exports
src/index.ts
Adds a "Version & channel utilities" block re-exporting CHANNELS, all version/channel helpers, and ChannelMaturity/ChannelStatus types from ./lib/versions.ts.
CLI --json for version and doctor
src/cli/index.ts
Command router passes args.slice(1) to both commands; cmdVersion emits { channel: version } JSON (or {} offline); cmdDoctor introduces StaleCacheImage/findStaleCacheImages() and emits { ok, checks, staleImages } JSON with exit code tied to ok; help text updated.
Tests for version comparison, channel recency, and CLI JSON
test/unit/versions.test.ts, test/unit/cli-version-doctor.test.ts
Adds pre-release ordering assertions to compareRouterOsVersion tests; new channel recency classification suite for channelMaturity/classifyChannels/selectActiveChannels; mocked-fetch suite for async wrappers; integration tests for version --json and doctor --json using a runQuickchr subprocess helper with temporary env isolation.
Documentation
MANUAL.md, README.md, CHANGELOG.md, BACKLOG.md
Updates MANUAL.md with --json forms for doctor/version and a new barrel API subsection; adds a README "Version & channel API" section with TypeScript examples; records CHANGELOG entries for all new APIs and the comparator fix; closes the BACKLOG issue with a follow-up item for networks/disk JSON.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 Hop, hop! The channels line up in a row,
Beta before RC, then release steals the show.
--json flies out when the CI comes calling,
No more scraping stdout — no banner-text sprawling!
The stable is stable, the active are picked,
And the recency rabbit has gotten it licked. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature: exposing a recency-aware channel/version API for CI consumers, with reference to issue #3.
Linked Issues check ✅ Passed All four key requirements from issue #3 are fully met: public exports of version helpers added to src/index.ts, version --json and doctor --json implemented with proper JSON output, compareRouterOsVersion now correctly orders pre-release suffixes (beta < rc < release), and recency-aware channel classification (resolveActiveChannels, resolveChannelStatuses) implemented.
Out of Scope Changes check ✅ Passed All changes directly support issue #3 objectives: documentation updates (BACKLOG.md, CHANGELOG.md, MANUAL.md, README.md) explain the new features; CLI modifications implement --json flags and argument parsing; library exports and version comparison logic implement the core functionality; test additions validate the new features.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/recency-channel-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Address self-review findings on #4:
- cmdDoctor: only pre-compute staleImages (a network long-term lookup when
  the cache is non-empty) in --json mode. In human mode print the checks
  first, then do the lookup, so `quickchr doctor` no longer blanks the
  terminal on the network before showing any output.
- MANUAL.md: replace the broken `§[Library API]` shortcut-reference link
  with plain prose.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a public, recency-aware RouterOS channel/version API (library + CLI JSON output) so CI consumers can import version facts instead of scraping CLI text, while fixing RouterOS pre-release version ordering.

Changes:

  • Export version/channel helpers from the package entry (src/index.ts), including recency classification + “active channels” selection.
  • Fix compareRouterOsVersion to order betaN < rcN < release < patch, and add unit coverage.
  • Add --json to quickchr version and quickchr doctor, with unit tests and docs/changelog updates.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
test/unit/versions.test.ts Adds comparator suffix tests + recency/active-channel tests + network-wrapper tests.
test/unit/cli-version-doctor.test.ts Adds CLI-level tests asserting version --json / doctor --json emit clean JSON.
src/lib/versions.ts Implements suffix-aware comparator and adds channel recency/active-channel APIs.
src/index.ts Re-exports version/channel utilities via the public entry point.
src/cli/index.ts Adds version --json and doctor --json, refactors stale-cache image detection helper.
README.md Documents new version/channel library API and CLI JSON option.
MANUAL.md Documents doctor --json, version --json, and the new library helpers.
CHANGELOG.md Adds unreleased entries for the new exports/CLI flags and comparator fix.
BACKLOG.md Marks issue #3 items complete and records follow-up JSON gaps.

Comment thread src/lib/versions.ts
Comment on lines 5 to 8
import type { Arch, Channel } from "./types.ts";
import { QuickCHRError } from "./types.ts";
import { CHANNELS, QuickCHRError } from "./types.ts";
import { fetchResilient } from "./net.ts";

Comment thread src/lib/versions.ts
Comment on lines 95 to 97
const results = await Promise.all(
channels.map(async (ch) => [ch, await resolveVersion(ch)] as const),
CHANNELS.map(async (ch) => [ch, await resolveVersion(ch)] as const),
);
Comment thread src/lib/versions.ts
Comment on lines +239 to +242
export function classifyChannels(versions: Record<Channel, string>): ChannelStatus[] {
const stableVersion = versions.stable;
return CHANNELS.map((channel) => ({
channel,
Comment thread src/lib/versions.ts
Comment on lines +260 to +263
return CHANNELS.filter((channel) => {
if (channelMaturity(channel) === "released") return true;
return compareRouterOsVersion(versions[channel], referenceVersion) >= 0;
});

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Line 19: Standardize the hyphenation of "prerelease" across all documentation
files to match the canonical type definition ChannelMaturity in
src/lib/versions.ts which uses "prerelease" without a hyphen. Search for all
occurrences of "pre-release" (with hyphen) in documentation files including
MANUAL.md line 548 and any other docs, and replace them with "prerelease" (no
hyphen) to maintain consistency with the actual API type definition.

In `@MANUAL.md`:
- Around line 557-562: The import block in MANUAL.md around line 557 is
incomplete and missing the re-exported helper functions `resolveVersion`,
`parseVersionParts`, `isValidVersion`, and `isProvisioningSupportedVersion` that
are documented in CHANGELOG.md as being exported from the public
`@tikoci/quickchr` entry point. Add these missing imports to the existing import
block, and then include a brief explanation after the code block describing when
these basic helper functions should be used, such as for pre-flight version
validation checks before calling start(). Alternatively, if this section is
intended to cover only the recency-classification API, add a clarifying note
that points readers to CHANGELOG.md for the complete list of re-exported
functions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 45b6ce95-850b-4a2c-b604-404f2981570c

📥 Commits

Reviewing files that changed from the base of the PR and between 2f9acb2 and 5b7e536.

📒 Files selected for processing (9)
  • BACKLOG.md
  • CHANGELOG.md
  • MANUAL.md
  • README.md
  • src/cli/index.ts
  • src/index.ts
  • src/lib/versions.ts
  • test/unit/cli-version-doctor.test.ts
  • test/unit/versions.test.ts

Comment thread CHANGELOG.md
`isProvisioningSupportedVersion`, `CHANNELS`, and the `Channel` type — no more
blocked deep `src/lib/...` imports.
- Recency-aware channel API: `resolveChannelStatuses()` / `classifyChannels()`
classify each channel by `maturity` (`released` | `prerelease`) and

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Standardize hyphenation of "prerelease" across docs.

Line 19 uses prerelease (no hyphen), which matches the upstream type definition (ChannelMaturity = "released" | "prerelease" in src/lib/versions.ts). However, MANUAL.md line 548 and other docs use "pre-release" (with hyphen). Standardize all documentation to use prerelease (no hyphen) to match the canonical form in the codebase.

Locations to update (search and replace)
  • MANUAL.md line 548: "prerelease" (currently shows "pre-release")
  • Any other occurrences of "pre-release" in docs should become "prerelease" when referring to the API type or classifier

Also applies to: 548-548

🧰 Tools
🪛 LanguageTool

[uncategorized] ~19-~19: Do not mix variants of the same word (‘prerelease’ and ‘pre-release’) within a single text.
Context: ...ach channel by maturity (released | prerelease) and aheadOfStable; `resolveActive...

(EN_WORD_COHERENCY)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` at line 19, Standardize the hyphenation of "prerelease" across
all documentation files to match the canonical type definition ChannelMaturity
in src/lib/versions.ts which uses "prerelease" without a hyphen. Search for all
occurrences of "pre-release" (with hyphen) in documentation files including
MANUAL.md line 548 and any other docs, and replace them with "prerelease" (no
hyphen) to maintain consistency with the actual API type definition.

Source: Linters/SAST tools

Comment thread MANUAL.md
Comment on lines +557 to +562
```ts
import {
resolveAllVersions, compareRouterOsVersion,
resolveChannelStatuses, resolveActiveChannels,
classifyChannels, selectActiveChannels, channelMaturity,
CHANNELS, type Channel, type ChannelStatus,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Incomplete import block — missing re-exported helpers from CHANGELOG.

The import block omits resolveVersion, parseVersionParts, isValidVersion, and isProvisioningSupportedVersion, which are documented in CHANGELOG.md lines 14–16 as being re-exported from the public @tikoci/quickchr entry point.

Either add these to the import block and explain their use (e.g., for pre-flight version validation), or clarify that this section covers only the recency-classification API and point readers to CHANGELOG for the full re-export list.

Proposed fix: Expand the import block to include all re-exported version helpers
 import {
+  resolveVersion, resolveAllVersions, parseVersionParts,
-  resolveAllVersions, compareRouterOsVersion,
+  compareRouterOsVersion, isValidVersion, isProvisioningSupportedVersion,
   resolveChannelStatuses, resolveActiveChannels,
   classifyChannels, selectActiveChannels, channelMaturity,
   CHANNELS, type Channel, type ChannelStatus,
 } from "`@tikoci/quickchr`";

Then optionally add a brief line after the code block explaining when the basic helpers are useful (e.g., "Use isValidVersion and isProvisioningSupportedVersion to validate versions in pre-flight checks before calling start()").

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@MANUAL.md` around lines 557 - 562, The import block in MANUAL.md around line
557 is incomplete and missing the re-exported helper functions `resolveVersion`,
`parseVersionParts`, `isValidVersion`, and `isProvisioningSupportedVersion` that
are documented in CHANGELOG.md as being exported from the public
`@tikoci/quickchr` entry point. Add these missing imports to the existing import
block, and then include a brief explanation after the code block describing when
these basic helper functions should be used, such as for pre-flight version
validation checks before calling start(). Alternatively, if this section is
intended to cover only the recency-classification API, add a clarifying note
that points readers to CHANGELOG.md for the complete list of re-exported
functions.

Address PR #4 review feedback:
- types.ts: CHANNELS is now `readonly Channel[]` + `Object.freeze`d, so
  neither library code (which iterates it in versions.ts) nor consumers can
  mutate the canonical channel order at runtime (Copilot). Root-cause fix at
  the source — no per-call snapshot. Updated the one `as string[]` cast that
  assumed mutability; added a frozen-array anchor test.
- MANUAL.md: note the entry also re-exports the lower-level version helpers
  (resolveVersion, parseVersionParts, isValidVersion,
  isProvisioningSupportedVersion) for pre-flight checks (CodeRabbit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mobileskyfi

Copy link
Copy Markdown
Contributor Author

Thanks for the reviews — addressed in 713b757 and 6a58f28.

Copilot — CHANNELS exported-mutable-array (×4): fixed at the source rather than per call site. CHANNELS is now readonly Channel[] and Object.freezed in src/lib/types.ts, so neither the library (which iterates it in resolveAllVersions/classifyChannels/selectActiveChannels) nor a consumer can mutate the canonical order at runtime. Preferred this over a local ALL_CHANNELS snapshot since the snapshot duplicates state and only protects the internal reads, not the exported array. Updated the one (CHANNELS as string[]) cast that assumed mutability and added a frozen-array anchor test.

CodeRabbit — MANUAL import block incomplete: added a line noting the entry also re-exports the lower-level helpers (resolveVersion, parseVersionParts, isValidVersion, isProvisioningSupportedVersion) for pre-flight version checks.

CodeRabbit — prerelease vs pre-release hyphenation: keeping as-is. Prose uses pre-release (hyphenated), which matches the pre-existing repo convention (CHANGELOG.md lines 7, 56, 141; MANUAL.md line 71); the only non-hyphenated form is the backticked code literal `prerelease`, which must match the ChannelMaturity union value. Changing the prose to prerelease would make these docs inconsistent with the rest of the changelog, so the register split (code literal vs prose) is intentional.

CI — Integration (linux/x86_64) failure: unrelated to this change. 34/35 integration tests passed, including every path this PR touches (resolveVersion, import QuickCHR, doctor returns DoctorResult). The single failure is a BOOT_TIMEOUT ("CHR did not respond within 180s, accel=kvm") on the clean() disk-reset reboot — the documented nested-KVM boot flakiness, not the version/CLI/exports code. The new push re-runs the job.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose recency-aware channel/version API for CI consumers (public exports, version --json, suffix-aware comparator, active-channels)

2 participants