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:
2026-06-17 14:29:56 +07:00
parent 2894ed751d
commit 8f48040eac
6 changed files with 175 additions and 2 deletions
+3
View File
@@ -6,12 +6,15 @@ export interface CompactionRuntime {
pendingCompactionSource: CompactionSource | null;
/** Manual compaction with default settings: clear stale pending on next user prompt (SPEC §16.5, §12.3). */
clearPendingReinjectOnNextUserInput: boolean;
/** Last compact firstKeptEntryId for debug kept-window snapshots (Phase 14). */
lastCompactionFirstKeptEntryId: string | null;
}
export function createCompactionRuntime(): CompactionRuntime {
return {
pendingCompactionSource: null,
clearPendingReinjectOnNextUserInput: false,
lastCompactionFirstKeptEntryId: null,
};
}
+42
View File
@@ -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
View File
@@ -14,7 +14,12 @@ import {
markAutoCompactionBeforeCompact,
markManualCompactionFromInput,
} from "./compaction.js";
import { buildReinjectDiagSnapshot, notifyReinjectDiag } from "./diag.js";
import { detectSlashSkill, matchReadPathToSkillWhenEnabled, parseSkillBlocksFromText, userMessageText } from "./detect.js";
import {
getKeptEntries,
skillsPresentInKeptWindow,
} from "./kept.js";
import {
applyPendingReinjectAfterCompact,
clearPendingReinjectOnUserPrompt,
@@ -92,6 +97,11 @@ export default function skillReinject(pi: ExtensionAPI): void {
function handleSessionCompact(event: SessionCompactEvent, ctx: ExtensionContext): void {
const settings = readSettings(ctx);
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 shouldReinject = consumeCompactionOnSessionCompact(
compactionRuntime,
@@ -101,13 +111,19 @@ export default function skillReinject(pi: ExtensionAPI): void {
);
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") {
applyPendingReinjectAfterCompact(state, compactionRuntime, shouldReinject, planned);
persistState();
return;
}
applyPendingReinjectAfterCompact(state, compactionRuntime, shouldReinject, planned);
if (!shouldReinject) {
persistState();
return;
@@ -149,6 +165,24 @@ export default function skillReinject(pi: ExtensionAPI): void {
registeredSkills = event.systemPromptOptions.skills ?? registeredSkills;
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 deferred = tryConsumeDeferredReinject(
state,
@@ -157,6 +191,9 @@ export default function skillReinject(pi: ExtensionAPI): void {
ctx,
compactionRuntime,
);
if (deferred) {
compactionRuntime.lastCompactionFirstKeptEntryId = null;
}
if (pendingBefore > 0) {
persistState();
}
+6
View File
@@ -26,6 +26,8 @@ export interface SkillReinjectSettings {
suffix: string;
/** Soft warn threshold; omit for unlimited with default warn above 3 (SPEC §15). */
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. */
@@ -36,6 +38,7 @@ export const DEFAULT_SKILL_REINJECT_SETTINGS: Readonly<SkillReinjectSettings> =
reinjectOnManualCompaction: false,
autoCompactIntegration: "auto",
suffix: "[skill-reinject] Re-applied after compaction.",
debug: false,
};
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) {
result.maxSkills = obj.maxSkills;
}
if (typeof obj.debug === "boolean") {
result.debug = obj.debug;
}
return result;
}