nodebench-mcp 2.21.1 → 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 (40) hide show
  1. package/README.md +366 -280
  2. package/dist/__tests__/dynamicLoading.test.js +4 -2
  3. package/dist/__tests__/dynamicLoading.test.js.map +1 -1
  4. package/dist/__tests__/multiHopDogfood.test.d.ts +12 -0
  5. package/dist/__tests__/multiHopDogfood.test.js +303 -0
  6. package/dist/__tests__/multiHopDogfood.test.js.map +1 -0
  7. package/dist/__tests__/presetRealWorldBench.test.js +2 -0
  8. package/dist/__tests__/presetRealWorldBench.test.js.map +1 -1
  9. package/dist/__tests__/tools.test.js +158 -6
  10. package/dist/__tests__/tools.test.js.map +1 -1
  11. package/dist/__tests__/toolsetGatingEval.test.js +2 -0
  12. package/dist/__tests__/toolsetGatingEval.test.js.map +1 -1
  13. package/dist/dashboard/html.d.ts +18 -0
  14. package/dist/dashboard/html.js +1251 -0
  15. package/dist/dashboard/html.js.map +1 -0
  16. package/dist/dashboard/server.d.ts +17 -0
  17. package/dist/dashboard/server.js +278 -0
  18. package/dist/dashboard/server.js.map +1 -0
  19. package/dist/db.js +108 -0
  20. package/dist/db.js.map +1 -1
  21. package/dist/index.js +19 -9
  22. package/dist/index.js.map +1 -1
  23. package/dist/tools/prReportTools.d.ts +11 -0
  24. package/dist/tools/prReportTools.js +911 -0
  25. package/dist/tools/prReportTools.js.map +1 -0
  26. package/dist/tools/progressiveDiscoveryTools.js +111 -24
  27. package/dist/tools/progressiveDiscoveryTools.js.map +1 -1
  28. package/dist/tools/skillUpdateTools.d.ts +24 -0
  29. package/dist/tools/skillUpdateTools.js +469 -0
  30. package/dist/tools/skillUpdateTools.js.map +1 -0
  31. package/dist/tools/toolRegistry.d.ts +15 -1
  32. package/dist/tools/toolRegistry.js +379 -11
  33. package/dist/tools/toolRegistry.js.map +1 -1
  34. package/dist/tools/uiUxDiveAdvancedTools.js +688 -0
  35. package/dist/tools/uiUxDiveAdvancedTools.js.map +1 -1
  36. package/dist/tools/uiUxDiveTools.js +154 -1
  37. package/dist/tools/uiUxDiveTools.js.map +1 -1
  38. package/dist/toolsetRegistry.js +4 -0
  39. package/dist/toolsetRegistry.js.map +1 -1
  40. 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,
@@ -2172,6 +2184,156 @@ const REGISTRY_ENTRIES = [
2172
2184
  phase: "ship",
2173
2185
  },
2174
2186
  // ═══════════════════════════════════════════
