nodebench-mcp 2.22.0 → 2.25.0

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.
Files changed (38) hide show
  1. package/README.md +366 -280
  2. package/dist/__tests__/multiHopDogfood.test.d.ts +12 -0
  3. package/dist/__tests__/multiHopDogfood.test.js +303 -0
  4. package/dist/__tests__/multiHopDogfood.test.js.map +1 -0
  5. package/dist/__tests__/presetRealWorldBench.test.js +2 -0
  6. package/dist/__tests__/presetRealWorldBench.test.js.map +1 -1
  7. package/dist/__tests__/tools.test.js +158 -6
  8. package/dist/__tests__/tools.test.js.map +1 -1
  9. package/dist/__tests__/toolsetGatingEval.test.js +2 -0
  10. package/dist/__tests__/toolsetGatingEval.test.js.map +1 -1
  11. package/dist/dashboard/html.d.ts +18 -0
  12. package/dist/dashboard/html.js +1251 -0
  13. package/dist/dashboard/html.js.map +1 -0
  14. package/dist/dashboard/server.d.ts +17 -0
  15. package/dist/dashboard/server.js +278 -0
  16. package/dist/dashboard/server.js.map +1 -0
  17. package/dist/db.js +38 -0
  18. package/dist/db.js.map +1 -1
  19. package/dist/index.js +19 -9
  20. package/dist/index.js.map +1 -1
  21. package/dist/tools/prReportTools.d.ts +11 -0
  22. package/dist/tools/prReportTools.js +911 -0
  23. package/dist/tools/prReportTools.js.map +1 -0
  24. package/dist/tools/progressiveDiscoveryTools.js +111 -24
  25. package/dist/tools/progressiveDiscoveryTools.js.map +1 -1
  26. package/dist/tools/skillUpdateTools.d.ts +24 -0
  27. package/dist/tools/skillUpdateTools.js +469 -0
  28. package/dist/tools/skillUpdateTools.js.map +1 -0
  29. package/dist/tools/toolRegistry.d.ts +15 -1
  30. package/dist/tools/toolRegistry.js +315 -11
  31. package/dist/tools/toolRegistry.js.map +1 -1
  32. package/dist/tools/uiUxDiveAdvancedTools.js +61 -0
  33. package/dist/tools/uiUxDiveAdvancedTools.js.map +1 -1
  34. package/dist/tools/uiUxDiveTools.js +154 -1
  35. package/dist/tools/uiUxDiveTools.js.map +1 -1
  36. package/dist/toolsetRegistry.js +4 -0
  37. package/dist/toolsetRegistry.js.map +1 -1
  38. package/package.json +2 -2
@@ -2070,6 +2070,18 @@ const REGISTRY_ENTRIES = [
2070
2070
  },
2071
2071
  phase: "research",
2072
2072
  },
2073
+ {
2074
+ name: "ingest_dive_screenshots",
2075
+ category: "ui_ux_dive",
2076
+ tags: ["ui", "screenshot", "ingest", "import", "bulk", "png", "jpg", "gallery", "dive", "disk", "file"],
2077
+ quickRef: {
2078
+ nextAction: "Screenshots ingested into session DB. View them in the dashboard gallery or reference in dive_changelog entries.",
2079
+ nextTools: ["dive_changelog", "get_dive_report", "open_dive_dashboard"],
2080
+ methodology: "agentic_vision",
2081
+ tip: "Use after external Playwright MCP captures screenshots to disk. Scans directory recursively, base64-encodes, and inserts into the session DB for dashboard display.",
2082
+ },
2083
+ phase: "utility",
2084
+ },
2073
2085
  // ═══════════════════════════════════════════
2074
2086
  // UI/UX DIVE V2 — Deep interaction testing,
2075
2087
  // screenshots, design audit, backend links,
@@ -2235,6 +2247,92 @@ const REGISTRY_ENTRIES = [
2235
2247
  },
2236
2248
  phase: "ship",
2237
2249
  },
