vibe-code-explainer 0.2.1 → 0.3.1

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.
Files changed (31) hide show
  1. package/README.md +105 -44
  2. package/dist/{chunk-5NCRRHU7.js → chunk-RK7ZFN4W.js} +10 -2
  3. package/dist/chunk-RK7ZFN4W.js.map +1 -0
  4. package/dist/chunk-Y45OIJ6Q.js +429 -0
  5. package/dist/chunk-Y45OIJ6Q.js.map +1 -0
  6. package/dist/chunk-Y55I7ZS5.js +604 -0
  7. package/dist/chunk-Y55I7ZS5.js.map +1 -0
  8. package/dist/cli/index.js +6 -6
  9. package/dist/{config-5PDPXG7Z.js → config-AHHWBME7.js} +19 -2
  10. package/dist/config-AHHWBME7.js.map +1 -0
  11. package/dist/hooks/post-tool.js +62 -26
  12. package/dist/hooks/post-tool.js.map +1 -1
  13. package/dist/{init-KUVD2YGA.js → init-ZR6C4F7Q.js} +19 -3
  14. package/dist/init-ZR6C4F7Q.js.map +1 -0
  15. package/dist/ollama-LAUI7N5U.js +14 -0
  16. package/dist/{schema-TBXFNCIG.js → schema-YEJIXFMK.js} +4 -2
  17. package/dist/{tracker-HCWPUZIO.js → tracker-4ORSFJQB.js} +4 -2
  18. package/dist/{uninstall-CNGJWJYQ.js → uninstall-AIH4HVPZ.js} +2 -2
  19. package/package.json +1 -1
  20. package/dist/chunk-5NCRRHU7.js.map +0 -1
  21. package/dist/chunk-W67RX53R.js +0 -347
  22. package/dist/chunk-W67RX53R.js.map +0 -1
  23. package/dist/chunk-YS2XIZIA.js +0 -544
  24. package/dist/chunk-YS2XIZIA.js.map +0 -1
  25. package/dist/config-5PDPXG7Z.js.map +0 -1
  26. package/dist/init-KUVD2YGA.js.map +0 -1
  27. package/dist/ollama-34TOVCUY.js +0 -12
  28. /package/dist/{ollama-34TOVCUY.js.map → ollama-LAUI7N5U.js.map} +0 -0
  29. /package/dist/{schema-TBXFNCIG.js.map → schema-YEJIXFMK.js.map} +0 -0
  30. /package/dist/{tracker-HCWPUZIO.js.map → tracker-4ORSFJQB.js.map} +0 -0
  31. /package/dist/{uninstall-CNGJWJYQ.js.map → uninstall-AIH4HVPZ.js.map} +0 -0