2187
+ // UI/UX DIVE v3 — Flywheel: Bug→Code→Fix→
2188
+ // Verify→Reexplore→Test→Review
2189
+ // ═══════════════════════════════════════════
2190
+ {
2191
+ name: "dive_code_locate",
2192
+ category: "ui_ux_dive_v2",
2193
+ tags: ["ui", "code", "locate", "grep", "ripgrep", "bug", "source", "file", "line", "root-cause", "traceability", "dive", "flywheel"],
2194
+ quickRef: {
2195
+ nextAction: "Code located. Review the snippets, fix the code, then verify with dive_fix_verify.",
2196
+ nextTools: ["dive_fix_verify", "dive_generate_tests"],
2197
+ methodology: "agentic_vision",
2198
+ tip: "Pass multiple searchQueries — tries each in order. Links bug→file:line for full traceability. Uses ripgrep with fallback to findstr.",
2199
+ },
2200
+ phase: "research",
2201
+ },
2202
+ {
2203
+ name: "dive_fix_verify",
2204
+ category: "ui_ux_dive_v2",
2205
+ tags: ["ui", "fix", "verify", "flywheel", "bug", "resolve", "changelog", "before-after", "screenshot", "regression", "dive"],
2206
+ quickRef: {
2207
+ nextAction: "Fix verified. Re-explore the route to check for regressions, then generate a regression test.",
2208
+ nextTools: ["dive_reexplore", "dive_generate_tests", "dive_code_review"],
2209
+ methodology: "agentic_vision",
2210
+ tip: "Core flywheel step. Auto-creates changelog, updates bug status. Set verified:true only after visually confirming the fix via Playwright.",
2211
+ },
2212
+ phase: "test",
2213
+ },
2214
+ {
2215
+ name: "dive_reexplore",
2216
+ category: "ui_ux_dive_v2",
2217
+ tags: ["ui", "reexplore", "regression", "diff", "route", "verify", "components", "flywheel", "dive"],
2218
+ quickRef: {
2219
+ nextAction: "Route diffed. If regression-free, generate tests. If regressions found, fix and re-verify.",
2220
+ nextTools: ["dive_generate_tests", "tag_ui_bug", "dive_fix_verify"],
2221
+ methodology: "agentic_vision",
2222
+ tip: "Navigate to the route via Playwright first, then pass what you observe. Diffs against previously registered components and flags missing ones.",
2223
+ },
2224
+ phase: "test",
2225
+ },
2226
+ {
2227
+ name: "dive_generate_tests",
2228
+ category: "ui_ux_dive_v2",
2229
+ tags: ["ui", "test", "generate", "playwright", "regression", "bug", "interaction", "code", "ci", "flywheel", "dive"],
2230
+ quickRef: {
2231
+ nextAction: "Tests generated. Save to a file and run with 'npx playwright test'. Add to CI for ongoing protection.",
2232
+ nextTools: ["dive_code_review", "dive_reexplore"],
2233
+ methodology: "agentic_vision",
2234
+ tip: "Generates Playwright test code from bugs, interaction tests, and design issues. Use outputPath to save directly to a .spec.ts file.",
2235
+ },
2236
+ phase: "ship",
2237
+ },
2238
+ {
2239
+ name: "dive_code_review",
2240
+ category: "ui_ux_dive_v2",
2241
+ tags: ["ui", "code-review", "review", "quality", "score", "grade", "findings", "recommendations", "coderabbit", "augment", "pr", "github", "flywheel", "dive"],
2242
+ quickRef: {
2243
+ nextAction: "Review complete. Address critical/high findings first. Use github_comments format to post to PRs.",
2244
+ nextTools: ["dive_code_locate", "dive_fix_verify", "dive_generate_tests"],
2245
+ methodology: "agentic_vision",
2246
+ tip: "Like CodeRabbit/Augment but from live UI exploration. Produces score, grade, prioritized findings with file:line, and PR-ready comments.",
2247
+ },
2248
+ phase: "ship",
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)
2336
+ // ═══════════════════════════════════════════
2175
2337
  // MCP BRIDGE — Connect external MCP servers
2176
2338
  // ═══════════════════════════════════════════
2177
2339
  {
@@ -2269,12 +2431,155 @@ const REGISTRY_ENTRIES = [
2269
2431
  },
2270
2432
  phase: "implement",
2271
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
+ },
2272
2473
  ];
2273
2474
  // ── Exported lookup structures ───────────────────────────────────────────
2274
2475
  /** Map of tool name → registry entry for O(1) lookup */
2275
2476
  export const TOOL_REGISTRY = new Map(REGISTRY_ENTRIES.map((e) => [e.name, e]));
2276
2477
  /** All registry entries as array */
2277
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
+ }
2278
2583
  /** Get quick ref for a tool, with fallback for unregistered tools */
