@tikoci/rosetta 0.8.6 → 0.8.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tikoci/rosetta",
3
- "version": "0.8.6",
3
+ "version": "0.8.7",
4
4
  "description": "RouterOS documentation as SQLite FTS5 — RAG search + command glossary via MCP",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/browse.ts CHANGED
@@ -51,6 +51,7 @@ import {
51
51
  searchDude,
52
52
  searchProperties,
53
53
  searchVideos,
54
+ truncateDeviceTestResultsPrefer512,
54
55
  } from "./query.ts";
55
56
 
56
57
  // ── ANSI utilities (zero deps) ──
@@ -785,15 +786,16 @@ function renderDeviceCard(d: DeviceResult): string {
785
786
 
786
787
  // Test results (attached for exact matches)
787
788
  if (d.test_results && d.test_results.length > 0) {
789
+ const truncated = truncateDeviceTestResultsPrefer512(d.test_results, 12);
788
790
  out.push("");
789
791
  out.push(` ${bold("Benchmarks:")} ${dim(`(${d.test_results.length} tests)`)}`);
790
- for (const t of d.test_results.slice(0, 12)) {
792
+ for (const t of truncated.rows) {
791
793
  const mbps = t.throughput_mbps ? `${fmt(t.throughput_mbps)} Mbps` : "";
792
794
  const kpps = t.throughput_kpps ? `${fmt(t.throughput_kpps)} Kpps` : "";
793
795
  out.push(` ${dim(pad(t.test_type, 9))} ${pad(t.mode, 16)} ${dim(pad(t.configuration, 28))} ${pad(`${t.packet_size}B`, 6)} ${bold(mbps)} ${dim(kpps)}`);
794
796
  }
795
- if (d.test_results.length > 12) {
796
- out.push(` ${dim(`... and ${d.test_results.length - 12} more (use`)} ${cyan("tests")} ${dim("for full listing)")}`);
797
+ if (truncated.omitted > 0) {
798
+ out.push(` ${dim(`... and ${truncated.omitted} more (use`)} ${cyan("tests")} ${dim("for full listing)")}`);
797
799
  }
798
800
  }
799
801
 
package/src/query.test.ts CHANGED
@@ -43,6 +43,7 @@ const {
43
43
  searchDeviceTests,
44
44
  getTestResultMeta,
45
45
  normalizeDeviceQuery,
46
+ truncateDeviceTestResultsPrefer512,
46
47
  searchVideos,
47
48
  searchDude,
48
49
  getDudePage,
@@ -1426,6 +1427,48 @@ describe("dataset CSV exports", () => {
1426
1427
  });
1427
1428
  });
1428
1429
 
1430
+ describe("truncateDeviceTestResultsPrefer512", () => {
1431
+ test("keeps all 512B rows when truncating", () => {
1432
+ const rows = [
1433
+ { packet_size: 1518, id: "a" },
1434
+ { packet_size: 512, id: "b" },
1435
+ { packet_size: 64, id: "c" },
1436
+ { packet_size: 512, id: "d" },
1437
+ ];
1438
+ const res = truncateDeviceTestResultsPrefer512(rows, 2);
1439
+
1440
+ expect(res.rows.map((r) => r.id)).toEqual(["b", "d"]);
1441
+ expect(res.omitted).toBe(2);
1442
+ });
1443
+
1444
+ test("fills remaining slots with non-512 rows", () => {
1445
+ const rows = [
1446
+ { packet_size: 1518, id: "a" },
1447
+ { packet_size: 512, id: "b" },
1448
+ { packet_size: 64, id: "c" },
1449
+ { packet_size: 512, id: "d" },
1450
+ ];
1451
+ const res = truncateDeviceTestResultsPrefer512(rows, 3);
1452
+
1453
+ expect(res.rows.map((r) => r.id)).toEqual(["b", "d", "a"]);
1454
+ expect(res.omitted).toBe(1);
1455
+ });
1456
+
1457
+ test("returns all 512B rows even if they exceed maxRows", () => {
1458
+ const rows = [
1459
+ { packet_size: 512, id: "a" },
1460
+ { packet_size: 512, id: "b" },
1461
+ { packet_size: 1518, id: "c" },
1462
+ { packet_size: 512, id: "d" },
1463
+ { packet_size: 64, id: "e" },
1464
+ ];
1465
+ const res = truncateDeviceTestResultsPrefer512(rows, 2);
1466
+
1467
+ expect(res.rows.map((r) => r.id)).toEqual(["a", "b", "d"]);
1468
+ expect(res.omitted).toBe(2);
1469
+ });
1470
+ });
1471
+
1429
1472
  describe("getTestResultMeta", () => {
1430
1473
  test("returns distinct values", () => {
1431
1474
  const meta = getTestResultMeta();
package/src/query.ts CHANGED
@@ -298,8 +298,8 @@ export function searchAll(query: string, limit = DEFAULT_LIMIT): SearchAllRespon
298
298
  path: node.path,
299
299
  type: node.type,
300
300
  description: node.description,
301
- ...(node.page_id
302
- ? { linked_page: { id: node.page_id, title: node.page_title!, url: node.page_url! } }
301
+ ...(node.page_id && node.page_title && node.page_url
302
+ ? { linked_page: { id: node.page_id, title: node.page_title, url: node.page_url } }
303
303
  : {}),
304
304
  };
305
305
  }
@@ -1330,6 +1330,43 @@ export type DeviceTestResult = {
1330
1330
  throughput_mbps: number | null;
1331
1331
  };
1332
1332
 
1333
+ /**
1334
+ * Truncate benchmark rows for display while always keeping all 512-byte rows.
1335
+ *
1336
+ * Used by both TUI and MCP-facing adapters when they need a compact view.
1337
+ * If the 512B bucket itself exceeds maxRows, return all 512B rows anyway.
1338
+ */
1339
+ export function truncateDeviceTestResultsPrefer512<T extends { packet_size: number }>(
1340
+ rows: T[],
1341
+ maxRows: number,
1342
+ ): { rows: T[]; omitted: number } {
1343
+ if (rows.length === 0) return { rows: [], omitted: 0 };
1344
+
1345
+ const safeMax = Math.max(0, Math.floor(maxRows));
1346
+ const rows512 = rows.filter((r) => r.packet_size === 512);
1347
+ const non512 = rows.filter((r) => r.packet_size !== 512);
1348
+
1349
+ if (safeMax === 0) {
1350
+ return { rows: rows512, omitted: rows.length - rows512.length };
1351
+ }
1352
+
1353
+ if (rows.length <= safeMax) {
1354
+ return { rows, omitted: 0 };
1355
+ }
1356
+
1357
+ if (rows512.length === 0) {
1358
+ return { rows: rows.slice(0, safeMax), omitted: rows.length - safeMax };
1359
+ }
1360
+
1361
+ if (rows512.length >= safeMax) {
1362
+ return { rows: rows512, omitted: rows.length - rows512.length };
1363
+ }
1364
+
1365
+ const takeNon512 = safeMax - rows512.length;
1366
+ const selected = [...rows512, ...non512.slice(0, takeNon512)];
1367
+ return { rows: selected, omitted: rows.length - selected.length };
1368
+ }
1369
+
1333
1370
  /** Map Unicode superscript digits to ASCII equivalents for product name matching.
1334
1371
  * MikroTik uses ² and ³ in product names (hAP ax³, hAP ac²), but users type ASCII. */
1335
1372
  const SUPERSCRIPT_TO_ASCII: [string, string][] = [