Phase 14: add debug reinject diag logging — B-002 filter visibility
Expose settings.debug snapshots on session_compact and before_agent_start so Phase 14 can see which filter stage drops --skill paths. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,12 +6,15 @@ export interface CompactionRuntime {
|
|||||||
pendingCompactionSource: CompactionSource | null;
|
pendingCompactionSource: CompactionSource | null;
|
||||||
/** Manual compaction with default settings: clear stale pending on next user prompt (SPEC §16.5, §12.3). */
|
/** Manual compaction with default settings: clear stale pending on next user prompt (SPEC §16.5, §12.3). */
|
||||||
clearPendingReinjectOnNextUserInput: boolean;
|
clearPendingReinjectOnNextUserInput: boolean;
|
||||||
|
/** Last compact firstKeptEntryId for debug kept-window snapshots (Phase 14). */
|
||||||
|
lastCompactionFirstKeptEntryId: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createCompactionRuntime(): CompactionRuntime {
|
export function createCompactionRuntime(): CompactionRuntime {
|
||||||
return {
|
return {
|
||||||
pendingCompactionSource: null,
|
pendingCompactionSource: null,
|
||||||
clearPendingReinjectOnNextUserInput: false,
|
clearPendingReinjectOnNextUserInput: false,
|
||||||
|
lastCompactionFirstKeptEntryId: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
import type { ExtensionContext, Skill } from "@earendil-works/pi-coding-agent";
|
||||||
|
import type { SkillReinjectSettings } from "./settings.js";
|
||||||
|
import type { ExtensionState } from "./state.js";
|
||||||
|
|
||||||
|
export type ReinjectDiagPhase = "session_compact" | "before_agent_start";
|
||||||
|
|
||||||
|
/** Filter snapshot for debug logging (Phase 14 / B-002). */
|
||||||
|
export interface ReinjectDiagSnapshot {
|
||||||
|
tracked: string[];
|
||||||
|
kept: string[];
|
||||||
|
registered: string[];
|
||||||
|
planned: string[];
|
||||||
|
pending: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildReinjectDiagSnapshot(
|
||||||
|
state: ExtensionState,
|
||||||
|
registeredSkills: readonly Pick<Skill, "name">[],
|
||||||
|
keptPresent: ReadonlySet<string>,
|
||||||
|
planned: readonly string[],
|
||||||
|
): ReinjectDiagSnapshot {
|
||||||
|
return {
|
||||||
|
tracked: state.skills.map((skill) => skill.name),
|
||||||
|
kept: [...keptPresent],
|
||||||
|
registered: registeredSkills.map((skill) => skill.name),
|
||||||
|
planned: [...planned],
|
||||||
|
pending: [...state.pendingReinject],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Log reinject filter state when settings.debug is on (Phase 14). */
|
||||||
|
export function notifyReinjectDiag(
|
||||||
|
ctx: ExtensionContext | undefined,
|
||||||
|
settings: SkillReinjectSettings,
|
||||||
|
phase: ReinjectDiagPhase,
|
||||||
|
snapshot: ReinjectDiagSnapshot,
|
||||||
|
): void {
|
||||||
|
if (!settings.debug || !ctx?.hasUI) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.ui.notify(`skill-reinject [${phase}]: ${JSON.stringify(snapshot)}`, "info");
|
||||||
|
}
|
||||||
+39
-2
@@ -14,7 +14,12 @@ import {
|
|||||||
markAutoCompactionBeforeCompact,
|
markAutoCompactionBeforeCompact,
|
||||||
markManualCompactionFromInput,
|
markManualCompactionFromInput,
|
||||||
} from "./compaction.js";
|
} from "./compaction.js";
|
||||||
|
import { buildReinjectDiagSnapshot, notifyReinjectDiag } from "./diag.js";
|
||||||
import { detectSlashSkill, matchReadPathToSkillWhenEnabled, parseSkillBlocksFromText, userMessageText } from "./detect.js";
|
import { detectSlashSkill, matchReadPathToSkillWhenEnabled, parseSkillBlocksFromText, userMessageText } from "./detect.js";
|
||||||
|
import {
|
||||||
|
getKeptEntries,
|
||||||
|
skillsPresentInKeptWindow,
|
||||||
|
} from "./kept.js";
|
||||||
import {
|
import {
|
||||||
applyPendingReinjectAfterCompact,
|
applyPendingReinjectAfterCompact,
|
||||||
clearPendingReinjectOnUserPrompt,
|
clearPendingReinjectOnUserPrompt,
|
||||||
@@ -92,6 +97,11 @@ export default function skillReinject(pi: ExtensionAPI): void {
|
|||||||
function handleSessionCompact(event: SessionCompactEvent, ctx: ExtensionContext): void {
|
function handleSessionCompact(event: SessionCompactEvent, ctx: ExtensionContext): void {
|
||||||
const settings = readSettings(ctx);
|
const settings = readSettings(ctx);
|
||||||
const skills = resolveRegisteredSkills(ctx.cwd, registeredSkills);
|
const skills = resolveRegisteredSkills(ctx.cwd, registeredSkills);
|
||||||
|
const branch = ctx.sessionManager.getBranch();
|
||||||
|
compactionRuntime.lastCompactionFirstKeptEntryId = event.compactionEntry.firstKeptEntryId;
|
||||||
|
const trackedNames = state.skills.map((skill) => skill.name);
|
||||||
|
const keptEntries = getKeptEntries(branch, event.compactionEntry.firstKeptEntryId);
|
||||||
|
const keptPresent = skillsPresentInKeptWindow(keptEntries, trackedNames);
|
||||||
const planned = planReinject(state, settings, ctx, event, skills);
|
const planned = planReinject(state, settings, ctx, event, skills);
|
||||||
const shouldReinject = consumeCompactionOnSessionCompact(
|
const shouldReinject = consumeCompactionOnSessionCompact(
|
||||||
compactionRuntime,
|
compactionRuntime,
|
||||||
@@ -101,13 +111,19 @@ export default function skillReinject(pi: ExtensionAPI): void {
|
|||||||
);
|
);
|
||||||
const deliveryMode = resolveDeliveryMode(settings, runtime, state.sessionIntegrationOverride);
|
const deliveryMode = resolveDeliveryMode(settings, runtime, state.sessionIntegrationOverride);
|
||||||
|
|
||||||
|
applyPendingReinjectAfterCompact(state, compactionRuntime, shouldReinject, planned);
|
||||||
|
notifyReinjectDiag(
|
||||||
|
ctx,
|
||||||
|
settings,
|
||||||
|
"session_compact",
|
||||||
|
buildReinjectDiagSnapshot(state, skills, keptPresent, planned),
|
||||||
|
);
|
||||||
|
|
||||||
if (deliveryMode === "defer") {
|
if (deliveryMode === "defer") {
|
||||||
applyPendingReinjectAfterCompact(state, compactionRuntime, shouldReinject, planned);
|
|
||||||
persistState();
|
persistState();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
applyPendingReinjectAfterCompact(state, compactionRuntime, shouldReinject, planned);
|
|
||||||
if (!shouldReinject) {
|
if (!shouldReinject) {
|
||||||
persistState();
|
persistState();
|
||||||
return;
|
return;
|
||||||
@@ -149,6 +165,24 @@ export default function skillReinject(pi: ExtensionAPI): void {
|
|||||||
registeredSkills = event.systemPromptOptions.skills ?? registeredSkills;
|
registeredSkills = event.systemPromptOptions.skills ?? registeredSkills;
|
||||||
|
|
||||||
const settings = readSettings(ctx);
|
const settings = readSettings(ctx);
|
||||||
|
const skills = resolveRegisteredSkills(ctx.cwd, registeredSkills);
|
||||||
|
const trackedNames = state.skills.map((skill) => skill.name);
|
||||||
|
const keptPresent =
|
||||||
|
compactionRuntime.lastCompactionFirstKeptEntryId === null
|
||||||
|
? new Set<string>()
|
||||||
|
: skillsPresentInKeptWindow(
|
||||||
|
getKeptEntries(
|
||||||
|
ctx.sessionManager.getBranch(),
|
||||||
|
compactionRuntime.lastCompactionFirstKeptEntryId,
|
||||||
|
),
|
||||||
|
trackedNames,
|
||||||
|
);
|
||||||
|
notifyReinjectDiag(
|
||||||
|
ctx,
|
||||||
|
settings,
|
||||||
|
"before_agent_start",
|
||||||
|
buildReinjectDiagSnapshot(state, skills, keptPresent, []),
|
||||||
|
);
|
||||||
const pendingBefore = state.pendingReinject.length;
|
const pendingBefore = state.pendingReinject.length;
|
||||||
const deferred = tryConsumeDeferredReinject(
|
const deferred = tryConsumeDeferredReinject(
|
||||||
state,
|
state,
|
||||||
@@ -157,6 +191,9 @@ export default function skillReinject(pi: ExtensionAPI): void {
|
|||||||
ctx,
|
ctx,
|
||||||
compactionRuntime,
|
compactionRuntime,
|
||||||
);
|
);
|
||||||
|
if (deferred) {
|
||||||
|
compactionRuntime.lastCompactionFirstKeptEntryId = null;
|
||||||
|
}
|
||||||
if (pendingBefore > 0) {
|
if (pendingBefore > 0) {
|
||||||
persistState();
|
persistState();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ export interface SkillReinjectSettings {
|
|||||||
suffix: string;
|
suffix: string;
|
||||||
/** Soft warn threshold; omit for unlimited with default warn above 3 (SPEC §15). */
|
/** Soft warn threshold; omit for unlimited with default warn above 3 (SPEC §15). */
|
||||||
maxSkills?: number;
|
maxSkills?: number;
|
||||||
|
/** Verbose reinject filter logging via ui.notify (Phase 14 / B-002). */
|
||||||
|
debug: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Defaults from SPEC §7.3 — extension off until explicitly enabled. */
|
/** Defaults from SPEC §7.3 — extension off until explicitly enabled. */
|
||||||
@@ -36,6 +38,7 @@ export const DEFAULT_SKILL_REINJECT_SETTINGS: Readonly<SkillReinjectSettings> =
|
|||||||
reinjectOnManualCompaction: false,
|
reinjectOnManualCompaction: false,
|
||||||
autoCompactIntegration: "auto",
|
autoCompactIntegration: "auto",
|
||||||
suffix: "[skill-reinject] Re-applied after compaction.",
|
suffix: "[skill-reinject] Re-applied after compaction.",
|
||||||
|
debug: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createDefaultSettings(): SkillReinjectSettings {
|
export function createDefaultSettings(): SkillReinjectSettings {
|
||||||
@@ -77,6 +80,9 @@ export function parseSkillReinjectPartial(raw: unknown): PartialSkillReinjectSet
|
|||||||
if (typeof obj.maxSkills === "number" && Number.isInteger(obj.maxSkills) && obj.maxSkills > 0) {
|
if (typeof obj.maxSkills === "number" && Number.isInteger(obj.maxSkills) && obj.maxSkills > 0) {
|
||||||
result.maxSkills = obj.maxSkills;
|
result.maxSkills = obj.maxSkills;
|
||||||
}
|
}
|
||||||
|
if (typeof obj.debug === "boolean") {
|
||||||
|
result.debug = obj.debug;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import { buildReinjectDiagSnapshot, notifyReinjectDiag } from "../src/diag.js";
|
||||||
|
import { createDefaultSettings } from "../src/settings.js";
|
||||||
|
import { createInitialState } from "../src/state.js";
|
||||||
|
|
||||||
|
describe("buildReinjectDiagSnapshot", () => {
|
||||||
|
it("collects tracked, kept, registered, planned, and pending", () => {
|
||||||
|
const state = createInitialState();
|
||||||
|
state.skills.push({
|
||||||
|
name: "alpha",
|
||||||
|
filePath: "/skills/alpha/SKILL.md",
|
||||||
|
baseDir: "/skills/alpha",
|
||||||
|
firstSeenAt: 1,
|
||||||
|
lastSeenAt: 1,
|
||||||
|
sources: ["slash"],
|
||||||
|
});
|
||||||
|
state.pendingReinject = ["alpha"];
|
||||||
|
|
||||||
|
expect(
|
||||||
|
buildReinjectDiagSnapshot(
|
||||||
|
state,
|
||||||
|
[{ name: "beta" }],
|
||||||
|
new Set(["gamma"]),
|
||||||
|
["alpha"],
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
tracked: ["alpha"],
|
||||||
|
kept: ["gamma"],
|
||||||
|
registered: ["beta"],
|
||||||
|
planned: ["alpha"],
|
||||||
|
pending: ["alpha"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("notifyReinjectDiag", () => {
|
||||||
|
it("no-ops when debug is off", () => {
|
||||||
|
const notify = vi.fn();
|
||||||
|
notifyReinjectDiag(
|
||||||
|
{
|
||||||
|
hasUI: true,
|
||||||
|
ui: { notify },
|
||||||
|
} as never,
|
||||||
|
createDefaultSettings(),
|
||||||
|
"session_compact",
|
||||||
|
{
|
||||||
|
tracked: [],
|
||||||
|
kept: [],
|
||||||
|
registered: [],
|
||||||
|
planned: [],
|
||||||
|
pending: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(notify).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("notifies with JSON snapshot when debug is on", () => {
|
||||||
|
const notify = vi.fn();
|
||||||
|
const settings = { ...createDefaultSettings(), debug: true };
|
||||||
|
const snapshot = {
|
||||||
|
tracked: ["a"],
|
||||||
|
kept: [],
|
||||||
|
registered: [],
|
||||||
|
planned: ["a"],
|
||||||
|
pending: ["a"],
|
||||||
|
};
|
||||||
|
notifyReinjectDiag(
|
||||||
|
{
|
||||||
|
hasUI: true,
|
||||||
|
ui: { notify },
|
||||||
|
} as never,
|
||||||
|
settings,
|
||||||
|
"before_agent_start",
|
||||||
|
snapshot,
|
||||||
|
);
|
||||||
|
expect(notify).toHaveBeenCalledWith(
|
||||||
|
`skill-reinject [before_agent_start]: ${JSON.stringify(snapshot)}`,
|
||||||
|
"info",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -54,6 +54,10 @@ describe("parseSkillReinjectPartial", () => {
|
|||||||
}),
|
}),
|
||||||
).toEqual({ enabled: true, suffix: "custom" });
|
).toEqual({ enabled: true, suffix: "custom" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("parses debug flag", () => {
|
||||||
|
expect(parseSkillReinjectPartial({ debug: true })).toEqual({ debug: true });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("mergeSkillReinjectSettings", () => {
|
describe("mergeSkillReinjectSettings", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user