import type { PluginInfo } from "./types.js"; import { type GitHubRepo, type RegistryPlugin } from "./registry-catalog.js"; /** * Decide which tracker a feedback report belongs in. * * ue-mcp is a core server plus a growing set of npm-distributed plugins, each * with its own repo. An agent that hits a wall in a plugin-owned surface (PIE * record/replay, Perforce, Meshy, Voxel) has no idea the surface is not core, * so every report lands on the core tracker and has to be re-filed by hand. * * This module reads the published registry (plugins.ue-mcp.com) plus the set * of plugins loaded into THIS project and works out whether a better home * exists. It never posts anything and never throws: the worst case is * "target: core", which is exactly what happened before it existed. * * Precedence, strongest first: * * 1. explicitRepo - the caller named a repo; honored if the registry * knows it (or it is core). Unknown repos are * refused, so the agent cannot aim a submission at * an arbitrary GitHub project. * 2. installed ownership - `idealTool` names a category or action that a * locally loaded plugin actually provides. This is * a fact, not a guess: confidence "certain". * 3. core anchor - the text names a real built-in category or action * (`editor(play_in_editor)`). Core owns it; any * keyword hit is demoted to a suggestion. * 4. keyword scoring - registry slug / name / tag terms weighed by where * they appear (call syntax > idealTool > title > * summary). */ export type RouteConfidence = "certain" | "likely" | "possible"; export interface RouteCandidate { slug: string; name: string; packageName?: string; /** Where issues for this plugin go. null when the registry has no repo. */ repo: GitHubRepo | null; repoUrl?: string; repoPrivate: boolean; /** True when this plugin is loaded in the current project. */ installed: boolean; score: number; confidence: RouteConfidence; reasons: string[]; } export interface RoutingDecision { /** Where the report will actually be filed. */ target: "core" | "plugin"; repo: GitHubRepo; /** The winning plugin, when target is "plugin". */ candidate: RouteCandidate | null; /** Plugins worth offering the user even though core is the default. */ suggestions: RouteCandidate[]; /** The built-in category/action the text named, if any. */ coreAnchor: string | null; /** False when the registry was unreachable and no cache existed. */ catalogAvailable: boolean; /** Why the decision landed where it did, in one line, when non-obvious. */ note?: string; } export interface RoutingInput { title: string; summary: string; idealTool?: string; /** Plugins loaded into the current project (ctx.getPlugins()). */ installed?: PluginInfo[]; /** `owner/name` override from the caller. */ explicitRepo?: string; /** Injected catalog. Tests pass this; production leaves it undefined. */ catalog?: RegistryPlugin[]; timeoutMs?: number; } /** Test seam: forget the memoised built-in surface. */ export declare function clearCoreSurfaceCache(): void; /** `blueprint(action=foo)` / `blueprint.foo` -> "blueprint". */ export declare function parseToolCategory(idealTool: string | undefined): string | null; export declare function routeFeedback(input: RoutingInput): Promise;