From ed21597690248be790a0b304c3e0b71ff404afcf Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Mon, 29 Jun 2026 19:17:37 -0700 Subject: [PATCH] FN-7251: suppress missing skill-pattern info logs Suppress non-actionable missing configured skill-pattern messages while preserving resolver diagnostics. - Filter configured skill-pattern miss diagnostics out of pi runtime logging paths. - Keep missing configured patterns available as resolver diagnostics for programmatic visibility. - Cover direct skill resolution and fn-agent creation logging with regression tests. - Add a patch changeset for the published Fusion CLI package. Files changed: .../fn-7251-suppress-missing-skill-pattern-logs.md | 7 +++ .../src/__tests__/pi-create-fn-agent.test.ts | 21 ++++++--- .../engine/src/__tests__/skill-resolver.test.ts | 50 ++++++++++++++++++---- packages/engine/src/pi.ts | 1 + packages/engine/src/skill-resolver.ts | 13 ++++++ 5 files changed, 78 insertions(+), 14 deletions(-) Fusion-Task-Id: FN-7251 Fusion-Task-Lineage: 3c61c349-f216-410b-9ead-24c310bb2415 Co-authored-by: Fusion (runfusion.ai) --- ...251-suppress-missing-skill-pattern-logs.md | 7 +++ .../src/__tests__/pi-create-fn-agent.test.ts | 21 ++++++-- .../src/__tests__/skill-resolver.test.ts | 50 +++++++++++++++---- packages/engine/src/pi.ts | 1 + packages/engine/src/skill-resolver.ts | 13 +++++ 5 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 .changeset/fn-7251-suppress-missing-skill-pattern-logs.md diff --git a/.changeset/fn-7251-suppress-missing-skill-pattern-logs.md b/.changeset/fn-7251-suppress-missing-skill-pattern-logs.md new file mode 100644 index 0000000000..40e2a20934 --- /dev/null +++ b/.changeset/fn-7251-suppress-missing-skill-pattern-logs.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Stop logging non-actionable missing configured skill-pattern info messages. +category: fix +dev: Missing configured skill patterns remain resolver diagnostics but are no longer emitted as runtime info logs. diff --git a/packages/engine/src/__tests__/pi-create-fn-agent.test.ts b/packages/engine/src/__tests__/pi-create-fn-agent.test.ts index 35b9a57208..934a32c024 100644 --- a/packages/engine/src/__tests__/pi-create-fn-agent.test.ts +++ b/packages/engine/src/__tests__/pi-create-fn-agent.test.ts @@ -647,7 +647,7 @@ describe("wrapToolsWithPermanentAgentGating", () => { expect(tool.execute).not.toHaveBeenCalled(); }); - it("allows exempt internal coordination fn_* tools without approval", async () => { + it("requires approval for governed internal task-mutation fn_* tools", async () => { const tool = { name: "fn_task_create", label: "Task Create", description: "", parameters: {}, execute: vi.fn().mockResolvedValue({ ok: true }) }; const createApprovalRequest = vi.fn().mockResolvedValue({ id: "apr-fn-1" }); const { wrapToolsWithPermanentAgentGating } = await import("../pi.js"); @@ -669,9 +669,19 @@ describe("wrapToolsWithPermanentAgentGating", () => { }); const result = await (wrapped[0] as any).execute("t1", { description: "create" }); - expect(result).toEqual({ ok: true }); - expect(createApprovalRequest).not.toHaveBeenCalled(); - expect(tool.execute).toHaveBeenCalledTimes(1); + expect((result as any).isError).toBe(true); + expect((result as any).details).toEqual(expect.objectContaining({ + approvalRequestId: "apr-fn-1", + category: "task_agent_mutation", + disposition: "require-approval", + requiresApproval: true, + toolName: "fn_task_create", + })); + expect(createApprovalRequest).toHaveBeenCalledWith(expect.objectContaining({ + category: "task_agent_mutation", + toolName: "fn_task_create", + })); + expect(tool.execute).not.toHaveBeenCalled(); }); it("keeps read-only tools allowed without approval-request creation", async () => { @@ -2404,13 +2414,14 @@ describe("createFnAgent", () => { const { createSkillsOverrideFromSelection } = await import("../skill-resolver.js"); const selection = { - allowedSkillPaths: new Set(["/path/nonexistent"]), + allowedSkillPaths: new Set(), excludedSkillPaths: new Set(), diagnostics: [], filterActive: true, }; const override = createSkillsOverrideFromSelection(selection, { + requestedSkillNames: ["nonexistent"], sessionPurpose: "executor", }); diff --git a/packages/engine/src/__tests__/skill-resolver.test.ts b/packages/engine/src/__tests__/skill-resolver.test.ts index 9e84a2e578..38eedf64be 100644 --- a/packages/engine/src/__tests__/skill-resolver.test.ts +++ b/packages/engine/src/__tests__/skill-resolver.test.ts @@ -730,15 +730,16 @@ describe("createSkillsOverrideFromSelection", () => { expect(result.diagnostics[0].message).toBe("base warning"); }); - it("logs diagnostics via structured logger", () => { + it("logs requested-skill diagnostics via structured logger", () => { const selection: SkillSelectionResult = { - allowedSkillPaths: new Set(["/path/nonexistent"]), + allowedSkillPaths: new Set(), excludedSkillPaths: new Set(), diagnostics: [], filterActive: true, }; const override = createSkillsOverrideFromSelection(selection, { + requestedSkillNames: ["nonexistent"], sessionPurpose: "executor", }); @@ -975,9 +976,12 @@ describe("createSkillsOverrideFromSelection", () => { ).toBe(false); }); - it("uses structured piLog.log for configured skill pattern diagnostics", () => { + it("keeps optional missing configured patterns diagnostic-only without the user-reported info log", () => { + const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {}); const selection: SkillSelectionResult = { - allowedSkillPaths: new Set(["/path/ghost"]), + allowedSkillPaths: new Set(["ce-optimize/SKILL.md", "lint/SKILL.md"]), excludedSkillPaths: new Set(), diagnostics: [], filterActive: true, @@ -987,11 +991,38 @@ describe("createSkillsOverrideFromSelection", () => { sessionPurpose: "executor", }); - override({ skills: [], diagnostics: [] }); + const result = override({ + skills: [ + { name: "lint", filePath: "/platform-neutral/skills/lint/SKILL.md", description: "", baseDir: "", sourceInfo: {} as any, disableModelInvocation: false }, + { name: "paperclip", filePath: "/platform-neutral/skills/paperclip/SKILL.md", description: "", baseDir: "", sourceInfo: {} as any, disableModelInvocation: false }, + ], + diagnostics: [], + }); + const userReportedLine = "Configured skill pattern 'ce-optimize/SKILL.md' not found in discovered skills [executor]"; - expect(mockPiLog.log).toHaveBeenCalledWith( - expect.stringContaining("[skills] info: Configured skill pattern '/path/ghost' not found in discovered skills [executor]"), - ); + expect(result.skills.map((skill) => skill.name)).toEqual(["lint"]); + expect(result.diagnostics).toContainEqual(expect.objectContaining({ + type: "info", + message: userReportedLine, + path: "ce-optimize/SKILL.md", + })); + expect(result.diagnostics.some((diagnostic) => diagnostic.message.includes("lint/SKILL.md") && diagnostic.message.includes("not found"))).toBe(false); + + const loggedMessages = [ + ...mockPiLog.log.mock.calls, + ...mockPiLog.warn.mock.calls, + ...mockPiLog.error.mock.calls, + ].map((call) => String(call[0])); + expect(loggedMessages.some((message) => message.includes(userReportedLine))).toBe(false); + expect(mockPiLog.warn).not.toHaveBeenCalled(); + expect(mockPiLog.error).not.toHaveBeenCalled(); + expect(consoleErrorSpy).not.toHaveBeenCalled(); + expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(consoleLogSpy).not.toHaveBeenCalled(); + + consoleErrorSpy.mockRestore(); + consoleWarnSpy.mockRestore(); + consoleLogSpy.mockRestore(); }); it("does not call console.error, console.warn, or console.log for diagnostics", () => { @@ -1000,13 +1031,14 @@ describe("createSkillsOverrideFromSelection", () => { const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {}); const selection: SkillSelectionResult = { - allowedSkillPaths: new Set(["/path/missing"]), + allowedSkillPaths: new Set(), excludedSkillPaths: new Set(), diagnostics: [], filterActive: true, }; const override = createSkillsOverrideFromSelection(selection, { + requestedSkillNames: ["missing"], sessionPurpose: "executor", }); diff --git a/packages/engine/src/pi.ts b/packages/engine/src/pi.ts index bead351727..570d583f56 100644 --- a/packages/engine/src/pi.ts +++ b/packages/engine/src/pi.ts @@ -2149,6 +2149,7 @@ export async function createFnAgent(options: AgentOptions): Promise if (selectionResult.diagnostics.length > 0) { const purpose = effectiveSkillSelection.sessionPurpose ?? "skills"; for (const diag of selectionResult.diagnostics) { + if (diag.type === "info" && diag.message.startsWith("Configured skill pattern:")) continue; const msg = `[skills] [${purpose}] ${diag.type}: ${diag.message}`; if (diag.type === "error") piLog.error(msg); else if (diag.type === "warning") piLog.warn(msg); diff --git a/packages/engine/src/skill-resolver.ts b/packages/engine/src/skill-resolver.ts index 47fb897156..765252ccee 100644 --- a/packages/engine/src/skill-resolver.ts +++ b/packages/engine/src/skill-resolver.ts @@ -336,6 +336,18 @@ export function resolveSessionSkills(context: SkillSelectionContext): SkillSelec // ── Skills Override Factory ───────────────────────────────────────────────── +/* +FNXC:SkillResolution 2026-06-29-12:30: +Configured allow-list misses such as ce-optimize/SKILL.md are common when optional skills are absent from a checkout. +Keep the ResourceDiagnostic for programmatic visibility, but classify it separately so override application never mirrors this non-actionable info into piLog or console output. +*/ +function isMissingConfiguredPatternDiagnostic(diag: ResourceDiagnostic): boolean { + const diagnosticType = diag.type as string; + return diagnosticType === "info" + && diag.message.startsWith("Configured skill pattern '") + && diag.message.includes("' not found in discovered skills"); +} + /** * Options for skills override filtering. * We track requested names here so we can validate against base.skills. @@ -489,6 +501,7 @@ export function createSkillsOverrideFromSelection( if (newDiagnostics.length > 0) { const _purpose = sessionPurpose ? `[${sessionPurpose}]` : "skills"; for (const diag of newDiagnostics) { + if (isMissingConfiguredPatternDiagnostic(diag)) continue; const msg = `[skills] ${diag.type}: ${diag.message}`; if (diag.type === "error") piLog.error(msg); else if (diag.type === "warning") piLog.warn(msg);