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.
- package/README.md +366 -280
- package/dist/__tests__/dynamicLoading.test.js +4 -2
- package/dist/__tests__/dynamicLoading.test.js.map +1 -1
- 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 +108 -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 +379 -11
- package/dist/tools/toolRegistry.js.map +1 -1
- package/dist/tools/uiUxDiveAdvancedTools.js +688 -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,
|
|
@@ -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
|
-
|
|
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
|
-
|
|
2651
|
-
|
|
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
|
-
|
|
2976
|
+
// Build direct edges first (always needed)
|
|
2977
|
+
const directEdges = new Map();
|
|
2654
2978
|
if (!_dbAccessor) {
|
|
2655
|
-
_cooccurrenceCache =
|
|
2979
|
+
_cooccurrenceCache = directEdges;
|
|
2656
2980
|
_cooccurrenceCacheTime = now;
|
|
2657
|
-
|
|
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 =
|
|
3026
|
+
const list = directEdges.get(toolA) ?? [];
|
|
2699
3027
|
if (list.length < 10) {
|
|
2700
3028
|
list.push(toolB);
|
|
2701
|
-
|
|
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
|
-
|
|
3036
|
+
// Cache direct edges
|
|
3037
|
+
_cooccurrenceCache = directEdges;
|
|
2709
3038
|
_cooccurrenceCacheTime = now;
|
|
2710
|
-
|
|
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
|
-
|
|
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
|