feat(FN-3513): add eval domain store, plugin dashboard view registry, and G

This merge adds three major features: an eval domain (`eval-store.ts`, `eval-types.ts`) with persistence schema for evaluation data; a plugin dashboard view registry with navigation integration for third-party dashboard extensions; and GitHub source metadata traceability that locks and enforces issu

Fusion-Task-Id: FN-3513
This commit is contained in:
Fusion
2026-05-05 12:20:28 -07:00
committed by gsxdsm
parent d7880c66b0
commit 30f6381ec0
11 changed files with 125 additions and 61 deletions

View File

@@ -2505,6 +2505,19 @@ describe("buildExecutionPrompt", () => {
expect(result).toContain('git commit -m "feat(FN-001): complete Step N — description" -m "Ref: runfusion/fusion#2915"');
});
it("falls back to externalIssueId for commit source issue reference when issueNumber is missing", () => {
const task = createMockTaskDetail({
sourceIssue: {
provider: "github",
repository: "runfusion/fusion",
externalIssueId: "2915",
},
} as any);
const result = buildExecutionPrompt(task, "/home/user/project");
expect(result).toContain('git commit -m "feat(FN-001): complete Step N — description" -m "Ref: runfusion/fusion#2915"');
});
it("omits source issue reference from commit instruction when sourceIssue is missing", () => {
const task = createMockTaskDetail();
const result = buildExecutionPrompt(task, "/home/user/project");

View File

@@ -5582,6 +5582,15 @@ describe("buildSourceIssueRef", () => {
})).toBe("runfusion/fusion#123");
});
it("falls back to externalIssueId when issueNumber is missing", async () => {
const { buildSourceIssueRef } = await import("../merger.js");
expect(buildSourceIssueRef({
provider: "github",
repository: "runfusion/fusion",
externalIssueId: "321",
} as any)).toBe("runfusion/fusion#321");
});
it("returns empty string for non-GitHub providers", async () => {
const { buildSourceIssueRef } = await import("../merger.js");
expect(buildSourceIssueRef({
@@ -5596,6 +5605,11 @@ describe("buildSourceIssueRef", () => {
const { buildSourceIssueRef } = await import("../merger.js");
expect(buildSourceIssueRef(undefined)).toBe("");
expect(buildSourceIssueRef(null)).toBe("");
expect(buildSourceIssueRef({
provider: "github",
repository: "runfusion/fusion",
externalIssueId: "not-a-number",
} as any)).toBe("");
});
});

View File

@@ -6890,6 +6890,21 @@ function scopePromptToWorktree(prompt: string, rootDir?: string, worktreePath?:
.replaceAll(`${worktreePath}/.fusion/`, `${rootDir}/.fusion/`);
}
function buildSourceIssueRef(sourceIssue: TaskDetail["sourceIssue"]): string {
if (!sourceIssue || sourceIssue.provider !== "github" || !sourceIssue.repository) {
return "";
}
const issueNumber = sourceIssue.issueNumber
?? Number.parseInt(sourceIssue.externalIssueId ?? "", 10);
if (!Number.isInteger(issueNumber) || issueNumber < 1) {
return "";
}
return `${sourceIssue.repository}#${issueNumber}`;
}
export function buildExecutionPrompt(task: TaskDetail, rootDir?: string, settings?: Settings, worktreePath?: string): string {
const prompt = scopePromptToWorktree(task.prompt, rootDir, worktreePath);
const reviewMatch = prompt.match(/##\s*Review Level[:\s]*(\d)/);
@@ -6900,9 +6915,7 @@ export function buildExecutionPrompt(task: TaskDetail, rootDir?: string, setting
? ` --author="${settings?.commitAuthorName || "Fusion"} <${settings?.commitAuthorEmail || "noreply@runfusion.ai"}>"`
: "";
const sourceIssueRef = task.sourceIssue?.provider === "github" && task.sourceIssue.repository && task.sourceIssue.issueNumber
? `${task.sourceIssue.repository}#${task.sourceIssue.issueNumber}`
: "";
const sourceIssueRef = buildSourceIssueRef(task.sourceIssue);
// Build step progress for resume
const hasProgress = task.steps.length > 0 && task.steps.some((s) => s.status !== "pending");

View File

@@ -1770,9 +1770,13 @@ function getCommitAuthorArg(settings: {
}
export function buildSourceIssueRef(sourceIssue?: TaskSourceIssue | null): string {
if (!sourceIssue || sourceIssue.provider !== "github") return "";
if (!sourceIssue.repository || !sourceIssue.issueNumber) return "";
return `${sourceIssue.repository}#${sourceIssue.issueNumber}`;
if (!sourceIssue || sourceIssue.provider !== "github" || !sourceIssue.repository) return "";
const issueNumber = sourceIssue.issueNumber
?? Number.parseInt(sourceIssue.externalIssueId ?? "", 10);
if (!Number.isInteger(issueNumber) || issueNumber < 1) return "";
return `${sourceIssue.repository}#${issueNumber}`;
}
/**