2250
+ {
2251
+ name: "open_dive_dashboard",
2252
+ category: "ui_ux_dive_v2",
2253
+ tags: ["ui", "dashboard", "dive", "flywheel", "browser", "local", "report", "overview", "session", "open", "visualization"],
2254
+ quickRef: {
2255
+ nextAction: "Dashboard is open. Continue the dive — the dashboard auto-refreshes every 5s to show live progress.",
2256
+ nextTools: ["start_ui_dive", "dive_auto_discover", "dive_code_locate", "dive_fix_verify"],
2257
+ methodology: "agentic_vision",
2258
+ tip: "Opens a local web dashboard (port 6274) showing the full flywheel cycle: routes, components, bugs, fixes, tests, reviews. Like Serena MCP's local page but for UI dives.",
2259
+ },
2260
+ phase: "utility",
2261
+ },
2262
+ // ═══════════════════════════════════════════
2263
+ // SKILL SELF-UPDATE PROTOCOL — Track rule
2264
+ // file provenance, staleness, and resync
2265
+ // ═══════════════════════════════════════════
2266
+ {
2267
+ name: "register_skill",
2268
+ category: "skill_update",
2269
+ tags: ["skill", "rule", "register", "source", "hash", "frontmatter", "provenance", "memory", "agents-md", "cursor", "windsurf", "update", "reexamine", "related_"],
2270
+ quickRef: {
2271
+ nextAction: "Skill registered. Use check_skill_freshness periodically to detect when source files change.",
2272
+ nextTools: ["check_skill_freshness", "list_skills"],
2273
+ methodology: "self_reinforced_learning",
2274
+ tip: "Register every .md rule file (e.g. .windsurf/rules/, AGENTS.md) with its source files, triggers, and update instructions. Enables automatic staleness detection.",
2275
+ },
2276
+ phase: "verify",
2277
+ },
2278
+ {
2279
+ name: "check_skill_freshness",
2280
+ category: "skill_update",
2281
+ tags: ["skill", "freshness", "stale", "hash", "check", "drift", "source", "detect", "sync", "update", "rule"],
2282
+ quickRef: {
2283
+ nextAction: "If stale skills found, follow their update_instructions then call sync_skill to record the resync.",
2284
+ nextTools: ["sync_skill", "list_skills", "register_skill"],
2285
+ methodology: "self_reinforced_learning",
2286
+ tip: "Run at session start or after big code changes. Compares SHA-256 hashes of source files to detect drift. Auto-updates skill status in DB.",
2287
+ },
2288
+ phase: "verify",
2289
+ },
2290
+ {
2291
+ name: "sync_skill",
2292
+ category: "skill_update",
2293
+ tags: ["skill", "sync", "resync", "update", "hash", "refresh", "frontmatter", "rule", "source", "stale"],
2294
+ quickRef: {
2295
+ nextAction: "Skill synced. Verify the updated skill file is correct, then continue with your task.",
2296
+ nextTools: ["check_skill_freshness", "list_skills"],
2297
+ methodology: "self_reinforced_learning",
2298
+ tip: "Call AFTER you have read the changed source files and updated the skill .md content. This tool records the sync and updates the hash.",
2299
+ },
2300
+ phase: "verify",
2301
+ },
2302
+ {
2303
+ name: "list_skills",
2304
+ category: "skill_update",
2305
+ tags: ["skill", "list", "status", "overview", "rule", "memory", "history", "sync", "fresh", "stale"],
2306
+ quickRef: {
2307
+ nextAction: "Review skill statuses. Register any untracked rule files, check freshness for stale ones.",
2308
+ nextTools: ["register_skill", "check_skill_freshness", "sync_skill"],
2309
+ methodology: "self_reinforced_learning",
2310
+ tip: "Use includeHistory:true to see the full sync timeline for each skill. Filter by status:'stale' to focus on what needs updating.",
2311
+ },
2312
+ phase: "utility",
2313
+ },
2314
+ // ═══════════════════════════════════════════
2315
+ // RE-EXAMINE 11/10 — Fresh-eyes quality pass
2316
+ // Modular rules: reexamine_process → a11y,
2317
+ // resilience, polish, keyboard, performance
2318
+ // Cross-ref via related_ frontmatter hops
2319
+ // ═══════════════════════════════════════════
2320
+ // NOTE: These are not MCP tools — they are rule
2321
+ // files in .cursor/rules/ and .windsurf/rules/.
2322
+ // The skill_update tools above (register_skill,
2323
+ // check_skill_freshness) track their freshness.
2324
+ // The related_ field in each rule's frontmatter
2325
+ // enables one-hop and two-hop cross-referencing:
2326
+ //
2327
+ // reexamine_process
2328
+ // └─ related_: [a11y, resilience, polish, keyboard, performance]
2329
+ // └─ reexamine_a11y.related_: [keyboard, polish, process]
2330
+ // └─ reexamine_resilience.related_: [performance, process, polish]
2331
+ // └─ reexamine_polish.related_: [a11y, performance, process]
2332
+ // └─ reexamine_keyboard.related_: [a11y, process]
2333
+ // └─ reexamine_performance.related_: [resilience, polish, process]
2334
+ //
2335
+ // Two-hop example: process → a11y → keyboard (discovers keyboard via a11y)
2238
2336
  // ═══════════════════════════════════════════
