feat(FN-1114): merge fusion/fn-1114 (auto-resolved)

- feat(FN-1114): complete all steps — plugin examples, docs, and scaffold command
This commit is contained in:
gsxdsm
2026-04-09 19:04:12 -07:00
parent 26a2f3ff6b
commit 749babf522
24 changed files with 3078 additions and 1 deletions

View File

@@ -0,0 +1,139 @@
import { definePlugin } from "@fusion/plugin-sdk";
import type {
FusionPlugin,
PluginContext,
PluginToolDefinition,
PluginToolResult,
} from "@fusion/plugin-sdk";
// ── Category Rules ─────────────────────────────────────────────────────────────
interface CategoryRule {
keywords: string[];
label: string;
}
const CATEGORY_RULES: CategoryRule[] = [
{
keywords: ["bug", "fix", "broken", "crash", "error"],
label: "bug",
},
{
keywords: ["feature", "add", "new", "implement"],
label: "feature",
},
{
keywords: ["docs", "documentation", "readme", "guide"],
label: "documentation",
},
{
keywords: ["test", "testing", "spec", "coverage"],
label: "testing",
},
{
keywords: ["refactor", "cleanup", "clean up", "reorganize"],
label: "refactor",
},
{
keywords: ["perf", "performance", "optimize", "slow"],
label: "performance",
},
];
// ── Text Classification ────────────────────────────────────────────────────────
/**
* Classify text into categories based on keyword matching.
* Returns an array of matching category labels.
*/
export function classifyText(text: string): string[] {
const lowerText = text.toLowerCase();
const matchedLabels: string[] = [];
for (const rule of CATEGORY_RULES) {
for (const keyword of rule.keywords) {
// Match whole word boundaries using word characters
const regex = new RegExp(`\\b${keyword}\\b`, "i");
if (regex.test(lowerText)) {
if (!matchedLabels.includes(rule.label)) {
matchedLabels.push(rule.label);
}
break; // Move to next rule once keyword matches
}
}
}
return matchedLabels;
}
// ── Plugin Tool ─────────────────────────────────────────────────────────────────
const autoLabelTool: PluginToolDefinition = {
name: "auto_label_classify",
description:
"Classify a text description into categories (bug, feature, documentation, testing, refactor, performance). Returns an array of matching labels.",
parameters: {
type: "object",
properties: {
text: {
type: "string",
description: "The text to classify",
},
},
required: ["text"],
},
execute: async (
params: Record<string, unknown>,
_ctx: PluginContext,
): Promise<PluginToolResult> => {
const text = params.text as string;
if (!text || typeof text !== "string") {
return {
content: [{ type: "text", text: JSON.stringify([]) }],
isError: false,
};
}
const labels = classifyText(text);
return {
content: [{ type: "text", text: JSON.stringify(labels) }],
};
},
};
// ── Plugin Definition ───────────────────────────────────────────────────────────
const plugin: FusionPlugin = definePlugin({
manifest: {
id: "fusion-plugin-auto-label",
name: "Auto-Label Plugin",
version: "0.1.0",
description: "Automatically labels tasks based on description content",
},
state: "installed",
tools: [autoLabelTool],
hooks: {
onLoad: (ctx) => {
ctx.logger.info(
`Auto-Label plugin loaded with ${CATEGORY_RULES.length} category rules`,
);
},
onTaskCreated: (task, ctx) => {
const labels = classifyText(task.description || "");
if (labels.length > 0) {
ctx.logger.info(
`Task ${task.id} classified with labels: ${labels.join(", ")}`,
);
ctx.emitEvent("auto-label:classified", {
taskId: task.id,
labels,
});
} else {
ctx.logger.info(`Task ${task.id} did not match any categories`);
}
},
},
});
export default plugin;