All notable user-visible changes to @tikoci/rosetta are recorded here.
Format follows Keep a Changelog; project
uses Semantic Versioning.
Agentic rule. Any change with a user-visible effect (CLI, MCP tool shape, DB schema, CI behaviour, install flow) adds an entry under Unreleased in the same PR / commit. Promoting
[Unreleased]to a dated version header is a manual step, done by hand alongside thepackage.jsonversion bump before dispatching a latest-channel release (seeMANUAL.md"Release Workflow") — CI no longer auto-bumps versions or auto-promotes CHANGELOG on any channel. Prerelease (alpha/beta/rc) release runs never promote[Unreleased]either, since they don't represent the next stable version. CI-only auto-bumps and pure refactors with no external effect are intentionally omitted — git history is authoritative for those.Not a git log. Don't list every commit. One bullet per behaviour change, grouped under
Added/Changed/Fixed/Removed/Deprecated/Security. Details and rationale belong inDESIGN.md; the "what's next" backlog belongs inBACKLOG.md.
- CLI-Reference overlay (
cliref_*tables +rosetta exportdatasets, issue #124). A source-faithful, version-less overlay ofmanual.mikrotik.com/docs/cli-reference/*:cliref_pages/cliref_entries/cliref_fields/cliref_flagsretain each page's byte-exact Markdown (+ SHA-256), verbatim entry/field/flag descriptions, occurrence order, and line spans. Entries carry the manual-only facts a CHR/console/inspectcannot self-report (package/conditions/syscap). A storedcliref_entry_schema_linkscrosswalk resolves each entry to itsschema_nodescommand node (exact / single-internal-segment alias / manual-only), and thecliref_field_inspect_linksview derives the zero-to-many field→argument mapping.rosetta exportgains sevencli-reference/*outputs (six TSVs + byte-exactsource/<slug>.md). Build viamake extract-cliref+make link-cliref. schema_nodes.inspect_type. Preserves RouterOS' raw/console/inspectclass (path|dir|cmd|arg) that the existingtypecolumn normalizes (rawpath→dir). Additive; existingtypeconsumers are unchanged.- Released DBs now carry the CLI-Reference overlay, with CI gates (issue #127).
release.yml's build runsextract-cliref+link-cliref, so published DBs and thecli-reference/*export datasets are populated (previously the overlay only built locally). Two new blocking gates run inqa.ymldb-content against the built DB:V-cliref-link-driftbyte-compares the entry→inspect crosswalk against a committed, reviewablecli-reference-links.tsv(24 alias + 120 manual-only rows) and rejects any alias naming a segment outside the known allowlist;V-cliref-db-integrityasserts structural + semantic invariants (integrity/FK/orphans/enums/occurrence-order uniqueness, and the field view exposing only settableArgument→argmappings). Thedb-contentfloor now also requires populatedcliref_*tables + crosswalk. Regenerate the baseline after a corpus refresh withmake link-cliref-baseline.
- Property tables wider than two columns stored the wrong cell as the description (issue #132).
parsePropertieshardcodedcells[1], so the five| Property | Type | Default | Description |tables in the corpus (Apps, VETH) had their Type cell stored as the description withtype/default_valleft NULL — 52 rows, e.g./interface/veth/add addressreturning "IPv4/IPv6 address" instead of its prose, athighconfidence. Columns are now resolved by header name (Property/Parameter, optionalType, optionalDefault,Description), and the Markdown pipe escape|is decoded once the cell is split (page_tables.raw_markdownstays byte-exact). Requiring an explicitDescriptioncolumn also drops 14 rows that were never properties — adevice-modefeature matrix (| **Feature / Property** | **Home** | ... |) whose "descriptions" were the Home column'sYes/No. Apps' twoauto-updaterecords are retained: they only looked duplicated because the shift replaced both descriptions with the same Type cell. - CLI-Reference discovery silently dropped every branching menu's own Directory page (issue #137). MikroTik decomposed the CLI Reference from module pages into per-command leaf pages, and serves a branching menu (
app,caps-man/interface,interface/ethernet/switch/port, …) as a trailing-slash category URL insitemap.xml— which has no Markdown of its own. The menu's Directory entry is published at<dir>/<basename>.mdand listed only inllms.txt, so sitemap-only discovery lost 256 of 1,070 pages — and the field-heavy quarter of them:cliref_fieldscame out at 5,944 instead of 10,938. Those menus, their gates, and their argument tables were absent from the DB, fromcli-reference/*export datasets, and from anything reading the overlay. Discovery is now the union of both inventories, and the new blockingV-cliref-discoverygate fails the build if any sitemap category dir stops contributing its Directory leaf. Rebuilt corpus: 1,070 pages / 1,077 entries / 10,938 fields / 976 flags / 967 crosswalk links (was 228 / 1,051 / 10,118 / 948 / 931). - CLI-Reference crosswalk linked three commands to the wrong inspect node (issue #136).
KNOWN_ALIAS_SEGMENTStreated "this word is a doc-internal module name" as a global property, but it is positional:poeandqosare real menu segments published elsewhere in the same source.interface/ethernet/poe/monitorwas linked to/interface/ethernet/monitor(PoE-out fields vs SFP/link fields — disjoint sets), andinterface/ethernet/switch/qos/port{,/reset-counters}to their non-QoS namesakes. The failure was silent because the alias branch only runs when the exact path is missing fromschema_nodes— precisely the hardware-gated commands nobody can check from a CHR. A segment is now never dropped when the prefix ending at it is itself a published entry; all three become correctly manual-only. With the source reshape above, the crosswalk is 967 exact / 0 alias / 110 manual-only. - CLI-Reference extractor truncated
<ArgTableRow>attributes at a>inside a quoted value. Atyplikeiface_enum { <l2tp>:0xfffffffe }(e.g. oninterface/pppoe-server) contains a literal>, which the row parser mistook for the tag close — the field'sraw_typewas silently stored empty with a garbled description. The tag matcher now skips over whole quoted attribute values, so those fields carry their full type.
- Schema version 10 → 11 for the CLI-Reference overlay tables/view and
schema_nodes.inspect_type. routeros_lookup_propertyconfidence now grades the row, not the query branch (B-0024 step 4). Previouslyhighmeant only "this row is on the pagecommands.page_idlinks to" andlowmeant "the scoped branch found nothing" — the label described which SQL ran, so every property on a linked page was equallyhighand a correct row found by the global fallback was alwayslow. Each row is now scored against the requested menu using the menu paths named in its own documentation section, and results come back best tier first:high= the section is about that menu (it names the menu, and of the menus it names that one owns most of its properties) and the command tree does not contradict it;medium= the section names a neighbouring menu, names the requested one only in passing while documenting another, or only the page matches;low= nothing but the property name links the row to the menu. Withoutcommand_pathevery row staysmedium— there is no menu to align to.routeros_lookup_propertyno longer lets a mislinked page hide the right answer. Candidates are drawn from every page documenting the property rather than only the pagecommands.page_idlinks to, with page alignment applied per row. Off-page rows are kept only when their tier is at least as good as the best the linked page offers, and linked-page rows win ties within a tier, so a correct link returns the same tight, same-ordered result as before. Previously/interface/bridge/host+vidreturned two rows from the IGMP-snooping page it is mislinked to and never surfaced the "Static Entries" section that names/interface/bridge/hostoutright; the same suppression hit/interface/wifi/provisioning+radio-mac. Measured over the 14,832 (menu, property) pairs the command tree says are real: 31.5% of the labels that shipped ashighsurvive, 2,295 demote tomedium, 1,940 rows escape a wrongly-lowlabel (835 tohigh), and 153 rows that were suppressed entirely are now returned (80high). Regenerate withbun run src/eval/command-prose-join.ts. This is what makes/interface/bridge/port+pvidrank the two bridge-port sections above the unrelated Apps row (#131) and/ip/dhcp-server+address-poolrank the DHCP server table above the DHCPv6 one (#58).
0.11.1 — 2026-07-20
hAP ax2andhAP ax3linked to the wrong/hardwaremanual pages. MikroTik shifted the slugs by one upstream: the hAP ax² page moved/hardware/hap-ax→/hardware/hap-ax-2, and the hAP ax³ page moved/hardware/hap-ax-2→/hardware/hap-ax-3. The committed device map still held the pre-shift slugs, which were correct when generated, so afterwardshAP ax3resolved to/hardware/hap-ax-2— a URL that now serves hAP ax² — andhAP ax2pointed at a slug that no longer exists. Refreshing the hardware assessment corrects both rows.
- YouTube transcript corpus refreshed — 658 → 722 videos, ~2,316 non-empty transcript segments. The per-video duration cap rose from 1500s to 1600s so longer first-party channel videos qualify; recent MikroTik feature videos run past the old 25-minute cut, including the "Hermes explained" video (1528s) that discusses running an AI agent against RouterOS.
MUM_TITLE_PATTERNSremains the direct filter for conference talks — the duration cap is only a coarse backstop for the untitled long tail. A residual handful of videos still fail per sweep on transient YouTube throttling rather than missing captions, and converge over subsequent reruns; they are deliberately not recorded intranscripts/known-bad.json, which is reserved for videos with genuinely no English captions. - Product matrix snapshot refreshed to
matrix/2026-07-20/— no products added or removed;Cube 60Pro acandCubeSA 60Pro accorrectPoE outfrom18-48 Vto18-57 V. - Hardware overlay artifacts re-scraped from live sources (
ros-hardware-assessment.json,ros-www-assessment.json,device-map.tsv), clearing the drift the scheduled Device Map Refresh reported. Theros-www-assessment.jsondiff is large but content-neutral: the same 288 products with identical values, reordered because the committed copy predated the candidate-sorting change — regenerating with current code canonicalizes it once rather than repeatedly.
0.11.0 — 2026-07-20
-
The resolved DB now reports whether it can be trusted to ground claims about the code querying it (issue #94, B-0022 export-audit umbrella).
routeros_statsgains aprovenanceblock — resolveddb_path, invocationmode, thedb_metastamps (release_tag/source_commit/built_at/schema_version), and a grounding verdict (ok/schema_mismatch/internal_inconsistent/tag_behind/unstamped) — so one call self-checks the DB against the checkout instead of shelling into sqlite (the TUIstatsscreen shows it too, via sharedgetDbStats). A newmake db-doctor/bun run db:doctorprints the same verdict and exits non-zero when notok(CI/pre-commit-ready);make db-sync/bun run db:syncfetches the latest CI release DB — the documented grounding source of truth — into the resolved path (atomic, safe while a server holds the file open). In a dev checkout, MCP startup now emits a loud but non-fatal banner when the served DB has drifted (e.g. a corpus whosePRAGMA user_versionwas bumped in place so its stamped provenance no longer describes its bytes); it never fetches, so a contributor's local build is never clobbered. The load-bearing new signal isinternal_inconsistent(db_meta.schema_version≠ pragma), which every prior check missed. Durable guidance lives in.github/instructions/local-db-grounding.instructions.md. -
New
rosetta export <dir>command writes a DB-only dataset directory for local audit (issue #101, the B-0022 export track). Producesmanifest.tomlplus seven flat TSVs —changelog.tsv,callouts.tsv,properties.tsv(joined to pages with a resolvedsection_anchor),videos.tsv(metadata + per-video transcript segment/word/byte counts),commands.tsv, and the pairedpages.tsv(one row per page) /sections.tsv(one row per section, pivot-able) — from the runtime SQLite database alone — dataset generation reads only the resolved DB, with no caches or re-extraction (startup may still download/refresh the DB, as every command does). The serialization contract is a reversible Postgres-COPY-style escape (\t/\n/\r/\\backstopped on every value, whole-field\Nfor SQL NULL as distinct from the empty string) with stable ordering so a rebuild on the same DB is byte-identical, andmanifest.tomlcarriesdb_metaprovenance, per-file row counts, and honest disclosures of what the DB cannot provide (transcript provenance #21, per-version architecture #91). A column the DB cannot produce is omitted and disclosed, never recovered from a source artifact. -
rosetta exportnow emitstables.tsv, an inventory of every captured doc table (issue #111, under the #104 B-0022 export track). One row perpage_tablesrecord (855 on the rc.99 corpus) with page/section provenance, the table's shape (column_count/data_row_count/is_ragged), source size (raw_bytes, UTF-8 length of the stored Markdown), and atable_urlthat deep-links to the section containing the table (url#anchor; the bare page URL when no section resolves, since Docusaurus has no per-table anchor). This is the "what tables exist and where" list — it answers most table-audit questions without exporting the cell data itself (the per-fragment cell files remain E4). Reuses the settled TSV/manifest contract and is pruned/owned the same way as every other dataset. -
tables.tsvgains anis_property_sourceflag and reorders its columns human-readable-first (issue #118, rounding out the 0.11.0 export track for B-0077).is_property_sourceis1if and only if a row in thepropertiestable (the datarouteros_lookup_propertysurfaces) has asource_table_row_idresolving back to that table — i.e. extraction actually produced properties from it, not merely that it looks property-shaped, so a property-headed table the extractor gates skipped (issue #100) is honestly0. This lets a human reviewer see at a glance which captured tables are already recognized as property sources versus candidates B-0077 still needs to pick up. Columns are reordered so identity (title,source_heading) and the shape/size stats sit up front and the two long URL columns (table_url,url) sit last; sincetables.tsvfirst ships in this same 0.11.0 release, no already-released consumer is affected. -
Every Docusaurus pipe table is now retained as normalized, queryable data in schema v10 (issue #92).
page_tableskeeps raw Markdown plus page order, nearest h1–h6 heading, enclosing h1–h3section_id, width, and raggedness;page_table_rows/page_table_cellspreserve every header/data cell at its actual width with escaped pipes decoded. Table-derivedpropertiesnow point to their exact source row throughsource_table_row_id, while bullet-derived and historical properties remain honestly NULL. Property output is byte-for-byte unchanged; the shared fence-aware parser also corrects the B-0022 census counting error caused by an ordered-list code fence inzerotier.md(the rc.98 corpus contains 855 tables / 8,287 data rows, not 852 / 8,258). -
README.md and DESIGN.md now describe the live Docusaurus corpus as current, not a pending migration (issue #77). README's intro and "What's Inside" table dropped the "still needs a major migration away from the retired Confluence export" framing and refreshed prose-corpus figures for the actual Docusaurus build (363 pages/~653K words, 4,402 properties, 943 callouts), and gained a "Hardware overlay" row for
hardware_catalog/device_aliases(255 devices incl. legacy/EOL + accessories, ~750 alias mappings) that was previously absent from the feature list entirely. DESIGN.md's "Corpus Snapshot" section is retitled "Historical Corpus Snapshot (legacy Confluence)" to stop reading as current-state to a skimmer; its already-caveated legacy figures are otherwise untouched. -
YouTube transcript cache refreshed — 538 → 658 videos, ~2,090 non-empty transcript segments.
make extract-videospicked up a large backlog of previously-unswept videos (not just the handful published since the last 2026-07-07 sweep), converging over several incremental reruns as transient yt-dlp/YouTube request failures cleared on retry. Three genuinely non-English videos (Finnish, Russian, Japanese) that fail every run because YouTube has no English auto-captions for them are now recorded intranscripts/known-bad.jsonso future sweeps don't keep re-attempting them. -
Retrieval-quality net hardened for the 0.11 corpus swap — golden set 24 → 35, plus a direct-surface eval matrix (issue #53). The Phase 0 golden set (
fixtures/eval/queries.json) grows by 11 hand-verified queries: +2 natural-language (pppoe,dns), +4 property, +3 changelog (fixed-CVE / feature-introduced / version-scoped), +2 video — closing the zero-coverage gap on property/changelog/video retrieval that the Confluence→Docusaurus swap left untested.src/eval/retrieval.tsgains a per-querysurfacefield:search(default) still drives the durablerouteros_searchrecall/MRR/classifier gate (the entry point most exposed to the swap), whileproperty/changelog/videodispatch to the dedicated tools (lookupProperty/searchChangelogs/searchVideos) and feed a separate, informationalsurface_matrix(per-surface hit@5) kept out of the gated aggregates so those stay sensitive — a starting coverage board to grow over time. Golden_thresholdsrecomputed for the larger set (0.88/0.82/0.75/0.90; the old 0.85/0.70/0.65 had drifted loose over 27 search queries — the 2pp regression check vsbaseline.jsonremains the tight forward guard). Bothbaseline.jsonand the stale April-2026 Confluence-eraself-supervised-baseline.jsonregenerated against the live Docusaurus corpus. Grounded audit (0.10.0 vs current compare-and-contrast via the new one-offsrc/eval/corpus-compare.ts, blocking self-eval rehearsal, and an ETL Bug Ledger) recorded inbriefings/B-0020-0.11-retrieval-quality-audit.md: no net retrieval regression from the corpus swap (14/16 topic-hit on both corpora; existing golden set 91.7% Recall@5 on Docusaurus), and #26's two "regressions" confirmed to miss on 0.10.0 too (pre-existing query-core weakness, not swap-induced). Queries that legitimately fail today stay pointed at the correct answer and fail loudly — no fixture was loosened to green the board. -
Hardware overlay Phase 2A —
hardware_catalog/device_aliasesnow drive device lookup and enrichment (MCP + TUI).routeros_device_lookupandrouteros_searchresolve devices from free-form input far more accurately: an alias stage (exact-normalized, authoritative — never fuzzy) maps product codes, old names, and www/hardware slugs the matrix name misses to the canonical device (cap_ac→cAP ac,RB750Gr3→hEX), reported asmode: "alias"with thematched_alias. A single matrix-linked result now carries a compacthardwareoverlay block —rosetta_device_id(stable, persistable key),category,discontinued,also_known_as(name-like aliases only; slug/table artifacts excluded), genuinenon_default_ips, and reconstructedproduct_page_url(mikrotik.com/product) +hardware_page_url— whosenotesteers agents to the device tool rather than fetching a page. Non-matrix entities (accessories, series, legacy/EOL) surface in a newcatalogarray as labeled thin rows with akindfield (accessory/series/discontinued/device), so an agent never mistakes a GPeR for a router. The classifier gains catalog-prominent families (Chateau, Audience, OmniTik, PowerBox, KNOT, Cube, DISC) plus a whole-input alias probe insearchAllfor bare codes the regexes miss;routeros_search'srelated.devicesnow carriescategory/discontinued; the browse TUI device card shows the overlay and a thin-row variant for catalog entities, and its stats screen lists the two overlay tables. No/hardwareprose enters the FTS corpus (fields only). Reads the clean data from the #47/#48 cleanup. Seebriefings/B-0019-hardware-overlay-phase2-mcp-tui-surfacing.mdand issue #49. -
New Docusaurus
/docsprose extractor (extract-docusaurus.ts) replacesextract-html.tsas the default prose source. Discovers pages viasitemap.xml, fetches raw Markdown from manual.mikrotik.com, and populatespages/sections/properties/callouts— 360 in-scope/docspages as of 2026-07-07 (CLI Reference and/hardwareremain out of scope, tracked as follow-up work).make extract/make extract-fullnow run it by default; the legacy Confluence pipeline survives asmake extract-legacy-confluencefor rebuilding historical release DBs. -
New
pages.rosetta_idcolumn (schema v6) gives Docusaurus-sourced pages a stable, URL-derived identifier alongside the existing integerid— seeDESIGN.mdandbriefings/B-0012-docusaurus-manual-migration.md"H7 — Identity / rosetta-id design". -
npm prerelease dist-tag channel. Testers can now opt into an in-progress build via
bunx @tikoci/rosetta@next(newest prerelease of any stage) or@alpha/@beta/@rc(pinned to one stage), without moving the defaultlatestchannel. Channel is driven entirely bypackage.json's committed version (a-alpha/-beta/-rcsuffix means prerelease); OCI image tags (:alpha/:beta/:rc/:next) mirror the same scheme, and the bare:latestOCI tag now never moves on a prerelease release run. SeeREADME.md"Prerelease channels" andMANUAL.md"Release Workflow". -
bun test --coveragenow runs on every release build, summarized in the workflow's step summary and uploaded as acoverage-lcovartifact — informational only, not a gate. -
New
hardware_catalog+device_aliasestables +device_overviewview (schema v8) overlaymanual.mikrotik.com/hardwareandmikrotik.com/product/<code>onto the existingdevicesdata.hardware_catalogis a superset ofdevices— it also covers accessories and legacy/EOL SKUsmatrix.csvdoesn't track, carries a never-null displayname, an optionaldevice_idlink back (renamed fromdevices_idto matchdevice_test_results.device_id) for the ~156 rows matrix.csv already knows about, and lands each device's non-default management IP and FCC/IC regulatory IDs intospecs_json;devicesitself is unchanged.device_aliasesresolves every observed slug/code/name variant (matrix.csv,/hardwareslug/link/table code, www requested/declared/compare code) back to one rosetta-curatedrosetta_device_id, with priority-ranked, collision-counted assignment so no code is silently claimed by the wrong device.device_overviewis the documented read surface (catalog + devices spec columns + alias counts). A www product is only accepted as a device's spec source when its own identity agrees with the device's code/slug family (killing cross-sell misattribution such as a mounting bracket's specs landing on a cube page), attaches to at most one row outside a tiny justified shared-kit allowlist, and a declared-code matching tier resolves ROSE Data server and both KNOT Embedded LTE4 rows. Built by the newsrc/extract-hardware-catalog.ts, now wired intomake extract/make extract-fulland therelease.ymlextraction pipeline (ordered afterextract-devices, which wipes both tables) so the catalog + aliases ship in the published DB; a release is blocked if the DB carries fewer than 200hardware_catalogor 600device_aliasesrows. It emits a committed, deterministicfixtures/hardware-catalog/catalog.json(sorted rows + aliases + drop ledger — the change-review diff gate), enforces hard output invariants (declared code present in own aliases; one www product per row; every input entity attached or dropped-with-reason), and fails loudly — DB untouched — on category-taxonomy drift, a www page-template change, matrix coverage regressing, a previously-resolved device disappearing, an alias-collision/drop surge, or a www 404-rate swing. Seebriefings/B-0017-hardware-overlay-device-resolution.mdand issue #35. MCP/TUI surfacing of this data is deferred to a follow-up (B-0017 "Phased implementation plan," phase 2). -
New scheduled
Device Map Refreshworkflow (device-map-refresh.yml) watches for upstream hardware-page drift. Weekly cron (+ manual dispatch) does a LIVE re-scrape of manual.mikrotik.com/hardwareand mikrotik.com/productviamake assess-hardware assess-www device-map— the network pathtest.yml's per-PR drift gate deliberately never runs. It detects drift only: when the regenerated artifacts differ from the committed snapshot (or a device no longer resolves without adevice-exceptions.tomlentry), it fails the run (red check + scheduled-failure notification), uploads the regenerated artifacts, and opens/refreshes a single tracking issue with a diff-stat summary; the full diff is in the run logs. A human then resolves it through the normal flow — regenerate locally, review perbriefings/B-0018"How to audit", open a standard PR that runs the requireddevice-map-checkgate. Closes the "how do we notice MikroTik added/moved/removed a page" half of B-0018's audit loop that the committed-snapshot coherence check can't cover on its own. -
Reviewable device→URL map + drift gate (
make device-map).device-map.tsv(one row per matrix device →/hardware+ www URLs, with aneeds_reviewcolumn) andhardware-unmatched.tsv(the reverse audit view:/hardwarepages with no matrix device), backed by a curateddevice-exceptions.tomland a blocking drift gate (make device-map-check) that fails when a device stops auto-resolving without an exception, a curated exception goes stale, or a committed artifact drifts from freshly computed output — now run on every PR viatest.yml(no network; validates only the committed snapshot). Human/MikroTik-readable legend inbriefings/B-0018-product-naming-three-source-map.md. -
New
QAworkflow (qa.yml) rehearses the release-locked quality gates without publishing (issue #40, B-0014 Option B). Aworkflow_dispatch+workflow_callworkflow that runs the checks previously provable only insiderelease.yml— DB content floors (incl. the hardware-overlayhardware_catalog/device_aliasesfloors), MCP contract, golden + self-supervised retrieval eval, docs-count, device-map drift, anddb_metaprovenance — against a freshly built (db_source: local-build) or published DB, on any ref, with no npm publish, OCI push, or GitHub Release. Focused dispatch viatest_scope(single gate orall),db_source,full_versions, andeval_self_blocking(flip the self-supervised eval to a hard gate on demand). This is the intended way to verify release-pipeline changes — such as the hardware-overlay extraction wired in this same release — before an actual dispatch.release.ymlnow calls this workflow directly (issue #42): its singlebuild-and-releasejob was split intobuild→qa(uses: ./.github/workflows/qa.ymlwithdb_source=artifact) →publish, so the gates that fence a release are byte-for-byte the ones aqa.ymldispatch runs against the exact DB about to ship — one definition, no drift (the old cross-file floor guard is gone). SeeMANUAL.md"Release Workflow" / "Rehearsing release quality gates without publishing" andVALIDATION.mdV-qa-rehearsal.
rosetta exportnow replaces a prior export safely — manifest-owned prune-then-publish + a--forcegate (issue #108, part of #95).manifest.tomlbecomes the ownership record as well as the index: re-running into a directory that holds a manifest rosetta wrote rewrites the current file set and prunes any file the old manifest listed that the new run no longer produces (cleaning directories that empties), so a renamed page slug or dropped product from an earlier run no longer lingers — the stale-file hazard the variableproducts/**/pages/<slug>/**sets (E3/E4) introduce. It only ever deletes files a prior rosetta manifest named, so files you added yourself are untouched; a new/empty directory just gets written; a non-empty directory with no rosetta manifest is refused unless--force(or, on a TTY, an interactive confirm), so the command can never clobber a directory it did not create.[[files]].nameis a directory-relative path, so the manifest already describes directory-nested files. Within a run the order is prune → write datasets → write the manifest last, so a name reused across runs transitions cleanly between file and directory and a crash never leaves a manifest naming half-written data (the directory is a regenerable audit surface, not a transactional store — a crash can still leave a mix of generations until the next run reconciles). Writes and prunes are contained to the export root both lexically and through symlinks, so a tampered manifest or a planted symlink can never act outside it.MANUAL.mddocuments the scheme; the DB-only boundary is unchanged (the export still reads only the DB handle, plus a single read-back of its own prior manifest).rosetta exportoutput shape refined so the two page-level files read cleanly and the section rollup reconciles (issue #106, post-#103 per-TSV review; part of #95). The per-section file is renamedpages.tsv→sections.tsvand the per-page rolluppages_summary.tsv→pages.tsv, so each filename now matches its source table like every other dataset (changelog←changelogs,callouts←callouts, …); groupingsections.tsvbypage_idin a spreadsheet pivots up to (near-)thepages.tsvword count, the small residual being heading-text lines that live in no fragment (disclosed in the manifest).callouts.tsvgainsrosetta_id(page) and resolvedsection_anchorcolumns so a reader identifies the owning page/section by name without joining back to the DB — the same pairingproperties.tsvalready emits — with the longcontentfield moved last.changelog.tsvmovessort_orderbeforedescriptionfor the same long-column-last reason. No schema change, no re-extraction, no MCP/TUI change; the export still reads the DB alone and rebuilds byte-identically.MANUAL.mdupdated.- Section coverage of a page is now (near-)total — a synthetic lead ("H0") fragment captures pre-first-heading prose (
briefings/B-0023-page-section-normalization.md, part of #95).parseSectionssplit the page body only on h1–h3, so the lead-in prose before the first heading — an AI-summary blockquote, an intro paragraph, and the entire body of pages whose only heading is the title — belonged to nosectionsrow: 11.7% of the corpus by word, and 40 whole pages, had no section to resolve to and didn't roll up to the page total. Each page with non-empty lead prose now gets a lead fragment (level = 0, reservedanchor_id = "_lead", heading = the page title) holding that content, so section words cover ~98% of page words (the residual is heading-text lines, which belong to no fragment) and every table/callout/property before the first heading resolves to a realsection_idinstead of NULL (measured on the local corpus: tables/properties/callouts with no section context all drop to 0). A page whose only pre-heading line is the title mints no lead fragment (no empty fragments). No schema DDL change (thesectionstable already storeslevel/anchor_id), no MCP tool-surface change; therosetta exportpages.tsv/sections.tsvrollup and thesection_iddisclosure reflect the fuller coverage. hardware-unmatched.tsvreframed as a classified inventory — newkindcolumn. Every/hardwarepage with no matrix device is now labeleddevice/accessory/module/series-or-doc(67/12/8/15 of the current 102) by a shared, rule-based classifier (src/hardware-kind.ts), so the reverse-audit set is a typed inventory rather than an undifferentiated pile:kind = deviceisolates real off-matrix routers worth filing as matrix gaps, whileseries-or-doc/accessory/moduleare expected non-devices to skip. Classification carries a single per-slug override today (nray-series→device; one kit, not a family — see the wiring entry below), and the logic is shared so a futureinclude-accessories-style filter or "include module" cross-link reuses it instead of re-deriving. Regenerated deterministically bymake device-map; validated by the existingdevice-map-checkgate. Seebriefings/B-0018-product-naming-three-source-map.md.hardware-unmatched.tsvnow shows the ETL www-spec mapping status — newwww_code/www_specscolumns. Each off-matrix/hardwarepage now records whichmikrotik.com/productcode the catalog actually pulled specs from and how many real fields it captured, read from the committedfixtures/hardware-catalog/catalog.jsonso the file view matches the DB rather than approximating it (no DB or network dependency in the drift gate). A blankwww_codemeans the ETL mapped no marketing product to that page: filteringkind = device+ blankwww_codeyields the concrete spec-backfill worklist (15 real devices as of 2026-07-13 —dynadish-6,sxt-2,chateau-lte6, theltap-lr8-*LoRa kits, …), each of which usually does have a product pageassess-wwwsimply never fetched.make device-mapreadscatalog.jsonwhen present (blank columns + a console note if absent); the feedback mechanism to close the gaps is tracked in #70. Seebriefings/B-0018-product-naming-three-source-map.md.- Curated
hardware-www-map.tomlwired into the ETL — off-matrix spec gaps closed (device gaps 15 → 4; #70 first task). The maintainer answer-key of/hardware-slug →mikrotik.com/productcodes the auto-resolver can't derive (e.g.sxt-2→RBSXTG-2HnDr2-168,dynadish-6→dynadish_6) now feeds two ETL points through a shared loader (src/hardware-www-map.ts):assess-wwwseeds every code into its candidate fetch, andextract-hardware-catalogforce-attaches single-code products to their row past the identity-agreement gate (a human vouched for them). 14 off-matrix devices that shipped with no specs —sxt-2,dynadish-6,cube-60g-ac,chateau-lte6/-lte12,intercell,lhg-xl-2,wireless-wire-cube, … — now carry full spec sheets inhardware_catalog; the reverse-audit spec-backfill worklist (kind = device+ blankwww_code) drops from 15 to 4, the remainder being genuinely un-single-attachable (seed_onlymodule/kit pages whose identity is owned by the kits that embed them, and series-style multi-variant pages — both still fetched for the deferred surfacing work). All 65 curated codes were cross-checked againstmikrotik.com/sitemap.xml.nray-seriesreclassifiedseries-or-doc→device(one kit, not a family). Baseline canaries updated for the +52 seeded products (dropped-www 30→67, all accounted in the drop ledger; matrix coverage unchanged). Seebriefings/B-0018-product-naming-three-source-map.mdand issue #70. - Hardware-overlay data-quality follow-ups (B-0017 items 1–2), before Phase 2 surfacing. Two extract-time filters clean
device_aliases/hardware_catalogso any consumer can trust them without re-deriving classifications: (1) ahardware-linkalias whose token names a dropped www product is dropped as cross-sell pollution (e.g.qm_x→sxtsq-5-ax,mant_lte_5o→chateau-lte12) — a device's/hardwarepage merely linking an accessory no longer leaves that accessory code as the device's alias; standalone series pages (hw-*-series) stay exempt since they legitimately claim member/kit codes (wap_60g→hw-wap-60g-series). (2)specs_json._non_default_ipsnow keeps only genuine subnet deviations (outside192.168.88.0/24) — same-subnet.88.xsecondaries are filtered, shrinking the field from 63 rows to 11 (the192.168.188.1embedded-LTE cluster + intercell + woobm-usb). Both are proven by newcheckInvariantsprobes (#8/#9) and anchor tests;fixtures/hardware-catalog/catalog.jsonmoves once (20 cross-sell aliases removed, 16 collisions cleared). Seebriefings/B-0017-hardware-overlay-device-resolution.md"Lingering data-quality items" and issue #47. /hardware↔ matrix.csv matcher correctness fixes (feedshardware_catalog). Canonical matching (canon/canonNoRev/canonForms) withcode > slug > tableprecedence, cross-page table suppression, a shared-&-base guard, a bogus-accessory-link filter, and an own-slug collision guard (kept only when corroborated or title-agreeing). Fixes previously wrong binds — Chateau LTE7/LTE12 →chateau-lte6-us;R11e-LR8/-LR9→wAP LR2 kit(acanonNoRevover-strip);/hardware/hap-ax-2(titled hAP ax³) also claiming hAP ax2 — and regeneratesros-hardware-assessment.json+ thehardware_catalogoverlay accordingly. Anchor-tested insrc/assess-hardware.test.ts. Seebriefings/B-0018-product-naming-three-source-map.md.- Relative Markdown links inside property descriptions now resolve to live
manual.mikrotik.comURLs instead of being left as broken relative paths once extracted out of their source page. release.ymlCI now builds the DB from the live Docusaurus extractor, not the legacy Confluence HTML export. Thehtml_urlworkflow input is gone; a newextract-docusaurus.ts --check-counts --strictstep proves the docs-count invariant on every release run instead of only in manual/local runs. Rebuilding a historical pre-migration DB remains possible via the local-onlymake extract-legacy-confluencetarget — it is no longer reachable from CI.- Work tracking moved from in-repo
tasks/T-*.mdfiles to GitHub Issues. Issues start as discussion and earn theagent-readylabel once acceptance criteria settle;umbrellamarks theme-tracking issues andblockedmarks named waits. PRs close issues viaCloses #N, and partial landings must spawn follow-up issues before merge (newissue-pr-linking.instructions.md).tasks/is now a frozen archive; the 17 remaining active task files were closed with per-file disposition notes (superseded / won't-fix / migrated to an issue) in tasks/done/T-*.md. See issue #18. - Release version bumps are now a manual step for every channel, including
latest. CI's oldbump-versionjob (blindPATCH + 1, auto-committed straight tomain) is gone entirely — it couldn't reason across the new prerelease channels. A new preflight fails a latest-channel release ifCHANGELOG.mdlacks a## [<version>]heading for the barepackage.jsonversion being released, so CHANGELOG promotion can't be skipped by accident. SeeMANUAL.md"Version bumps are a manual step". make lint(andtest.yml's Lint step, nowmake lint) covers Markdown and spelling, not just Biome. Addsmarkdownlint-cli2(config in.markdownlint-cli2.yaml, rules shared with the IDE via.markdownlint.yaml; LLM instruction files,briefings/,tasks/,fixtures/, and themanual/cache excluded) andcspell(cspell.json+ a seededproject-words.txtRouterOS/tikoci dictionary) asbun run lint:md/lint:spell, so localmake lintand CI check the same three things.release.yml's fast-fail Lint stays Biome-only.
-
A latent test-suite hazard that presented as a CI flake is now blocked structurally, not just caught on unlucky file order (issue #98). A
*.test.tsthat setsprocess.env.DB_PATH = ":memory:"but statically imports a module transitively reachingdb.tsopens the real on-diskros-help.db— the static import is hoisted above the env assignment, sodb.ts's module-scopenew sqlite(DB_PATH)runs before:memory:is set.query.test.ts'sV-db-wipe-guardonly trips on this when the offending file happens to loaddb.tsfirst, and bun's test-file order is not stable, so it hid across many green runs and did not reproduce locally. The live occurrence (a static import inextract-docusaurus.test.ts) was fixed in PR #97; newsrc/source-hygiene.test.ts(V-test-db-import-static-guard) now makes theextractor-import-side-effectsrule enforced — it scans everyDB_PATH-setting test file and fails at author time if any statically value-imports a db.ts-reaching module (statement-levelimport typeis erased and stays allowed). Folded in a companionV-source-no-control-charsscan: no source.tsundersrc/orscripts/may carry a NUL or other disallowed C0/DEL control byte — the class where a stray NUL (also fixed in #97) makesfilereport the source asdataand grep silently match nothing while tests/typecheck/biome all pass. Both are DB-less structural scans with no runtime cost. -
Video search now matches the video title, not just the transcript — correctly-titled changelog videos no longer get buried (issue #89).
searchVideos()queried onlyvideo_segments_fts(chapter title + transcript), so a query word that lives only in the human-curated title — "changelog" is never spoken in the narration — was unmatchable, and a video like "RouterOS 7.23 changelog — VRF offloading, …" ranked #15 onchangelog 7.23 routeros, below the display cutoff. It now also consults the already-trigger-maintainedvideos_fts(title + description; no schema change), ranking strong-before-weak: title-AND, then transcript-AND, then the OR fallbacks in the same order, deduped by video. On the live corpus that target video moves from #15 to #1, andchangelog 7.NN routerosresolves to the matching release video for each version. Deliberately scoped to the FTS match surface: the sharedextractTerms/stop-word handling that shreds dotted versions (7.23→7,23) and dropsrouterosis left untouched here — recognizing RouterOS version strings as first-class query tokens acrossrouteros_searchis tracked as separate, broader work. -
The shared version comparator now orders same-base prereleases instead of collapsing them (issue #107).
compareVersions()insrc/version-compare.tsstrippedbeta\d*/rc\d*wholesale, so7.24beta1,7.24beta2,7.24beta3all compared equal (likewise7.20rc1vs7.20rc2) — deterministic only because callers (changelog.tsv,command_versions/routeros_command_diffordering) bolt on a secondary sort key. The comparator now folds the prerelease number into the sort so the order is strict and total for one base:beta1 < beta2 < beta3 < rc1 < 7.24(a barebeta/rcwith no number sorts below its numbered siblings). Every caller improves at once; the module stays dependency-free so the DB-onlyexport.tsboundary is unaffected. The honesty disclosure this bug forced intorosetta export'schangelog.tsvmanifest is removed, and a focusedsrc/version-compare.test.tspins the precedence. -
Agent-facing corpus counts no longer carry stale Confluence-era numbers (issue #88).
src/mcp.ts's schema-guide resource androuteros_search's tool-description "Documentation: 317 pages from March 2026 Confluence export" line, plusMANUAL.md's Tables reference, still advertised frozen Confluence figures (pages317,properties4,860,callouts1,034) and drifted command/version counts (command_versions1.67M vs an actual ~2.2M) even though the live Docusaurus extractor has been the default prose source since theT-0035cutover. Rather than re-baking exact numbers that drift every extraction, these surfaces now state order-of-magnitude counts, correct the Docusaurus provenance wording, and point atrouteros_stats(already the live single source of truth) for exact figures — so the counts can't go silently wrong again. The#87caveat note inMANUAL.mdis dropped. -
165 properties were silently destroyed on every build;
propertiesandcalloutsnow carry a realsection_id(issue #90, schema v9).properties.sectionstored heading text, andUNIQUE(page_id, name, section)plusINSERT OR IGNOREdropped any property whose page repeated a heading — the shippedv0.11.0-rc.97DB holds 4,416 of 4,581 parsed properties, and nothing logged the gap (the extractor's own "Properties:" line counted attempted inserts, so it reported rows the DB did not contain).ppp-aaaalone lost most of its user/profile properties to its fourPropertiesheadings. A corpus-wide measurement showed the obvious fix is insufficient: keying onsection_idstill destroys 87 rows, because the manual documents one property name several times within a single section (dot1xdefinesinterfacetwice underServer— once for the server table, once for the client one), so section is not an identity at all. The constraint is therefore removed, not re-keyed; extractors now assert parsed == stored and fail the build on any gap (V-extractor-no-silent-drops). All 4,575 parsed properties are stored, and the 141 distinct properties the constraint had been eating —name= "Name used for authentication" and friends — are searchable for the first time.properties.section_idand the brand-newcallouts.section_id(callouts previously had no section attribution whatsoever) resolve for 99.7% and 89% of rows respectively; the remainder are genuinely above any heading and stay NULL rather than being forced into a section. Property→section attribution rises from 72% resolvable to 99.7%. A property under an h4–h6 folds to its enclosing h1–h3 section, sosectionsremains the retrieval unit androuteros_get_pageis unchanged;sectionkeeps the raw h4 text, so the finer heading stays recoverable. -
routeros_lookup_propertyandrouteros_search'srelated.propertiesnow returnsection_anchor, which finally makes same-named properties tellable apart (issue #90). Storing every property surfaces a fact the old constraint hid by deleting it: 111 name/section groups (276 rows) are indistinguishable bysection, because it holds heading text that repeats — alookup_property(name="name")on PPP AAA returns three rows all reading "Properties", meaning the profile name, the login name, and the active-user name.section_anchorcarries the section'sanchor_id(properties,properties-1,properties-2), which is unique within a page and feeds straight back intorouteros_get_page(page, section=…)to read that exact fragment — the workflow the tool description now spells out. Null when a property sits above any heading. -
bunx/global-install DB staleness across same-schema releases (e.g.0.11.0-beta.92→beta.93, or any future patch that doesn't bumpSCHEMA_VERSION) is now self-healing (issues #76, #23).ensureDbReady()'s redownload check widens from "schema version differs" to also comparedb_meta.release_tagagainst the running package's own version — the newcheckDbFreshness()(src/setup.ts) — sincedbDownloadUrls()already resolves a version-pinned DB per exact release andstamp-db-meta.tsalready stamps the matching tag; the missing piece was thatensureDbReadynever looked at it. A local repro (documented on #76) confirmed bunx re-resolves the registry manifest fresh, unconditionally, on every invocation, so the running package version is always current — only the cached DB could go stale. Dev-mode checkouts are exempt (a locally-extracted DB'srelease_tagis expected to drift frompackage.json's in-progress version and must never trigger a network fetch). A release-tag-only mismatch (schema still current) that fails to redownload — e.g. offline — degrades gracefully to the existing, still-queryable DB with a warning instead of crashing startup; only a genuine schema mismatch remains a hard failure. -
New
ROSETTA_OFFLINE=1environment variable short-circuits all DB freshness network attempts instead of letting them time out. A release-tag-only mismatch falls back to the existing DB with a warning (same as an unintentional offline failure); a genuine schema mismatch, or no DB at all, still fails hard — offline can't fix either. SeeMANUAL.md"How Updates Work". -
New
bunx-upgrade-smokerelease CI job closes the gapbunx-smokecouldn't cover: it only ever installs onto an empty cache, so it never exercised the actual upgrade path. The new job seeds a realbunx-installed DB from the previously-published version on the same dist-tag, then invokes the just-published version bare (no--refresh) and asserts the startup banner shows the new release — provingcheckDbFreshnessactually self-heals a live install, not just its unit tests. Also exercisesROSETTA_OFFLINE=1against that same stale DB to prove the graceful-degradation path end to end. Runs on macOS/Linux/Windows; skipped when there's no prior publish yet on that channel (issues #76, #23). -
Device search resolves concatenated product-name spellings —
hapax3now finds hAP ax³ (issue #67). The whole separator-free class (hexs,capax,chateaulte12,fiberboxplus, …) previously fell through every stage:device_aliasesstoredhap ax3/hap_ax3but never the concatenation, and thesearchDevicesstage written for exactly this case (a slug-normalized LIKE overdevices.product_url) was dead in production — no extractor populatesproduct_url, only the test fixtures did.extract-hardware-catalog.tsnow derives acanon()-collapsed alias (sourcecollapsed, lowest rank, zero cross-device collisions) from every surviving name-bearing alias, plus a standalonehw-<slug>row's own page slug (its only identity — howchateaulte12finds the catalog-only original Chateau LTE12);hardware-tablecodes and matrix-linked rows' page slugs stay uncollapsed so hAP ax³'s disambiguation slughap-ax-2can't claimhapax2from the real hAP ax². Read side, both alias probes (searchDevicesstage 1.5 andsearchAll's whole-input probe) fall back to the collapsed query key, so any separator spelling (hap.ax3,hap-ax3) resolves asmode: "alias"; a new per-token probe insearchAllsurfaces a device mentioned inside prose ("hapax3 wifi settings") for tokens that look like model spellings (digit or code separator — bare English words that double as product names stay regex-gated on their styled form). Derived spellings resolve lookups but never appear inalso_known_as; the deadproduct_urlLIKE stage is removed. MCP + TUI both inherit via the query core. -
Category/index pages no longer arrive content-free — leaked
<DocCardList />is now expanded into a real child-link list (issue #65). manual.mikrotik.com's Docusaurus.md(llms.txt) output emits the@theme/DocCardListcomponent unrendered, so ~50 index pages (e.g.bgp.md) reached the corpus as a bare tag plus itsimportline with their entire child-page navigation missing — the one MDX leak that is real data loss rather than cosmetic scaffolding.extract-docusaurus.tsnow reconstructs the list from the rosetta-id path tree it already holds (a pre-parse pass, so links flow intopages.text/sectionslike ordinary prose), rendering each direct child as- [Title](url) — summarysorted by title. Honest fallback: a<DocCardList />with no discoverable children is left untouched rather than silently erased. Upstream MDX passthrough (imports,WideTable,Tabs,:::admonitions) is characterized in #65 and intentionally left as-is — readable or no data loss. Reported to MikroTik; this is the local stopgap. -
Classifier no longer turns natural-language prose into a bogus command path (issue #59, BL-4 from
briefings/B-0020). A no-slash question whose first word happens to be a top-level menu (port forward to an internal server,port 8291 access) was greedily pathified by the canonicalizer into/port/forward/to/an/internal/serverat medium confidence, becausedetectCommandPathinsrc/classify.tsruns the (DB-less, tree-unaware) canonicalizer on any input starting with a top-level word. The no-slash branch now only treats the input as navigation when every trailing token is a bare menu segment (^[a-z][a-z0-9-]*$, not a prose stopword liketo/an/from); a stopword or non-identifier token means it's a question and it's left for FTS instead. Legit space-separated navigation is unaffected (ip firewall filter,interface wifi,routing bgp,port). Pinned by a classifier-only golden anchor (cmd-path-nl-not-a-path, using a newexpected_classifiednull= "must be absent" convention). Being a pure heuristic without command-tree access, two clean prose tokens can still slip through (port forwarding→/port/forwarding); a tree-validated post-check in query-core is the deeper fix, deferred. Retrieving the NAT/port-forwarding page for such verbose NL is the separate #26 FTS-candidate-pool weakness, not this bug. -
Command→page linkage now ranks candidates instead of picking an arbitrary one, fixing wrong-page property lookups on core menus (issue #58, BL-1/BL-2 from
briefings/B-0020).link-commands.tscollected every page that merely mentions a command path in a code block, then linkedcommands.page_idtocandidatePageIds[0]— the first in insertion order — so on the Docusaurus corpus 433 of 493 linked dirs (88%) pointed at an unrelated page (/ip/firewall/filter→l3-hardware-offloading,/ip/dhcp-server→hotspot,/ip/service→containers). The pick is now the authoritative page: the one whose own/docs/slug (or breadcrumb) trails the command path (new pure, unit-testedsrc/link-ranking.ts— contiguous trailing-segment match anchored at the leaf, prefix-tolerant so/ip/dhcp-server↔.../dhcpstill matches; property count only tie-breaks among aligned pages). A command with no aligned page is left unlinked rather than mis-linked, sorouteros_lookup_propertyfalls back to an honest low-confidence global search instead of resolving high-confidence to the wrong page. Effect: aligned links ~2× (60→111), and real properties that exist now resolve high-confidence to the correct page (/ip/dhcp-server lease-time,/ip/ipsec/peer exchange-mode,/ip/dns servers) while the existing clean anchors (prop-wireguard-listen-port,prop-vxlan-vni) stay green. Properties the new manual documents only as prose (firewallaction, BGPdistance) and dotted/nested wifi properties remain unresolved — a separate property-extraction/CLI-Reference-overlay concern tracked apart from linkage. -
Flaky CI: two timer/IO-bound
src/setup.test.tscases (waitForUsableDb returns true…,probeDb closes statements…) no longer trip bun's default 5s per-test timeout under GitHub shared-runner contention (issue #32). Both resolve in a few hundred ms locally but do realsetTimeout/bun:sqlitefile I/O; each now gets an explicit 15s bun-level timeout (well above its own 2s internal deadline) so runner jitter can't fail them without weakening any assertion. -
release.yml's first real0.11.0-alphadispatch (2026-07-09) failed at "Run tests (fast-fail)" despite 0 failing tests (649 pass / 0 fail). Root cause:bunfig.tomlcarried a long-dormant[test].coverageThreshold = { lines: 0.70, functions: 0.80 }(present since April 2026, never actually exercised becausetest.ymlnever ranbun testwith--coverage) — the first time--coverageran anywhere in CI wasT-0037's new step, and real coverage (55.64% lines / 62.78% functions) tripped the dormant threshold, failing the whole release despite every test passing.bunfig.toml's threshold is removed — coverage reporting is informational only, perVALIDATION.mdV-coverage-reported, and should never gate on its own. Also hardenedrelease.yml's error reporting so a real future failure of this kind is self-explanatory instead of a bare "Process completed with exit code 1": the "Run tests (fast-fail)", "MCP contract tests (real DB)", and "MCP retrieval eval (Phase 0)" steps now each emit an explicit::error::annotation distinguishing an actual test failure from a nonzero exit with no failing tests reported. -
Phase 0 golden-query retrieval eval (
src/eval/retrieval.ts,fixtures/eval/queries.json) now matches onpages.rosetta_idinstead of the numericpages.id.extract-docusaurus.tsre-mintspages.idas a fresh rowid on every run, so the golden set's old Confluence-era numeric ids could never match again after theT-0036cutover — this is what failedrelease.yml's "MCP retrieval eval (Phase 0)" step onT-0037's first real dispatch (all 12nl-questionqueries scored 0% recall) even thoughrouteros_searchwas actually finding the right page for 10 of the 12.fixtures/eval/baseline.jsonregenerated against the live Docusaurus corpus. Two real ranking regressions the fixture fix uncovered (a firewall-filter query and a BGP-peering query both miss their real page) are left failing visibly in the eval's per-query output rather than papered over — tracked bytasks/T-0038-docusaurus-retrieval-ranking-regressions.md. -
Release skill and command-tree extraction now use authenticated GitHub API requests with retry handling. This avoids shared-runner unauthenticated rate-limit failures while fetching
tikoci/routeros-skillsand discovering restraml versions duringrelease.yml; theextract-skills-from-cacheMakefile comment now correctly describes it as a local/offline rebuild path, not the release CI path. The shared retry helper (src/github.ts) now also backs off on GitHub's secondary/abuse-detection403 + Retry-Afterresponses (not just primary-quota exhaustion) and no longer misreads a missingx-ratelimit-resetheader as an immediate-retry signal; skill file bodies are fetched with the documentedapplication/vnd.github.v3.rawaccept header. -
extract-docusaurus.ts'sparseProperties()now also parses bullet-list property definitions (- **name** (type[; default: x]) : description), not just Markdown tables. Characterizing the 32 pages a naive bold-bullet grep flags (issue #20) showed the overwhelming majority are non-property prose (command menus, chain/enum-value lists, JSON API fields, naming-convention explainers) that must stay excluded, not force-fit; the real gain is scoped to 4 pages whose bullet lists are genuine property definitions:queues.md(+51),scheduler.md(+6, including one real upstream doc typo now correctly flaggedmalformedEmphasisinstead of silently mis-parsed),clock.md(+2), andqueues/htb-hierarchical-token-bucket.md(+3) — +62 properties corpus-wide (4509 → 4571), with zero new false positives across all 365 in-scope pages. -
FTS
snippet()excerpt highlighting no longer collides with native Markdown bold in the Docusaurus corpus (issue #24). SinceT-0035, indexed prose is raw Markdown and can already contain**bold**; wrapping a match in the same**marker produced visibly broken output like****hAP****. All 7snippet()call sites (pages,properties,calloutsx2,changelogs,video_segments,dude_pages) now share one exported sentinel-marker pair (query.ts'sEXCERPT_MARK_START/EXCERPT_MARK_END,>>>/<<<) — corpus-safe, since the only literal occurrences across all 365 in-scope pages are confined to fenced code blocks (api.md's wire-protocol notation), never in prose. Also fixed 3 adjacent TUI rendering bugs inbrowse.tsfound in the same footprint: the pages-excerpt bold conversion was correct but dead code (query.ts still emitted**); callouts/changelogs/videos/dude-pages excerpt rendering had no bold-off pairing (bold never turned back off once triggered); andrenderPropertiesaccepted but never rendered its ownexcerptfield, always showing the plain unhighlighted description.
- CI's automatic
package.json/CHANGELOG.mdversion-bump commit (thebump-versionjob inrelease.yml) — superseded by the manual version-bump step above.
0.10.0 — 2026-07-08
- v0.10.x will be last release(s) based on Confluence HTML doc extraction. v0.11.x and beyond will use new manual.mikrotik.com as source for MikroTik documentation pages.
- Only changelog and version bump. Promote to an even-number release. Otherwise, identical to 0.9.3.
0.9.3 — 2026-07-07
- YouTube transcript cache refreshed for the final Confluence-corpus release. The committed transcript cache now covers 538 MikroTik channel videos through June 19, 2026, and remains the source release CI imports via
extract-videos-from-cache. - Product matrix snapshot refreshed for the final Confluence-corpus release. The default device extractor now consumes the July 7, 2026 browser-exported matrix snapshot with 156 products.
- Release docs now identify local-only source refreshes.
MANUAL.mddistinguishes cache inputs that CI consumes from live sources CI already refreshes, including the product-matrix browser export caveat.
0.9.2 — 2026-06-18
- Docs now flag MikroTik's manual.mikrotik.com migration. Project docs record that future RouterOS manual updates are Docusaurus-based, not Confluence HTML exports, and outline the extraction/MCP/TUI redesign options.
- Schema-mismatch setup guidance now points MCP users at a fresh bunx resolution. The refresh message tells users to restart the MCP client or run
bunx @tikoci/rosetta@latest --refreshwhen their cached package is older than the published DB.
- Stale
ros-toc.jsonPDF-era artifact removed. The current HTML extraction pipeline is the source of RouterOS page metadata, and no runtime code consumed the empty-title TOC file.
0.9.1 — 2026-05-26
- Release npm publish now fails fast on missing or unauthorized
NPM_TOKEN. The release workflow verifies npm package availability and read-write package access before extraction, artifact publishing, OCI pushes, or GitHub Release creation; partial release retries can update existing GitHub Release assets before publishing to the explicit npm registry. - Release skill extraction now authenticates GitHub API reads. The
extract-skills.tsGitHub API calls useGITHUB_TOKEN/GH_TOKENwhen available, and release CI passesgithub.tokento avoid unauthenticated API 403s while fetchingtikoci/routeros-skills.
0.8.13 — 2026-05-03
make verifytarget. Runs typecheck + lint + tests + MCP contract tests + Phase 0 retrieval eval in one command. Requires a populated DB (make extractfirst). Covers V-typecheck, V-lint, V-unit, V-tool-registry, V-tool-shapes, V-tool-budget, V-retrieval-floor. Skips the clean-tree check thatmake preflightenforces.
- CLAUDE.md is now a thin routing index, and agent rules live in narrow
.github/instructions/*.instructions.mdfiles. Canonical reference material moved toMANUAL.mdandDESIGN.md, legacy broad instruction files became routing stubs, and Copilot-facing guidance now points at the scoped rule files instead of duplicating the whole project reference. - TUI/MCP parity and CLI help/manual parity are now CI-enforced. New
src/browse-parity.test.tsproves every MCP tool has a matching.routeros_*browse dot-command, and newsrc/cli-help.test.tslocksMANUAL.md's CLI Flags table tobun src/mcp.ts --help. The--helpoutput now documentsbrowse <cmd> [args],browse --once <cmd>, and TLS env var names explicitly. - MCP contract tests and Phase 0 retrieval eval are now blocking in release CI. Both were previously
continue-on-error: truepending a first green CI run; both have now passed —continue-on-errorremoved and step names updated (dropped(non-blocking)suffix). - Work tracking restructured.
BACKLOG.mdslimmed to an inbox + triggers list. Active work now lives intasks/T-NNNN-*.md(frontmatter: status, depends_on, conflicts_with, validation, acceptance). Research and decision notes live inbriefings/B-NNNN-*.md. NewVALIDATION.mdmatrix names every load-bearing invariant and the CI step that proves it. Three new.github/skills/(pick-next-task,promote-idea,verify-task) wrap the conventions.CLAUDE.mdand.github/copilot-instructions.mddoc-rule tables updated to match. - Task verification docs now distinguish current proofs from planned ones.
tasks/README.mdand theverify-taskskill no longer assume amake verifytarget already exists,VALIDATION.mdnow pointsV-db-min-contentat the real inline release step, andV-retrieval-selfis recorded honestly as a tracked gap until release CI actually runs the self-supervised eval. - The test workflow now exercises the real stdio MCP client path.
.github/workflows/test.ymlrunssrc/mcp-stdio-client.test.ts, which spawnsbun src/mcp.tsthrough@modelcontextprotocol/sdk'sStdioClientTransport, proves the 14-tool registry/resources surface over stdio, and catches stdout framing pollution. - Release
bunx-smokematrix now includeswindows-latest. Catches the EBUSY / readonly-WAL / temp-file class of bugs on Windows. Step usesRUNNER_TEMPinstead ofmktemp(not available in Git Bash) and setsshell: bashas the job default. - Release CI now runs the Phase 1 self-supervised retrieval eval (non-blocking). After the existing Phase 0 hand-curated eval step,
release.ymlnow executessrc/eval/self-supervised.tsagainst the freshly built full DB and appends the pass/fail result to the workflow summary. Results are visible but non-blocking until a stable baseline is established.
make release,make build-release,make bump-versionremoved from Makefile. All release artifact production now goes through the GitHub Actionsrelease.ymlworkflow. The Makefile retains ETL targets and developer checks (make preflight,make verify).
- CodeQL ignore scope now anchors the root
skills/cache explicitly..github/codeql/codeql-config.ymlnow ignores/skills/**instead of an unanchoredskills/**, keeping the committed.github/skills/workflow docs distinct from the extracted root-level skill cache.
0.8.12 — 2026-05-02
- Windows package-mode DB installation no longer renames a SQLite-opened temp
file. Download validation now finalizes every SQLite statement before close,
and
replaceDbFileretries transientEBUSY/EEXIST/EPERMrename failures for up to 30 seconds to cover delayed handle release, antivirus, or indexers. - Abandoned
.tmp.*DB artifacts are removed immediately when no active download lock exists, so failed Windows installs do not keep accumulating 274 MB temp databases between launches.
0.8.11 — 2026-05-02
- Stale
.tmp.*cleanup now runs on every startup, not only when a download is triggered. This removes accumulated 274 MB temp files from previous failed downloads even when the DB is already healthy. - Windows rename now handles
EBUSYin addition toEEXIST/EPERMin thereplaceDbFilefallback path, providing better defense against antivirus or indexer locks on the destination. - Schema-mismatch recovery messages no longer reference
bun pm cache rmor use shell&&syntax. The actionable command is nowbunx @tikoci/rosetta@latest --refresh, which works cross-platform and handles both package and DB refresh in one step.
0.8.10 — 2026-05-02
- CodeQL + Dependency Review wired up. New
.github/workflows/codeql.ymlruns thesecurity-and-qualitysuite (security-extended + code-quality queries) againstjavascript-typescriptandactionson push, PR, and a weekly cron..github/codeql/codeql-config.ymlexcludes vendored/generated content (box/,dude/,transcripts/,matrix/,skills/,fixtures/,dist/,images/) so scans focus on shipped/runtime TypeScript, extractors, the bin shim, release scripts, and workflow YAML; test/eval harnesses are excluded to avoid temp-file/file-race noise outside shipped code. New.github/workflows/dependency-review.ymlblocks PRs introducing high-severity dependency advisories. New.github/dependabot.ymlopens weekly grouped update PRs forgithub-actionsandbunecosystems. The Test workflow gains an "AI findings probe" step that polls candidate Code Quality endpoints and prints a CI notice (no-op until GitHub ships a stable API). Repo-level Dependabot security updates, secret scanning with push protection, and private vulnerability reporting are enabled. SeeSECURITY.mdfor the configured posture summary.
- Documentation/instruction cleanup: agent-facing instructions, release/extraction docs,
and BACKLOG structure now match the current CI pipeline, MCP resource surface,
and
DESIGN.mdsource-of-truth for cross-tikoci command validation strategy. - MCP search/property confidence metadata:
routeros_search.classifiednow includescommand_path_confidence, androuteros_lookup_propertyrows includeconfidence(high/medium/low) to distinguish scoped command-page matches from global fallbacks. - CI release hygiene: the
Releaseworkflow input formerly namedforceis nowrepublish_assets, making clear that it reuploads GitHub Release assets / OCI tags while skipping immutable npm publication. Release CI also runsbun testin the early fast-fail gate before downloading the HTML export while preserving the post-extraction DB-wipe guard. - DB retention: release builds now run
make gc-versionsafter command linking to pruneschema_node_presenceto active RouterOS channel heads (stable, long-term, testing, development). Full command-version history and changelogs remain untouched. - Tool descriptions:
routeros_statsandrouteros_current_versionsnow follow the workflow-arrow (→) convention.statssuggests→ routeros_search;current_versionssuggests→ routeros_search_changelogswith afrom_version/to_versionhint. The Phase 2 contract test'sKNOWN_EXCEPTIONSallow-list is removed — every registered tool now carries a follow-up arrow.
- bunx/package startup no longer races the shared
~/.rosetta/ros-help.db. Package-mode DB preparation now uses a sidecar lock so concurrent MCP clients wait for the first download instead of competing to rename the file on Windows. Waiters no longer probe-lock the canonical DB while another process is replacing it, startup aborts instead of falling through to a schema-only empty DB when recovery fails, probes no longer create a missing canonical DB as a side effect, and stale.tmp/-wal/-shmartifacts are cleaned up instead of accumulating in~/.rosetta/. - Video transcript VTT cleanup: malformed cue markup is dropped without leaking tag fragments into extracted transcript text.
- Release workflow npm propagation log: the bunx smoke-test polling loop now reports the correct attempt number while waiting for the npm registry.
routeros_explain_commandMCP tool: read-only CLI command explanation with canonical path/verb, argument property matches, warnings, docs, changelogs, version check, and TUI dot-command parity.canonicalize.ts: pluggable verb resolver,extractMentions(), per-command confidence flag (issue #5 — H4, H6, H8).CanonicalizeOptions { isVerb?: (token, parentPath) => boolean }lets callers plug in a path-aware verb classifier. rosetta wires a DB-backed resolver against thecommandstable so/interface/wifi-qcom/info,/system/script/run, and other menu-specific verbs classify correctly instead of falling back to bare navigation. The resolver supplements the curated universal verb heuristic (it does not replace helpers likefind, which are not enumerated everywhere in the command tree).extractMentions(input, cwd?, options?)— surfaces every distinct path the input references, including bare navigation with no verb (e.g./ip/firewall/filterstanding alone in prose). Superset ofextractPaths().ParseResultalso carries a newmentions: string[]field for callers that already usecanonicalize()directly.CanonicalCommand.confidence: 'high' | 'medium' | 'low'—highfor well-formed CLI (absolute path with directly-identified verb),mediumfor relative-with-cwd or pure navigation,lowwhen the verb was inferred from a trailing path segment (looser/prose-shaped input). Lets consumers filter prose-extracted results when they need higher precision.
src/canonicalize-resolver.ts— DB-backedisVerbadapter for rosetta'scommandstable, with per-resolver in-memory caching. Wired intosearchAll()via aClassifyOptions { isVerb? }pass-through onclassifyQuery, so MCProuteros_searchand TUIsbenefit automatically when input contains a path with a menu-specific verb.
- Changelog version lookup and bridge VLAN retrieval.
routeros_search/routeros_search_changelogsnow keep exact patch-version lookups exact, but fall back from an absent major.minor changelog (for example7.22) to its patch rows (7.22.*). Generic "what changed in X.Y" questions now populaterelated.changelogs, and bridge VLAN filtering searches treat "switch" as context so the dedicated Bridge VLAN Table page ranks in the top results. canonicalize.tsrobustness — markdown / prose / common-verb gaps. Tokenizer now strips a leading U+FEFF BOM and treats backticks (`) and zero-width space (U+200B) as whitespace in both the outer and word loops, so inputs from markdown fences, doc snippets, and BOM-prefixed files extract cleanly instead of embedding the noise into the first path segment.GENERAL_COMMANDSgains four verbs that are universal in the rosettacommandstable but were missing:clear,unset,reset-counters,reset-counters-all. Cross-checked against the DB to confirm zero path collisions —info/warning/error/debugare intentionally NOT added (/erroris itself a top-level cmd;infois a dir at/interface/wireless). Menu-specific verbs need a path-aware resolver (tracked as H4 in the audit). Newsrc/canonicalize.fuzz.test.tsdocuments both the shipped behaviour and the still-on-the-books H1–H8 hardenings.
0.8.9 — 2026-04-23
0.8.8 — 2026-04-22
- CI:
bump-versionnow auto-promotes[Unreleased]→[VERSION] — DATEand prepends a fresh## [Unreleased]skeleton after every release. No manual CHANGELOG fixup is needed — agents and developers only write to[Unreleased]; the version heading is filled in automatically. - CI: Phase 0 retrieval eval runs on release builds (non-blocking).
release.ymlnow executesbun run src/eval/retrieval.tsagainst the freshly built full DB after extraction and writes the report to the job summary. Non-blocking while the baseline adapts to the real-DB corpus — flip to blocking after one green real-DB run refreshes the baseline. - CI: Phase 2 contract checks run in a dedicated real-DB step on release
(non-blocking).
release.ymlexecutesbun test src/mcp-contract.test.tsafter the fullbun testsuite so the token-budget and shape-invariant blocks run against the freshly built full DB in a fresh process (the sharedbun testrun pins the DB singleton to:memory:before this file loads, so Blocks B/C would otherwise skip). Non-blocking:continue-on-error: truewhile we observe the step green across a few rebuilds; test output is written to the job summary.test.ymlintentionally does not get a dedicated step: a clean CI checkout has noros-help.db, so B/C would skip regardless and the step would be redundant with Block A in the main run.
- MCP behavioural eval framework (Phases 0–2) — three new surfaces for
validating that the MCP tool layer keeps doing what we expect, with no LLM
cost in the default flow:
- Phase 0 (
make eval) — 20 hand-curated golden queries infixtures/eval/queries.json, scored on recall@k / MRR / classifier accuracy with baseline regression gating (2pp tolerance). - Phase 1 (
make eval-self) — ~170 auto-generated queries from section headings, property names, and page titles using deterministic seeded sampling. Per-strategy thresholds + 5pp baseline tolerance. - Phase 2 (
bun test src/mcp-contract.test.ts) — frozen tool registry test, workflow-arrow (→) convention check, token-budget guardrails on 10 canonical queries, and response-shape invariants for 5 representative queries (portable across DBs of varying richness). Runs insidebun test. - See
BACKLOG.md"MCP Behavioral Testing — research + roadmap" for the full 5-phase plan.
- Phase 0 (
- Tool-surface change ritual documented in
CLAUDE.md: adding, removing, or renaming an MCP tool requires updating bothsrc/mcp.tsand theEXPECTED_TOOLSarray insrc/mcp-contract.test.ts, plus aCHANGELOG.mdentry under[Unreleased].
-
Phase 1 self-supervised sampling is now deterministic on full DBs. The cmd-path strategy no longer uses SQL randomness; it samples from a stable ordered set using the same seeded shuffle as the other strategies, so
self-supervised-baseline.jsonstays reproducible across runs. -
CHANGELOG.md(Keep a Changelog format, back-filled from v0.1.0) with an agentic "update[Unreleased]on every user-visible change" rule inCLAUDE.md+CONTRIBUTING.md. -
TUI:
view/vcommand. Re-renders the current context (page, results, sections, etc.) without popping the navigation stack the waybdoes. Useful after exiting the pager to re-read what you were looking at. -
TUI: bare
pagere-renders current page. When already in a page or sections context,pagewith no args re-renders the current page instead of erroring. -
CI: fast-fail quality gate.
release.ymlnow runstypecheck+lintimmediately afterbun install, before the ~2-minute extraction pipeline. Tests continue to run post-extraction as the DB-wipe guard. -
CI:
bump-versionrebase-retry. Back-to-back release runs no longer fail with! [rejected] HEAD -> main (fetch first). The job fetches + rebases ontoorigin/mainand retries the push up to 3× (safe because the commit only touchespackage.json). -
routeros_search_tests: 512-byte rows surface first when nopacket_sizefilter is set. 512B is the conventional mid-size benchmark RouterOS admins compare on, so within the LIMIT they now precede 1518B "best case" rows that previously crowded them out. Pinpacket_sizeto override. -
TUI dot-commands print usage on missing required args. Calling e.g.
.routeros_get_pagewith no args now prints the args, brief description, and TUI equivalent instead of silently returningnull.
- TUI device detail benchmark truncation now always keeps all 512B rows. When compacting long per-device test lists, the renderer now preserves every 512-byte result (the common comparison size) and only truncates non-512 rows.
- Tests/CI: importing
extract-test-results.tsno longer opens the DB or runs extraction side effects at module-load time. The extractor now runs only underimport.meta.main, andextract-test-results.test.tssetsDB_PATH=:memory:before dynamic import to prevent cross-file DB singleton contamination that could makequery.test.tsfail depending on test order. extract-test-results: throughput values with thousands separators now parse correctly. Values like7,112.3Mbps were truncated to7becauseparseFloatstops at a comma. The extractor now strips commas before parsing, so the DB will contain correct figures after the next re-extraction.- TUI pager: navigation keystrokes no longer bleed into the REPL prompt.
Pager ran in raw mode while readline's data handler was still active, so
each keystroke (
1,4,q, etc.) accumulated in readline's internal line buffer and reappeared echoed after the next prompt (e.g.> 1432q). Fixed by clearingrl.line/rl.cursorbefore re-prompting after dispatch. - TUI:
[p]and[cal]page hints now work on pages with sections. Pages with headings pushctx.type = "sections"(not"page"), so thep/propandcal/calloutscontext-scoped handlers were silently falling back to "no page, show usage" even while a page was showing. Both handlers now check forsectionscontext too, so all five footer hints ([N],[p],[cmd],[cal],[b]) work correctly regardless of whether the page has headings. - TUI help text mentions
[N]section navigation. The post-pager hint line now reads[N] = go to section Nalongside[p]/[cal]/[b]. - TUI pager: digits open the listed result. In a results pager
(search, devices, callouts, videos, properties, changelogs, sections,
command tree, dude), pressing
1..N(where N is the number of visible results) now opens that result and exits the pager. Previously digits were always interpreted as page jumps, so users had to quit the pager (q) and then type the number — wasted keystrokes on the most common path. Page-jump still works for digits beyond the visible result count. routeros_search_changelogsX..Yversion range is now inclusive on both ends, normalises reversed ranges (7.21..7.20→7.20..7.21), and returns entries chronologically (oldest first).- Build: missing
compareVersionsimport insrc/browse.ts— was failing typecheck on bothtest.ymlandrelease.yml.
0.8.2 – 0.8.3 — 2026-04-22
- TUI polish round-2. Dot-command aliases (
.s→.routeros_search), back navigation re-renders, page calendar rendering, Markdown → ANSI sweep across skills/pages. - CI:
bump-versiondecoupled frombunx-smoke— a smoke regression no longer blocks the next version from being available for the fix release. Force-mode runs also skip the npm publish step (npm versions are immutable). - Lint rule sharpened.
bun run lintmust be zero errors repo-wide, not just on touched files.
browseCLI args now route through the normal TUI dispatcher, so every TUI command (not justs) works when passed at launch.- Resolved
noNonNullAssertionlint errors incanonicalize.test.tsthat were blocking CI.
0.8.0 – 0.8.1 — 2026-04-21
bunxinstall path is now rock-solid on macOS. The last{ readonly: true }DB open (inmcp.ts::ensureDbReadyandsetup.ts::dbHasData) was removed. Freshly-written WAL-mode SQLite DBs with no.shmsibling cannot be opened readonly on macOS, which causedValidated … | Still incompatible after re-download (DB=unreadable)for v0.8.0 users. Added a structural anchor test that forbids{ readonly: true }on DB opens.
- Cross-platform bunx smoke job in CI.
release.ymlnow runs abunx-smokematrix on macOS + Linux after npm publish, pinning the just- published version and exercising--refresh,--version, and the full MCP server boot path. Linux-only CI had green-lit v0.8.0 before this was added.
0.7.5 – 0.7.8 — 2026-04-21
db_metatable (schema v5). Database provenance —release_tag,built_at,source_commit,schema_version. Stamped at release time, shown in the startup banner.- Auto-update story for bunx. DB download URL pins to the running package
version (
releases/download/v<VER>/ros-help.db.gz) withlatestas fallback. Atomic.tmp.<pid>write, magic-byte + size + schema probe, thenrenameSync. Stale.db-wal/.db-shmsiblings are cleaned up in the same step. Schema mismatch is a hard error with an actionable message. - TUI usability: MCP probe via dot-commands.
.routeros_search,.page,.deviceetc. invoke the same code path as the MCP server tool and dump raw JSON..helplists all 13 dot-commands. Contract: "a human can always see exactly what the agent would receive." - Hunger-knob
relatedcaps.routeros_search.limitscales callout / video caps proportionally viarelatedCaps(limit). - Glossary in
related. Short queries that match a glossary term/alias surface the definition inrelated.glossary.
- CI DB-wipe regression (v0.7.6).
extract-dude.test.tshad importedextract-dude.ts(which loadsdb.ts) before anyDB_PATH=:memory:was set;query.test.ts:beforeAllthenDELETE FROM …'d the CI-built DB, shipping a 3-page release. Fixed withDB_PATH=:memory:hoisting, aquery.test.tshard-fail if the singleton isn't:memory:, and arelease.ymlDB content gate (pages ≥ 200,commands ≥ 1000,devices ≥ 100,properties ≥ 1000) that runs before publish. extract-html.tsexits non-zero if 0 pages are extracted.probeDbandensureDbReadyopen the DB read-write so WAL-mode init doesn't fail on macOS.
0.7.0 – 0.7.4 — 2026-04-20
- North Star — unified
routeros_search. New pre-search regex classifier insrc/classify.ts(pure module, 42 table-driven tests) detects command path, version, topic, device model, command fragment, and property-name candidate.searchAll()insrc/query.tswrapssearchPagesand runs classifier-driven side queries in parallel, returning{ query, classified, pages, related: {command_node, properties, devices, callouts, videos, changelogs, skills, glossary}, next_steps }. - Glossary table. Seeded at DB init. Resolves RouterOS domain jargon (product codes, abbreviations, subsystem names).
- Known-topics table. Union of changelog categories and command path segments for soft topic routing in the classifier.
- Changelog range expansion.
buildChangelogVersionSetincludes channel head versions and latest long-term patches.
routeros_search_calloutsandrouteros_search_videos— folded intorouteros_search.related. Tool count: 15 → 13. The underlyingsearchCallouts()/searchVideos()functions remain inquery.tsas internal helpers used bysearchAll()andgetPage()TOC mode.routeros_search_properties— previously removed (useless without command-tree context); internal function retained for TUI.
routeros_get_pageis budget-aware: TOC mode surfaces top properties, related videos, and callout summary inline, so small-budget callers rarely need a second tool call.
0.6.4 – 0.6.9 — 2026-04-13 → 2026-04-20
schema_nodestable + multi-arch import.deep-inspect.jsonfromtikoci/restramlis now the preferred source. Dual-arch (x86/arm64) trees,_completiondata (11K+ args with valid values + 17 style types),schema_node_presenceflat junction,_attrsJSON catch-all. Thecommands+command_versionstables are regenerated fromschema_nodesfor backward compatibility.desc_rawdecomposition. Parsed intodata_type,enum_values,range_min/range_max,max_lengthat import time.- Completion data in
browseCommands()/browseCommandsAtVersion(). - RouterOS agent skills as MCP resources.
rosetta://skills(listing) androsetta://skills/{name}(per-skill content) with provenance header noting community/AI-generated/human-reviewed status. - CLI flag support for DB path. Explicit
--db <path>overrides all discovery modes. - Section-level excerpts in search + server-wide instructions surfaced
via
SERVER_INSTRUCTIONS. - RouterOS CLI path canonicaliser.
src/canonicalize.tsmaps any input form to{ path, verb, args }tuples (61 tests covering subshells, blocks, navigation). - Release workflow version resolution.
release.ymlreadspackage.jsonfor version when workflow input is blank.
dude_pages: stripped Wayback / wiki chrome from extracted text; removed stub entries;routeros_dude_get_pageacceptsmax_length.browseCommandsarch filtering corrected and tests added.- Removed
{ readonly: true }from early DB validation in setup (repeat regression trail — finally closed in 0.8.1). - Stop words + compound terms counts corrected in tool descriptions.
0.5.x – 0.6.3 — 2026-04-09 → 2026-04-13
- MCP Registry metadata.
server.jsonmanifest + CI validation job. - MCP dataset resources.
rosetta://datasets/device-test-results.csv,rosetta://datasets/devices.csv,rosetta://schema.sql,rosetta://schema-guide.md. routeros_command_diff. Structural diff of command trees between two RouterOS versions.PRAGMA user_versionwritten at DB init; MCP server validates on boot.- Sitemap-based device slug resolution for 100% product-page coverage; AKA / alias matching via dash-split + slug-normalised LIKE.
- Changelog extraction: legacy version support with CI verification; version-set building tests.
ensureDbReadyfunction inmcp.ts— hard validation before the server starts serving.
- Per-session HTTP transport routing (each MCP client session gets its own
McpServer+ transport). .dockerignoreadded to slim the build context.- OCI smoke test via
docker pull(notdocker load); container entrypoint restored in Docker build context.
0.4.x — 2026-04-04 → 2026-04-09
routeros_search_tests. Cross-device ethernet + IPSec benchmark search with mode, configuration, and packet-size filters.- Device test results + block diagrams. Scraped from
mikrotik.com/product/<slug>: 2,874 measurements across 125 devices, 110 block-diagram URLs. - Experimental TUI (
browse). Interactive terminal browser — REPL with paging, OSC 8 links, context-scoped navigation. - Video transcripts via yt-dlp. 518 MikroTik channel videos, ~1,890
chapter-level segments with timestamps. NDJSON cache in
transcripts/makes CI reproducible without a yt-dlp dependency. - Unicode superscript / subscript normalisation in product names.
- Auto-bump patch version after release (Makefile + CI).
- Security policy documentation (
SECURITY.md) + build-script hardening against shell injection.
- HTTP transport test stabilisation; lint sweep.
search_testsresponse slimmed to reduce context bloat.
0.3.x — 2026-03-31 → 2026-04-01
- Streamable HTTP transport via
--httpflag. Built onBun.serve()+WebStandardStreamableHTTPServerTransport, stateful per-session routing, optional--tls-cert/--tls-keyfor direct HTTPS. Defaults to localhost;--host 0.0.0.0logs a warning. - OCI image publishing (
ammo74/rosettaon Docker Hub,ghcr.io/tikoci/rosettaon GHCR). Multi-arch linux/amd64 + linux/arm64. Smoke-tested in CI viadocker pull. get_pagesmart budgeting.max_lengthdefault 16000, compact callout summary in TOC mode.
- Replaced crane with
Dockerfile + docker buildxfor OCI builds — several crane approaches all failed identically on Docker 28 with containerd image store. - Per-session HTTP transport routing.
0.2.x — 2026-03-30
- npm distribution.
bunx @tikoci/rosettaas canonical install. Runtime version resolution (import.meta.dirname+package.jsonread) so--versionshows a real number. Claude Desktop full-path PATH workaround documented in--setupoutput. - Changelog extraction from
download.mikrotik.com/routeros/<ver>/CHANGELOG;routeros_search_changelogstool with version range + category + breaking filters. - Markdownlint configuration (
.markdownlint.yaml,.markdownlintignore).
- CI release workflow: pass HTML dir to
extract-properties; tolerate Confluence zip absolute-path entry; lint issues; TypeScript dev-dependency for typecheck. inspect.jsonfetched from restraml GitHub Pages (removed~/restramldependency).
0.1.0 — 2026-03-26
Initial public release.
- Core MCP server (
src/mcp.ts) with 8 tools:routeros_search,routeros_get_page,routeros_lookup_property,routeros_command_tree,routeros_device_lookup,routeros_command_version_check,routeros_current_versions,routeros_stats. - HTML extraction pipeline (317 pages, 4,860 properties, 1,034 callouts, 2,984 sections) + command tree (46 RouterOS versions, 1.67M command-version junction rows) + product matrix (144 devices).
- SQL-as-RAG with FTS5 (
porter unicode61for prose, plainunicode61for device model numbers), BM25 ranking, compound-term recognition, AND→OR fallback. - Compiled single-file binaries for macOS arm64/x64, Linux x64, Windows
x64 via
bun build --compile. --setupflow. Downloads DB from GitHub Releases, prints MCP client config snippets for Claude Desktop, Claude Code, VS Code Copilot, Copilot CLI, Cursor, Codex.DB_PATHenv override + three-mode DB path resolution (compiled / dev / package at~/.rosetta/).- Bun tests for the query planner + schema health.