2239
2337
  // MCP BRIDGE — Connect external MCP servers
2240
2338
  // ═══════════════════════════════════════════
@@ -2333,12 +2431,155 @@ const REGISTRY_ENTRIES = [
2333
2431
  },
2334
2432
  phase: "implement",
2335
2433
  },
2434
+ // ═══════════════════════════════════════════
2435
+ // PR REPORT — Visual PR creation from dives
2436
+ // ═══════════════════════════════════════════
2437
+ {
2438
+ name: "generate_pr_report",
2439
+ category: "pr_report",
2440
+ tags: ["pr", "pull-request", "report", "markdown", "visual", "screenshot", "before-after", "timeline", "changelog", "dive", "github", "review", "evidence"],
2441
+ quickRef: {
2442
+ nextAction: "PR report generated. Use the markdown with `gh pr create --body-file` or call create_visual_pr for end-to-end PR creation.",
2443
+ nextTools: ["create_visual_pr", "export_pr_screenshots", "review_pr_checklist"],
2444
+ methodology: "agentic_vision",
2445
+ tip: "Pass asset_dir to export screenshots as PNGs that can be committed alongside the PR.",
2446
+ },
2447
+ phase: "ship",
2448
+ },
2449
+ {
2450
+ name: "export_pr_screenshots",
2451
+ category: "pr_report",
2452
+ tags: ["pr", "screenshot", "export", "png", "before-after", "visual", "evidence", "assets", "commit", "dive", "changelog", "fix"],
2453
+ quickRef: {
2454
+ nextAction: "Screenshots exported. Stage and commit them, then use generate_pr_report or create_visual_pr to reference them in the PR body.",
2455
+ nextTools: ["generate_pr_report", "create_visual_pr"],
2456
+ methodology: "agentic_vision",
2457
+ tip: "Naming convention: {index}-{type}-before.png / after.png. Commit these with your branch.",
2458
+ },
2459
+ phase: "ship",
2460
+ },
2461
+ {
2462
+ name: "create_visual_pr",
2463
+ category: "pr_report",
2464
+ tags: ["pr", "pull-request", "create", "github", "gh", "visual", "screenshot", "end-to-end", "push", "merge", "review", "dive", "timeline", "evidence"],
2465
+ quickRef: {
2466
+ nextAction: "PR created! Share the URL with reviewers. The PR body contains visual evidence and dashboard links for interactive browsing.",
2467
+ nextTools: ["review_pr_checklist", "enforce_merge_gate"],
2468
+ methodology: "agentic_vision",
2469
+ tip: "Set draft:true for WIP PRs. Combines export_pr_screenshots + generate_pr_report + gh pr create in one call.",
2470
+ },
2471
+ phase: "ship",
2472
+ },
2336
2473
  ];
2337
2474
  // ── Exported lookup structures ───────────────────────────────────────────
2338
2475
  /** Map of tool name → registry entry for O(1) lookup */
2339
2476
  export const TOOL_REGISTRY = new Map(REGISTRY_ENTRIES.map((e) => [e.name, e]));
2340
2477
  /** All registry entries as array */
2341
2478
  export const ALL_REGISTRY_ENTRIES = REGISTRY_ENTRIES;
