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:
@@ -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.
|
||||
@@ -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",
|
||||
});
|
||||
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user