vibe-code-explainer 0.1.2 → 0.1.3
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-DPYDNBM3.js → chunk-UJLSOUEZ.js} +13 -2
- package/dist/{chunk-DPYDNBM3.js.map → chunk-UJLSOUEZ.js.map} +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/hooks/post-tool.js +1 -1
- package/dist/{tracker-E5FBZ64E.js → tracker-ZE4B2IQS.js} +2 -2
- package/package.json +1 -1
- /package/dist/{tracker-E5FBZ64E.js.map → tracker-ZE4B2IQS.js.map} +0 -0
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
__require
|
|
4
|
+
} from "./chunk-7OCVIDC7.js";
|
|
2
5
|
|
|
3
6
|
// src/session/tracker.ts
|
|
4
7
|
import { existsSync as existsSync2, readFileSync as readFileSync2, appendFileSync as appendFileSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync2, readdirSync, statSync } from "fs";
|
|
@@ -187,7 +190,15 @@ function formatErrorNotice(problem, cause, fix) {
|
|
|
187
190
|
return color(pc.yellow, `[code-explainer] ${problem}. ${cause}. Fix: ${fix}.`);
|
|
188
191
|
}
|
|
189
192
|
function printToStderr(text) {
|
|
190
|
-
|
|
193
|
+
try {
|
|
194
|
+
const fs = __require("fs");
|
|
195
|
+
const ttyPath = process.platform === "win32" ? "\\\\.\\CONOUT$" : "/dev/tty";
|
|
196
|
+
const fd = fs.openSync(ttyPath, "w");
|
|
197
|
+
fs.writeSync(fd, text + "\n");
|
|
198
|
+
fs.closeSync(fd);
|
|
199
|
+
} catch {
|
|
200
|
+
process.stderr.write(text + "\n");
|
|
201
|
+
}
|
|
191
202
|
}
|
|
192
203
|
|
|
193
204
|
// src/session/tracker.ts
|
|
@@ -334,4 +345,4 @@ export {
|
|
|
334
345
|
printSummary,
|
|
335
346
|
endSession
|
|
336
347
|
};
|
|
337
|
-
//# sourceMappingURL=chunk-
|
|
348
|
+
//# sourceMappingURL=chunk-UJLSOUEZ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/session/tracker.ts","../src/cache/explanation-cache.ts","../src/format/box.ts"],"sourcesContent":["import { existsSync, readFileSync, appendFileSync, unlinkSync, mkdirSync, readdirSync, statSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\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\";\n\nconst TWO_HOURS_MS = 2 * 60 * 60 * 1000;\n\nexport interface SessionEntry {\n file: string;\n timestamp: number;\n risk: RiskLevel;\n summary: string;\n unrelated?: boolean;\n}\n\nfunction getUserTmpDir(): string {\n const dir = join(tmpdir(), `code-explainer-${process.getuid?.() ?? \"user\"}`);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n return dir;\n}\n\nexport function getSessionFilePath(sessionId: string): string {\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\nexport function cleanStaleSessionFiles(): void {\n try {\n const dir = getUserTmpDir();\n const now = Date.now();\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(): Promise<void> {\n const sessionId = getSessionIdFromEnv() ?? findLatestSession();\n if (!sessionId) {\n process.stderr.write(\"[code-explainer] No active session found. Session data is created when Claude Code makes changes.\\n\");\n return;\n }\n\n const entries = readSession(sessionId);\n if (entries.length === 0) {\n process.stderr.write(`[code-explainer] Session '${sessionId}' has no recorded changes yet.\\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 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 const risks: Record<RiskLevel, number> = { none: 0, low: 0, medium: 0, high: 0 };\n for (const e of entries) risks[e.risk]++;\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, unlinkSync, mkdirSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { ExplanationResult } from \"../config/schema.js\";\n\nfunction getUserTmpDir(): string {\n const dir = join(tmpdir(), `code-explainer-${process.getuid?.() ?? \"user\"}`);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n return dir;\n}\n\nexport function getCacheFilePath(sessionId: string): string {\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\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 } 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","import pc from \"picocolors\";\nimport type { RiskLevel } from \"../config/schema.js\";\n\nfunction isNoColor(): boolean {\n return \"NO_COLOR\" in process.env || process.env.TERM === \"dumb\";\n}\n\nfunction color(fn: (s: string) => string, text: string): string {\n return isNoColor() ? text : fn(text);\n}\n\nfunction getTerminalWidth(): number {\n return process.stderr.columns || 80;\n}\n\nfunction wrapText(text: string, maxWidth: number): string[] {\n const lines: string[] = [];\n for (const raw of text.split(\"\\n\")) {\n if (raw.length <= maxWidth) {\n lines.push(raw);\n } else {\n let remaining = raw;\n while (remaining.length > maxWidth) {\n let breakAt = remaining.lastIndexOf(\" \", maxWidth);\n if (breakAt <= 0) breakAt = maxWidth;\n lines.push(remaining.slice(0, breakAt));\n remaining = remaining.slice(breakAt).trimStart();\n }\n if (remaining) lines.push(remaining);\n }\n }\n return lines;\n}\n\nfunction riskIcon(risk: RiskLevel): string {\n if (isNoColor()) {\n switch (risk) {\n case \"none\": return \"[OK]\";\n case \"low\": return \"[LOW]\";\n case \"medium\": return \"[WARN]\";\n case \"high\": return \"[DANGER]\";\n }\n }\n switch (risk) {\n case \"none\": return color(pc.green, \"\\u2705\");\n case \"low\": return color(pc.yellow, \"\\u26A0\\uFE0F\");\n case \"medium\": return color(pc.yellow, \"\\u26A0\\uFE0F\");\n case \"high\": return color(pc.red, \"\\u{1F6A8}\");\n }\n}\n\nfunction riskLabel(risk: RiskLevel): string {\n switch (risk) {\n case \"none\": return \"None\";\n case \"low\": return \"Low\";\n case \"medium\": return \"Medium\";\n case \"high\": return \"High\";\n }\n}\n\nexport function formatExplanationBox(\n filePath: string,\n summary: string,\n risk: RiskLevel,\n riskReason: string\n): string {\n const width = Math.min(getTerminalWidth() - 4, 60);\n const contentWidth = width - 4; // \"│ \" prefix + \" \" suffix\n\n const isRisky = risk === \"medium\" || risk === \"high\";\n const headerLabel = isRisky ? \"code-explainer \\u26A0\\uFE0F\" : \"code-explainer\";\n const topBorder = `\\u250C\\u2500 ${headerLabel} ${ \"\\u2500\".repeat(Math.max(0, width - headerLabel.length - 4))}\\u2500`;\n const bottomBorder = `\\u2514${ \"\\u2500\".repeat(width)}\\u2500`;\n\n const lines: string[] = [];\n lines.push(isRisky ? color(pc.yellow, topBorder) : color(pc.dim, topBorder));\n\n // File path\n const fileDisplay = isRisky ? `${riskIcon(risk)} ${filePath}` : ` ${filePath}`;\n lines.push(`${color(pc.dim, \"\\u2502\")}${fileDisplay}`);\n lines.push(`${color(pc.dim, \"\\u2502\")}`);\n\n // Summary\n const summaryLines = wrapText(summary, contentWidth);\n for (const line of summaryLines) {\n lines.push(`${color(pc.dim, \"\\u2502\")} ${line}`);\n }\n\n lines.push(`${color(pc.dim, \"\\u2502\")}`);\n\n // Risk line\n const riskText = `Risk: ${riskIcon(risk)} ${riskLabel(risk)}`;\n lines.push(`${color(pc.dim, \"\\u2502\")} ${riskText}`);\n\n // Risk reason (if any)\n if (riskReason) {\n const reasonLines = wrapText(riskReason, contentWidth);\n for (const line of reasonLines) {\n lines.push(`${color(pc.dim, \"\\u2502\")} ${color(pc.dim, line)}`);\n }\n }\n\n lines.push(isRisky ? color(pc.yellow, bottomBorder) : color(pc.dim, bottomBorder));\n\n return lines.join(\"\\n\");\n}\n\nexport function formatDriftAlert(\n totalFiles: number,\n unrelatedFiles: string[],\n userRequest?: string\n): string {\n const width = Math.min(getTerminalWidth() - 4, 60);\n const contentWidth = width - 4;\n\n const headerLabel = \"code-explainer \\u26A1 SESSION DRIFT\";\n const topBorder = `\\u250C\\u2500 ${headerLabel} ${ \"\\u2500\".repeat(Math.max(0, width - headerLabel.length - 4))}\\u2500`;\n const bottomBorder = `\\u2514${ \"\\u2500\".repeat(width)}\\u2500`;\n\n const lines: string[] = [];\n lines.push(color(pc.yellow, topBorder));\n\n lines.push(`${color(pc.dim, \"\\u2502\")} Claude has modified ${totalFiles} files this session.`);\n lines.push(`${color(pc.dim, \"\\u2502\")} ${unrelatedFiles.length} may be unrelated:`);\n\n for (const file of unrelatedFiles) {\n const truncated = file.length > contentWidth - 4 ? file.slice(0, contentWidth - 7) + \"...\" : file;\n lines.push(`${color(pc.dim, \"\\u2502\")} ${color(pc.yellow, \"\\u2022\")} ${truncated}`);\n }\n\n if (userRequest) {\n lines.push(`${color(pc.dim, \"\\u2502\")}`);\n const requestLines = wrapText(`Your request: \"${userRequest}\"`, contentWidth);\n for (const line of requestLines) {\n lines.push(`${color(pc.dim, \"\\u2502\")} ${line}`);\n }\n }\n\n lines.push(`${color(pc.dim, \"\\u2502\")} ${color(pc.yellow, \"\\u26A0\\uFE0F Consider reviewing these changes.\")}`);\n lines.push(color(pc.yellow, bottomBorder));\n\n return lines.join(\"\\n\");\n}\n\nexport function formatSkipNotice(reason: string): string {\n return color(pc.dim, `[code-explainer] skipped: ${reason}`);\n}\n\nexport function formatErrorNotice(problem: string, cause: string, fix: string): string {\n return color(pc.yellow, `[code-explainer] ${problem}. ${cause}. Fix: ${fix}.`);\n}\n\nexport function printToStderr(text: string): void {\n process.stderr.write(text + \"\\n\");\n}\n"],"mappings":"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","names":["existsSync","readFileSync","appendFileSync","unlinkSync","mkdirSync","tmpdir","join","getUserTmpDir","join","tmpdir","existsSync","mkdirSync","appendFileSync","readFileSync","unlinkSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/session/tracker.ts","../src/cache/explanation-cache.ts","../src/format/box.ts"],"sourcesContent":["import { existsSync, readFileSync, appendFileSync, unlinkSync, mkdirSync, readdirSync, statSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\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\";\n\nconst TWO_HOURS_MS = 2 * 60 * 60 * 1000;\n\nexport interface SessionEntry {\n file: string;\n timestamp: number;\n risk: RiskLevel;\n summary: string;\n unrelated?: boolean;\n}\n\nfunction getUserTmpDir(): string {\n const dir = join(tmpdir(), `code-explainer-${process.getuid?.() ?? \"user\"}`);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n return dir;\n}\n\nexport function getSessionFilePath(sessionId: string): string {\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\nexport function cleanStaleSessionFiles(): void {\n try {\n const dir = getUserTmpDir();\n const now = Date.now();\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(): Promise<void> {\n const sessionId = getSessionIdFromEnv() ?? findLatestSession();\n if (!sessionId) {\n process.stderr.write(\"[code-explainer] No active session found. Session data is created when Claude Code makes changes.\\n\");\n return;\n }\n\n const entries = readSession(sessionId);\n if (entries.length === 0) {\n process.stderr.write(`[code-explainer] Session '${sessionId}' has no recorded changes yet.\\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 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 const risks: Record<RiskLevel, number> = { none: 0, low: 0, medium: 0, high: 0 };\n for (const e of entries) risks[e.risk]++;\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, unlinkSync, mkdirSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { ExplanationResult } from \"../config/schema.js\";\n\nfunction getUserTmpDir(): string {\n const dir = join(tmpdir(), `code-explainer-${process.getuid?.() ?? \"user\"}`);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n return dir;\n}\n\nexport function getCacheFilePath(sessionId: string): string {\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\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 } 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","import pc from \"picocolors\";\nimport type { RiskLevel } from \"../config/schema.js\";\n\nfunction isNoColor(): boolean {\n return \"NO_COLOR\" in process.env || process.env.TERM === \"dumb\";\n}\n\nfunction color(fn: (s: string) => string, text: string): string {\n return isNoColor() ? text : fn(text);\n}\n\nfunction getTerminalWidth(): number {\n return process.stderr.columns || 80;\n}\n\nfunction wrapText(text: string, maxWidth: number): string[] {\n const lines: string[] = [];\n for (const raw of text.split(\"\\n\")) {\n if (raw.length <= maxWidth) {\n lines.push(raw);\n } else {\n let remaining = raw;\n while (remaining.length > maxWidth) {\n let breakAt = remaining.lastIndexOf(\" \", maxWidth);\n if (breakAt <= 0) breakAt = maxWidth;\n lines.push(remaining.slice(0, breakAt));\n remaining = remaining.slice(breakAt).trimStart();\n }\n if (remaining) lines.push(remaining);\n }\n }\n return lines;\n}\n\nfunction riskIcon(risk: RiskLevel): string {\n if (isNoColor()) {\n switch (risk) {\n case \"none\": return \"[OK]\";\n case \"low\": return \"[LOW]\";\n case \"medium\": return \"[WARN]\";\n case \"high\": return \"[DANGER]\";\n }\n }\n switch (risk) {\n case \"none\": return color(pc.green, \"\\u2705\");\n case \"low\": return color(pc.yellow, \"\\u26A0\\uFE0F\");\n case \"medium\": return color(pc.yellow, \"\\u26A0\\uFE0F\");\n case \"high\": return color(pc.red, \"\\u{1F6A8}\");\n }\n}\n\nfunction riskLabel(risk: RiskLevel): string {\n switch (risk) {\n case \"none\": return \"None\";\n case \"low\": return \"Low\";\n case \"medium\": return \"Medium\";\n case \"high\": return \"High\";\n }\n}\n\nexport function formatExplanationBox(\n filePath: string,\n summary: string,\n risk: RiskLevel,\n riskReason: string\n): string {\n const width = Math.min(getTerminalWidth() - 4, 60);\n const contentWidth = width - 4; // \"│ \" prefix + \" \" suffix\n\n const isRisky = risk === \"medium\" || risk === \"high\";\n const headerLabel = isRisky ? \"code-explainer \\u26A0\\uFE0F\" : \"code-explainer\";\n const topBorder = `\\u250C\\u2500 ${headerLabel} ${ \"\\u2500\".repeat(Math.max(0, width - headerLabel.length - 4))}\\u2500`;\n const bottomBorder = `\\u2514${ \"\\u2500\".repeat(width)}\\u2500`;\n\n const lines: string[] = [];\n lines.push(isRisky ? color(pc.yellow, topBorder) : color(pc.dim, topBorder));\n\n // File path\n const fileDisplay = isRisky ? `${riskIcon(risk)} ${filePath}` : ` ${filePath}`;\n lines.push(`${color(pc.dim, \"\\u2502\")}${fileDisplay}`);\n lines.push(`${color(pc.dim, \"\\u2502\")}`);\n\n // Summary\n const summaryLines = wrapText(summary, contentWidth);\n for (const line of summaryLines) {\n lines.push(`${color(pc.dim, \"\\u2502\")} ${line}`);\n }\n\n lines.push(`${color(pc.dim, \"\\u2502\")}`);\n\n // Risk line\n const riskText = `Risk: ${riskIcon(risk)} ${riskLabel(risk)}`;\n lines.push(`${color(pc.dim, \"\\u2502\")} ${riskText}`);\n\n // Risk reason (if any)\n if (riskReason) {\n const reasonLines = wrapText(riskReason, contentWidth);\n for (const line of reasonLines) {\n lines.push(`${color(pc.dim, \"\\u2502\")} ${color(pc.dim, line)}`);\n }\n }\n\n lines.push(isRisky ? color(pc.yellow, bottomBorder) : color(pc.dim, bottomBorder));\n\n return lines.join(\"\\n\");\n}\n\nexport function formatDriftAlert(\n totalFiles: number,\n unrelatedFiles: string[],\n userRequest?: string\n): string {\n const width = Math.min(getTerminalWidth() - 4, 60);\n const contentWidth = width - 4;\n\n const headerLabel = \"code-explainer \\u26A1 SESSION DRIFT\";\n const topBorder = `\\u250C\\u2500 ${headerLabel} ${ \"\\u2500\".repeat(Math.max(0, width - headerLabel.length - 4))}\\u2500`;\n const bottomBorder = `\\u2514${ \"\\u2500\".repeat(width)}\\u2500`;\n\n const lines: string[] = [];\n lines.push(color(pc.yellow, topBorder));\n\n lines.push(`${color(pc.dim, \"\\u2502\")} Claude has modified ${totalFiles} files this session.`);\n lines.push(`${color(pc.dim, \"\\u2502\")} ${unrelatedFiles.length} may be unrelated:`);\n\n for (const file of unrelatedFiles) {\n const truncated = file.length > contentWidth - 4 ? file.slice(0, contentWidth - 7) + \"...\" : file;\n lines.push(`${color(pc.dim, \"\\u2502\")} ${color(pc.yellow, \"\\u2022\")} ${truncated}`);\n }\n\n if (userRequest) {\n lines.push(`${color(pc.dim, \"\\u2502\")}`);\n const requestLines = wrapText(`Your request: \"${userRequest}\"`, contentWidth);\n for (const line of requestLines) {\n lines.push(`${color(pc.dim, \"\\u2502\")} ${line}`);\n }\n }\n\n lines.push(`${color(pc.dim, \"\\u2502\")} ${color(pc.yellow, \"\\u26A0\\uFE0F Consider reviewing these changes.\")}`);\n lines.push(color(pc.yellow, bottomBorder));\n\n return lines.join(\"\\n\");\n}\n\nexport function formatSkipNotice(reason: string): string {\n return color(pc.dim, `[code-explainer] skipped: ${reason}`);\n}\n\nexport function formatErrorNotice(problem: string, cause: string, fix: string): string {\n return color(pc.yellow, `[code-explainer] ${problem}. ${cause}. Fix: ${fix}.`);\n}\n\n/**\n * Write directly to the controlling terminal. Claude Code captures stdout\n * and stderr from hooks, so they never appear in the user's terminal unless\n * we bypass stdio and write to the tty device directly.\n *\n * Falls back to stderr if the tty cannot be opened (non-interactive runs,\n * CI, tests) so output is not silently swallowed.\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"],"mappings":"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","names":["existsSync","readFileSync","appendFileSync","unlinkSync","mkdirSync","tmpdir","join","getUserTmpDir","join","tmpdir","existsSync","mkdirSync","appendFileSync","readFileSync","unlinkSync"]}
|
package/dist/cli/index.js
CHANGED
|
@@ -21,14 +21,14 @@ async function main() {
|
|
|
21
21
|
break;
|
|
22
22
|
}
|
|
23
23
|
case "summary": {
|
|
24
|
-
const { printSummary } = await import("../tracker-
|
|
24
|
+
const { printSummary } = await import("../tracker-ZE4B2IQS.js");
|
|
25
25
|
await printSummary();
|
|
26
26
|
break;
|
|
27
27
|
}
|
|
28
28
|
case "session": {
|
|
29
29
|
const subcommand = args[1];
|
|
30
30
|
if (subcommand === "end") {
|
|
31
|
-
const { endSession } = await import("../tracker-
|
|
31
|
+
const { endSession } = await import("../tracker-ZE4B2IQS.js");
|
|
32
32
|
await endSession();
|
|
33
33
|
} else {
|
|
34
34
|
console.error("[code-explainer] Unknown session command. Usage: code-explainer session end");
|
package/dist/hooks/post-tool.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
printSummary,
|
|
7
7
|
readSession,
|
|
8
8
|
recordEntry
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-UJLSOUEZ.js";
|
|
10
10
|
import "./chunk-7OCVIDC7.js";
|
|
11
11
|
export {
|
|
12
12
|
cleanStaleSessionFiles,
|
|
@@ -16,4 +16,4 @@ export {
|
|
|
16
16
|
readSession,
|
|
17
17
|
recordEntry
|
|
18
18
|
};
|
|
19
|
-
//# sourceMappingURL=tracker-
|
|
19
|
+
//# sourceMappingURL=tracker-ZE4B2IQS.js.map
|
package/package.json
CHANGED
|
File without changes
|