2479
+ // ── Auto-derive relatedTools for entries that don't have manual overrides ──
2480
+ // Uses 3 signals: same-category siblings, DOMAIN_CLUSTERS neighbors, tag overlap.
2481
+ // Must run after REGISTRY_ENTRIES is fully built. Forward-reference to DOMAIN_CLUSTERS
2482
+ // is fine because this runs at module load time (DOMAIN_CLUSTERS is defined below).
2483
+ /** Late-init: populated by _populateRelatedTools() at bottom of file */
2484
+ let _domainClusters = null;
2485
+ export function _setDomainClustersRef(clusters) {
2486
+ _domainClusters = clusters;
2487
+ }
2488
+ function computeRelatedTools(entry) {
2489
+ // If manually specified, use that
2490
+ if (entry.quickRef.relatedTools && entry.quickRef.relatedTools.length > 0) {
2491
+ return entry.quickRef.relatedTools;
2492
+ }
2493
+ const related = new Set();
2494
+ const nextToolsSet = new Set(entry.quickRef.nextTools);
2495
+ // 1. Same-category siblings (excluding self and nextTools), up to 3
2496
+ let sibCount = 0;
2497
+ for (const e of REGISTRY_ENTRIES) {
2498
+ if (sibCount >= 3)
2499
+ break;
2500
+ if (e.category === entry.category && e.name !== entry.name && !nextToolsSet.has(e.name)) {
2501
+ related.add(e.name);
2502
+ sibCount++;
2503
+ }
2504
+ }
2505
+ // 2. DOMAIN_CLUSTERS neighbors: tools from related categories, up to 2
2506
+ if (_domainClusters) {
2507
+ let clusterCount = 0;
2508
+ for (const cluster of Object.values(_domainClusters)) {
2509
+ if (clusterCount >= 2)
2510
+ break;
2511
+ if (cluster.includes(entry.category)) {
2512
+ for (const neighborCat of cluster) {
2513
+ if (clusterCount >= 2)
2514
+ break;
2515
+ if (neighborCat === entry.category)
2516
+ continue;
2517
+ for (const e of REGISTRY_ENTRIES) {
2518
+ if (e.category === neighborCat && !nextToolsSet.has(e.name) && !related.has(e.name)) {
2519
+ related.add(e.name);
2520
+ clusterCount++;
2521
+ break; // one tool per neighbor category
2522
+ }
2523
+ }
2524
+ }
2525
+ }
2526
+ }
2527
+ }
2528
+ // 3. Tag overlap: tools sharing 2+ tags (not in nextTools or already related), up to 2
2529
+ const myTags = new Set(entry.tags);
2530
+ let tagCount = 0;
2531
+ for (const other of REGISTRY_ENTRIES) {
2532
+ if (tagCount >= 2)
2533
+ break;
2534
+ if (other.name === entry.name || nextToolsSet.has(other.name) || related.has(other.name))
2535
+ continue;
2536
+ let overlap = 0;
2537
+ for (const t of other.tags) {
2538
+ if (myTags.has(t))
2539
+ overlap++;
2540
+ if (overlap >= 2)
2541
+ break;
2542
+ }
2543
+ if (overlap >= 2) {
2544
+ related.add(other.name);
2545
+ tagCount++;
2546
+ }
2547
+ }
2548
+ // 4. Fallback: if still empty (small category, all siblings in nextTools), accept 1-tag overlap
2549
+ if (related.size === 0) {
2550
+ for (const other of REGISTRY_ENTRIES) {
2551
+ if (related.size >= 3)
2552
+ break;
2553
+ if (other.name === entry.name || nextToolsSet.has(other.name))
2554
+ continue;
2555
+ const hasTagOverlap = other.tags.some((t) => myTags.has(t));
2556
+ if (hasTagOverlap) {
2557
+ related.add(other.name);
2558
+ }
2559
+ }
2560
+ }
2561
+ // 5. Last resort: if STILL empty, pick tools from the same phase (workflow adjacency)
2562
+ if (related.size === 0) {
2563
+ for (const other of REGISTRY_ENTRIES) {
2564
+ if (related.size >= 3)
2565
+ break;
2566
+ if (other.name === entry.name || nextToolsSet.has(other.name))
2567
+ continue;
2568
+ if (other.phase === entry.phase) {
2569
+ related.add(other.name);
2570
+ }
2571
+ }
2572
+ }
2573
+ return [...related].slice(0, 7); // hard cap at 7
2574
+ }
2575
+ /** Populate relatedTools for all registry entries. Called once at module load after DOMAIN_CLUSTERS exists. */
2576
+ export function _populateRelatedTools() {
2577
+ for (const entry of REGISTRY_ENTRIES) {
2578
+ if (!entry.quickRef.relatedTools || entry.quickRef.relatedTools.length === 0) {
2579
+ entry.quickRef.relatedTools = computeRelatedTools(entry);
2580
+ }
2581
+ }
2582
+ }
2342
2583
  /** Get quick ref for a tool, with fallback for unregistered tools */
