@tikoci/rosetta 0.6.9 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -5
- package/package.json +1 -1
- package/src/browse.ts +135 -15
- package/src/classify.test.ts +205 -0
- package/src/classify.ts +216 -0
- package/src/db.ts +100 -0
- package/src/mcp-http.test.ts +8 -7
- package/src/mcp.ts +49 -185
- package/src/query.test.ts +129 -0
- package/src/query.ts +427 -4
package/src/mcp.ts
CHANGED
|
@@ -186,14 +186,11 @@ const {
|
|
|
186
186
|
getSkill,
|
|
187
187
|
listSkills,
|
|
188
188
|
lookupProperty,
|
|
189
|
-
searchCallouts,
|
|
190
189
|
searchChangelogs,
|
|
191
190
|
searchDevices,
|
|
192
191
|
searchDeviceTests,
|
|
193
192
|
getTestResultMeta,
|
|
194
|
-
|
|
195
|
-
searchProperties,
|
|
196
|
-
searchVideos,
|
|
193
|
+
searchAll,
|
|
197
194
|
searchDude,
|
|
198
195
|
getDudePage,
|
|
199
196
|
} = await import("./query.ts");
|
|
@@ -208,7 +205,7 @@ const server = new McpServer({
|
|
|
208
205
|
name: "rosetta",
|
|
209
206
|
version: RESOLVED_VERSION,
|
|
210
207
|
}, {
|
|
211
|
-
instructions: "RouterOS documentation search. Start with routeros_search for any RouterOS question — it
|
|
208
|
+
instructions: "RouterOS documentation search. Start with routeros_search for any RouterOS question — it runs a classifier (detects command paths, versions, devices, topics) + BM25 FTS, and returns pages plus a `related` block (command_node, properties, devices, callouts, videos, changelogs, skills) + next-step hints. One call usually answers the question. Drill into specific pages with routeros_get_page; for hardware specs use routeros_device_lookup; for version-specific command changes use routeros_command_diff. Only v7 data exists (7.9+) — v6 is out of scope.",
|
|
212
209
|
});
|
|
213
210
|
|
|
214
211
|
server.registerResource(
|
|
@@ -519,33 +516,43 @@ server.registerResource(
|
|
|
519
516
|
server.registerTool(
|
|
520
517
|
"routeros_search",
|
|
521
518
|
{
|
|
522
|
-
description: `
|
|
519
|
+
description: `Unified RouterOS search — start here for any question.
|
|
523
520
|
|
|
524
|
-
|
|
521
|
+
One call runs an input classifier (command-path, version, device, topic, property)
|
|
522
|
+
and FTS in parallel, returning pages plus classifier-informed side queries in a
|
|
523
|
+
single response. Consolidates what used to require 3–5 separate tool calls.
|
|
524
|
+
|
|
525
|
+
Response shape:
|
|
526
|
+
- classified: { version, topics, command_path, device, property } — what the
|
|
527
|
+
classifier detected from your input
|
|
528
|
+
- pages: top FTS matches (title, path, URL, excerpt, best_section)
|
|
529
|
+
- related: callouts, properties, changelogs, videos, commands, devices, skills —
|
|
530
|
+
each capped at 2–3 entries, empty sections omitted
|
|
531
|
+
- next_steps: concrete follow-up tool calls informed by the classification
|
|
525
532
|
|
|
526
533
|
Capabilities:
|
|
527
|
-
-
|
|
528
|
-
("configuring" matches "configuration", "configured", etc.)
|
|
534
|
+
- BM25 ranking with Porter stemming ("configuring" matches "configuration")
|
|
529
535
|
- Proximity matching for compound terms ("firewall filter", "bridge vlan")
|
|
530
|
-
-
|
|
531
|
-
-
|
|
536
|
+
- Automatic AND → OR fallback on empty page results
|
|
537
|
+
- Version/device/topic detection steers related lookups
|
|
532
538
|
|
|
533
|
-
|
|
534
|
-
→ routeros_get_page:
|
|
535
|
-
→
|
|
536
|
-
→
|
|
537
|
-
→
|
|
538
|
-
→
|
|
539
|
+
Drill-down tools (still standalone for specific needs):
|
|
540
|
+
→ routeros_get_page: full page content (or section) for any result
|
|
541
|
+
→ routeros_lookup_property: exact property lookup, optionally filtered by command path
|
|
542
|
+
→ routeros_command_tree: browse command hierarchy
|
|
543
|
+
→ routeros_search_changelogs: version range + category + breaking-only filters
|
|
544
|
+
→ routeros_device_lookup: detailed device specs and test results
|
|
545
|
+
→ routeros_command_diff / routeros_command_version_check: version-specific command tracking
|
|
539
546
|
|
|
540
547
|
Tips:
|
|
541
548
|
- Use specific technical terms: "DHCP relay agent" not "how to set up DHCP"
|
|
542
|
-
-
|
|
543
|
-
|
|
544
|
-
-
|
|
545
|
-
-
|
|
546
|
-
-
|
|
549
|
+
- Pass a command path directly ("/ip/firewall/filter") and related.commands +
|
|
550
|
+
related.command_node surface children and linked docs without a second call
|
|
551
|
+
- For retired Dude GUI topics, use routeros_dude_search instead
|
|
552
|
+
- Documentation: 317 pages from March 2026 Confluence export, ~7.22 long-term
|
|
553
|
+
- Command data: RouterOS 7.9–7.23beta2. No v6 data.`,
|
|
547
554
|
inputSchema: {
|
|
548
|
-
query: z.string().describe("Natural language search query"),
|
|
555
|
+
query: z.string().describe("Natural language search query, command path, or identifier"),
|
|
549
556
|
limit: z
|
|
550
557
|
.number()
|
|
551
558
|
.int()
|
|
@@ -553,11 +560,11 @@ Tips:
|
|
|
553
560
|
.max(50)
|
|
554
561
|
.optional()
|
|
555
562
|
.default(8)
|
|
556
|
-
.describe("Max results (default 8)"),
|
|
563
|
+
.describe("Max page results (default 8). Related sections are always capped at 2–3."),
|
|
557
564
|
},
|
|
558
565
|
},
|
|
559
566
|
async ({ query, limit }) => {
|
|
560
|
-
const result =
|
|
567
|
+
const result = searchAll(query, limit);
|
|
561
568
|
return {
|
|
562
569
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
563
570
|
};
|
|
@@ -579,23 +586,25 @@ Callouts contain crucial caveats and edge-case details — always review them.
|
|
|
579
586
|
|
|
580
587
|
**Large page handling:** max_length defaults to 16000. When page content exceeds it,
|
|
581
588
|
pages with sections return a **table of contents** instead of truncated text.
|
|
582
|
-
The TOC
|
|
583
|
-
|
|
589
|
+
The TOC response surfaces high-signal content up front so you rarely need a
|
|
590
|
+
second call: top **properties** (name + type + description), **related_videos**
|
|
591
|
+
(FTS match on page title), callout_summary (count by type), and the section list
|
|
592
|
+
(heading, level, char_count, deep-link URL). Re-call with the section parameter
|
|
593
|
+
for full section text.
|
|
584
594
|
|
|
585
595
|
**Section parameter:** Pass a section heading or anchor_id (from the TOC)
|
|
586
596
|
to get that section's content. If a section is still too large, its sub-section
|
|
587
597
|
TOC is returned instead — request a more specific sub-section.
|
|
588
598
|
|
|
589
599
|
Recommended workflow for large pages:
|
|
590
|
-
1. First call → get TOC
|
|
591
|
-
2.
|
|
592
|
-
3.
|
|
600
|
+
1. First call → get TOC (+ properties, related_videos, callout_summary)
|
|
601
|
+
2. Answer directly if the surfaced signal is enough
|
|
602
|
+
3. Otherwise call again with section="Section Name" for specific content
|
|
593
603
|
|
|
594
604
|
Workflow — what to do with this content:
|
|
595
|
-
→ routeros_search_properties: look up specific properties mentioned in text
|
|
596
605
|
→ routeros_lookup_property: get exact details for a named property
|
|
597
|
-
→
|
|
598
|
-
→
|
|
606
|
+
→ routeros_command_tree: browse the command path for features on this page
|
|
607
|
+
→ routeros_search: related warnings, video segments, and device specs now surface via search's related block`,
|
|
599
608
|
inputSchema: {
|
|
600
609
|
page: z
|
|
601
610
|
.string()
|
|
@@ -637,8 +646,8 @@ Returns type, default value, description, and documentation page.
|
|
|
637
646
|
Optionally filter by command path to disambiguate (e.g., "disabled" appears everywhere).
|
|
638
647
|
|
|
639
648
|
This requires the **exact property name**. If you don't know the name:
|
|
640
|
-
→
|
|
641
|
-
→
|
|
649
|
+
→ routeros_search: find the documentation page, then routeros_get_page to read properties in context
|
|
650
|
+
→ routeros_command_tree: browse args at the command path to discover property names
|
|
642
651
|
|
|
643
652
|
Examples:
|
|
644
653
|
- name: "add-default-route" → DHCP client property
|
|
@@ -660,7 +669,7 @@ Examples:
|
|
|
660
669
|
content: [
|
|
661
670
|
{
|
|
662
671
|
type: "text",
|
|
663
|
-
text: `No property found: "${name}"${command_path ? ` under ${command_path}` : ""}\n\nTry instead:\n-
|
|
672
|
+
text: `No property found: "${name}"${command_path ? ` under ${command_path}` : ""}\n\nTry instead:\n- routeros_search to find the documentation page, then routeros_get_page to read properties in context\n- routeros_command_tree at the command path to see available args`,
|
|
664
673
|
},
|
|
665
674
|
],
|
|
666
675
|
};
|
|
@@ -671,51 +680,6 @@ Examples:
|
|
|
671
680
|
},
|
|
672
681
|
);
|
|
673
682
|
|
|
674
|
-
// ---- routeros_search_properties ----
|
|
675
|
-
|
|
676
|
-
server.registerTool(
|
|
677
|
-
"routeros_search_properties",
|
|
678
|
-
{
|
|
679
|
-
description: `Search RouterOS properties by name or description text.
|
|
680
|
-
|
|
681
|
-
Full-text search across 4,860 property names and descriptions from 145 pages.
|
|
682
|
-
Use when you don't know the exact property name but know what it does.
|
|
683
|
-
If AND returns nothing, the engine automatically retries with OR.
|
|
684
|
-
|
|
685
|
-
If this returns empty:
|
|
686
|
-
→ routeros_search: find the documentation page containing the feature
|
|
687
|
-
→ routeros_get_page: read the page — properties are embedded in page text
|
|
688
|
-
→ routeros_command_tree: browse args at a command path for property names
|
|
689
|
-
|
|
690
|
-
Examples:
|
|
691
|
-
- "gateway reachability check" → finds check-gateway properties
|
|
692
|
-
- "snooping" → finds dhcp-snooping, igmp-snooping properties
|
|
693
|
-
- "trusted" → finds bridge port trusted property`,
|
|
694
|
-
inputSchema: {
|
|
695
|
-
query: z.string().describe("Search query for property descriptions"),
|
|
696
|
-
limit: z
|
|
697
|
-
.number()
|
|
698
|
-
.int()
|
|
699
|
-
.min(1)
|
|
700
|
-
.max(50)
|
|
701
|
-
.optional()
|
|
702
|
-
.default(10)
|
|
703
|
-
.describe("Max results (default 10)"),
|
|
704
|
-
},
|
|
705
|
-
},
|
|
706
|
-
async ({ query, limit }) => {
|
|
707
|
-
const results = searchProperties(query, limit);
|
|
708
|
-
if (results.length === 0) {
|
|
709
|
-
return {
|
|
710
|
-
content: [{ type: "text", text: `No properties matched: "${query}"\n\nTry instead:\n- routeros_search to find the documentation page containing this feature\n- routeros_get_page to read properties directly from page text\n- routeros_command_tree to browse args at the command path\n- Shorter/different keywords (property descriptions are brief)` }],
|
|
711
|
-
};
|
|
712
|
-
}
|
|
713
|
-
return {
|
|
714
|
-
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
|
715
|
-
};
|
|
716
|
-
},
|
|
717
|
-
);
|
|
718
|
-
|
|
719
683
|
// ---- routeros_command_tree ----
|
|
720
684
|
|
|
721
685
|
server.registerTool(
|
|
@@ -735,7 +699,6 @@ Workflow — combine with other tools:
|
|
|
735
699
|
→ routeros_get_page: read the linked documentation page for a command
|
|
736
700
|
→ routeros_lookup_property: look up arg names as properties for details
|
|
737
701
|
→ routeros_command_version_check: check when a command was added
|
|
738
|
-
→ routeros_search_properties: search for properties under this path
|
|
739
702
|
|
|
740
703
|
Examples:
|
|
741
704
|
- path: "/ip" → address, arp, dhcp-client, dhcp-server, firewall, route, etc.
|
|
@@ -814,54 +777,6 @@ Knowledge boundaries:
|
|
|
814
777
|
},
|
|
815
778
|
);
|
|
816
779
|
|
|
817
|
-
// ---- routeros_search_callouts ----
|
|
818
|
-
|
|
819
|
-
server.registerTool(
|
|
820
|
-
"routeros_search_callouts",
|
|
821
|
-
{
|
|
822
|
-
description: `Search note, warning, tip, and info callout blocks across all RouterOS documentation.
|
|
823
|
-
|
|
824
|
-
1,034 callouts containing important caveats, edge cases, and non-obvious behavior.
|
|
825
|
-
Useful for finding warnings about hardware offloading, compatibility notes,
|
|
826
|
-
or unexpected feature interactions that aren't obvious from main page text.
|
|
827
|
-
|
|
828
|
-
Query tips:
|
|
829
|
-
- Use SHORT keyword queries (1-2 terms). Callouts are brief — multi-word NL phrases often miss.
|
|
830
|
-
- "bridge" finds more than "bridge VLAN spanning tree conflict"
|
|
831
|
-
- Pass type only (no query) to browse callouts of that type
|
|
832
|
-
- If AND finds nothing, the engine automatically retries with OR
|
|
833
|
-
|
|
834
|
-
Optionally filter by callout type: "note" (426), "info" (357), "warning" (213), or "tip" (38).
|
|
835
|
-
|
|
836
|
-
Examples:
|
|
837
|
-
- query: "hardware offload" → warnings about bridge HW offloading limitations
|
|
838
|
-
- query: "VLAN", type: "warning" → only VLAN-related warnings
|
|
839
|
-
- query: "bridge", type: "warning" → bridge-related warnings
|
|
840
|
-
- type: "warning", limit: 20 → browse 20 warnings (no search term needed)`,
|
|
841
|
-
inputSchema: {
|
|
842
|
-
query: z.string().optional().default("").describe("Search keywords for callout content (keep short — 1-2 terms work best)"),
|
|
843
|
-
type: z
|
|
844
|
-
.enum(["note", "warning", "info", "tip"])
|
|
845
|
-
.optional()
|
|
846
|
-
.describe("Filter by callout type"),
|
|
847
|
-
limit: z
|
|
848
|
-
.number()
|
|
849
|
-
.int()
|
|
850
|
-
.min(1)
|
|
851
|
-
.max(50)
|
|
852
|
-
.optional()
|
|
853
|
-
.default(10)
|
|
854
|
-
.describe("Max results (default 10)"),
|
|
855
|
-
},
|
|
856
|
-
},
|
|
857
|
-
async ({ query, type, limit }) => {
|
|
858
|
-
const results = searchCallouts(query, type, limit);
|
|
859
|
-
return {
|
|
860
|
-
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
|
861
|
-
};
|
|
862
|
-
},
|
|
863
|
-
);
|
|
864
|
-
|
|
865
780
|
// ---- routeros_search_changelogs ----
|
|
866
781
|
|
|
867
782
|
/** Group flat changelog results by version for compact output. */
|
|
@@ -899,8 +814,7 @@ Entries marked !) are breaking changes that may require config adjustments after
|
|
|
899
814
|
1. Search changelogs with from_version=A, to_version=B, and the subsystem as query
|
|
900
815
|
2. Look for !) breaking changes that explain the behavior change
|
|
901
816
|
3. → routeros_get_page for the subsystem's documentation
|
|
902
|
-
4. →
|
|
903
|
-
5. → routeros_command_version_check to see if commands were added/removed
|
|
817
|
+
4. → routeros_command_version_check to see if commands were added/removed
|
|
904
818
|
|
|
905
819
|
Supports: FTS keyword search, version range filtering, category filtering, breaking-only mode.
|
|
906
820
|
Categories are subsystem names: bgp, bridge, dhcpv4-server, wifi, ipsec, console, container, etc.
|
|
@@ -960,7 +874,7 @@ Coverage depends on which versions were extracted — typically matches ros_vers
|
|
|
960
874
|
from_version || to_version ? "Try widening the version range" : null,
|
|
961
875
|
category ? `Try without category filter, or check spelling (categories are lowercase: bgp, bridge, wifi, etc.)` : null,
|
|
962
876
|
breaking_only ? "Try without breaking_only — the change may not be marked as breaking" : null,
|
|
963
|
-
"Use routeros_search
|
|
877
|
+
"Use routeros_search for documentation-based answers — callouts and videos surface in its related block",
|
|
964
878
|
].filter(Boolean);
|
|
965
879
|
return {
|
|
966
880
|
content: [
|
|
@@ -979,56 +893,6 @@ Coverage depends on which versions were extracted — typically matches ros_vers
|
|
|
979
893
|
},
|
|
980
894
|
);
|
|
981
895
|
|
|
982
|
-
// ---- routeros_search_videos ----
|
|
983
|
-
|
|
984
|
-
server.registerTool(
|
|
985
|
-
"routeros_search_videos",
|
|
986
|
-
{
|
|
987
|
-
description: `Search MikroTik YouTube video transcripts for RouterOS topics.
|
|
988
|
-
|
|
989
|
-
Searches chapter-level transcript segments from official MikroTik YouTube videos.
|
|
990
|
-
Returns matching segments with video title, URL, chapter name, and an excerpt.
|
|
991
|
-
Auto-caption quality varies — short config snippets may not appear verbatim.
|
|
992
|
-
|
|
993
|
-
MUM conference talks are excluded (those are long, off-topic lectures).
|
|
994
|
-
Results include the video URL and start timestamp for direct chapter linking.
|
|
995
|
-
|
|
996
|
-
Useful for: finding tutorial walkthroughs, feature announcements, demo configs,
|
|
997
|
-
and explanations that complement the text documentation.
|
|
998
|
-
|
|
999
|
-
→ routeros_search: search official text documentation (more precise for property names)
|
|
1000
|
-
→ routeros_get_page: read full documentation page for a topic
|
|
1001
|
-
→ routeros_search_callouts: find Warnings/Notes embedded in documentation`,
|
|
1002
|
-
inputSchema: {
|
|
1003
|
-
query: z.string().describe("Topic to search for in video transcripts (e.g., 'VLAN trunking', 'BGP route reflection')"),
|
|
1004
|
-
limit: z
|
|
1005
|
-
.number()
|
|
1006
|
-
.int()
|
|
1007
|
-
.min(1)
|
|
1008
|
-
.max(20)
|
|
1009
|
-
.default(5)
|
|
1010
|
-
.optional()
|
|
1011
|
-
.describe("Max results (1–20, default 5)"),
|
|
1012
|
-
},
|
|
1013
|
-
},
|
|
1014
|
-
async ({ query, limit }) => {
|
|
1015
|
-
const results = searchVideos(query, limit ?? 5);
|
|
1016
|
-
if (results.length === 0) {
|
|
1017
|
-
return {
|
|
1018
|
-
content: [
|
|
1019
|
-
{
|
|
1020
|
-
type: "text",
|
|
1021
|
-
text: `No video transcript results for: "${query}"\n\nTry:\n- Broader or simpler search terms\n- routeros_search for official documentation\n- routeros_search_callouts for Notes and Warnings in docs`,
|
|
1022
|
-
},
|
|
1023
|
-
],
|
|
1024
|
-
};
|
|
1025
|
-
}
|
|
1026
|
-
return {
|
|
1027
|
-
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
|
1028
|
-
};
|
|
1029
|
-
},
|
|
1030
|
-
);
|
|
1031
|
-
|
|
1032
896
|
// ---- routeros_dude_search ----
|
|
1033
897
|
|
|
1034
898
|
server.registerTool(
|
|
@@ -1137,8 +1001,8 @@ Useful for answering "is /container supported in 7.12?" or "when was /ip/firewal
|
|
|
1137
1001
|
|
|
1138
1002
|
Command data covers versions 7.9–7.23beta2. No v6 data.
|
|
1139
1003
|
For versions below 7.9, no command tree data exists — the command may still exist there.
|
|
1140
|
-
Cross-reference with routeros_get_page
|
|
1141
|
-
in
|
|
1004
|
+
Cross-reference with routeros_get_page for version mentions in documentation text (callouts
|
|
1005
|
+
surface in routeros_search's related block). → routeros_search_changelogs to see what changed between versions.
|
|
1142
1006
|
|
|
1143
1007
|
Examples:
|
|
1144
1008
|
- command_path: "/container" → shows versions where container support exists
|
package/src/query.test.ts
CHANGED
|
@@ -37,6 +37,9 @@ const {
|
|
|
37
37
|
listSkills,
|
|
38
38
|
getSkill,
|
|
39
39
|
getSkillReference,
|
|
40
|
+
lookupGlossary,
|
|
41
|
+
listGlossary,
|
|
42
|
+
KNOWN_TOPICS,
|
|
40
43
|
} = await import("./query.ts");
|
|
41
44
|
const { parseChangelog } = await import("./extract-changelogs.ts");
|
|
42
45
|
const { parseVtt, segmentTranscript } = await import("./extract-videos.ts");
|
|
@@ -48,6 +51,31 @@ const { parseVtt, segmentTranscript } = await import("./extract-videos.ts");
|
|
|
48
51
|
beforeAll(() => {
|
|
49
52
|
initDb();
|
|
50
53
|
|
|
54
|
+
// Keep this test deterministic even if another test file initialized db.ts first
|
|
55
|
+
// against a non-empty DB (e.g., workflow-generated ros-help.db).
|
|
56
|
+
db.run("PRAGMA foreign_keys=OFF;");
|
|
57
|
+
db.run("DELETE FROM skill_references");
|
|
58
|
+
db.run("DELETE FROM skills");
|
|
59
|
+
db.run("DELETE FROM dude_images");
|
|
60
|
+
db.run("DELETE FROM dude_pages");
|
|
61
|
+
db.run("DELETE FROM video_segments");
|
|
62
|
+
db.run("DELETE FROM videos");
|
|
63
|
+
db.run("DELETE FROM changelogs");
|
|
64
|
+
db.run("DELETE FROM device_test_results");
|
|
65
|
+
db.run("DELETE FROM devices");
|
|
66
|
+
db.run("DELETE FROM schema_node_presence");
|
|
67
|
+
db.run("DELETE FROM schema_nodes");
|
|
68
|
+
db.run("DELETE FROM command_versions");
|
|
69
|
+
db.run("DELETE FROM commands");
|
|
70
|
+
db.run("DELETE FROM ros_versions");
|
|
71
|
+
db.run("DELETE FROM sections");
|
|
72
|
+
db.run("DELETE FROM callouts");
|
|
73
|
+
db.run("DELETE FROM properties");
|
|
74
|
+
db.run("DELETE FROM pages");
|
|
75
|
+
// Reset autoincrement counters so fixture IDs are deterministic
|
|
76
|
+
db.run("DELETE FROM sqlite_sequence");
|
|
77
|
+
db.run("PRAGMA foreign_keys=ON;");
|
|
78
|
+
|
|
51
79
|
db.run(`INSERT INTO pages
|
|
52
80
|
(id, slug, title, path, depth, parent_id, url, text, code, code_lang,
|
|
53
81
|
author, last_updated, word_count, code_lines, html_file)
|
|
@@ -1935,3 +1963,104 @@ describe("getDudePage", () => {
|
|
|
1935
1963
|
expect(getDudePage("NonExistent")).toBeNull();
|
|
1936
1964
|
});
|
|
1937
1965
|
});
|
|
1966
|
+
|
|
1967
|
+
// ---------------------------------------------------------------------------
|
|
1968
|
+
// KNOWN_TOPICS
|
|
1969
|
+
// ---------------------------------------------------------------------------
|
|
1970
|
+
|
|
1971
|
+
describe("KNOWN_TOPICS", () => {
|
|
1972
|
+
test("contains core RouterOS subsystems", () => {
|
|
1973
|
+
expect(KNOWN_TOPICS.has("firewall")).toBe(true);
|
|
1974
|
+
expect(KNOWN_TOPICS.has("bridge")).toBe(true);
|
|
1975
|
+
expect(KNOWN_TOPICS.has("bgp")).toBe(true);
|
|
1976
|
+
expect(KNOWN_TOPICS.has("ospf")).toBe(true);
|
|
1977
|
+
expect(KNOWN_TOPICS.has("container")).toBe(true);
|
|
1978
|
+
expect(KNOWN_TOPICS.has("wifi")).toBe(true);
|
|
1979
|
+
expect(KNOWN_TOPICS.has("ipsec")).toBe(true);
|
|
1980
|
+
expect(KNOWN_TOPICS.has("dns")).toBe(true);
|
|
1981
|
+
});
|
|
1982
|
+
|
|
1983
|
+
test("contains changelog-derived categories", () => {
|
|
1984
|
+
expect(KNOWN_TOPICS.has("winbox")).toBe(true);
|
|
1985
|
+
expect(KNOWN_TOPICS.has("hotspot")).toBe(true);
|
|
1986
|
+
expect(KNOWN_TOPICS.has("lte")).toBe(true);
|
|
1987
|
+
expect(KNOWN_TOPICS.has("wireguard")).toBe(true);
|
|
1988
|
+
expect(KNOWN_TOPICS.has("zerotier")).toBe(true);
|
|
1989
|
+
});
|
|
1990
|
+
|
|
1991
|
+
test("contains top-level command paths", () => {
|
|
1992
|
+
expect(KNOWN_TOPICS.has("ip")).toBe(true);
|
|
1993
|
+
expect(KNOWN_TOPICS.has("system")).toBe(true);
|
|
1994
|
+
expect(KNOWN_TOPICS.has("interface")).toBe(true);
|
|
1995
|
+
expect(KNOWN_TOPICS.has("app")).toBe(true);
|
|
1996
|
+
});
|
|
1997
|
+
|
|
1998
|
+
test("does not contain stop words", () => {
|
|
1999
|
+
expect(KNOWN_TOPICS.has("the")).toBe(false);
|
|
2000
|
+
expect(KNOWN_TOPICS.has("how")).toBe(false);
|
|
2001
|
+
expect(KNOWN_TOPICS.has("configure")).toBe(false);
|
|
2002
|
+
});
|
|
2003
|
+
|
|
2004
|
+
test("has reasonable size (80-200 entries)", () => {
|
|
2005
|
+
expect(KNOWN_TOPICS.size).toBeGreaterThanOrEqual(80);
|
|
2006
|
+
expect(KNOWN_TOPICS.size).toBeLessThanOrEqual(200);
|
|
2007
|
+
});
|
|
2008
|
+
});
|
|
2009
|
+
|
|
2010
|
+
// ---------------------------------------------------------------------------
|
|
2011
|
+
// Glossary
|
|
2012
|
+
// ---------------------------------------------------------------------------
|
|
2013
|
+
|
|
2014
|
+
describe("lookupGlossary", () => {
|
|
2015
|
+
test("finds exact term match", () => {
|
|
2016
|
+
const result = lookupGlossary("chr");
|
|
2017
|
+
expect(result).not.toBeNull();
|
|
2018
|
+
expect(result!.term).toBe("chr");
|
|
2019
|
+
expect(result!.definition).toContain("Cloud Hosted Router");
|
|
2020
|
+
expect(result!.category).toBe("product");
|
|
2021
|
+
});
|
|
2022
|
+
|
|
2023
|
+
test("case-insensitive lookup", () => {
|
|
2024
|
+
const result = lookupGlossary("CHR");
|
|
2025
|
+
expect(result).not.toBeNull();
|
|
2026
|
+
expect(result!.term).toBe("chr");
|
|
2027
|
+
});
|
|
2028
|
+
|
|
2029
|
+
test("finds term by alias", () => {
|
|
2030
|
+
const result = lookupGlossary("openvpn");
|
|
2031
|
+
expect(result).not.toBeNull();
|
|
2032
|
+
expect(result!.term).toBe("ovpn");
|
|
2033
|
+
});
|
|
2034
|
+
|
|
2035
|
+
test("returns null for unknown term", () => {
|
|
2036
|
+
expect(lookupGlossary("nonexistentterm")).toBeNull();
|
|
2037
|
+
});
|
|
2038
|
+
|
|
2039
|
+
test("returns null for empty input", () => {
|
|
2040
|
+
expect(lookupGlossary("")).toBeNull();
|
|
2041
|
+
});
|
|
2042
|
+
|
|
2043
|
+
test("search_hint field is populated", () => {
|
|
2044
|
+
const result = lookupGlossary("capsman");
|
|
2045
|
+
expect(result).not.toBeNull();
|
|
2046
|
+
expect(result!.search_hint).toBeTruthy();
|
|
2047
|
+
expect(result!.search_hint).toContain("CAPsMAN");
|
|
2048
|
+
});
|
|
2049
|
+
});
|
|
2050
|
+
|
|
2051
|
+
describe("listGlossary", () => {
|
|
2052
|
+
test("returns all entries when no category", () => {
|
|
2053
|
+
const entries = listGlossary();
|
|
2054
|
+
expect(entries.length).toBeGreaterThanOrEqual(40);
|
|
2055
|
+
});
|
|
2056
|
+
|
|
2057
|
+
test("filters by category", () => {
|
|
2058
|
+
const products = listGlossary("product");
|
|
2059
|
+
expect(products.length).toBeGreaterThan(0);
|
|
2060
|
+
expect(products.every(e => e.category === "product")).toBe(true);
|
|
2061
|
+
});
|
|
2062
|
+
|
|
2063
|
+
test("returns empty for non-existent category", () => {
|
|
2064
|
+
expect(listGlossary("nonexistent")).toEqual([]);
|
|
2065
|
+
});
|
|
2066
|
+
});
|