@@ -0,0 +1,604 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ __require
4
+ } from "./chunk-7OCVIDC7.js";
5
+
6
+ // src/session/tracker.ts
7
+ import { existsSync as existsSync2, readFileSync as readFileSync2, appendFileSync as appendFileSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync2, readdirSync, statSync } from "fs";
8
+ import { tmpdir as tmpdir2 } from "os";
9
+ import { join as join2 } from "path";
10
+
11
+ // src/cache/explanation-cache.ts
12
+ import { createHash } from "crypto";
13
+ import { existsSync, readFileSync, appendFileSync, unlinkSync, mkdirSync } from "fs";
14
+ import { tmpdir } from "os";
15
+ import { join } from "path";
16
+ function getUserTmpDir() {
17
+ const dir = join(tmpdir(), `code-explainer-${process.getuid?.() ?? "user"}`);
18
+ if (!existsSync(dir)) {
19
+ mkdirSync(dir, { recursive: true, mode: 448 });
20
+ }
21
+ return dir;
22
+ }
23
+ function getCacheFilePath(sessionId) {
24
+ return join(getUserTmpDir(), `cache-${sessionId}.jsonl`);
25
+ }
26
+ function hashDiff(diff) {
27
+ return createHash("sha256").update(diff, "utf-8").digest("hex");
28
+ }
29
+ function getCached(sessionId, diff) {
30
+ const path = getCacheFilePath(sessionId);
31
+ if (!existsSync(path)) return void 0;
32
+ const hash = hashDiff(diff);
33
+ try {
34
+ const content = readFileSync(path, "utf-8");
35
+ const lines = content.split("\n").filter((l) => l.trim());
36
+ for (let i = lines.length - 1; i >= 0; i--) {
37
+ try {
38
+ const entry = JSON.parse(lines[i]);
39
+ if (entry.hash === hash) {
40
+ return entry.result;
41
+ }
42
+ } catch {
43
+ }
44
+ }
45
+ } catch {
46
+ return void 0;
47
+ }
48
+ return void 0;
49
+ }
50
+ function setCached(sessionId, diff, result) {
51
+ const path = getCacheFilePath(sessionId);
52
+ const entry = { hash: hashDiff(diff), result };
53
+ try {
54
+ appendFileSync(path, JSON.stringify(entry) + "\n", { mode: 384 });
55
+ } catch {
56
+ }
57
+ }
58
+ function clearCache(sessionId) {
59
+ const path = getCacheFilePath(sessionId);
60
+ if (existsSync(path)) {
61
+ try {
62
+ unlinkSync(path);
63
+ } catch {
64
+ }
65
+ }
66
+ }
67
+
68
+ // src/format/box.ts
69
+ import pc from "picocolors";
70
+ var LABELS = {
71
+ en: {
72
+ impact: "Impact",
73
+ howItWorks: "How it works",
74
+ why: "Why",
75
+ deepDive: "Deeper dive",
76
+ risk: "Risk",
77
+ riskNone: "None",
78
+ riskLow: "Low",
79
+ riskMedium: "Medium",
80
+ riskHigh: "High",
81
+ samePatternFallback: "Same pattern as before applied to this file."
82
+ },
83
+ pt: {
84
+ impact: "Impacto",
85
+ howItWorks: "Como funciona",
86
+ why: "Por que",
87
+ deepDive: "Pra aprofundar",
88
+ risk: "Risco",
89
+ riskNone: "Nenhum",
90
+ riskLow: "Baixo",
91
+ riskMedium: "M\xE9dio",
92
+ riskHigh: "Alto",
93
+ samePatternFallback: "Mesmo padr\xE3o anterior aplicado a este arquivo."
94
+ },
95
+ es: {
96
+ impact: "Impacto",
97
+ howItWorks: "C\xF3mo funciona",
98
+ why: "Por qu\xE9",
99
+ deepDive: "Para profundizar",
100
+ risk: "Riesgo",
101
+ riskNone: "Ninguno",
102
+ riskLow: "Bajo",
103
+ riskMedium: "Medio",
104
+ riskHigh: "Alto",
105
+ samePatternFallback: "Mismo patr\xF3n anterior aplicado a este archivo."
106
+ },
107
+ fr: {
108
+ impact: "Impact",
109
+ howItWorks: "Comment \xE7a marche",
110
+ why: "Pourquoi",
111
+ deepDive: "Pour approfondir",
112
+ risk: "Risque",
113
+ riskNone: "Aucun",
114
+ riskLow: "Faible",
115
+ riskMedium: "Moyen",
116
+ riskHigh: "\xC9lev\xE9",
117
+ samePatternFallback: "M\xEAme motif que pr\xE9c\xE9demment, appliqu\xE9 \xE0 ce fichier."
118
+ },
119
+ de: {
120
+ impact: "Auswirkung",
121
+ howItWorks: "Wie es funktioniert",
122
+ why: "Warum",
123
+ deepDive: "Mehr lernen",
124
+ risk: "Risiko",
125
+ riskNone: "Keines",
126
+ riskLow: "Gering",
127
+ riskMedium: "Mittel",
128
+ riskHigh: "Hoch",
129
+ samePatternFallback: "Gleiches Muster wie zuvor auf diese Datei angewendet."
130
+ },
131
+ it: {
132
+ impact: "Impatto",
133
+ howItWorks: "Come funziona",
134
+ why: "Perch\xE9",
135
+ deepDive: "Per approfondire",
136
+ risk: "Rischio",
137
+ riskNone: "Nessuno",
138
+ riskLow: "Basso",
139
+ riskMedium: "Medio",
140
+ riskHigh: "Alto",
141
+ samePatternFallback: "Stesso schema applicato a questo file."
142
+ },
143
+ zh: {
144
+ impact: "\u5F71\u54CD",
145
+ howItWorks: "\u5982\u4F55\u5DE5\u4F5C",
146
+ why: "\u4E3A\u4EC0\u4E48",
147
+ deepDive: "\u6DF1\u5165\u5B66\u4E60",
148
+ risk: "\u98CE\u9669",
149
+ riskNone: "\u65E0",
150
+ riskLow: "\u4F4E",
151
+ riskMedium: "\u4E2D",
152
+ riskHigh: "\u9AD8",
153
+ samePatternFallback: "\u540C\u6837\u7684\u6A21\u5F0F\u5E94\u7528\u5230\u6B64\u6587\u4EF6\u3002"
154
+ },
155
+ ja: {
156
+ impact: "\u5F71\u97FF",
157
+ howItWorks: "\u4ED5\u7D44\u307F",
158
+ why: "\u306A\u305C",
159
+ deepDive: "\u3055\u3089\u306B\u5B66\u3076",
160
+ risk: "\u30EA\u30B9\u30AF",
161
+ riskNone: "\u306A\u3057",
162
+ riskLow: "\u4F4E",
163
+ riskMedium: "\u4E2D",
164
+ riskHigh: "\u9AD8",
165
+ samePatternFallback: "\u4EE5\u524D\u3068\u540C\u3058\u30D1\u30BF\u30FC\u30F3\u3092\u3053\u306E\u30D5\u30A1\u30A4\u30EB\u306B\u9069\u7528\u3002"
166
+ },
167
+ ko: {
168
+ impact: "\uC601\uD5A5",
169
+ howItWorks: "\uC791\uB3D9 \uBC29\uC2DD",
170
+ why: "\uC774\uC720",
171
+ deepDive: "\uB354 \uC54C\uC544\uBCF4\uAE30",
172
+ risk: "\uC704\uD5D8",
173
+ riskNone: "\uC5C6\uC74C",
174
+ riskLow: "\uB0AE\uC74C",
175
+ riskMedium: "\uBCF4\uD1B5",
176
+ riskHigh: "\uB192\uC74C",
177
+ samePatternFallback: "\uC774\uC804\uACFC \uB3D9\uC77C\uD55C \uD328\uD134\uC774 \uC774 \uD30C\uC77C\uC5D0 \uC801\uC6A9\uB418\uC5C8\uC2B5\uB2C8\uB2E4."
178
+ }
179
+ };
180
+ function getLabels(language) {
181
+ return LABELS[language] ?? LABELS.en;
182
+ }
183
+ function isNoColor() {
184
+ return "NO_COLOR" in process.env || process.env.TERM === "dumb";
185
+ }
186
+ function color(fn, text) {
187
+ return isNoColor() ? text : fn(text);
188
+ }
189
+ function getTerminalWidth() {
190
+ return process.stderr.columns || 80;
191
+ }
192
+ function riskBorderColor(risk) {
193
+ switch (risk) {
194
+ case "none":
195
+ return pc.green;
196
+ case "low":
197
+ return pc.yellow;
198
+ case "medium":
199
+ return pc.yellow;
200
+ case "high":
201
+ return pc.red;
202
+ }
203
+ }
204
+ function riskIcon(risk) {
205
+ if (isNoColor()) {
206
+ switch (risk) {
207
+ case "none":
208
+ return "[OK]";
209
+ case "low":
210
+ return "[!]";
211
+ case "medium":
212
+ return "[!!]";
213
+ case "high":
214
+ return "[!!!]";
215
+ }
216
+ }
217
+ switch (risk) {
218
+ case "none":
219
+ return color(pc.green, "\u2713");
220
+ case "low":
221
+ return color(pc.yellow, "\u26A0");
222
+ case "medium":
223
+ return color(pc.yellow, "\u26A0");
224
+ case "high":
225
+ return color(pc.red, "\u{1F6A8}");
226
+ }
227
+ }
228
+ function riskLabelText(risk, labels) {
229
+ switch (risk) {
230
+ case "none":
231
+ return labels.riskNone;
232
+ case "low":
233
+ return labels.riskLow;
234
+ case "medium":
235
+ return labels.riskMedium;
236
+ case "high":
237
+ return labels.riskHigh;
238
+ }
239
+ }
240
+ function riskLabelColor(risk) {
241
+ switch (risk) {
242
+ case "none":
243
+ return pc.green;
244
+ case "low":
245
+ return pc.yellow;
246
+ case "medium":
247
+ return pc.yellow;
248
+ case "high":
249
+ return pc.red;
250
+ }
251
+ }
252
+ function highlightInlineCode(text) {
253
+ if (isNoColor()) return text;
254
+ return text.replace(/`([^`]+)`/g, (_, code) => color(pc.cyan, code));
255
+ }
256
+ function wrapText(text, maxWidth) {
257
+ const out = [];
258
+ for (const raw of text.split("\n")) {
259
+ if (raw.length <= maxWidth) {
260
+ out.push(raw);
261
+ continue;
262
+ }
263
+ let remaining = raw;
264
+ while (remaining.length > maxWidth) {
265
+ let breakAt = remaining.lastIndexOf(" ", maxWidth);
266
+ if (breakAt <= 0) breakAt = maxWidth;
267
+ out.push(remaining.slice(0, breakAt));
268
+ remaining = remaining.slice(breakAt).trimStart();
269
+ }
270
+ if (remaining) out.push(remaining);
271
+ }
272
+ return out;
273
+ }
274
+ var BOX_TITLE = "vibe-code-explainer";
275
+ var PAD_LEFT = 2;
276
+ var PAD_RIGHT = 2;
277
+ function line(raw, styled) {
278
+ return { text: styled ?? raw, raw };
279
+ }
280
+ function blankLine() {
281
+ return line("");
282
+ }
283
+ function buildBoxOutput(contentLines, borderColor) {
284
+ const width = Math.min(getTerminalWidth() - 2, 70);
285
+ const innerWidth = width - 2;
286
+ const top = `\u256D\u2500 ${color(pc.dim, BOX_TITLE)} ${"\u2500".repeat(Math.max(0, innerWidth - BOX_TITLE.length - 4))}\u2500\u256E`;
287
+ const bottom = `\u2570${"\u2500".repeat(innerWidth)}\u256F`;
288
+ const sideChar = color(borderColor, "\u2502");
289
+ const middle = contentLines.map((bl) => {
290
+ const padding = " ".repeat(Math.max(0, innerWidth - bl.raw.length - PAD_LEFT - PAD_RIGHT));
291
+ return `${sideChar}${" ".repeat(PAD_LEFT)}${bl.text}${padding}${" ".repeat(PAD_RIGHT)}${sideChar}`;
292
+ });
293
+ return [color(borderColor, top), ...middle, color(borderColor, bottom)].join("\n");
294
+ }
295
+ function renderSection(def) {
296
+ const out = [];
297
+ const headerRaw = `\u25B8 ${def.header}`;
298
+ const headerStyled = color(pc.bold, color(def.headerColor, headerRaw));
299
+ out.push(line(headerRaw, headerStyled));
300
+ const bodyMax = def.innerWidth - PAD_LEFT - PAD_RIGHT - 2;
301
+ const wrapped = wrapText(def.body, bodyMax);
302
+ for (const w of wrapped) {
303
+ const indented = ` ${w}`;
304
+ const styled = ` ${highlightInlineCode(w)}`;
305
+ out.push(line(indented, styled));
306
+ }
307
+ return out;
308
+ }
309
+ function formatExplanationBox(inputs) {
310
+ const labels = getLabels(inputs.language);
311
+ const result = inputs.result;
312
+ const borderFn = riskBorderColor(result.risk);
313
+ const lines = [];
314
+ const innerWidth = Math.min(getTerminalWidth() - 2, 70) - 2;
315
+ lines.push(blankLine());
316
+ const filePathRaw = `\u{1F4C4} ${inputs.filePath}`;
317
+ const filePathStyled = color(pc.bold, color(pc.cyan, filePathRaw));
318
+ lines.push(line(filePathRaw, filePathStyled));
319
+ if (result.isSamePattern) {
320
+ lines.push(blankLine());
321
+ const noteRaw = result.samePatternNote || labels.samePatternFallback;
322
+ const noteWrapped = wrapText(noteRaw, innerWidth - PAD_LEFT - PAD_RIGHT);
323
+ for (const w of noteWrapped) {
324
+ lines.push(line(w, color(pc.dim, w)));
325
+ }
326
+ } else {
327
+ if (result.impact) {
328
+ lines.push(blankLine());
329
+ if (inputs.detailLevel === "minimal") {
330
+ const wrapped = wrapText(result.impact, innerWidth - PAD_LEFT - PAD_RIGHT);
331
+ for (const w of wrapped) {
332
+ lines.push(line(w, highlightInlineCode(w)));
333
+ }
334
+ } else {
335
+ const sec = renderSection({
336
+ header: labels.impact,
337
+ headerColor: pc.cyan,
338
+ body: result.impact,
339
+ innerWidth
340
+ });
341
+ lines.push(...sec);
342
+ }
343
+ }
344
+ if (inputs.detailLevel !== "minimal" && result.howItWorks) {
345
+ lines.push(blankLine());
346
+ const sec = renderSection({
347
+ header: labels.howItWorks,
348
+ headerColor: pc.green,
349
+ body: result.howItWorks,
350
+ innerWidth
351
+ });
352
+ lines.push(...sec);
353
+ }
354
+ if (inputs.detailLevel !== "minimal" && result.why) {
355
+ lines.push(blankLine());
356
+ const sec = renderSection({
357
+ header: labels.why,
358
+ headerColor: pc.magenta,
359
+ body: result.why,
360
+ innerWidth
361
+ });
362
+ lines.push(...sec);
363
+ }
364
+ if (inputs.detailLevel === "verbose" && result.deepDive && result.deepDive.length > 0) {
365
+ lines.push(blankLine());
366
+ const headerRaw = `\u25B8 ${labels.deepDive}`;
367
+ const headerStyled = color(pc.bold, color(pc.dim, headerRaw));
368
+ lines.push(line(headerRaw, headerStyled));
369
+ const itemMax = innerWidth - PAD_LEFT - PAD_RIGHT - 4;
370
+ for (const item of result.deepDive) {
371
+ const text = `${item.term}: ${item.explanation}`;
372
+ const wrapped = wrapText(text, itemMax);
373
+ for (let i = 0; i < wrapped.length; i++) {
374
+ const prefix = i === 0 ? " \u2014 " : " ";
375
+ const raw = `${prefix}${wrapped[i]}`;
376
+ const styled = `${prefix}${highlightInlineCode(wrapped[i])}`;
377
+ lines.push(line(raw, styled));
378
+ }
379
+ }
380
+ }
381
+ }
382
+ lines.push(blankLine());
383
+ const dividerWidth = innerWidth - PAD_LEFT - PAD_RIGHT;
384
+ const dividerRaw = "\u2504".repeat(dividerWidth);
385
+ lines.push(line(dividerRaw, color(pc.dim, dividerRaw)));
386
+ lines.push(blankLine());
387
+ const riskHeaderRaw = `${stripAnsi(riskIcon(result.risk))} ${labels.risk}: ${riskLabelText(result.risk, labels)}`;
388
+ const riskHeaderStyled = `${riskIcon(result.risk)} ${color(pc.bold, color(riskLabelColor(result.risk), `${labels.risk}: ${riskLabelText(result.risk, labels)}`))}`;
389
+ lines.push(line(riskHeaderRaw, riskHeaderStyled));
390
+ if (result.risk !== "none" && result.riskReason) {
391
+ const reasonMax = innerWidth - PAD_LEFT - PAD_RIGHT - 3;
392
+ const wrapped = wrapText(result.riskReason, reasonMax);
393
+ for (const w of wrapped) {
394
+ const raw = ` ${w}`;
395
+ const styled = ` ${color(pc.dim, color(riskLabelColor(result.risk), w))}`;
396
+ lines.push(line(raw, styled));
397
+ }
398
+ }
399
+ lines.push(blankLine());
400
+ return buildBoxOutput(lines, borderFn);
401
+ }
402
+ function formatSkipNotice(reason) {
403
+ return color(pc.dim, `[code-explainer] skipped: ${reason}`);
404
+ }
405
+ function formatErrorNotice(problem, cause, fix) {
406
+ return color(pc.yellow, `[code-explainer] ${problem}. ${cause}. Fix: ${fix}.`);
407
+ }
408
+ function formatDriftAlert(totalFiles, unrelatedFiles, userRequest, language = "en") {
409
+ const labels = getLabels(language);
410
+ const lines = [];
411
+ const innerWidth = Math.min(getTerminalWidth() - 2, 70) - 2;
412
+ lines.push(blankLine());
413
+ const headerRaw = `\u26A1 SESSION DRIFT`;
414
+ const headerStyled = color(pc.bold, color(pc.yellow, headerRaw));
415
+ lines.push(line(headerRaw, headerStyled));
416
+ lines.push(blankLine());
417
+ const summaryRaw = `Claude has modified ${totalFiles} files this session.`;
418
+ lines.push(line(summaryRaw));
419
+ const unrelatedRaw = `${unrelatedFiles.length} may be unrelated:`;
420
+ lines.push(line(unrelatedRaw));
421
+ for (const file of unrelatedFiles) {
422
+ const truncated = file.length > innerWidth - 8 ? file.slice(0, innerWidth - 11) + "..." : file;
423
+ const raw = ` \u2022 ${truncated}`;
424
+ const styled = ` ${color(pc.yellow, "\u2022")} ${truncated}`;
425
+ lines.push(line(raw, styled));
426
+ }
427
+ if (userRequest) {
428
+ lines.push(blankLine());
429
+ const requestLines = wrapText(`Your request: "${userRequest}"`, innerWidth - PAD_LEFT - PAD_RIGHT);
430
+ for (const w of requestLines) {
431
+ lines.push(line(w, color(pc.dim, w)));
432
+ }
433
+ }
434
+ lines.push(blankLine());
435
+ const noticeRaw = `\u26A0 Consider reviewing these changes.`;
436
+ lines.push(line(noticeRaw, color(pc.bold, color(pc.yellow, noticeRaw))));
437
+ lines.push(blankLine());
438
+ return buildBoxOutput(lines, pc.yellow);
439
+ }
440
+ function printToStderr(text) {
441
+ try {
442
+ const fs = __require("fs");
443
+ const ttyPath = process.platform === "win32" ? "\\\\.\\CONOUT$" : "/dev/tty";
444
+ const fd = fs.openSync(ttyPath, "w");
445
+ fs.writeSync(fd, text + "\n");
446
+ fs.closeSync(fd);
447
+ } catch {
448
+ process.stderr.write(text + "\n");
449
+ }
450
+ }
451
+ function stripAnsi(s) {
452
+ return s.replace(/\u001b\[[0-9;]*m/g, "");
453
+ }
454
+
455
+ // src/session/tracker.ts
456
+ var TWO_HOURS_MS = 2 * 60 * 60 * 1e3;
457
+ function getUserTmpDir2() {
458
+ const dir = join2(tmpdir2(), `code-explainer-${process.getuid?.() ?? "user"}`);
459
+ if (!existsSync2(dir)) {
460
+ mkdirSync2(dir, { recursive: true, mode: 448 });
461
+ }
462
+ return dir;
463
+ }
464
+ function getSessionFilePath(sessionId) {
465
+ return join2(getUserTmpDir2(), `session-${sessionId}.jsonl`);
466
+ }
467
+ function recordEntry(sessionId, entry) {
468
+ const path = getSessionFilePath(sessionId);
469
+ try {
470
+ appendFileSync2(path, JSON.stringify(entry) + "\n", { mode: 384 });
471
+ } catch {
472
+ }
473
+ }
474
+ function readSession(sessionId) {
475
+ const path = getSessionFilePath(sessionId);
476
+ if (!existsSync2(path)) return [];
477
+ try {
478
+ const content = readFileSync2(path, "utf-8");
479
+ return content.split("\n").filter((l) => l.trim()).map((line2) => {
480
+ try {
481
+ return JSON.parse(line2);
482
+ } catch {
483
+ return null;
484
+ }
485
+ }).filter((e) => e !== null);
486
+ } catch {
487
+ return [];
488
+ }
489
+ }
490
+ function getRecentSummaries(sessionId, n) {
491
+ const entries = readSession(sessionId);
492
+ if (entries.length === 0) return [];
493
+ return entries.slice(-n).map((e) => `${e.file}: ${e.summary}`);
494
+ }
495
+ function cleanStaleSessionFiles() {
496
+ try {
497
+ const dir = getUserTmpDir2();
498
+ const now = Date.now();
499
+ const entries = readdirSync(dir);
500
+ for (const name of entries) {
501
+ if (!name.startsWith("session-") && !name.startsWith("cache-")) continue;
502
+ const filePath = join2(dir, name);
503
+ try {
504
+ const stat = statSync(filePath);
505
+ if (now - stat.mtimeMs > TWO_HOURS_MS) {
506
+ unlinkSync2(filePath);
507
+ }
508
+ } catch {
509
+ }
510
+ }
511
+ } catch {
512
+ }
513
+ }
514
+ function getSessionIdFromEnv() {
515
+ return process.env.CODE_EXPLAINER_SESSION_ID;
516
+ }
517
+ function findLatestSession() {
518
+ try {
519
+ const dir = getUserTmpDir2();
520
+ const entries = readdirSync(dir).filter((n) => n.startsWith("session-") && n.endsWith(".jsonl")).map((n) => ({
521
+ name: n,
522
+ id: n.slice("session-".length, -".jsonl".length),
523
+ mtime: statSync(join2(dir, n)).mtimeMs
524
+ })).sort((a, b) => b.mtime - a.mtime);
525
+ return entries[0]?.id;
526
+ } catch {
527
+ return void 0;
528
+ }
529
+ }
530
+ async function printSummary() {
531
+ const sessionId = getSessionIdFromEnv() ?? findLatestSession();
532
+ if (!sessionId) {
533
+ process.stderr.write("[code-explainer] No active session found. Session data is created when Claude Code makes changes.\n");
534
+ return;
535
+ }
536
+ const entries = readSession(sessionId);
537
+ if (entries.length === 0) {
538
+ process.stderr.write(`[code-explainer] Session '${sessionId}' has no recorded changes yet.
539
+ `);
540
+ return;
541
+ }
542
+ const related = entries.filter((e) => !e.unrelated);
543
+ const unrelated = entries.filter((e) => e.unrelated);
544
+ const uniqueFiles = Array.from(new Set(entries.map((e) => e.file)));
545
+ const unrelatedFiles = Array.from(new Set(unrelated.map((e) => e.file)));
546
+ const alert = formatDriftAlert(uniqueFiles.length, unrelatedFiles);
547
+ printToStderr(alert);
548
+ process.stderr.write(`
549
+ Total changes: ${entries.length}
550
+ `);
551
+ process.stderr.write(`Files touched: ${uniqueFiles.length}
552
+ `);
553
+ process.stderr.write(`Related changes: ${related.length}
554
+ `);
555
+ process.stderr.write(`Unrelated/risky: ${unrelated.length}
556
+ `);
557
+ const risks = { none: 0, low: 0, medium: 0, high: 0 };
558
+ for (const e of entries) risks[e.risk]++;
559
+ process.stderr.write(`
560
+ Risk breakdown:
561
+ `);
562
+ process.stderr.write(` None: ${risks.none}
563
+ `);
564
+ process.stderr.write(` Low: ${risks.low}
565
+ `);
566
+ process.stderr.write(` Medium: ${risks.medium}
567
+ `);
568
+ process.stderr.write(` High: ${risks.high}
569
+ `);
570
+ }
571
+ async function endSession() {
572
+ const sessionId = getSessionIdFromEnv() ?? findLatestSession();
573
+ if (!sessionId) {
574
+ process.stderr.write("[code-explainer] No active session to end.\n");
575
+ return;
576
+ }
577
+ const sessionPath = getSessionFilePath(sessionId);
578
+ if (existsSync2(sessionPath)) {
579
+ try {
580
+ unlinkSync2(sessionPath);
581
+ } catch {
582
+ }
583
+ }
584
+ clearCache(sessionId);
585
+ process.stderr.write(`[code-explainer] Session '${sessionId}' ended. State cleared.
586
+ `);
587
+ }
588
+
589
+ export {
590
+ getCached,
591
+ setCached,
592
+ formatExplanationBox,
593
+ formatSkipNotice,
594
+ formatErrorNotice,
595
+ formatDriftAlert,
596
+ getSessionFilePath,
597
+ recordEntry,
598
+ readSession,
599
+ getRecentSummaries,
600
+ cleanStaleSessionFiles,
601
+ printSummary,
602
+ endSession
603
+ };
604
+ //# sourceMappingURL=chunk-Y55I7ZS5.js.map