2343
2584
  export function getQuickRef(toolName) {
2344
2585
  return TOOL_REGISTRY.get(toolName)?.quickRef ?? null;
@@ -2390,6 +2631,7 @@ const CATEGORY_COMPLEXITY = {
2390
2631
  email: "medium",
2391
2632
  rss: "low",
2392
2633
  architect: "low",
2634
+ pr_report: "medium",
2393
2635
  };
2394
2636
  /** Per-tool complexity overrides (when category default is wrong) */
2395
2637
  const TOOL_COMPLEXITY_OVERRIDES = {
@@ -2668,6 +2910,9 @@ const DOMAIN_CLUSTERS = {
2668
2910
  writing: ["research_writing", "documentation"],
2669
2911
  measurement: ["eval", "benchmark", "self_eval"],
2670
2912
  };
2913
+ // Wire up domain clusters and auto-populate relatedTools for all registry entries
2914
+ _setDomainClustersRef(DOMAIN_CLUSTERS);
2915
+ _populateRelatedTools();
2671
2916
  // ── Execution trace edges — co-occurrence mining from tool_call_log ────────
2672
2917
  // Based on Agent-as-a-Graph (arxiv:2511.18194): execution trace edges
2673
2918
  // mine sequential co-occurrence patterns to discover implicit tool relationships.
@@ -2708,17 +2953,36 @@ export function _setDbAccessor(accessor) {
2708
2953
  *
2709
2954
  * Approach: for each session, pull the ordered tool sequence, then count
2710
2955
  * pairs within a sliding window of 5 calls. O(n) per session, no self-join.
2956
+ *
2957
+ * When transitive=true, infer A→C via A→B + B→C (two-hop co-occurrence).
2958
+ * Extended cap of 15 edges/tool (vs 10 for direct-only).
2711
2959
  */
2712
- function getCooccurrenceEdges() {
2960
+ let _transitiveCooccurrenceCache = null;
2961
+ let _transitiveCooccurrenceCacheTime = 0;
2962
+ function getCooccurrenceEdges(options) {
2963
+ const transitive = options?.transitive ?? false;
2713
2964
  const now = Date.now();
2714
- if (_cooccurrenceCache && now - _cooccurrenceCacheTime < COOCCURRENCE_TTL_MS) {
2715
- return _cooccurrenceCache;
2965
+ // Check appropriate cache
2966
+ if (transitive) {
2967
+ if (_transitiveCooccurrenceCache && now - _transitiveCooccurrenceCacheTime < COOCCURRENCE_TTL_MS) {
2968
+ return _transitiveCooccurrenceCache;
2969
+ }
2970
+ }
2971
+ else {
2972
+ if (_cooccurrenceCache && now - _cooccurrenceCacheTime < COOCCURRENCE_TTL_MS) {
2973
+ return _cooccurrenceCache;
2974
+ }
2716
2975
  }
2717
- const edges = new Map();
2976
+ // Build direct edges first (always needed)
2977
+ const directEdges = new Map();
2718
2978
  if (!_dbAccessor) {
2719
- _cooccurrenceCache = edges;
2979
+ _cooccurrenceCache = directEdges;
2720
2980
  _cooccurrenceCacheTime = now;
2721
- return edges;
2981
+ if (transitive) {
2982
+ _transitiveCooccurrenceCache = directEdges;
2983
+ _transitiveCooccurrenceCacheTime = now;
2984
+ }
2985
+ return directEdges;
2722
2986
  }
2723
2987
  try {
2724
2988
  const db = _dbAccessor();
@@ -2759,24 +3023,51 @@ function getCooccurrenceEdges() {
2759
3023
  .sort((a, b) => b[1] - a[1]);
2760
3024
  for (const [key] of sorted) {
2761
3025
  const [toolA, toolB] = key.split("\0");
2762
- const list = edges.get(toolA) ?? [];
3026
+ const list = directEdges.get(toolA) ?? [];
2763
3027
  if (list.length < 10) {
2764
3028
  list.push(toolB);
2765
- edges.set(toolA, list);
3029
+ directEdges.set(toolA, list);
2766
3030
  }
2767
3031
  }
2768
3032
  }
2769
3033
  catch {
2770
3034
  // No DB or table not yet created — return empty (graceful degradation)
2771
3035
  }
2772
- _cooccurrenceCache = edges;
3036
+ // Cache direct edges
3037
+ _cooccurrenceCache = directEdges;
2773
3038
  _cooccurrenceCacheTime = now;
2774
- return edges;
3039
+ if (!transitive)
3040
+ return directEdges;
3041
+ // Transitive inference: A→B and B→C ⟹ A→C (two-hop)
3042
+ const transitiveEdges = new Map([...directEdges.entries()].map(([k, v]) => [k, [...v]]));
3043
+ for (const [toolA, directNeighbors] of directEdges) {
3044
+ const existingSet = new Set(directNeighbors);
3045
+ existingSet.add(toolA); // avoid self-loops
3046
+ for (const toolB of directNeighbors) {
3047
+ const bNeighbors = directEdges.get(toolB);
3048
+ if (!bNeighbors)
3049
+ continue;
3050
+ const list = transitiveEdges.get(toolA);
3051
+ for (const toolC of bNeighbors) {
3052
+ if (existingSet.has(toolC))
3053
+ continue;
3054
+ if (list.length >= 15)
3055
+ break; // extended cap for transitive
3056
+ list.push(toolC);
3057
+ existingSet.add(toolC);
3058
+ }
3059
+ }
3060
+ }
3061
+ _transitiveCooccurrenceCache = transitiveEdges;
3062
+ _transitiveCooccurrenceCacheTime = now;
3063
+ return transitiveEdges;
2775
3064
  }
2776
3065
  /** Reset co-occurrence cache — for testing only. */
2777
3066
  export function _resetCooccurrenceCache() {
2778
3067
  _cooccurrenceCache = null;
2779
3068
  _cooccurrenceCacheTime = 0;
3069
+ _transitiveCooccurrenceCache = null;
3070
+ _transitiveCooccurrenceCacheTime = 0;
2780
3071
  }
2781
3072
  /** Inject co-occurrence edges directly — for testing only. */
2782
3073
  export function _setCooccurrenceForTesting(edges) {
@@ -3168,7 +3459,8 @@ export function hybridSearch(query, tools, options) {
3168
3459
  });
3169
3460
  }
3170
3461
  results.sort((a, b) => b.score - a.score);
3171
- return results.slice(0, limit);
3462
+ const offset = options?.offset ?? 0;
3463
+ return results.slice(offset, offset + limit);
3172
3464
  }
3173
3465
  /** Available search modes for discover_tools */
3174
3466
  export const SEARCH_MODES = ["hybrid", "fuzzy", "regex", "prefix", "semantic", "exact", "dense", "embedding"];
@@ -3532,5 +3824,17 @@ export const WORKFLOW_CHAINS = {
3532
3824
  { tool: "save_session_note", action: "Log sent emails so you have an audit trail that survives compaction" },
3533
3825
  ],
3534
3826
  },
3827
+ pr_creation: {
3828
+ name: "Visual PR Creation",
3829
+ description: "Create a PR with visual evidence from a UI Dive session — screenshots, timeline, bug fixes, past session links",
3830
+ steps: [
3831
+ { tool: "get_dive_report", action: "Review the dive findings and health score before creating PR" },
3832
+ { tool: "export_pr_screenshots", action: "Export before/after screenshot pairs to a directory for committing" },
3833
+ { tool: "generate_pr_report", action: "Generate rich markdown PR body with visual evidence, timeline, and past session links" },
3834
+ { tool: "create_visual_pr", action: "End-to-end PR creation: exports assets, generates markdown, pushes branch, creates GitHub PR" },
3835
+ { tool: "review_pr_checklist", action: "Validate the PR against the checklist (title, description, tests, verification)" },
3836
+ { tool: "enforce_merge_gate", action: "Pre-merge validation — git state, quality gates, verification cycles" },
3837
+ ],
3838
+ },
3535
3839
  };
3536
3840
  //# sourceMappingURL=toolRegistry.js.map