@pi-unipi/compactor 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/compactor",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Context engine for Pi — zero-LLM compaction, session continuity, sandbox execution, FTS5 search, and tool display optimization",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,7 +27,7 @@ export interface CommandDeps {
27
27
  }
28
28
 
29
29
  export function registerCommands(pi: ExtensionAPI, deps?: CommandDeps): void {
30
- pi.registerCommand("compact", {
30
+ pi.registerCommand("unipi:compact", {
31
31
  description: "Trigger manual compaction with stats",
32
32
  handler: async (_args: string, ctx: any) => {
33
33
  const result = compactTool();
@@ -39,7 +39,7 @@ export function registerCommands(pi: ExtensionAPI, deps?: CommandDeps): void {
39
39
  },
40
40
  });
41
41
 
42
- pi.registerCommand("compact-recall", {
42
+ pi.registerCommand("unipi:compact-recall", {
43
43
  description: "Search session history (BM25 or regex)",
44
44
  handler: async (args: string, ctx: any) => {
45
45
  const query = args.trim();
@@ -64,7 +64,7 @@ export function registerCommands(pi: ExtensionAPI, deps?: CommandDeps): void {
64
64
  },
65
65
  });
66
66
 
67
- pi.registerCommand("compact-stats", {
67
+ pi.registerCommand("unipi:compact-stats", {
68
68
  description: "Show context savings dashboard",
69
69
  handler: async (_args: string, ctx: any) => {
70
70
  if (!deps?.sessionDB || !deps?.contentStore) {
@@ -89,7 +89,7 @@ export function registerCommands(pi: ExtensionAPI, deps?: CommandDeps): void {
89
89
  },
90
90
  });
91
91
 
92
- pi.registerCommand("compact-doctor", {
92
+ pi.registerCommand("unipi:compact-doctor", {
93
93
  description: "Run diagnostics checklist",
94
94
  handler: async (_args: string, ctx: any) => {
95
95
  if (!deps?.sessionDB || !deps?.contentStore) {
@@ -111,7 +111,7 @@ export function registerCommands(pi: ExtensionAPI, deps?: CommandDeps): void {
111
111
  },
112
112
  });
113
113
 
114
- pi.registerCommand("compact-settings", {
114
+ pi.registerCommand("unipi:compact-settings", {
115
115
  description: "Open TUI settings overlay",
116
116
  handler: async (_args: string, ctx: any) => {
117
117
  try {
@@ -128,7 +128,7 @@ export function registerCommands(pi: ExtensionAPI, deps?: CommandDeps): void {
128
128
  },
129
129
  });
130
130
 
131
- pi.registerCommand("compact-preset", {
131
+ pi.registerCommand("unipi:compact-preset", {
132
132
  description: "Apply quick preset (opencode/balanced/verbose/minimal)",
133
133
  handler: async (args: string, ctx: any) => {
134
134
  const presetName = parsePreset(args.trim());
@@ -146,7 +146,7 @@ export function registerCommands(pi: ExtensionAPI, deps?: CommandDeps): void {
146
146
  },
147
147
  });
148
148
 
149
- pi.registerCommand("compact-index", {
149
+ pi.registerCommand("unipi:compact-index", {
150
150
  description: "Index current project files into FTS5",
151
151
  handler: async (_args: string, ctx: any) => {
152
152
  if (!deps?.contentStore) {
@@ -202,7 +202,7 @@ export function registerCommands(pi: ExtensionAPI, deps?: CommandDeps): void {
202
202
  },
203
203
  });
204
204
 
205
- pi.registerCommand("compact-search", {
205
+ pi.registerCommand("unipi:compact-search", {
206
206
  description: "Search indexed content",
207
207
  handler: async (args: string, ctx: any) => {
208
208
  const query = args.trim();
@@ -230,7 +230,7 @@ export function registerCommands(pi: ExtensionAPI, deps?: CommandDeps): void {
230
230
  },
231
231
  });
232
232
 
233
- pi.registerCommand("compact-purge", {
233
+ pi.registerCommand("unipi:compact-purge", {
234
234
  description: "Wipe all indexed content from FTS5",
235
235
  handler: async (_args: string, ctx: any) => {
236
236
  if (!deps?.contentStore) {
@@ -4,7 +4,6 @@
4
4
 
5
5
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
6
6
  import { convertToLlm } from "@mariozechner/pi-coding-agent";
7
- import { writeFileSync } from "node:fs";
8
7
  import { compile } from "./summarize.js";
9
8
  import { loadConfig } from "../config/manager.js";
10
9
  import { buildOwnCut } from "./cut.js";
@@ -21,9 +20,11 @@ const formatTokens = (n: number): string => {
21
20
  return String(n);
22
21
  };
23
22
 
24
- const dbg = (debug: boolean, data: Record<string, unknown>) => {
23
+ const dbg = (debug: boolean, event: string, data?: Record<string, unknown>) => {
25
24
  if (!debug) return;
26
- try { writeFileSync("/tmp/compactor-debug.json", JSON.stringify(data, null, 2)); } catch {}
25
+ const ts = new Date().toISOString().slice(11, 23);
26
+ const details = data ? " " + JSON.stringify(data) : "";
27
+ console.error(`[compactor:${ts}] ${event}${details}`);
27
28
  };
28
29
 
29
30
  const previewContent = (content: unknown): string => {
@@ -53,11 +54,16 @@ export function registerCompactionHooks(pi: ExtensionAPI): void {
53
54
  pi.on("session_before_compact", (event, ctx) => {
54
55
  const { preparation, branchEntries, customInstructions } = event;
55
56
  const config = loadConfig();
57
+ dbg(config.debug, "session_before_compact:enter", { entryCount: (branchEntries as any[])?.length, hasPrevSummary: !!preparation?.previousSummary, isCompactor: customInstructions === COMPACTOR_INSTRUCTION });
56
58
 
57
59
  const isCompactor = customInstructions === COMPACTOR_INSTRUCTION;
58
- if (!isCompactor && !config.overrideDefaultCompaction) return;
60
+ if (!isCompactor && !config.overrideDefaultCompaction) {
61
+ dbg(config.debug, "session_before_compact:skip", { reason: "not_compactor_and_no_override" });
62
+ return;
63
+ }
59
64
 
60
65
  const ownCut = buildOwnCut(branchEntries as any[]);
66
+ dbg(config.debug, "buildOwnCut", { ok: ownCut.ok, reason: !ownCut.ok ? (ownCut as any).reason : undefined });
61
67
  if (!ownCut.ok) {
62
68
  try {
63
69
  ctx?.ui?.notify?.(REASON_MESSAGES[ownCut.reason], "warning");
@@ -90,6 +96,7 @@ export function registerCompactionHooks(pi: ExtensionAPI): void {
90
96
  keptTokensEst: Math.round(keptChars / 4),
91
97
  };
92
98
 
99
+ dbg(config.debug, "compile", { messageCount: messages.length, hasPrevSummary: !!preparation.previousSummary });
93
100
  const summary = compile({
94
101
  messages,
95
102
  previousSummary: preparation.previousSummary,
@@ -99,7 +106,7 @@ export function registerCompactionHooks(pi: ExtensionAPI): void {
99
106
  },
100
107
  });
101
108
 
102
- dbg(config.debug, {
109
+ dbg(config.debug, "compaction_pipeline", {
103
110
  usedOwnCut: true,
104
111
  messagesToSummarize: agentMessages.length,
105
112
  firstKeptEntryId,
@@ -129,6 +136,8 @@ export function registerCompactionHooks(pi: ExtensionAPI): void {
129
136
  });
130
137
 
131
138
  pi.on("session_compact", (event, ctx) => {
139
+ const config = loadConfig();
140
+ dbg(config.debug, "session_compact", { fromExtension: event.fromExtension, lastCompactWasCompactor });
132
141
  if (!event.fromExtension) return;
133
142
  if (lastCompactWasCompactor) return;
134
143
  const stats = lastStats;
package/src/index.ts CHANGED
@@ -17,6 +17,16 @@ import { normalizeMessages } from "./compaction/normalize.js";
17
17
  import { filterNoise } from "./compaction/filter-noise.js";
18
18
  import type { NormalizedBlock } from "./types.js";
19
19
 
20
+ /** Debug logger — only logs when config.debug === true */
21
+ function createDebugLogger(getConfig: () => { debug: boolean }) {
22
+ return (event: string, data?: Record<string, unknown>) => {
23
+ if (!getConfig().debug) return;
24
+ const ts = new Date().toISOString().slice(11, 23);
25
+ const details = data ? " " + JSON.stringify(data) : "";
26
+ console.error(`[compactor:${ts}] ${event}${details}`);
27
+ };
28
+ }
29
+
20
30
  export default function compactorExtension(pi: ExtensionAPI): void {
21
31
  let sessionDB: SessionDB | null = null;
22
32
  let contentStore: ContentStore | null = null;
@@ -25,6 +35,8 @@ export default function compactorExtension(pi: ExtensionAPI): void {
25
35
  let cachedBlocks: NormalizedBlock[] = [];
26
36
  let currentSessionId = "default";
27
37
 
38
+ const debug = createDebugLogger(() => config);
39
+
28
40
  const init = async () => {
29
41
  scaffoldConfig();
30
42
  config = loadConfig();
@@ -42,14 +54,13 @@ export default function compactorExtension(pi: ExtensionAPI): void {
42
54
 
43
55
  registerCompactionHooks(pi);
44
56
 
45
- // Register commands with deps (they need sessionDB/contentStore)
57
+ // Commands will be registered inside session_start when deps are ready
46
58
  const getCommandDeps = () => ({
47
59
  sessionDB,
48
60
  contentStore,
49
61
  getSessionId: () => currentSessionId,
50
62
  getBlocks: () => cachedBlocks,
51
63
  });
52
- registerCommands(pi, getCommandDeps());
53
64
 
54
65
  pi.on("session_start", async (_event, ctx) => {
55
66
  await init();
@@ -60,6 +71,8 @@ export default function compactorExtension(pi: ExtensionAPI): void {
60
71
  const fullSessionId = `${sessionId}${suffix}`;
61
72
  currentSessionId = fullSessionId;
62
73
 
74
+ debug("session_start", { sessionId: fullSessionId, projectDir });
75
+
63
76
  sessionDB?.ensureSession(fullSessionId, projectDir);
64
77
 
65
78
  // Register all compactor tools with Pi
@@ -82,6 +95,8 @@ export default function compactorExtension(pi: ExtensionAPI): void {
82
95
  tools: Object.values(COMPACTOR_TOOLS),
83
96
  });
84
97
 
98
+ debug("MODULE_READY", { commands: Object.values(COMPACTOR_COMMANDS), tools: Object.values(COMPACTOR_TOOLS) });
99
+
85
100
  if (config.fts5Index.mode === "auto" && contentStore) {
86
101
  // TODO: index project files
87
102
  }
@@ -92,6 +107,7 @@ export default function compactorExtension(pi: ExtensionAPI): void {
92
107
  pi.on("before_agent_start", async (_event, ctx) => {
93
108
  config = loadConfig();
94
109
  currentSessionId = `${(ctx as any).sessionId ?? "default"}${getWorktreeSuffix()}`;
110
+ debug("before_agent_start", { sessionId: currentSessionId, configDebug: config.debug });
95
111
 
96
112
  // Re-cache normalized blocks for vcc_recall
97
113
  try {
@@ -106,9 +122,7 @@ export default function compactorExtension(pi: ExtensionAPI): void {
106
122
 
107
123
  if (sessionDB) {
108
124
  const snapshot = await injectResumeSnapshot(sessionDB, currentSessionId);
109
- if (snapshot) {
110
- // Snapshot injected as context
111
- }
125
+ debug("resume_snapshot", { injected: !!snapshot });
112
126
  }
113
127
  });
114
128
 
@@ -117,6 +131,7 @@ export default function compactorExtension(pi: ExtensionAPI): void {
117
131
  const sessionId = `${(event as any).sessionId ?? "default"}${getWorktreeSuffix()}`;
118
132
  const events = sessionDB.getEvents(sessionId, { limit: 1000 });
119
133
  const stats = sessionDB.getSessionStats(sessionId);
134
+ debug("session_before_compact", { sessionId, eventCount: events.length, compactCount: stats?.compact_count ?? 0 });
120
135
  const { buildResumeSnapshot } = await import("./session/snapshot.js");
121
136
  const snapshot = buildResumeSnapshot(events, {
122
137
  compactCount: stats?.compact_count ?? 1,
@@ -129,10 +144,12 @@ export default function compactorExtension(pi: ExtensionAPI): void {
129
144
  if (sessionDB) {
130
145
  const sessionId = `${(event as any).sessionId ?? "default"}${getWorktreeSuffix()}`;
131
146
  sessionDB.incrementCompactCount(sessionId);
147
+ debug("session_compact", { sessionId });
132
148
  }
133
149
  });
134
150
 
135
151
  pi.on("session_shutdown", async (_event, _ctx) => {
152
+ debug("session_shutdown");
136
153
  if (sessionDB) {
137
154
  sessionDB.cleanupOldSessions(7);
138
155
  }
@@ -144,6 +161,7 @@ export default function compactorExtension(pi: ExtensionAPI): void {
144
161
  pi.on("input", async (event, _ctx) => {
145
162
  const toolName = (event as any).toolName ?? "";
146
163
  const args = (event as any).args ?? {};
164
+ debug("input", { toolName, args: JSON.stringify(args).slice(0, 200) });
147
165
  if (toolName === "bash" || toolName === "Bash") {
148
166
  const cmd = String(args.command ?? "");
149
167
  if (/\b(curl|wget|nc|netcat)\b/.test(cmd)) {
@@ -156,6 +174,10 @@ export default function compactorExtension(pi: ExtensionAPI): void {
156
174
  pi.on("tool_result", async (event, _ctx) => {
157
175
  if (!sessionDB) return;
158
176
  const sessionId = `${(event as any).sessionId ?? "default"}${getWorktreeSuffix()}`;
177
+ const toolNameRaw = (event as any).toolName ?? "";
178
+ const isError = (event as any).isError ?? false;
179
+
180
+ debug("tool_result", { toolName: toolNameRaw, isError, sessionId });
159
181
 
160
182
  // Extract and store session events
161
183
  const toolEvents = extractEventsFromToolResult({
@@ -167,6 +189,7 @@ export default function compactorExtension(pi: ExtensionAPI): void {
167
189
 
168
190
  for (const ev of toolEvents) {
169
191
  sessionDB.insertEvent(sessionId, ev, "PostToolUse");
192
+ debug("event_stored", { category: ev.category, type: ev.type });
170
193
  }
171
194
 
172
195
  // Apply display overrides for built-in tools
@@ -192,20 +215,25 @@ export default function compactorExtension(pi: ExtensionAPI): void {
192
215
  });
193
216
 
194
217
  pi.on("message_update", async (event, _ctx) => {
195
- if ((event as any).message?.thinking) {
196
- // Handled by display engine
218
+ const msg = (event as any).message;
219
+ if (msg?.thinking) {
220
+ debug("message_update", { thinking: true, length: String(msg.thinking).length });
197
221
  }
198
222
  });
199
223
 
200
224
  pi.on("message_end", async (_event, _ctx) => {
201
- // Thinking label persistence
225
+ debug("message_end");
202
226
  });
203
227
 
204
228
  pi.on("context", async (event, _ctx) => {
205
229
  const { sanitizeThinkingArtifacts } = await import("./display/thinking-label.js");
206
- const ctx = (event as any).context;
207
- if (typeof ctx === "string") {
208
- (event as any).context = sanitizeThinkingArtifacts(ctx);
230
+ const ctxStr = (event as any).context;
231
+ if (typeof ctxStr === "string") {
232
+ const sanitized = sanitizeThinkingArtifacts(ctxStr);
233
+ if (sanitized !== ctxStr) {
234
+ debug("context", { sanitized: true, beforeLen: ctxStr.length, afterLen: sanitized.length });
235
+ }
236
+ (event as any).context = sanitized;
209
237
  }
210
238
  });
211
239
  }
package/src/session/db.ts CHANGED
@@ -4,8 +4,9 @@
4
4
 
5
5
  import { createHash } from "node:crypto";
6
6
  import { execFileSync } from "node:child_process";
7
+ import { existsSync, mkdirSync } from "node:fs";
7
8
  import { homedir } from "node:os";
8
- import { join } from "node:path";
9
+ import { dirname, join } from "node:path";
9
10
  import type { SessionEvent, StoredEvent, SessionMeta, ResumeRow } from "../types.js";
10
11
 
11
12
  export function getWorktreeSuffix(): string {
@@ -44,17 +45,16 @@ let sqliteLib: any = null;
44
45
 
45
46
  async function getSQLite() {
46
47
  if (sqliteLib) return sqliteLib;
48
+ // Try bun:sqlite first (Bun runtime)
47
49
  try {
48
50
  sqliteLib = await import("bun:sqlite" as any);
49
51
  return sqliteLib;
50
52
  } catch {
51
- try {
52
- sqliteLib = await import("node:sqlite" as any);
53
- return sqliteLib;
54
- } catch {
55
- sqliteLib = await import("better-sqlite3");
56
- return sqliteLib;
57
- }
53
+ // Skip node:sqlite — its API (DatabaseSync) is incompatible with
54
+ // better-sqlite3's constructor pattern used by SessionDB.
55
+ // Go straight to better-sqlite3 which has the expected shape.
56
+ sqliteLib = await import("better-sqlite3");
57
+ return sqliteLib;
58
58
  }
59
59
  }
60
60
 
@@ -77,9 +77,14 @@ export class SessionDB {
77
77
  }
78
78
 
79
79
  async init(): Promise<void> {
80
+ const dir = dirname(this.dbPath);
81
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
82
+
80
83
  const sqlite: any = await getSQLite();
81
- // Handle different SQLite API shapes
82
- const Database = sqlite.Database ?? sqlite.default?.Database ?? sqlite;
84
+ // Handle different SQLite API shapes:
85
+ // - bun:sqlite exports Database as a named export
86
+ // - better-sqlite3 (CJS) exports the constructor as default when imported via ESM
87
+ const Database = sqlite.Database ?? sqlite.default?.Database ?? sqlite.default ?? sqlite;
83
88
  this.db = new Database(this.dbPath);
84
89
  this.db.exec("PRAGMA journal_mode = WAL;");
85
90
  this.initSchema();
@@ -14,30 +14,27 @@ export function defaultDBPath(name: string): string {
14
14
  }
15
15
 
16
16
  let sqliteLib: any = null;
17
- let sqliteFlavor: "bun" | "node" | "better-sqlite3" | null = null;
17
+ let sqliteFlavor: "bun" | "better-sqlite3" | null = null;
18
18
 
19
19
  export async function loadSQLite() {
20
20
  if (sqliteLib) return { lib: sqliteLib, flavor: sqliteFlavor! };
21
21
 
22
+ // Try bun:sqlite first (Bun runtime)
22
23
  try {
23
24
  sqliteLib = await import("bun:sqlite" as any);
24
25
  sqliteFlavor = "bun";
25
26
  return { lib: sqliteLib, flavor: sqliteFlavor };
26
27
  } catch {
28
+ // Skip node:sqlite — its API (DatabaseSync) is incompatible with
29
+ // better-sqlite3's constructor pattern (Database class).
27
30
  try {
28
- sqliteLib = await import("node:sqlite" as any);
29
- sqliteFlavor = "node";
31
+ sqliteLib = await import("better-sqlite3");
32
+ sqliteFlavor = "better-sqlite3";
30
33
  return { lib: sqliteLib, flavor: sqliteFlavor };
31
34
  } catch {
32
- try {
33
- sqliteLib = await import("better-sqlite3");
34
- sqliteFlavor = "better-sqlite3";
35
- return { lib: sqliteLib, flavor: sqliteFlavor };
36
- } catch {
37
- sqliteLib = {};
38
- sqliteFlavor = "better-sqlite3";
39
- return { lib: sqliteLib, flavor: sqliteFlavor };
40
- }
35
+ sqliteLib = {};
36
+ sqliteFlavor = "better-sqlite3";
37
+ return { lib: sqliteLib, flavor: sqliteFlavor };
41
38
  }
42
39
  }
43
40
  }
@@ -183,7 +183,10 @@ export class ContentStore {
183
183
 
184
184
  async init(): Promise<void> {
185
185
  const { lib } = await loadSQLite();
186
- const Database = lib.Database ?? lib.default?.Database ?? lib;
186
+ // Handle different SQLite API shapes:
187
+ // - bun:sqlite exports Database as a named export
188
+ // - better-sqlite3 (CJS) exports the constructor as default when imported via ESM
189
+ const Database = lib.Database ?? lib.default?.Database ?? lib.default ?? lib;
187
190
  this.db = new Database(this.dbPath);
188
191
  applyWALPragmas(this.db);
189
192
  this.initSchema();
@@ -40,6 +40,18 @@ interface StrategyItem {
40
40
  setMode: (c: CompactorConfig, v: string) => void;
41
41
  }
42
42
 
43
+ /** Top-level debug toggle that mirrors config.debug */
44
+ const GLOBAL_DEBUG: StrategyItem = {
45
+ key: "debug",
46
+ label: "Verbose Debug",
47
+ description: "Log ALL compaction events to console",
48
+ modes: ["on", "off"],
49
+ getEnabled: (c) => c.debug,
50
+ setEnabled: (c, v) => (c.debug = v),
51
+ getMode: (c) => (c.debug ? "on" : "off"),
52
+ setMode: (c, v) => (c.debug = v === "on"),
53
+ };
54
+
43
55
  /** All configurable strategies */
44
56
  const STRATEGIES: StrategyItem[] = [
45
57
  {
@@ -144,6 +156,9 @@ const STRATEGIES: StrategyItem[] = [
144
156
  },
145
157
  ];
146
158
 
159
+ /** All navigable items: debug toggle first, then strategies */
160
+ const ALL_ITEMS: StrategyItem[] = [GLOBAL_DEBUG, ...STRATEGIES];
161
+
147
162
  const PRESETS: CompactorPreset[] = ["opencode", "balanced", "verbose", "minimal"];
148
163
 
149
164
  /**
@@ -170,7 +185,7 @@ export class CompactorSettingsOverlay implements Component {
170
185
  case "\x1b[A": // Up
171
186
  case "k":
172
187
  if (this.mode === "strategy") {
173
- this.selectedIndex = (this.selectedIndex - 1 + STRATEGIES.length) % STRATEGIES.length;
188
+ this.selectedIndex = (this.selectedIndex - 1 + ALL_ITEMS.length) % ALL_ITEMS.length;
174
189
  } else {
175
190
  this.presetIndex = (this.presetIndex - 1 + PRESETS.length) % PRESETS.length;
176
191
  }
@@ -178,21 +193,21 @@ export class CompactorSettingsOverlay implements Component {
178
193
  case "\x1b[B": // Down
179
194
  case "j":
180
195
  if (this.mode === "strategy") {
181
- this.selectedIndex = (this.selectedIndex + 1) % STRATEGIES.length;
196
+ this.selectedIndex = (this.selectedIndex + 1) % ALL_ITEMS.length;
182
197
  } else {
183
198
  this.presetIndex = (this.presetIndex + 1) % PRESETS.length;
184
199
  }
185
200
  break;
186
201
  case " ": // Space - toggle enabled
187
202
  if (this.mode === "strategy") {
188
- const item = STRATEGIES[this.selectedIndex];
203
+ const item = ALL_ITEMS[this.selectedIndex];
189
204
  item.setEnabled(this.config, !item.getEnabled(this.config));
190
205
  }
191
206
  break;
192
207
  case "\x1b[C": // Right - cycle mode forward
193
208
  case "\r": // Enter
194
209
  if (this.mode === "strategy") {
195
- const strat = STRATEGIES[this.selectedIndex];
210
+ const strat = ALL_ITEMS[this.selectedIndex];
196
211
  const modes = strat.modes;
197
212
  const currentIdx = modes.indexOf(strat.getMode(this.config));
198
213
  const nextIdx = (currentIdx + 1) % modes.length;
@@ -205,7 +220,7 @@ export class CompactorSettingsOverlay implements Component {
205
220
  break;
206
221
  case "\x1b[D": // Left - cycle mode backward
207
222
  if (this.mode === "strategy") {
208
- const strat2 = STRATEGIES[this.selectedIndex];
223
+ const strat2 = ALL_ITEMS[this.selectedIndex];
209
224
  const modes2 = strat2.modes;
210
225
  const curIdx = modes2.indexOf(strat2.getMode(this.config));
211
226
  const prevIdx = (curIdx - 1 + modes2.length) % modes2.length;
@@ -248,9 +263,9 @@ export class CompactorSettingsOverlay implements Component {
248
263
  add("");
249
264
  add(`${ansi.dim}↑↓ navigate • Enter apply • p back to strategies • s save • Esc cancel${ansi.reset}`);
250
265
  } else {
251
- // Strategy list
252
- for (let i = 0; i < STRATEGIES.length; i++) {
253
- const item = STRATEGIES[i];
266
+ // Strategy list (GLOBAL_DEBUG at top, then all strategies)
267
+ for (let i = 0; i < ALL_ITEMS.length; i++) {
268
+ const item = ALL_ITEMS[i];
254
269
  const isSelected = i === this.selectedIndex;
255
270
  const enabled = item.getEnabled(this.config);
256
271
  const mode = item.getMode(this.config);