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.
- package/README.md +366 -280
- package/dist/__tests__/multiHopDogfood.test.d.ts +12 -0
- package/dist/__tests__/multiHopDogfood.test.js +303 -0
- package/dist/__tests__/multiHopDogfood.test.js.map +1 -0
- package/dist/__tests__/presetRealWorldBench.test.js +2 -0
- package/dist/__tests__/presetRealWorldBench.test.js.map +1 -1
- package/dist/__tests__/tools.test.js +158 -6
- package/dist/__tests__/tools.test.js.map +1 -1
- package/dist/__tests__/toolsetGatingEval.test.js +2 -0
- package/dist/__tests__/toolsetGatingEval.test.js.map +1 -1
- package/dist/dashboard/html.d.ts +18 -0
- package/dist/dashboard/html.js +1251 -0
- package/dist/dashboard/html.js.map +1 -0
- package/dist/dashboard/server.d.ts +17 -0
- package/dist/dashboard/server.js +278 -0
- package/dist/dashboard/server.js.map +1 -0
- package/dist/db.js +38 -0
- package/dist/db.js.map +1 -1
- package/dist/index.js +19 -9
- package/dist/index.js.map +1 -1
- package/dist/tools/prReportTools.d.ts +11 -0
- package/dist/tools/prReportTools.js +911 -0
- package/dist/tools/prReportTools.js.map +1 -0
- package/dist/tools/progressiveDiscoveryTools.js +111 -24
- package/dist/tools/progressiveDiscoveryTools.js.map +1 -1
- package/dist/tools/skillUpdateTools.d.ts +24 -0
- package/dist/tools/skillUpdateTools.js +469 -0
- package/dist/tools/skillUpdateTools.js.map +1 -0
- package/dist/tools/toolRegistry.d.ts +15 -1
- package/dist/tools/toolRegistry.js +315 -11
- package/dist/tools/toolRegistry.js.map +1 -1
- package/dist/tools/uiUxDiveAdvancedTools.js +61 -0
- package/dist/tools/uiUxDiveAdvancedTools.js.map +1 -1
- package/dist/tools/uiUxDiveTools.js +154 -1
- package/dist/tools/uiUxDiveTools.js.map +1 -1
- package/dist/toolsetRegistry.js +4 -0
- package/dist/toolsetRegistry.js.map +1 -1
- 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
|
-
|
|
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
|
-
|
|
2715
|
-
|
|
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
|
-
|
|
2976
|
+
// Build direct edges first (always needed)
|
|
2977
|
+
const directEdges = new Map();
|
|
2718
2978
|
if (!_dbAccessor) {
|
|
2719
|
-
_cooccurrenceCache =
|
|
2979
|
+
_cooccurrenceCache = directEdges;
|
|
2720
2980
|
_cooccurrenceCacheTime = now;
|
|
2721
|
-
|
|
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 =
|
|
3026
|
+
const list = directEdges.get(toolA) ?? [];
|
|
2763
3027
|
if (list.length < 10) {
|
|
2764
3028
|
list.push(toolB);
|
|
2765
|
-
|
|
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
|
-
|
|
3036
|
+
// Cache direct edges
|
|
3037
|
+
_cooccurrenceCache = directEdges;
|
|
2773
3038
|
_cooccurrenceCacheTime = now;
|
|
2774
|
-
|
|
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
|
-
|
|
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
|