2279
2584
  export function getQuickRef(toolName) {
2280
2585
  return TOOL_REGISTRY.get(toolName)?.quickRef ?? null;
@@ -2326,6 +2631,7 @@ const CATEGORY_COMPLEXITY = {
2326
2631
  email: "medium",
2327
2632
  rss: "low",
2328
2633
  architect: "low",
2634
+ pr_report: "medium",
2329
2635
  };
2330
2636
  /** Per-tool complexity overrides (when category default is wrong) */
2331
2637
  const TOOL_COMPLEXITY_OVERRIDES = {
@@ -2604,6 +2910,9 @@ const DOMAIN_CLUSTERS = {
2604
2910
  writing: ["research_writing", "documentation"],
2605
2911
  measurement: ["eval", "benchmark", "self_eval"],
2606
2912
  };
2913
+ // Wire up domain clusters and auto-populate relatedTools for all registry entries
2914
+ _setDomainClustersRef(DOMAIN_CLUSTERS);
2915
+ _populateRelatedTools();
2607
2916
  // ── Execution trace edges — co-occurrence mining from tool_call_log ────────
2608
2917
  // Based on Agent-as-a-Graph (arxiv:2511.18194): execution trace edges
2609
2918
  // mine sequential co-occurrence patterns to discover implicit tool relationships.
@@ -2644,17 +2953,36 @@ export function _setDbAccessor(accessor) {
2644
2953
  *
2645
2954
  * Approach: for each session, pull the ordered tool sequence, then count
2646
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).
2647
2959
  */
2648
- function getCooccurrenceEdges() {
2960
+ let _transitiveCooccurrenceCache = null;
2961
+ let _transitiveCooccurrenceCacheTime = 0;
2962
+ function getCooccurrenceEdges(options) {
2963
+ const transitive = options?.transitive ?? false;
2649
2964
  const now = Date.now();
2650
- if (_cooccurrenceCache && now - _cooccurrenceCacheTime < COOCCURRENCE_TTL_MS) {
2651
- 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
+ }
2652
2975
  }
2653
- const edges = new Map();
2976
+ // Build direct edges first (always needed)
2977
+ const directEdges = new Map();
2654
2978
  if (!_dbAccessor) {
2655
- _cooccurrenceCache = edges;
2979
+ _cooccurrenceCache = directEdges;
2656
2980
  _cooccurrenceCacheTime = now;
2657
- return edges;
2981
+ if (transitive) {
2982
+ _transitiveCooccurrenceCache = directEdges;
2983
+ _transitiveCooccurrenceCacheTime = now;
2984
+ }
2985
+ return directEdges;
2658
2986
  }
2659
2987
  try {
2660
2988
  const db = _dbAccessor();
@@ -2695,24 +3023,51 @@ function getCooccurrenceEdges() {
2695
3023
  .sort((a, b) => b[1] - a[1]);
2696
3024
  for (const [key] of sorted) {
2697
3025
  const [toolA, toolB] = key.split("\0");
2698
- const list = edges.get(toolA) ?? [];
3026
+ const list = directEdges.get(toolA) ?? [];
2699
3027
  if (list.length < 10) {
2700
3028
  list.push(toolB);
2701
- edges.set(toolA, list);
3029
+ directEdges.set(toolA, list);
2702
3030
  }
2703
3031
  }
2704
3032
  }
2705
3033
  catch {
2706
3034
  // No DB or table not yet created — return empty (graceful degradation)
2707
3035
  }
2708
- _cooccurrenceCache = edges;
3036
+ // Cache direct edges
3037
+ _cooccurrenceCache = directEdges;
2709
3038
  _cooccurrenceCacheTime = now;
2710
- 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;
2711
3064
  }
2712
3065
  /** Reset co-occurrence cache — for testing only. */
2713
3066
  export function _resetCooccurrenceCache() {
2714
3067
  _cooccurrenceCache = null;
2715
3068
  _cooccurrenceCacheTime = 0;
3069
+ _transitiveCooccurrenceCache = null;
3070
+ _transitiveCooccurrenceCacheTime = 0;
2716
3071
  }
2717
3072
  /** Inject co-occurrence edges directly — for testing only. */
2718
3073
  export function _setCooccurrenceForTesting(edges) {
@@ -3104,7 +3459,8 @@ export function hybridSearch(query, tools, options) {
3104
3459
  });
3105
3460
  }
3106
3461
  results.sort((a, b) => b.score - a.score);
3107
- return results.slice(0, limit);
3462
+ const offset = options?.offset ?? 0;
3463
+ return results.slice(offset, offset + limit);
3108
3464
  }
3109
3465
  /** Available search modes for discover_tools */
3110
3466
  export const SEARCH_MODES = ["hybrid", "fuzzy", "regex", "prefix", "semantic", "exact", "dense", "embedding"];
@@ -3468,5 +3824,17 @@ export const WORKFLOW_CHAINS = {
3468
3824
  { tool: "save_session_note", action: "Log sent emails so you have an audit trail that survives compaction" },
3469
3825
  ],
3470
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
+ },
3471
3839
  };
3472
3840
  //# sourceMappingURL=toolRegistry.js.map