vibe-code-explainer 0.3.2 → 0.3.5
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/dist/chunk-GEAH6PTG.js +37 -0
- package/dist/chunk-GEAH6PTG.js.map +1 -0
- package/dist/chunk-GU4Y5ZWY.js +140 -0
- package/dist/chunk-GU4Y5ZWY.js.map +1 -0
- package/dist/{chunk-2PUO5G3C.js → chunk-KK76JK7S.js} +32 -92
- package/dist/chunk-KK76JK7S.js.map +1 -0
- package/dist/{chunk-Y55I7ZS5.js → chunk-VJN7Y4SI.js} +185 -71
- package/dist/chunk-VJN7Y4SI.js.map +1 -0
- package/dist/{chunk-2IARGRDK.js → chunk-ZZY3IDL2.js} +106 -63
- package/dist/chunk-ZZY3IDL2.js.map +1 -0
- package/dist/cli/index.js +37 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/{config-AHHWBME7.js → config-YLMDBCIR.js} +116 -6
- package/dist/config-YLMDBCIR.js.map +1 -0
- package/dist/hooks/post-tool.js +144 -129
- package/dist/hooks/post-tool.js.map +1 -1
- package/dist/{init-V5BIF357.js → init-UDODKO25.js} +12 -16
- package/dist/init-UDODKO25.js.map +1 -0
- package/dist/ollama-YSRRK7LL.js +12 -0
- package/dist/{schema-YEJIXFMK.js → schema-R3THK35H.js} +8 -4
- package/dist/{tracker-4ORSFJQB.js → tracker-Y2G5DW6Y.js} +2 -2
- package/dist/{uninstall-AIH4HVPZ.js → uninstall-5RVTDKTA.js} +3 -3
- package/package.json +3 -2
- package/dist/chunk-2IARGRDK.js.map +0 -1
- package/dist/chunk-2PUO5G3C.js.map +0 -1
- package/dist/chunk-RK7ZFN4W.js +0 -97
- package/dist/chunk-RK7ZFN4W.js.map +0 -1
- package/dist/chunk-Y55I7ZS5.js.map +0 -1
- package/dist/config-AHHWBME7.js.map +0 -1
- package/dist/init-V5BIF357.js.map +0 -1
- package/dist/ollama-V246A374.js +0 -14
- /package/dist/{ollama-V246A374.js.map → ollama-YSRRK7LL.js.map} +0 -0
- /package/dist/{schema-YEJIXFMK.js.map → schema-R3THK35H.js.map} +0 -0
- /package/dist/{tracker-4ORSFJQB.js.map → tracker-Y2G5DW6Y.js.map} +0 -0
- /package/dist/{uninstall-AIH4HVPZ.js.map → uninstall-5RVTDKTA.js.map} +0 -0
|
@@ -4,31 +4,79 @@ import {
|
|
|
4
4
|
} from "./chunk-7OCVIDC7.js";
|
|
5
5
|
|
|
6
6
|
// src/session/tracker.ts
|
|
7
|
-
import { existsSync as
|
|
8
|
-
import {
|
|
9
|
-
import { join as join2 } from "path";
|
|
7
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, appendFileSync as appendFileSync2, unlinkSync as unlinkSync2, readdirSync, statSync } from "fs";
|
|
8
|
+
import { join as join3 } from "path";
|
|
10
9
|
|
|
11
10
|
// src/cache/explanation-cache.ts
|
|
12
11
|
import { createHash } from "crypto";
|
|
13
|
-
import { existsSync, readFileSync, appendFileSync,
|
|
14
|
-
import {
|
|
12
|
+
import { existsSync as existsSync2, readFileSync, appendFileSync, writeFileSync, renameSync, unlinkSync } from "fs";
|
|
13
|
+
import { join as join2 } from "path";
|
|
14
|
+
|
|
15
|
+
// src/session/session-id.ts
|
|
16
|
+
var SAFE_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
17
|
+
function isSafeSessionId(id) {
|
|
18
|
+
return typeof id === "string" && SAFE_ID_PATTERN.test(id);
|
|
19
|
+
}
|
|
20
|
+
function assertSafeSessionId(id) {
|
|
21
|
+
if (!isSafeSessionId(id)) {
|
|
22
|
+
throw new Error(`unsafe session id: ${JSON.stringify(id)}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/session/tmpdir.ts
|
|
27
|
+
import { existsSync, mkdirSync } from "fs";
|
|
28
|
+
import { tmpdir, userInfo } from "os";
|
|
15
29
|
import { join } from "path";
|
|
16
30
|
function getUserTmpDir() {
|
|
17
|
-
|
|
31
|
+
let suffix;
|
|
32
|
+
try {
|
|
33
|
+
const info = userInfo();
|
|
34
|
+
suffix = typeof info.username === "string" && info.username ? info.username : "user";
|
|
35
|
+
} catch {
|
|
36
|
+
suffix = "user";
|
|
37
|
+
}
|
|
38
|
+
suffix = suffix.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 64) || "user";
|
|
39
|
+
const dir = join(tmpdir(), `code-explainer-${suffix}`);
|
|
18
40
|
if (!existsSync(dir)) {
|
|
19
41
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
20
42
|
}
|
|
21
43
|
return dir;
|
|
22
44
|
}
|
|
45
|
+
|
|
46
|
+
// src/cache/explanation-cache.ts
|
|
47
|
+
var CACHE_ROTATE_THRESHOLD = 500;
|
|
48
|
+
var CACHE_COMPACT_TARGET = 250;
|
|
23
49
|
function getCacheFilePath(sessionId) {
|
|
24
|
-
|
|
50
|
+
assertSafeSessionId(sessionId);
|
|
51
|
+
return join2(getUserTmpDir(), `cache-${sessionId}.jsonl`);
|
|
25
52
|
}
|
|
26
53
|
function hashDiff(diff) {
|
|
27
54
|
return createHash("sha256").update(diff, "utf-8").digest("hex");
|
|
28
55
|
}
|
|
56
|
+
function rotateCacheIfNeeded(path) {
|
|
57
|
+
try {
|
|
58
|
+
const content = readFileSync(path, "utf-8");
|
|
59
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
60
|
+
if (lines.length <= CACHE_ROTATE_THRESHOLD) return;
|
|
61
|
+
const seen = /* @__PURE__ */ new Map();
|
|
62
|
+
for (const line2 of lines) {
|
|
63
|
+
try {
|
|
64
|
+
const entry = JSON.parse(line2);
|
|
65
|
+
seen.set(entry.hash, entry);
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const unique = Array.from(seen.values());
|
|
70
|
+
const compacted = unique.slice(-CACHE_COMPACT_TARGET);
|
|
71
|
+
const tmp = path + ".tmp";
|
|
72
|
+
writeFileSync(tmp, compacted.map((e) => JSON.stringify(e)).join("\n") + "\n", { mode: 384 });
|
|
73
|
+
renameSync(tmp, path);
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
}
|
|
29
77
|
function getCached(sessionId, diff) {
|
|
30
78
|
const path = getCacheFilePath(sessionId);
|
|
31
|
-
if (!
|
|
79
|
+
if (!existsSync2(path)) return void 0;
|
|
32
80
|
const hash = hashDiff(diff);
|
|
33
81
|
try {
|
|
34
82
|
const content = readFileSync(path, "utf-8");
|
|
@@ -52,12 +100,13 @@ function setCached(sessionId, diff, result) {
|
|
|
52
100
|
const entry = { hash: hashDiff(diff), result };
|
|
53
101
|
try {
|
|
54
102
|
appendFileSync(path, JSON.stringify(entry) + "\n", { mode: 384 });
|
|
103
|
+
rotateCacheIfNeeded(path);
|
|
55
104
|
} catch {
|
|
56
105
|
}
|
|
57
106
|
}
|
|
58
107
|
function clearCache(sessionId) {
|
|
59
108
|
const path = getCacheFilePath(sessionId);
|
|
60
|
-
if (
|
|
109
|
+
if (existsSync2(path)) {
|
|
61
110
|
try {
|
|
62
111
|
unlinkSync(path);
|
|
63
112
|
} catch {
|
|
@@ -66,7 +115,6 @@ function clearCache(sessionId) {
|
|
|
66
115
|
}
|
|
67
116
|
|
|
68
117
|
// src/format/box.ts
|
|
69
|
-
import pc from "picocolors";
|
|
70
118
|
var LABELS = {
|
|
71
119
|
en: {
|
|
72
120
|
impact: "Impact",
|
|
@@ -180,11 +228,44 @@ var LABELS = {
|
|
|
180
228
|
function getLabels(language) {
|
|
181
229
|
return LABELS[language] ?? LABELS.en;
|
|
182
230
|
}
|
|
231
|
+
var RESET = "\x1B[0m";
|
|
232
|
+
var BOLD = "\x1B[1m";
|
|
233
|
+
var DIM = "\x1B[2m";
|
|
234
|
+
var PALETTE = {
|
|
235
|
+
blue: [91, 158, 245],
|
|
236
|
+
// #5B9EF5
|
|
237
|
+
green: [91, 245, 160],
|
|
238
|
+
// #5BF5A0
|
|
239
|
+
yellow: [245, 200, 91],
|
|
240
|
+
// #F5C85B
|
|
241
|
+
red: [245, 91, 91],
|
|
242
|
+
// #F55B5B
|
|
243
|
+
purple: [224, 91, 245],
|
|
244
|
+
// #E05BF5
|
|
245
|
+
white: [255, 255, 255]
|
|
246
|
+
// #FFFFFF
|
|
247
|
+
};
|
|
183
248
|
function isNoColor() {
|
|
184
249
|
return "NO_COLOR" in process.env || process.env.TERM === "dumb";
|
|
185
250
|
}
|
|
186
|
-
function
|
|
187
|
-
|
|
251
|
+
function rgb(name, text) {
|
|
252
|
+
if (isNoColor()) return text;
|
|
253
|
+
const [r, g, b] = PALETTE[name];
|
|
254
|
+
return `\x1B[38;2;${r};${g};${b}m${text}${RESET}`;
|
|
255
|
+
}
|
|
256
|
+
function dim(text) {
|
|
257
|
+
if (isNoColor()) return text;
|
|
258
|
+
return `${DIM}${text}${RESET}`;
|
|
259
|
+
}
|
|
260
|
+
function boldRgb(name, text) {
|
|
261
|
+
if (isNoColor()) return text;
|
|
262
|
+
const [r, g, b] = PALETTE[name];
|
|
263
|
+
return `${BOLD}\x1B[38;2;${r};${g};${b}m${text}${RESET}`;
|
|
264
|
+
}
|
|
265
|
+
function dimRgb(name, text) {
|
|
266
|
+
if (isNoColor()) return text;
|
|
267
|
+
const [r, g, b] = PALETTE[name];
|
|
268
|
+
return `${DIM}\x1B[38;2;${r};${g};${b}m${text}${RESET}`;
|
|
188
269
|
}
|
|
189
270
|
function getTerminalWidth() {
|
|
190
271
|
return process.stderr.columns || 80;
|
|
@@ -192,13 +273,13 @@ function getTerminalWidth() {
|
|
|
192
273
|
function riskBorderColor(risk) {
|
|
193
274
|
switch (risk) {
|
|
194
275
|
case "none":
|
|
195
|
-
return
|
|
276
|
+
return "green";
|
|
196
277
|
case "low":
|
|
197
|
-
return
|
|
278
|
+
return "yellow";
|
|
198
279
|
case "medium":
|
|
199
|
-
return
|
|
280
|
+
return "yellow";
|
|
200
281
|
case "high":
|
|
201
|
-
return
|
|
282
|
+
return "red";
|
|
202
283
|
}
|
|
203
284
|
}
|
|
204
285
|
function riskIcon(risk) {
|
|
@@ -216,13 +297,13 @@ function riskIcon(risk) {
|
|
|
216
297
|
}
|
|
217
298
|
switch (risk) {
|
|
218
299
|
case "none":
|
|
219
|
-
return
|
|
300
|
+
return rgb("green", "\u2713");
|
|
220
301
|
case "low":
|
|
221
|
-
return
|
|
302
|
+
return rgb("yellow", "\u26A0");
|
|
222
303
|
case "medium":
|
|
223
|
-
return
|
|
304
|
+
return rgb("yellow", "\u26A0");
|
|
224
305
|
case "high":
|
|
225
|
-
return
|
|
306
|
+
return rgb("red", "\u{1F6A8}");
|
|
226
307
|
}
|
|
227
308
|
}
|
|
228
309
|
function riskLabelText(risk, labels) {
|
|
@@ -240,18 +321,18 @@ function riskLabelText(risk, labels) {
|
|
|
240
321
|
function riskLabelColor(risk) {
|
|
241
322
|
switch (risk) {
|
|
242
323
|
case "none":
|
|
243
|
-
return
|
|
324
|
+
return "green";
|
|
244
325
|
case "low":
|
|
245
|
-
return
|
|
326
|
+
return "yellow";
|
|
246
327
|
case "medium":
|
|
247
|
-
return
|
|
328
|
+
return "yellow";
|
|
248
329
|
case "high":
|
|
249
|
-
return
|
|
330
|
+
return "red";
|
|
250
331
|
}
|
|
251
332
|
}
|
|
252
333
|
function highlightInlineCode(text) {
|
|
253
334
|
if (isNoColor()) return text;
|
|
254
|
-
return text.replace(/`([^`]+)`/g, (_, code) =>
|
|
335
|
+
return text.replace(/`([^`]+)`/g, (_, code) => rgb("blue", code));
|
|
255
336
|
}
|
|
256
337
|
function wrapText(text, maxWidth) {
|
|
257
338
|
const out = [];
|
|
@@ -283,19 +364,19 @@ function blankLine() {
|
|
|
283
364
|
function buildBoxOutput(contentLines, borderColor) {
|
|
284
365
|
const width = Math.min(getTerminalWidth() - 2, 70);
|
|
285
366
|
const innerWidth = width - 2;
|
|
286
|
-
const top = `\u256D\u2500 ${
|
|
367
|
+
const top = `\u256D\u2500 ${dim(BOX_TITLE)} ${"\u2500".repeat(Math.max(0, innerWidth - BOX_TITLE.length - 4))}\u2500\u256E`;
|
|
287
368
|
const bottom = `\u2570${"\u2500".repeat(innerWidth)}\u256F`;
|
|
288
|
-
const sideChar =
|
|
369
|
+
const sideChar = rgb(borderColor, "\u2502");
|
|
289
370
|
const middle = contentLines.map((bl) => {
|
|
290
371
|
const padding = " ".repeat(Math.max(0, innerWidth - bl.raw.length - PAD_LEFT - PAD_RIGHT));
|
|
291
372
|
return `${sideChar}${" ".repeat(PAD_LEFT)}${bl.text}${padding}${" ".repeat(PAD_RIGHT)}${sideChar}`;
|
|
292
373
|
});
|
|
293
|
-
return [
|
|
374
|
+
return [rgb(borderColor, top), ...middle, rgb(borderColor, bottom)].join("\n");
|
|
294
375
|
}
|
|
295
376
|
function renderSection(def) {
|
|
296
377
|
const out = [];
|
|
297
378
|
const headerRaw = `\u25B8 ${def.header}`;
|
|
298
|
-
const headerStyled =
|
|
379
|
+
const headerStyled = def.dimHeader ? dimRgb(def.headerColor, headerRaw) : boldRgb(def.headerColor, headerRaw);
|
|
299
380
|
out.push(line(headerRaw, headerStyled));
|
|
300
381
|
const bodyMax = def.innerWidth - PAD_LEFT - PAD_RIGHT - 2;
|
|
301
382
|
const wrapped = wrapText(def.body, bodyMax);
|
|
@@ -309,19 +390,19 @@ function renderSection(def) {
|
|
|
309
390
|
function formatExplanationBox(inputs) {
|
|
310
391
|
const labels = getLabels(inputs.language);
|
|
311
392
|
const result = inputs.result;
|
|
312
|
-
const
|
|
393
|
+
const borderKey = riskBorderColor(result.risk);
|
|
313
394
|
const lines = [];
|
|
314
395
|
const innerWidth = Math.min(getTerminalWidth() - 2, 70) - 2;
|
|
315
396
|
lines.push(blankLine());
|
|
316
397
|
const filePathRaw = `\u{1F4C4} ${inputs.filePath}`;
|
|
317
|
-
const filePathStyled =
|
|
398
|
+
const filePathStyled = boldRgb("blue", filePathRaw);
|
|
318
399
|
lines.push(line(filePathRaw, filePathStyled));
|
|
319
400
|
if (result.isSamePattern) {
|
|
320
401
|
lines.push(blankLine());
|
|
321
402
|
const noteRaw = result.samePatternNote || labels.samePatternFallback;
|
|
322
403
|
const noteWrapped = wrapText(noteRaw, innerWidth - PAD_LEFT - PAD_RIGHT);
|
|
323
404
|
for (const w of noteWrapped) {
|
|
324
|
-
lines.push(line(w,
|
|
405
|
+
lines.push(line(w, dim(w)));
|
|
325
406
|
}
|
|
326
407
|
} else {
|
|
327
408
|
if (result.impact) {
|
|
@@ -334,7 +415,7 @@ function formatExplanationBox(inputs) {
|
|
|
334
415
|
} else {
|
|
335
416
|
const sec = renderSection({
|
|
336
417
|
header: labels.impact,
|
|
337
|
-
headerColor:
|
|
418
|
+
headerColor: "blue",
|
|
338
419
|
body: result.impact,
|
|
339
420
|
innerWidth
|
|
340
421
|
});
|
|
@@ -345,7 +426,7 @@ function formatExplanationBox(inputs) {
|
|
|
345
426
|
lines.push(blankLine());
|
|
346
427
|
const sec = renderSection({
|
|
347
428
|
header: labels.howItWorks,
|
|
348
|
-
headerColor:
|
|
429
|
+
headerColor: "green",
|
|
349
430
|
body: result.howItWorks,
|
|
350
431
|
innerWidth
|
|
351
432
|
});
|
|
@@ -355,7 +436,7 @@ function formatExplanationBox(inputs) {
|
|
|
355
436
|
lines.push(blankLine());
|
|
356
437
|
const sec = renderSection({
|
|
357
438
|
header: labels.why,
|
|
358
|
-
headerColor:
|
|
439
|
+
headerColor: "purple",
|
|
359
440
|
body: result.why,
|
|
360
441
|
innerWidth
|
|
361
442
|
});
|
|
@@ -364,7 +445,7 @@ function formatExplanationBox(inputs) {
|
|
|
364
445
|
if (inputs.detailLevel === "verbose" && result.deepDive && result.deepDive.length > 0) {
|
|
365
446
|
lines.push(blankLine());
|
|
366
447
|
const headerRaw = `\u25B8 ${labels.deepDive}`;
|
|
367
|
-
const headerStyled =
|
|
448
|
+
const headerStyled = dimRgb("white", headerRaw);
|
|
368
449
|
lines.push(line(headerRaw, headerStyled));
|
|
369
450
|
const itemMax = innerWidth - PAD_LEFT - PAD_RIGHT - 4;
|
|
370
451
|
for (const item of result.deepDive) {
|
|
@@ -382,28 +463,29 @@ function formatExplanationBox(inputs) {
|
|
|
382
463
|
lines.push(blankLine());
|
|
383
464
|
const dividerWidth = innerWidth - PAD_LEFT - PAD_RIGHT;
|
|
384
465
|
const dividerRaw = "\u2504".repeat(dividerWidth);
|
|
385
|
-
lines.push(line(dividerRaw,
|
|
466
|
+
lines.push(line(dividerRaw, dim(dividerRaw)));
|
|
386
467
|
lines.push(blankLine());
|
|
468
|
+
const riskKey = riskLabelColor(result.risk);
|
|
387
469
|
const riskHeaderRaw = `${stripAnsi(riskIcon(result.risk))} ${labels.risk}: ${riskLabelText(result.risk, labels)}`;
|
|
388
|
-
const riskHeaderStyled = `${riskIcon(result.risk)} ${
|
|
470
|
+
const riskHeaderStyled = `${riskIcon(result.risk)} ${boldRgb(riskKey, `${labels.risk}: ${riskLabelText(result.risk, labels)}`)}`;
|
|
389
471
|
lines.push(line(riskHeaderRaw, riskHeaderStyled));
|
|
390
472
|
if (result.risk !== "none" && result.riskReason) {
|
|
391
473
|
const reasonMax = innerWidth - PAD_LEFT - PAD_RIGHT - 3;
|
|
392
474
|
const wrapped = wrapText(result.riskReason, reasonMax);
|
|
393
475
|
for (const w of wrapped) {
|
|
394
476
|
const raw = ` ${w}`;
|
|
395
|
-
const styled = ` ${
|
|
477
|
+
const styled = ` ${dimRgb(riskKey, w)}`;
|
|
396
478
|
lines.push(line(raw, styled));
|
|
397
479
|
}
|
|
398
480
|
}
|
|
399
481
|
lines.push(blankLine());
|
|
400
|
-
return buildBoxOutput(lines,
|
|
482
|
+
return buildBoxOutput(lines, borderKey);
|
|
401
483
|
}
|
|
402
484
|
function formatSkipNotice(reason) {
|
|
403
|
-
return
|
|
485
|
+
return dim(`[code-explainer] skipped: ${reason}`);
|
|
404
486
|
}
|
|
405
487
|
function formatErrorNotice(problem, cause, fix) {
|
|
406
|
-
return
|
|
488
|
+
return rgb("yellow", `[code-explainer] ${problem}. ${cause}. Fix: ${fix}.`);
|
|
407
489
|
}
|
|
408
490
|
function formatDriftAlert(totalFiles, unrelatedFiles, userRequest, language = "en") {
|
|
409
491
|
const labels = getLabels(language);
|
|
@@ -411,7 +493,7 @@ function formatDriftAlert(totalFiles, unrelatedFiles, userRequest, language = "e
|
|
|
411
493
|
const innerWidth = Math.min(getTerminalWidth() - 2, 70) - 2;
|
|
412
494
|
lines.push(blankLine());
|
|
413
495
|
const headerRaw = `\u26A1 SESSION DRIFT`;
|
|
414
|
-
const headerStyled =
|
|
496
|
+
const headerStyled = boldRgb("yellow", headerRaw);
|
|
415
497
|
lines.push(line(headerRaw, headerStyled));
|
|
416
498
|
lines.push(blankLine());
|
|
417
499
|
const summaryRaw = `Claude has modified ${totalFiles} files this session.`;
|
|
@@ -421,21 +503,21 @@ function formatDriftAlert(totalFiles, unrelatedFiles, userRequest, language = "e
|
|
|
421
503
|
for (const file of unrelatedFiles) {
|
|
422
504
|
const truncated = file.length > innerWidth - 8 ? file.slice(0, innerWidth - 11) + "..." : file;
|
|
423
505
|
const raw = ` \u2022 ${truncated}`;
|
|
424
|
-
const styled = ` ${
|
|
506
|
+
const styled = ` ${rgb("yellow", "\u2022")} ${truncated}`;
|
|
425
507
|
lines.push(line(raw, styled));
|
|
426
508
|
}
|
|
427
509
|
if (userRequest) {
|
|
428
510
|
lines.push(blankLine());
|
|
429
511
|
const requestLines = wrapText(`Your request: "${userRequest}"`, innerWidth - PAD_LEFT - PAD_RIGHT);
|
|
430
512
|
for (const w of requestLines) {
|
|
431
|
-
lines.push(line(w,
|
|
513
|
+
lines.push(line(w, dim(w)));
|
|
432
514
|
}
|
|
433
515
|
}
|
|
434
516
|
lines.push(blankLine());
|
|
435
517
|
const noticeRaw = `\u26A0 Consider reviewing these changes.`;
|
|
436
|
-
lines.push(line(noticeRaw,
|
|
518
|
+
lines.push(line(noticeRaw, boldRgb("yellow", noticeRaw)));
|
|
437
519
|
lines.push(blankLine());
|
|
438
|
-
return buildBoxOutput(lines,
|
|
520
|
+
return buildBoxOutput(lines, "yellow");
|
|
439
521
|
}
|
|
440
522
|
function printToStderr(text) {
|
|
441
523
|
try {
|
|
@@ -454,15 +536,10 @@ function stripAnsi(s) {
|
|
|
454
536
|
|
|
455
537
|
// src/session/tracker.ts
|
|
456
538
|
var TWO_HOURS_MS = 2 * 60 * 60 * 1e3;
|
|
457
|
-
|
|
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
|
-
}
|
|
539
|
+
var CLEANUP_THROTTLE_MS = 60 * 1e3;
|
|
464
540
|
function getSessionFilePath(sessionId) {
|
|
465
|
-
|
|
541
|
+
assertSafeSessionId(sessionId);
|
|
542
|
+
return join3(getUserTmpDir(), `session-${sessionId}.jsonl`);
|
|
466
543
|
}
|
|
467
544
|
function recordEntry(sessionId, entry) {
|
|
468
545
|
const path = getSessionFilePath(sessionId);
|
|
@@ -473,7 +550,7 @@ function recordEntry(sessionId, entry) {
|
|
|
473
550
|
}
|
|
474
551
|
function readSession(sessionId) {
|
|
475
552
|
const path = getSessionFilePath(sessionId);
|
|
476
|
-
if (!
|
|
553
|
+
if (!existsSync3(path)) return [];
|
|
477
554
|
try {
|
|
478
555
|
const content = readFileSync2(path, "utf-8");
|
|
479
556
|
return content.split("\n").filter((l) => l.trim()).map((line2) => {
|
|
@@ -487,19 +564,34 @@ function readSession(sessionId) {
|
|
|
487
564
|
return [];
|
|
488
565
|
}
|
|
489
566
|
}
|
|
490
|
-
function getRecentSummaries(sessionId, n) {
|
|
491
|
-
const
|
|
492
|
-
if (
|
|
493
|
-
return
|
|
567
|
+
function getRecentSummaries(sessionId, n, entries) {
|
|
568
|
+
const all = entries ?? readSession(sessionId);
|
|
569
|
+
if (all.length === 0) return [];
|
|
570
|
+
return all.slice(-n).map((e) => `${e.file}: ${e.summary}`);
|
|
571
|
+
}
|
|
572
|
+
function getCleanupTimestampPath() {
|
|
573
|
+
return join3(getUserTmpDir(), ".last-cleanup");
|
|
494
574
|
}
|
|
495
575
|
function cleanStaleSessionFiles() {
|
|
496
576
|
try {
|
|
497
|
-
const
|
|
577
|
+
const tsPath = getCleanupTimestampPath();
|
|
498
578
|
const now = Date.now();
|
|
579
|
+
if (existsSync3(tsPath)) {
|
|
580
|
+
try {
|
|
581
|
+
const ts = parseInt(readFileSync2(tsPath, "utf-8").trim(), 10);
|
|
582
|
+
if (!isNaN(ts) && now - ts < CLEANUP_THROTTLE_MS) return;
|
|
583
|
+
} catch {
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
try {
|
|
587
|
+
writeFileSync2(tsPath, String(now), { mode: 384 });
|
|
588
|
+
} catch {
|
|
589
|
+
}
|
|
590
|
+
const dir = getUserTmpDir();
|
|
499
591
|
const entries = readdirSync(dir);
|
|
500
592
|
for (const name of entries) {
|
|
501
593
|
if (!name.startsWith("session-") && !name.startsWith("cache-")) continue;
|
|
502
|
-
const filePath =
|
|
594
|
+
const filePath = join3(dir, name);
|
|
503
595
|
try {
|
|
504
596
|
const stat = statSync(filePath);
|
|
505
597
|
if (now - stat.mtimeMs > TWO_HOURS_MS) {
|
|
@@ -516,33 +608,56 @@ function getSessionIdFromEnv() {
|
|
|
516
608
|
}
|
|
517
609
|
function findLatestSession() {
|
|
518
610
|
try {
|
|
519
|
-
const dir =
|
|
611
|
+
const dir = getUserTmpDir();
|
|
520
612
|
const entries = readdirSync(dir).filter((n) => n.startsWith("session-") && n.endsWith(".jsonl")).map((n) => ({
|
|
521
613
|
name: n,
|
|
522
614
|
id: n.slice("session-".length, -".jsonl".length),
|
|
523
|
-
mtime: statSync(
|
|
615
|
+
mtime: statSync(join3(dir, n)).mtimeMs
|
|
524
616
|
})).sort((a, b) => b.mtime - a.mtime);
|
|
525
617
|
return entries[0]?.id;
|
|
526
618
|
} catch {
|
|
527
619
|
return void 0;
|
|
528
620
|
}
|
|
529
621
|
}
|
|
530
|
-
async function printSummary() {
|
|
622
|
+
async function printSummary({ json = false } = {}) {
|
|
531
623
|
const sessionId = getSessionIdFromEnv() ?? findLatestSession();
|
|
532
624
|
if (!sessionId) {
|
|
533
|
-
|
|
625
|
+
if (json) {
|
|
626
|
+
process.stdout.write(JSON.stringify({ error: "No active session found" }) + "\n");
|
|
627
|
+
} else {
|
|
628
|
+
process.stderr.write("[code-explainer] No active session found. Session data is created when Claude Code makes changes.\n");
|
|
629
|
+
}
|
|
534
630
|
return;
|
|
535
631
|
}
|
|
536
632
|
const entries = readSession(sessionId);
|
|
537
633
|
if (entries.length === 0) {
|
|
538
|
-
|
|
634
|
+
if (json) {
|
|
635
|
+
process.stdout.write(JSON.stringify({ sessionId, totalChanges: 0, files: [], risks: { none: 0, low: 0, medium: 0, high: 0 } }) + "\n");
|
|
636
|
+
} else {
|
|
637
|
+
process.stderr.write(`[code-explainer] Session '${sessionId}' has no recorded changes yet.
|
|
539
638
|
`);
|
|
639
|
+
}
|
|
540
640
|
return;
|
|
541
641
|
}
|
|
542
642
|
const related = entries.filter((e) => !e.unrelated);
|
|
543
643
|
const unrelated = entries.filter((e) => e.unrelated);
|
|
544
644
|
const uniqueFiles = Array.from(new Set(entries.map((e) => e.file)));
|
|
545
645
|
const unrelatedFiles = Array.from(new Set(unrelated.map((e) => e.file)));
|
|
646
|
+
const risks = { none: 0, low: 0, medium: 0, high: 0 };
|
|
647
|
+
for (const e of entries) risks[e.risk]++;
|
|
648
|
+
if (json) {
|
|
649
|
+
process.stdout.write(JSON.stringify({
|
|
650
|
+
sessionId,
|
|
651
|
+
totalChanges: entries.length,
|
|
652
|
+
filesCount: uniqueFiles.length,
|
|
653
|
+
relatedChanges: related.length,
|
|
654
|
+
unrelatedChanges: unrelated.length,
|
|
655
|
+
unrelatedFiles,
|
|
656
|
+
risks,
|
|
657
|
+
entries: entries.map((e) => ({ file: e.file, risk: e.risk, summary: e.summary, unrelated: !!e.unrelated }))
|
|
658
|
+
}, null, 2) + "\n");
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
546
661
|
const alert = formatDriftAlert(uniqueFiles.length, unrelatedFiles);
|
|
547
662
|
printToStderr(alert);
|
|
548
663
|
process.stderr.write(`
|
|
@@ -554,8 +669,6 @@ Total changes: ${entries.length}
|
|
|
554
669
|
`);
|
|
555
670
|
process.stderr.write(`Unrelated/risky: ${unrelated.length}
|
|
556
671
|
`);
|
|
557
|
-
const risks = { none: 0, low: 0, medium: 0, high: 0 };
|
|
558
|
-
for (const e of entries) risks[e.risk]++;
|
|
559
672
|
process.stderr.write(`
|
|
560
673
|
Risk breakdown:
|
|
561
674
|
`);
|
|
@@ -575,7 +688,7 @@ async function endSession() {
|
|
|
575
688
|
return;
|
|
576
689
|
}
|
|
577
690
|
const sessionPath = getSessionFilePath(sessionId);
|
|
578
|
-
if (
|
|
691
|
+
if (existsSync3(sessionPath)) {
|
|
579
692
|
try {
|
|
580
693
|
unlinkSync2(sessionPath);
|
|
581
694
|
} catch {
|
|
@@ -587,6 +700,7 @@ async function endSession() {
|
|
|
587
700
|
}
|
|
588
701
|
|
|
589
702
|
export {
|
|
703
|
+
isSafeSessionId,
|
|
590
704
|
getCached,
|
|
591
705
|
setCached,
|
|
592
706
|
formatExplanationBox,
|
|
@@ -601,4 +715,4 @@ export {
|
|
|
601
715
|
printSummary,
|
|
602
716
|
endSession
|
|
603
717
|
};
|
|
604
|
-
//# sourceMappingURL=chunk-
|
|
718
|
+
//# sourceMappingURL=chunk-VJN7Y4SI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/session/tracker.ts","../src/cache/explanation-cache.ts","../src/session/session-id.ts","../src/session/tmpdir.ts","../src/format/box.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, appendFileSync, unlinkSync, readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { RiskLevel } from \"../config/schema.js\";\nimport { clearCache } from \"../cache/explanation-cache.js\";\nimport { formatDriftAlert, printToStderr } from \"../format/box.js\";\nimport { assertSafeSessionId } from \"./session-id.js\";\nimport { getUserTmpDir } from \"./tmpdir.js\";\n\nconst TWO_HOURS_MS = 2 * 60 * 60 * 1000;\n// Minimum interval between stale-file cleanup runs to avoid stat-ing the\n// tmpdir on every hook invocation (which fires for every Edit/Write/Bash).\nconst CLEANUP_THROTTLE_MS = 60 * 1000;\n\nexport interface SessionEntry {\n file: string;\n timestamp: number;\n risk: RiskLevel;\n summary: string;\n unrelated?: boolean;\n}\n\nexport function getSessionFilePath(sessionId: string): string {\n assertSafeSessionId(sessionId);\n return join(getUserTmpDir(), `session-${sessionId}.jsonl`);\n}\n\nexport function recordEntry(sessionId: string, entry: SessionEntry): void {\n const path = getSessionFilePath(sessionId);\n try {\n appendFileSync(path, JSON.stringify(entry) + \"\\n\", { mode: 0o600 });\n } catch {\n // Non-fatal\n }\n}\n\nexport function readSession(sessionId: string): SessionEntry[] {\n const path = getSessionFilePath(sessionId);\n if (!existsSync(path)) return [];\n\n try {\n const content = readFileSync(path, \"utf-8\");\n return content\n .split(\"\\n\")\n .filter((l) => l.trim())\n .map((line) => {\n try {\n return JSON.parse(line) as SessionEntry;\n } catch {\n return null;\n }\n })\n .filter((e): e is SessionEntry => e !== null);\n } catch {\n return [];\n }\n}\n\n/**\n * Get the last N recorded summaries for this session, oldest-first.\n * Used to feed prompt context for \"same pattern\" detection.\n *\n * Pass `entries` if you've already called readSession() to avoid a second\n * disk read within the same hook invocation.\n */\nexport function getRecentSummaries(sessionId: string, n: number, entries?: SessionEntry[]): string[] {\n const all = entries ?? readSession(sessionId);\n if (all.length === 0) return [];\n return all.slice(-n).map((e) => `${e.file}: ${e.summary}`);\n}\n\nfunction getCleanupTimestampPath(): string {\n return join(getUserTmpDir(), \".last-cleanup\");\n}\n\nexport function cleanStaleSessionFiles(): void {\n try {\n const tsPath = getCleanupTimestampPath();\n const now = Date.now();\n\n // Throttle: skip if we cleaned up recently.\n if (existsSync(tsPath)) {\n try {\n const ts = parseInt(readFileSync(tsPath, \"utf-8\").trim(), 10);\n if (!isNaN(ts) && now - ts < CLEANUP_THROTTLE_MS) return;\n } catch {\n // If the timestamp file is malformed, proceed with cleanup.\n }\n }\n\n // Record the timestamp before cleaning so concurrent invocations see it.\n try {\n writeFileSync(tsPath, String(now), { mode: 0o600 });\n } catch {\n // Non-fatal — proceed with cleanup even if we can't update the timestamp.\n }\n\n const dir = getUserTmpDir();\n const entries = readdirSync(dir);\n for (const name of entries) {\n if (!name.startsWith(\"session-\") && !name.startsWith(\"cache-\")) continue;\n const filePath = join(dir, name);\n try {\n const stat = statSync(filePath);\n if (now - stat.mtimeMs > TWO_HOURS_MS) {\n unlinkSync(filePath);\n }\n } catch {\n // ignore\n }\n }\n } catch {\n // ignore\n }\n}\n\nfunction getSessionIdFromEnv(): string | undefined {\n return process.env.CODE_EXPLAINER_SESSION_ID;\n}\n\nfunction findLatestSession(): string | undefined {\n try {\n const dir = getUserTmpDir();\n const entries = readdirSync(dir)\n .filter((n) => n.startsWith(\"session-\") && n.endsWith(\".jsonl\"))\n .map((n) => ({\n name: n,\n id: n.slice(\"session-\".length, -\".jsonl\".length),\n mtime: statSync(join(dir, n)).mtimeMs,\n }))\n .sort((a, b) => b.mtime - a.mtime);\n return entries[0]?.id;\n } catch {\n return undefined;\n }\n}\n\nexport async function printSummary({ json = false }: { json?: boolean } = {}): Promise<void> {\n const sessionId = getSessionIdFromEnv() ?? findLatestSession();\n if (!sessionId) {\n if (json) {\n process.stdout.write(JSON.stringify({ error: \"No active session found\" }) + \"\\n\");\n } else {\n process.stderr.write(\"[code-explainer] No active session found. Session data is created when Claude Code makes changes.\\n\");\n }\n return;\n }\n\n const entries = readSession(sessionId);\n if (entries.length === 0) {\n if (json) {\n process.stdout.write(JSON.stringify({ sessionId, totalChanges: 0, files: [], risks: { none: 0, low: 0, medium: 0, high: 0 } }) + \"\\n\");\n } else {\n process.stderr.write(`[code-explainer] Session '${sessionId}' has no recorded changes yet.\\n`);\n }\n return;\n }\n\n const related = entries.filter((e) => !e.unrelated);\n const unrelated = entries.filter((e) => e.unrelated);\n const uniqueFiles = Array.from(new Set(entries.map((e) => e.file)));\n const unrelatedFiles = Array.from(new Set(unrelated.map((e) => e.file)));\n\n const risks: Record<RiskLevel, number> = { none: 0, low: 0, medium: 0, high: 0 };\n for (const e of entries) risks[e.risk]++;\n\n if (json) {\n process.stdout.write(JSON.stringify({\n sessionId,\n totalChanges: entries.length,\n filesCount: uniqueFiles.length,\n relatedChanges: related.length,\n unrelatedChanges: unrelated.length,\n unrelatedFiles,\n risks,\n entries: entries.map((e) => ({ file: e.file, risk: e.risk, summary: e.summary, unrelated: !!e.unrelated })),\n }, null, 2) + \"\\n\");\n return;\n }\n\n const alert = formatDriftAlert(uniqueFiles.length, unrelatedFiles);\n printToStderr(alert);\n\n process.stderr.write(`\\nTotal changes: ${entries.length}\\n`);\n process.stderr.write(`Files touched: ${uniqueFiles.length}\\n`);\n process.stderr.write(`Related changes: ${related.length}\\n`);\n process.stderr.write(`Unrelated/risky: ${unrelated.length}\\n`);\n\n process.stderr.write(`\\nRisk breakdown:\\n`);\n process.stderr.write(` None: ${risks.none}\\n`);\n process.stderr.write(` Low: ${risks.low}\\n`);\n process.stderr.write(` Medium: ${risks.medium}\\n`);\n process.stderr.write(` High: ${risks.high}\\n`);\n}\n\nexport async function endSession(): Promise<void> {\n const sessionId = getSessionIdFromEnv() ?? findLatestSession();\n if (!sessionId) {\n process.stderr.write(\"[code-explainer] No active session to end.\\n\");\n return;\n }\n\n const sessionPath = getSessionFilePath(sessionId);\n if (existsSync(sessionPath)) {\n try {\n unlinkSync(sessionPath);\n } catch {\n // ignore\n }\n }\n clearCache(sessionId);\n process.stderr.write(`[code-explainer] Session '${sessionId}' ended. State cleared.\\n`);\n}\n","import { createHash } from \"node:crypto\";\nimport { existsSync, readFileSync, appendFileSync, writeFileSync, renameSync, unlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ExplanationResult } from \"../config/schema.js\";\nimport { assertSafeSessionId } from \"../session/session-id.js\";\nimport { getUserTmpDir } from \"../session/tmpdir.js\";\n\n// Rotate the JSONL cache file when it reaches this many lines to prevent\n// unbounded growth within a single long session.\nconst CACHE_ROTATE_THRESHOLD = 500;\n// After rotation, keep the N most recent unique entries (by hash).\nconst CACHE_COMPACT_TARGET = 250;\n\nexport function getCacheFilePath(sessionId: string): string {\n assertSafeSessionId(sessionId);\n return join(getUserTmpDir(), `cache-${sessionId}.jsonl`);\n}\n\nexport function hashDiff(diff: string): string {\n return createHash(\"sha256\").update(diff, \"utf-8\").digest(\"hex\");\n}\n\ninterface CacheEntry {\n hash: string;\n result: ExplanationResult;\n}\n\n/**\n * If the cache file has grown beyond CACHE_ROTATE_THRESHOLD lines, compact\n * it: deduplicate by hash (keeping the last occurrence) and write back the\n * CACHE_COMPACT_TARGET most recent unique entries atomically via a .tmp file.\n *\n * Atomic write (writeFileSync + renameSync) prevents a crash mid-write from\n * leaving a truncated cache. Non-fatal — any error is silently swallowed.\n *\n * Note: concurrent hook invocations can both pass the size check and attempt\n * to compact the same file. The last rename wins; both will produce a valid\n * compacted file, so data integrity is preserved without a lockfile.\n */\nfunction rotateCacheIfNeeded(path: string): void {\n try {\n const content = readFileSync(path, \"utf-8\");\n const lines = content.split(\"\\n\").filter((l) => l.trim());\n if (lines.length <= CACHE_ROTATE_THRESHOLD) return;\n\n // Deduplicate by hash — later lines overwrite earlier (newest wins).\n const seen = new Map<string, CacheEntry>();\n for (const line of lines) {\n try {\n const entry = JSON.parse(line) as CacheEntry;\n seen.set(entry.hash, entry);\n } catch {\n // skip malformed lines\n }\n }\n\n // Keep the CACHE_COMPACT_TARGET most recent unique entries.\n const unique = Array.from(seen.values());\n const compacted = unique.slice(-CACHE_COMPACT_TARGET);\n\n const tmp = path + \".tmp\";\n writeFileSync(tmp, compacted.map((e) => JSON.stringify(e)).join(\"\\n\") + \"\\n\", { mode: 0o600 });\n renameSync(tmp, path);\n } catch {\n // Non-fatal — rotation failure just means the file keeps growing.\n }\n}\n\nexport function getCached(sessionId: string, diff: string): ExplanationResult | undefined {\n const path = getCacheFilePath(sessionId);\n if (!existsSync(path)) return undefined;\n\n const hash = hashDiff(diff);\n try {\n const content = readFileSync(path, \"utf-8\");\n const lines = content.split(\"\\n\").filter((l) => l.trim());\n\n // Iterate in reverse so the most recent entry wins on duplicates.\n for (let i = lines.length - 1; i >= 0; i--) {\n try {\n const entry = JSON.parse(lines[i]) as CacheEntry;\n if (entry.hash === hash) {\n return entry.result;\n }\n } catch {\n // Skip malformed line\n }\n }\n } catch {\n return undefined;\n }\n return undefined;\n}\n\nexport function setCached(sessionId: string, diff: string, result: ExplanationResult): void {\n const path = getCacheFilePath(sessionId);\n const entry: CacheEntry = { hash: hashDiff(diff), result };\n try {\n appendFileSync(path, JSON.stringify(entry) + \"\\n\", { mode: 0o600 });\n rotateCacheIfNeeded(path);\n } catch {\n // Cache write failures are non-fatal\n }\n}\n\nexport function clearCache(sessionId: string): void {\n const path = getCacheFilePath(sessionId);\n if (existsSync(path)) {\n try {\n unlinkSync(path);\n } catch {\n // ignore\n }\n }\n}\n","/**\n * Session ID validation. Claude Code generates UUID-style IDs, but the\n * value is untrusted input from the hook's stdin — an attacker-controlled\n * session_id like `../../evil` would escape the user tmpdir and drop files\n * at arbitrary paths. Reject anything outside [A-Za-z0-9_-]{1,64}.\n */\nconst SAFE_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;\n\nexport function isSafeSessionId(id: unknown): id is string {\n return typeof id === \"string\" && SAFE_ID_PATTERN.test(id);\n}\n\n/**\n * Defence-in-depth: throw when an unsafe ID reaches a path builder.\n * The hook's parsePayload already rejects unsafe IDs, so this is only\n * reachable via an internal caller that forgot to validate.\n */\nexport function assertSafeSessionId(id: string): void {\n if (!isSafeSessionId(id)) {\n throw new Error(`unsafe session id: ${JSON.stringify(id)}`);\n }\n}\n","import { existsSync, mkdirSync } from \"node:fs\";\nimport { tmpdir, userInfo } from \"node:os\";\nimport { join } from \"node:path\";\n\n/**\n * Per-user tmp subdirectory for session and cache files.\n * Suffixes with the OS username (works cross-platform; `process.getuid`\n * is undefined on Windows) so a shared %TEMP% on a multi-user Windows\n * box does not leak one user's session state into another's.\n */\nexport function getUserTmpDir(): string {\n let suffix: string;\n try {\n const info = userInfo();\n suffix = typeof info.username === \"string\" && info.username ? info.username : \"user\";\n } catch {\n suffix = \"user\";\n }\n // Defensive: keep the suffix itself path-safe.\n suffix = suffix.replace(/[^A-Za-z0-9_-]/g, \"_\").slice(0, 64) || \"user\";\n const dir = join(tmpdir(), `code-explainer-${suffix}`);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n return dir;\n}\n","import type { ExplanationResult, Language, RiskLevel, DetailLevel } from \"../config/schema.js\";\n\n// ===========================================================================\n// Section header translations per language\n// ===========================================================================\n\ninterface SectionLabels {\n impact: string;\n howItWorks: string;\n why: string;\n deepDive: string;\n risk: string;\n riskNone: string;\n riskLow: string;\n riskMedium: string;\n riskHigh: string;\n samePatternFallback: string;\n}\n\nconst LABELS: Record<Language, SectionLabels> = {\n en: {\n impact: \"Impact\",\n howItWorks: \"How it works\",\n why: \"Why\",\n deepDive: \"Deeper dive\",\n risk: \"Risk\",\n riskNone: \"None\",\n riskLow: \"Low\",\n riskMedium: \"Medium\",\n riskHigh: \"High\",\n samePatternFallback: \"Same pattern as before applied to this file.\",\n },\n pt: {\n impact: \"Impacto\",\n howItWorks: \"Como funciona\",\n why: \"Por que\",\n deepDive: \"Pra aprofundar\",\n risk: \"Risco\",\n riskNone: \"Nenhum\",\n riskLow: \"Baixo\",\n riskMedium: \"M\\u00e9dio\",\n riskHigh: \"Alto\",\n samePatternFallback: \"Mesmo padr\\u00e3o anterior aplicado a este arquivo.\",\n },\n es: {\n impact: \"Impacto\",\n howItWorks: \"C\\u00f3mo funciona\",\n why: \"Por qu\\u00e9\",\n deepDive: \"Para profundizar\",\n risk: \"Riesgo\",\n riskNone: \"Ninguno\",\n riskLow: \"Bajo\",\n riskMedium: \"Medio\",\n riskHigh: \"Alto\",\n samePatternFallback: \"Mismo patr\\u00f3n anterior aplicado a este archivo.\",\n },\n fr: {\n impact: \"Impact\",\n howItWorks: \"Comment \\u00e7a marche\",\n why: \"Pourquoi\",\n deepDive: \"Pour approfondir\",\n risk: \"Risque\",\n riskNone: \"Aucun\",\n riskLow: \"Faible\",\n riskMedium: \"Moyen\",\n riskHigh: \"\\u00c9lev\\u00e9\",\n samePatternFallback: \"M\\u00eame motif que pr\\u00e9c\\u00e9demment, appliqu\\u00e9 \\u00e0 ce fichier.\",\n },\n de: {\n impact: \"Auswirkung\",\n howItWorks: \"Wie es funktioniert\",\n why: \"Warum\",\n deepDive: \"Mehr lernen\",\n risk: \"Risiko\",\n riskNone: \"Keines\",\n riskLow: \"Gering\",\n riskMedium: \"Mittel\",\n riskHigh: \"Hoch\",\n samePatternFallback: \"Gleiches Muster wie zuvor auf diese Datei angewendet.\",\n },\n it: {\n impact: \"Impatto\",\n howItWorks: \"Come funziona\",\n why: \"Perch\\u00e9\",\n deepDive: \"Per approfondire\",\n risk: \"Rischio\",\n riskNone: \"Nessuno\",\n riskLow: \"Basso\",\n riskMedium: \"Medio\",\n riskHigh: \"Alto\",\n samePatternFallback: \"Stesso schema applicato a questo file.\",\n },\n zh: {\n impact: \"\\u5f71\\u54cd\",\n howItWorks: \"\\u5982\\u4f55\\u5de5\\u4f5c\",\n why: \"\\u4e3a\\u4ec0\\u4e48\",\n deepDive: \"\\u6df1\\u5165\\u5b66\\u4e60\",\n risk: \"\\u98ce\\u9669\",\n riskNone: \"\\u65e0\",\n riskLow: \"\\u4f4e\",\n riskMedium: \"\\u4e2d\",\n riskHigh: \"\\u9ad8\",\n samePatternFallback: \"\\u540c\\u6837\\u7684\\u6a21\\u5f0f\\u5e94\\u7528\\u5230\\u6b64\\u6587\\u4ef6\\u3002\",\n },\n ja: {\n impact: \"\\u5f71\\u97ff\",\n howItWorks: \"\\u4ed5\\u7d44\\u307f\",\n why: \"\\u306a\\u305c\",\n deepDive: \"\\u3055\\u3089\\u306b\\u5b66\\u3076\",\n risk: \"\\u30ea\\u30b9\\u30af\",\n riskNone: \"\\u306a\\u3057\",\n riskLow: \"\\u4f4e\",\n riskMedium: \"\\u4e2d\",\n riskHigh: \"\\u9ad8\",\n samePatternFallback: \"\\u4ee5\\u524d\\u3068\\u540c\\u3058\\u30d1\\u30bf\\u30fc\\u30f3\\u3092\\u3053\\u306e\\u30d5\\u30a1\\u30a4\\u30eb\\u306b\\u9069\\u7528\\u3002\",\n },\n ko: {\n impact: \"\\uc601\\ud5a5\",\n howItWorks: \"\\uc791\\ub3d9 \\ubc29\\uc2dd\",\n why: \"\\uc774\\uc720\",\n deepDive: \"\\ub354 \\uc54c\\uc544\\ubcf4\\uae30\",\n risk: \"\\uc704\\ud5d8\",\n riskNone: \"\\uc5c6\\uc74c\",\n riskLow: \"\\ub0ae\\uc74c\",\n riskMedium: \"\\ubcf4\\ud1b5\",\n riskHigh: \"\\ub192\\uc74c\",\n samePatternFallback: \"\\uc774\\uc804\\uacfc \\ub3d9\\uc77c\\ud55c \\ud328\\ud134\\uc774 \\uc774 \\ud30c\\uc77c\\uc5d0 \\uc801\\uc6a9\\ub418\\uc5c8\\uc2b5\\ub2c8\\ub2e4.\",\n },\n};\n\nfunction getLabels(language: Language): SectionLabels {\n return LABELS[language] ?? LABELS.en;\n}\n\n// ===========================================================================\n// Color helpers — soft palette via truecolor (24-bit) escapes.\n// Most modern terminals (Windows Terminal, iTerm2, VS Code, gnome-terminal)\n// support truecolor. NO_COLOR and TERM=dumb still produce plain text.\n// ===========================================================================\n\nconst RESET = \"\\u001b[0m\";\nconst BOLD = \"\\u001b[1m\";\nconst DIM = \"\\u001b[2m\";\n\n// Project palette (softer than saturated ANSI)\nconst PALETTE = {\n blue: [91, 158, 245], // #5B9EF5\n green: [91, 245, 160], // #5BF5A0\n yellow: [245, 200, 91], // #F5C85B\n red: [245, 91, 91], // #F55B5B\n purple: [224, 91, 245], // #E05BF5\n white: [255, 255, 255], // #FFFFFF\n} as const;\n\ntype PaletteKey = keyof typeof PALETTE;\n\nfunction isNoColor(): boolean {\n return \"NO_COLOR\" in process.env || process.env.TERM === \"dumb\";\n}\n\nfunction rgb(name: PaletteKey, text: string): string {\n if (isNoColor()) return text;\n const [r, g, b] = PALETTE[name];\n return `\\u001b[38;2;${r};${g};${b}m${text}${RESET}`;\n}\n\nfunction bold(text: string): string {\n if (isNoColor()) return text;\n return `${BOLD}${text}${RESET}`;\n}\n\nfunction dim(text: string): string {\n if (isNoColor()) return text;\n return `${DIM}${text}${RESET}`;\n}\n\nfunction boldRgb(name: PaletteKey, text: string): string {\n if (isNoColor()) return text;\n const [r, g, b] = PALETTE[name];\n return `${BOLD}\\u001b[38;2;${r};${g};${b}m${text}${RESET}`;\n}\n\nfunction dimRgb(name: PaletteKey, text: string): string {\n if (isNoColor()) return text;\n const [r, g, b] = PALETTE[name];\n return `${DIM}\\u001b[38;2;${r};${g};${b}m${text}${RESET}`;\n}\n\nfunction getTerminalWidth(): number {\n return process.stderr.columns || 80;\n}\n\n// ===========================================================================\n// Risk presentation\n// ===========================================================================\n\nfunction riskBorderColor(risk: RiskLevel): PaletteKey {\n switch (risk) {\n case \"none\": return \"green\";\n case \"low\": return \"yellow\";\n case \"medium\": return \"yellow\";\n case \"high\": return \"red\";\n }\n}\n\nfunction riskIcon(risk: RiskLevel): string {\n if (isNoColor()) {\n switch (risk) {\n case \"none\": return \"[OK]\";\n case \"low\": return \"[!]\";\n case \"medium\": return \"[!!]\";\n case \"high\": return \"[!!!]\";\n }\n }\n switch (risk) {\n case \"none\": return rgb(\"green\", \"\\u2713\");\n case \"low\": return rgb(\"yellow\", \"\\u26a0\");\n case \"medium\": return rgb(\"yellow\", \"\\u26a0\");\n case \"high\": return rgb(\"red\", \"\\u{1F6A8}\");\n }\n}\n\nfunction riskLabelText(risk: RiskLevel, labels: SectionLabels): string {\n switch (risk) {\n case \"none\": return labels.riskNone;\n case \"low\": return labels.riskLow;\n case \"medium\": return labels.riskMedium;\n case \"high\": return labels.riskHigh;\n }\n}\n\nfunction riskLabelColor(risk: RiskLevel): PaletteKey {\n switch (risk) {\n case \"none\": return \"green\";\n case \"low\": return \"yellow\";\n case \"medium\": return \"yellow\";\n case \"high\": return \"red\";\n }\n}\n\n// ===========================================================================\n// Inline code highlighting (`backticks` -> soft blue)\n// ===========================================================================\n\nfunction highlightInlineCode(text: string): string {\n if (isNoColor()) return text;\n return text.replace(/`([^`]+)`/g, (_, code: string) => rgb(\"blue\", code));\n}\n\n// ===========================================================================\n// Word wrap that respects a content width (no ANSI awareness needed since\n// we wrap BEFORE adding color)\n// ===========================================================================\n\nfunction wrapText(text: string, maxWidth: number): string[] {\n const out: string[] = [];\n for (const raw of text.split(\"\\n\")) {\n if (raw.length <= maxWidth) {\n out.push(raw);\n continue;\n }\n let remaining = raw;\n while (remaining.length > maxWidth) {\n let breakAt = remaining.lastIndexOf(\" \", maxWidth);\n if (breakAt <= 0) breakAt = maxWidth;\n out.push(remaining.slice(0, breakAt));\n remaining = remaining.slice(breakAt).trimStart();\n }\n if (remaining) out.push(remaining);\n }\n return out;\n}\n\n// ===========================================================================\n// Box construction\n// ===========================================================================\n\nconst BOX_TITLE = \"vibe-code-explainer\";\nconst PAD_LEFT = 2;\nconst PAD_RIGHT = 2;\n\ninterface BoxLine {\n text: string; // Already styled\n raw: string; // Raw (uncolored) version, used for width calculation\n}\n\nfunction line(raw: string, styled?: string): BoxLine {\n return { text: styled ?? raw, raw };\n}\n\nfunction blankLine(): BoxLine {\n return line(\"\");\n}\n\nfunction buildBoxOutput(\n contentLines: BoxLine[],\n borderColor: PaletteKey\n): string {\n const width = Math.min(getTerminalWidth() - 2, 70);\n const innerWidth = width - 2; // chars between │ │\n\n const top = `\\u256d\\u2500 ${dim(BOX_TITLE)} ${\"\\u2500\".repeat(Math.max(0, innerWidth - BOX_TITLE.length - 4))}\\u2500\\u256e`;\n const bottom = `\\u2570${\"\\u2500\".repeat(innerWidth)}\\u256f`;\n\n const sideChar = rgb(borderColor, \"\\u2502\");\n\n const middle = contentLines.map((bl) => {\n const padding = \" \".repeat(Math.max(0, innerWidth - bl.raw.length - PAD_LEFT - PAD_RIGHT));\n return `${sideChar}${\" \".repeat(PAD_LEFT)}${bl.text}${padding}${\" \".repeat(PAD_RIGHT)}${sideChar}`;\n });\n\n return [rgb(borderColor, top), ...middle, rgb(borderColor, bottom)].join(\"\\n\");\n}\n\n// ===========================================================================\n// Section rendering\n// ===========================================================================\n\ninterface SectionDef {\n header: string;\n headerColor: PaletteKey;\n body: string;\n innerWidth: number;\n dimHeader?: boolean;\n}\n\nfunction renderSection(def: SectionDef): BoxLine[] {\n const out: BoxLine[] = [];\n const headerRaw = `\\u25b8 ${def.header}`;\n const headerStyled = def.dimHeader\n ? dimRgb(def.headerColor, headerRaw)\n : boldRgb(def.headerColor, headerRaw);\n out.push(line(headerRaw, headerStyled));\n\n const bodyMax = def.innerWidth - PAD_LEFT - PAD_RIGHT - 2; // 2 = body indent\n const wrapped = wrapText(def.body, bodyMax);\n for (const w of wrapped) {\n const indented = ` ${w}`;\n const styled = ` ${highlightInlineCode(w)}`;\n out.push(line(indented, styled));\n }\n\n return out;\n}\n\n// ===========================================================================\n// Public API\n// ===========================================================================\n\nexport interface BoxInputs {\n filePath: string;\n result: ExplanationResult;\n detailLevel: DetailLevel;\n language: Language;\n}\n\nexport function formatExplanationBox(inputs: BoxInputs): string {\n const labels = getLabels(inputs.language);\n const result = inputs.result;\n const borderKey = riskBorderColor(result.risk);\n const lines: BoxLine[] = [];\n const innerWidth = Math.min(getTerminalWidth() - 2, 70) - 2;\n\n lines.push(blankLine());\n\n // File path with 📄 icon, soft blue + bold\n const filePathRaw = `\\ud83d\\udcc4 ${inputs.filePath}`;\n const filePathStyled = boldRgb(\"blue\", filePathRaw);\n lines.push(line(filePathRaw, filePathStyled));\n\n // Same-pattern collapse: short note, no teaching sections\n if (result.isSamePattern) {\n lines.push(blankLine());\n const noteRaw = result.samePatternNote || labels.samePatternFallback;\n const noteWrapped = wrapText(noteRaw, innerWidth - PAD_LEFT - PAD_RIGHT);\n for (const w of noteWrapped) {\n lines.push(line(w, dim(w)));\n }\n } else {\n // Impact (always shown when not collapsed)\n if (result.impact) {\n lines.push(blankLine());\n if (inputs.detailLevel === \"minimal\") {\n // Minimal: no header, just the text\n const wrapped = wrapText(result.impact, innerWidth - PAD_LEFT - PAD_RIGHT);\n for (const w of wrapped) {\n lines.push(line(w, highlightInlineCode(w)));\n }\n } else {\n const sec = renderSection({\n header: labels.impact,\n headerColor: \"blue\",\n body: result.impact,\n innerWidth,\n });\n lines.push(...sec);\n }\n }\n\n // How it works (standard + verbose)\n if (inputs.detailLevel !== \"minimal\" && result.howItWorks) {\n lines.push(blankLine());\n const sec = renderSection({\n header: labels.howItWorks,\n headerColor: \"green\",\n body: result.howItWorks,\n innerWidth,\n });\n lines.push(...sec);\n }\n\n // Why (standard + verbose)\n if (inputs.detailLevel !== \"minimal\" && result.why) {\n lines.push(blankLine());\n const sec = renderSection({\n header: labels.why,\n headerColor: \"purple\",\n body: result.why,\n innerWidth,\n });\n lines.push(...sec);\n }\n\n // Deep dive (verbose only) — uses white-dim header to sit quieter\n if (\n inputs.detailLevel === \"verbose\" &&\n result.deepDive &&\n result.deepDive.length > 0\n ) {\n lines.push(blankLine());\n const headerRaw = `\\u25b8 ${labels.deepDive}`;\n const headerStyled = dimRgb(\"white\", headerRaw);\n lines.push(line(headerRaw, headerStyled));\n const itemMax = innerWidth - PAD_LEFT - PAD_RIGHT - 4;\n for (const item of result.deepDive) {\n const text = `${item.term}: ${item.explanation}`;\n const wrapped = wrapText(text, itemMax);\n for (let i = 0; i < wrapped.length; i++) {\n const prefix = i === 0 ? \" \\u2014 \" : \" \";\n const raw = `${prefix}${wrapped[i]}`;\n const styled = `${prefix}${highlightInlineCode(wrapped[i])}`;\n lines.push(line(raw, styled));\n }\n }\n }\n }\n\n // Divider before risk\n lines.push(blankLine());\n const dividerWidth = innerWidth - PAD_LEFT - PAD_RIGHT;\n const dividerRaw = \"\\u2504\".repeat(dividerWidth);\n lines.push(line(dividerRaw, dim(dividerRaw)));\n lines.push(blankLine());\n\n // Risk row\n const riskKey = riskLabelColor(result.risk);\n const riskHeaderRaw = `${stripAnsi(riskIcon(result.risk))} ${labels.risk}: ${riskLabelText(result.risk, labels)}`;\n const riskHeaderStyled = `${riskIcon(result.risk)} ${boldRgb(riskKey, `${labels.risk}: ${riskLabelText(result.risk, labels)}`)}`;\n lines.push(line(riskHeaderRaw, riskHeaderStyled));\n\n if (result.risk !== \"none\" && result.riskReason) {\n const reasonMax = innerWidth - PAD_LEFT - PAD_RIGHT - 3;\n const wrapped = wrapText(result.riskReason, reasonMax);\n for (const w of wrapped) {\n const raw = ` ${w}`;\n const styled = ` ${dimRgb(riskKey, w)}`;\n lines.push(line(raw, styled));\n }\n }\n\n lines.push(blankLine());\n\n return buildBoxOutput(lines, borderKey);\n}\n\n// ===========================================================================\n// Misc box variants (skip notice, error notice, drift alert)\n// ===========================================================================\n\nexport function formatSkipNotice(reason: string): string {\n return dim(`[code-explainer] skipped: ${reason}`);\n}\n\nexport function formatErrorNotice(problem: string, cause: string, fix: string): string {\n return rgb(\"yellow\", `[code-explainer] ${problem}. ${cause}. Fix: ${fix}.`);\n}\n\nexport function formatDriftAlert(\n totalFiles: number,\n unrelatedFiles: string[],\n userRequest?: string,\n language: Language = \"en\"\n): string {\n const labels = getLabels(language);\n const lines: BoxLine[] = [];\n const innerWidth = Math.min(getTerminalWidth() - 2, 70) - 2;\n\n lines.push(blankLine());\n\n const headerRaw = `\\u26a1 SESSION DRIFT`;\n const headerStyled = boldRgb(\"yellow\", headerRaw);\n lines.push(line(headerRaw, headerStyled));\n\n lines.push(blankLine());\n\n const summaryRaw = `Claude has modified ${totalFiles} files this session.`;\n lines.push(line(summaryRaw));\n\n const unrelatedRaw = `${unrelatedFiles.length} may be unrelated:`;\n lines.push(line(unrelatedRaw));\n\n for (const file of unrelatedFiles) {\n const truncated = file.length > innerWidth - 8 ? file.slice(0, innerWidth - 11) + \"...\" : file;\n const raw = ` \\u2022 ${truncated}`;\n const styled = ` ${rgb(\"yellow\", \"\\u2022\")} ${truncated}`;\n lines.push(line(raw, styled));\n }\n\n if (userRequest) {\n lines.push(blankLine());\n const requestLines = wrapText(`Your request: \"${userRequest}\"`, innerWidth - PAD_LEFT - PAD_RIGHT);\n for (const w of requestLines) {\n lines.push(line(w, dim(w)));\n }\n }\n\n lines.push(blankLine());\n const noticeRaw = `\\u26a0 Consider reviewing these changes.`;\n lines.push(line(noticeRaw, boldRgb(\"yellow\", noticeRaw)));\n lines.push(blankLine());\n\n return buildBoxOutput(lines, \"yellow\");\n}\n\n/**\n * Write directly to the controlling terminal — Claude Code captures stdio,\n * but for non-hook contexts (init, summary, warmup) we want output on the\n * actual terminal. Falls back to stderr.\n */\nexport function printToStderr(text: string): void {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const fs = require(\"node:fs\") as typeof import(\"node:fs\");\n const ttyPath = process.platform === \"win32\" ? \"\\\\\\\\.\\\\CONOUT$\" : \"/dev/tty\";\n const fd = fs.openSync(ttyPath, \"w\");\n fs.writeSync(fd, text + \"\\n\");\n fs.closeSync(fd);\n } catch {\n process.stderr.write(text + \"\\n\");\n }\n}\n\nfunction stripAnsi(s: string): string {\n // eslint-disable-next-line no-control-regex\n return s.replace(/\\u001b\\[[0-9;]*m/g, \"\");\n}\n"],"mappings":"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","names":["existsSync","readFileSync","writeFileSync","appendFileSync","unlinkSync","join","existsSync","join","join","line","existsSync","join","appendFileSync","existsSync","readFileSync","line","writeFileSync","unlinkSync"]}
|