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) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-06-29 19:17:37 -07:00
parent 797b30ce5d
commit ed21597690
5 changed files with 78 additions and 14 deletions

View File

@@ -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.

View File

@@ -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<string>(),
excludedSkillPaths: new Set<string>(),
diagnostics: [],
filterActive: true,
};
const override = createSkillsOverrideFromSelection(selection, {
requestedSkillNames: ["nonexistent"],
sessionPurpose: "executor",
});

View File

@@ -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<string>(),
excludedSkillPaths: new Set<string>(),
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<string>(),
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<string>(),
excludedSkillPaths: new Set<string>(),
diagnostics: [],
filterActive: true,
};
const override = createSkillsOverrideFromSelection(selection, {
requestedSkillNames: ["missing"],
sessionPurpose: "executor",
});

View File

@@ -2149,6 +2149,7 @@ export async function createFnAgent(options: AgentOptions): Promise<AgentResult>
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);

View File

@@ -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);