Phase 12: maxSkills soft warn — SPEC §15.
Optional maxSkills setting sets the warn threshold; when unset, re-injecting more than three tracked skills emits a one-time UI warning without blocking delivery. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -96,6 +96,29 @@ export function enqueueDeferredReinjectFromCompact(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Warn when re-injecting many skills; unlimited by default with soft warn above 3 (SPEC §15). */
|
||||||
|
export function maybeWarnManySkills(
|
||||||
|
skillCount: number,
|
||||||
|
settings: SkillReinjectSettings,
|
||||||
|
ctx?: ExtensionContext,
|
||||||
|
): void {
|
||||||
|
if (skillCount <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const threshold = settings.maxSkills ?? 3;
|
||||||
|
if (skillCount <= threshold) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (settings.maxSkills !== undefined) {
|
||||||
|
notifyWarning(
|
||||||
|
ctx,
|
||||||
|
`skill-reinject: re-injecting ${skillCount} skills (maxSkills warn threshold: ${settings.maxSkills})`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
notifyWarning(ctx, `skill-reinject: re-injecting ${skillCount} tracked skills (soft warn above 3)`);
|
||||||
|
}
|
||||||
|
|
||||||
/** Expanded skill-block messages in queue order (SPEC §5.3). */
|
/** Expanded skill-block messages in queue order (SPEC §5.3). */
|
||||||
export function buildReinjectBlocks(
|
export function buildReinjectBlocks(
|
||||||
skillNames: readonly string[],
|
skillNames: readonly string[],
|
||||||
@@ -104,6 +127,7 @@ export function buildReinjectBlocks(
|
|||||||
registeredSkills: readonly Pick<Skill, "name" | "filePath" | "baseDir">[],
|
registeredSkills: readonly Pick<Skill, "name" | "filePath" | "baseDir">[],
|
||||||
ctx?: ExtensionContext,
|
ctx?: ExtensionContext,
|
||||||
): string[] {
|
): string[] {
|
||||||
|
maybeWarnManySkills(skillNames.length, settings, ctx);
|
||||||
const registeredByName = registeredSkillsByName(registeredSkills, ctx);
|
const registeredByName = registeredSkillsByName(registeredSkills, ctx);
|
||||||
const blocks: string[] = [];
|
const blocks: string[] = [];
|
||||||
for (const name of skillNames) {
|
for (const name of skillNames) {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export interface SkillReinjectSettings {
|
|||||||
reinjectOnManualCompaction: boolean;
|
reinjectOnManualCompaction: boolean;
|
||||||
autoCompactIntegration: AutoCompactIntegration;
|
autoCompactIntegration: AutoCompactIntegration;
|
||||||
suffix: string;
|
suffix: string;
|
||||||
|
/** Soft warn threshold; omit for unlimited with default warn above 3 (SPEC §15). */
|
||||||
|
maxSkills?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Defaults from SPEC §7.3 — extension off until explicitly enabled. */
|
/** Defaults from SPEC §7.3 — extension off until explicitly enabled. */
|
||||||
@@ -72,6 +74,9 @@ export function parseSkillReinjectPartial(raw: unknown): PartialSkillReinjectSet
|
|||||||
if (typeof obj.suffix === "string") {
|
if (typeof obj.suffix === "string") {
|
||||||
result.suffix = obj.suffix;
|
result.suffix = obj.suffix;
|
||||||
}
|
}
|
||||||
|
if (typeof obj.maxSkills === "number" && Number.isInteger(obj.maxSkills) && obj.maxSkills > 0) {
|
||||||
|
result.maxSkills = obj.maxSkills;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import { maybeWarnManySkills } from "../src/reinject";
|
||||||
|
import { createDefaultSettings } from "../src/settings";
|
||||||
|
|
||||||
|
describe("maybeWarnManySkills", () => {
|
||||||
|
it("warns above 3 when maxSkills is unset", () => {
|
||||||
|
const notify = vi.fn();
|
||||||
|
const ctx = { hasUI: true, ui: { notify } } as never;
|
||||||
|
|
||||||
|
maybeWarnManySkills(4, createDefaultSettings(), ctx);
|
||||||
|
|
||||||
|
expect(notify).toHaveBeenCalledWith(
|
||||||
|
"skill-reinject: re-injecting 4 tracked skills (soft warn above 3)",
|
||||||
|
"warning",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not warn at 3 skills when maxSkills is unset", () => {
|
||||||
|
const notify = vi.fn();
|
||||||
|
const ctx = { hasUI: true, ui: { notify } } as never;
|
||||||
|
|
||||||
|
maybeWarnManySkills(3, createDefaultSettings(), ctx);
|
||||||
|
|
||||||
|
expect(notify).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses configured maxSkills threshold", () => {
|
||||||
|
const notify = vi.fn();
|
||||||
|
const ctx = { hasUI: true, ui: { notify } } as never;
|
||||||
|
const settings = createDefaultSettings();
|
||||||
|
settings.maxSkills = 2;
|
||||||
|
|
||||||
|
maybeWarnManySkills(3, settings, ctx);
|
||||||
|
|
||||||
|
expect(notify).toHaveBeenCalledWith(
|
||||||
|
"skill-reinject: re-injecting 3 skills (maxSkills warn threshold: 2)",
|
||||||
|
"warning",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user