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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/config.ts"],"sourcesContent":["import { intro, outro, select, confirm, text, cancel, isCancel, note } from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { spawn } from \"node:child_process\";\nimport { existsSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport {\n DEFAULT_CONFIG,\n loadConfig,\n LANGUAGE_NAMES,\n LEARNER_LEVEL_NAMES,\n CONFIG_FILENAME,\n getGlobalConfigPath,\n type Config,\n type Engine,\n type DetailLevel,\n type Language,\n type LearnerLevel,\n} from \"../config/schema.js\";\nimport { MODEL_OPTIONS } from \"../detect/vram.js\";\nimport { parseFlags, flagBool } from \"./flags.js\";\n\ninterface OllamaTagResponse {\n models?: Array<{ name?: string; model?: string }>;\n}\n\nasync function listInstalledOllamaModels(url: string): Promise<string[] | null> {\n try {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), 3000);\n const res = await fetch(`${url}/api/tags`, { signal: ctrl.signal });\n clearTimeout(timer);\n if (!res.ok) return null;\n const data = (await res.json()) as OllamaTagResponse;\n if (!data.models) return [];\n return data.models\n .map((m) => m.name ?? m.model ?? \"\")\n .filter((n) => n.length > 0);\n } catch {\n return null;\n }\n}\n\nfunction normalizeModelName(name: string): string {\n // Ollama sometimes returns tags as \"qwen3.5:9b\" and sometimes as\n // \"qwen3.5:9b-q4_K_M\". Compare on the base \"<model>:<tag>\" prefix.\n return name.toLowerCase().split(/[-_]/)[0];\n}\n\nfunction hasModel(installed: string[], wanted: string): boolean {\n const wantedNorm = normalizeModelName(wanted);\n const wantedLower = wanted.toLowerCase();\n return installed.some((n) => {\n const base = n.toLowerCase();\n if (base === wantedLower) return true;\n // Looser match for variant tags (e.g. \"qwen3.5:9b-q4_K_M\" matches \"qwen3.5\")\n return normalizeModelName(base).startsWith(wantedNorm);\n });\n}\n\nasync function pullOllamaModel(model: string): Promise<boolean> {\n note(\n `Pulling ${pc.cyan(model)}\\n${pc.dim(\"This can take a while on the first run (several GB download).\")}`,\n \"Downloading model\"\n );\n return new Promise((resolvePromise) => {\n const child = spawn(\"ollama\", [\"pull\", model], { stdio: \"inherit\" });\n child.on(\"error\", () => {\n process.stderr.write(\n pc.red(\"\\nFailed to run `ollama pull`. Make sure Ollama is installed and running.\\n\")\n );\n resolvePromise(false);\n });\n child.on(\"close\", (code) => {\n if (code === 0) {\n process.stdout.write(pc.green(`\\n\\u2713 Pulled ${model}\\n`));\n resolvePromise(true);\n } else {\n process.stderr.write(pc.red(`\\n\\u2717 ollama pull exited with code ${code}\\n`));\n resolvePromise(false);\n }\n });\n });\n}\n\n\nfunction handleCancel<T>(value: T | symbol): asserts value is T {\n if (isCancel(value)) {\n cancel(\"Exited without saving.\");\n process.exit(0);\n }\n}\n\nfunction renderCurrent(config: Config): string {\n const hooks: string[] = [];\n if (config.hooks.edit) hooks.push(\"Edit\");\n if (config.hooks.write) hooks.push(\"Write\");\n if (config.hooks.bash) hooks.push(\"Bash\");\n\n const excluded = config.exclude.length > 0 ? config.exclude.join(\", \") : \"(none)\";\n const timeoutLabel =\n config.skipIfSlowMs === 0 ? \"Never skip\" : `${Math.round(config.skipIfSlowMs / 1000)}s`;\n\n return [\n `${pc.bold(\"Engine: \")} ${config.engine === \"ollama\" ? \"Local LLM (Ollama)\" : \"Claude Code (native)\"}`,\n `${pc.bold(\"Model: \")} ${config.ollamaModel}`,\n `${pc.bold(\"Ollama URL: \")} ${config.ollamaUrl}`,\n `${pc.bold(\"Detail level: \")} ${config.detailLevel}`,\n `${pc.bold(\"Language: \")} ${LANGUAGE_NAMES[config.language]}`,\n `${pc.bold(\"Learner level:\")} ${LEARNER_LEVEL_NAMES[config.learnerLevel]}`,\n `${pc.bold(\"Hooks: \")} ${hooks.join(\" \\u2713 \") || \"(all disabled)\"}`,\n `${pc.bold(\"Excluded: \")} ${excluded}`,\n `${pc.bold(\"Skip if slow: \")} ${timeoutLabel}`,\n ].join(\"\\n\");\n}\n\ntype MenuChoice =\n | \"engine\"\n | \"model\"\n | \"url\"\n | \"detail\"\n | \"language\"\n | \"level\"\n | \"hooks\"\n | \"exclude\"\n | \"timeout\"\n | \"back\";\n\nasync function changeEngine(config: Config): Promise<Config> {\n const value = await select<Engine>({\n message: \"Explanation engine\",\n options: [\n { label: \"Local LLM (Ollama)\", value: \"ollama\", hint: \"free, private, works offline\" },\n { label: \"Claude Code (native)\", value: \"claude\", hint: \"best quality, uses API tokens\" },\n ],\n initialValue: config.engine,\n });\n handleCancel(value);\n return { ...config, engine: value };\n}\n\nasync function changeModel(config: Config): Promise<Config> {\n const value = await select({\n message: \"Ollama model\",\n options: MODEL_OPTIONS.map((m) => ({\n label: m.label,\n value: m.model,\n hint: m.hint,\n })),\n initialValue: config.ollamaModel,\n });\n handleCancel(value);\n\n if (value === config.ollamaModel) {\n // Nothing actually changed; skip the download check.\n return config;\n }\n\n // Check whether Ollama already has the model pulled. If not, offer to pull it.\n const installed = await listInstalledOllamaModels(config.ollamaUrl);\n if (installed === null) {\n note(\n `Could not reach Ollama at ${pc.cyan(config.ollamaUrl)}. The model will be selected, but you'll need to pull it manually with ${pc.cyan(`ollama pull ${value}`)} before the first explanation.`,\n \"Ollama unreachable\"\n );\n return { ...config, ollamaModel: value };\n }\n\n if (hasModel(installed, value)) {\n note(`${pc.green(\"\\u2713\")} Model ${pc.cyan(value)} is already installed.`, \"Model ready\");\n return { ...config, ollamaModel: value };\n }\n\n const shouldPull = await confirm({\n message: `Model ${value} is not installed locally. Pull it now?`,\n initialValue: true,\n });\n handleCancel(shouldPull);\n\n if (!shouldPull) {\n note(\n `Saved the selection, but you must run ${pc.cyan(`ollama pull ${value}`)} before it works.`,\n \"Model not pulled\"\n );\n return { ...config, ollamaModel: value };\n }\n\n const pullOk = await pullOllamaModel(value);\n if (!pullOk) {\n note(\n `Pull failed. Saving the model selection anyway — run ${pc.cyan(`ollama pull ${value}`)} manually when Ollama is reachable.`,\n \"Pull failed\"\n );\n }\n return { ...config, ollamaModel: value };\n}\n\nasync function changeUrl(config: Config): Promise<Config> {\n const value = await text({\n message: \"Ollama endpoint URL\",\n initialValue: config.ollamaUrl,\n validate(v) {\n try {\n new URL(v);\n return;\n } catch {\n return \"Must be a valid URL (e.g., http://localhost:11434)\";\n }\n },\n });\n handleCancel(value);\n return { ...config, ollamaUrl: value };\n}\n\nasync function changeDetail(config: Config): Promise<Config> {\n const value = await select<DetailLevel>({\n message: \"Detail level\",\n options: [\n { label: \"Standard\", value: \"standard\", hint: \"1-2 sentence explanation per change (recommended)\" },\n { label: \"Minimal\", value: \"minimal\", hint: \"one short sentence per change\" },\n { label: \"Verbose\", value: \"verbose\", hint: \"detailed bullet-point breakdown\" },\n ],\n initialValue: config.detailLevel,\n });\n handleCancel(value);\n return { ...config, detailLevel: value };\n}\n\nasync function changeLanguage(config: Config): Promise<Config> {\n const value = await select<Language>({\n message: \"Language for explanations\",\n options: (Object.keys(LANGUAGE_NAMES) as Language[]).map((code) => ({\n label: LANGUAGE_NAMES[code],\n value: code,\n hint: code === \"en\" ? \"default\" : undefined,\n })),\n initialValue: config.language,\n });\n handleCancel(value);\n return { ...config, language: value };\n}\n\nasync function changeLevel(config: Config): Promise<Config> {\n const value = await select<LearnerLevel>({\n message: \"Programming knowledge level\",\n options: (Object.keys(LEARNER_LEVEL_NAMES) as LearnerLevel[]).map((code) => ({\n label: LEARNER_LEVEL_NAMES[code],\n value: code,\n hint: code === \"intermediate\" ? \"default\" : undefined,\n })),\n initialValue: config.learnerLevel,\n });\n handleCancel(value);\n return { ...config, learnerLevel: value };\n}\n\nasync function changeHooks(config: Config): Promise<Config> {\n const editOn = await confirm({ message: \"Explain file edits?\", initialValue: config.hooks.edit });\n handleCancel(editOn);\n const writeOn = await confirm({ message: \"Explain new files?\", initialValue: config.hooks.write });\n handleCancel(writeOn);\n const bashOn = await confirm({\n message: \"Explain destructive Bash commands (rm, git reset, etc.)?\",\n initialValue: config.hooks.bash,\n });\n handleCancel(bashOn);\n\n return {\n ...config,\n hooks: { edit: editOn, write: writeOn, bash: bashOn },\n };\n}\n\nasync function changeExclude(config: Config): Promise<Config> {\n const action = await select({\n message: `Current exclusions: ${config.exclude.join(\", \") || \"(none)\"}`,\n options: [\n { label: \"Add a pattern\", value: \"add\", hint: \"e.g., *.generated.*\" },\n { label: \"Remove a pattern\", value: \"remove\" },\n { label: \"Reset to defaults\", value: \"reset\", hint: DEFAULT_CONFIG.exclude.join(\", \") },\n { label: \"Back\", value: \"back\" },\n ],\n });\n handleCancel(action);\n\n if (action === \"back\") return config;\n if (action === \"reset\") return { ...config, exclude: [...DEFAULT_CONFIG.exclude] };\n\n if (action === \"add\") {\n const pattern = await text({ message: \"Glob pattern to exclude (e.g., *.generated.*)\" });\n handleCancel(pattern);\n if (!pattern.trim()) return config;\n const exclude = Array.from(new Set([...config.exclude, pattern.trim()]));\n return { ...config, exclude };\n }\n\n if (action === \"remove\") {\n if (config.exclude.length === 0) {\n note(\"No exclusions to remove.\", \"Exclusions\");\n return config;\n }\n const target = await select({\n message: \"Which pattern to remove?\",\n options: config.exclude.map((p) => ({ label: p, value: p })),\n });\n handleCancel(target);\n const exclude = config.exclude.filter((p) => p !== target);\n return { ...config, exclude };\n }\n\n return config;\n}\n\nasync function changeTimeout(config: Config): Promise<Config> {\n const value = await select<number>({\n message: \"Skip explanation if it takes longer than...\",\n options: [\n { label: \"5 seconds\", value: 5000, hint: \"fast, may skip complex changes\" },\n { label: \"8 seconds\", value: 8000, hint: \"balanced (recommended)\" },\n { label: \"15 seconds\", value: 15000, hint: \"patient, rarely skips\" },\n { label: \"Never skip\", value: 0, hint: \"always wait for the explanation\" },\n ],\n initialValue: config.skipIfSlowMs,\n });\n handleCancel(value);\n return { ...config, skipIfSlowMs: value };\n}\n\nfunction resolveConfigPath(): { configPath: string; scope: \"project\" | \"global\" } | null {\n const projectPath = join(process.cwd(), CONFIG_FILENAME);\n const globalPath = getGlobalConfigPath();\n if (existsSync(projectPath)) return { configPath: projectPath, scope: \"project\" };\n if (existsSync(globalPath)) return { configPath: globalPath, scope: \"global\" };\n return null;\n}\n\n/**\n * config show [--json]\n * Print the effective config. With --json, outputs machine-readable JSON so\n * agents can pipe to jq or parse directly.\n */\nfunction runConfigShow(args: string[]): void {\n const { flags } = parseFlags(args);\n const json = flagBool(flags, \"json\", \"j\");\n\n const resolved = resolveConfigPath();\n if (!resolved) {\n process.stderr.write(\"[code-explainer] No config file found. Run 'vibe-code-explainer init' first.\\n\");\n process.exit(1);\n }\n const config = loadConfig(resolved.configPath);\n if (json) {\n process.stdout.write(JSON.stringify(config, null, 2) + \"\\n\");\n } else {\n process.stderr.write(renderCurrent(config) + \"\\n\");\n }\n}\n\n/**\n * config get <key>\n * Print a single config field as a plain string (for scripting).\n * Key may be dot-separated for nested fields (e.g. hooks.bash).\n */\nfunction runConfigGet(args: string[]): void {\n const { positional } = parseFlags(args);\n const key = positional[0];\n if (!key) {\n process.stderr.write(\"[code-explainer] Usage: vibe-code-explainer config get <key>\\n\");\n process.exit(1);\n }\n const resolved = resolveConfigPath();\n if (!resolved) {\n process.stderr.write(\"[code-explainer] No config file found. Run 'vibe-code-explainer init' first.\\n\");\n process.exit(1);\n }\n const config = loadConfig(resolved.configPath) as Record<string, unknown>;\n const parts = key.split(\".\");\n let cur: unknown = config;\n for (const part of parts) {\n if (typeof cur !== \"object\" || cur === null) {\n process.stderr.write(`[code-explainer] Key '${key}' not found in config.\\n`);\n process.exit(1);\n }\n cur = (cur as Record<string, unknown>)[part];\n }\n if (cur === undefined) {\n process.stderr.write(`[code-explainer] Key '${key}' not found in config.\\n`);\n process.exit(1);\n }\n // Output plain scalar or JSON for objects/arrays.\n if (typeof cur === \"object\") {\n process.stdout.write(JSON.stringify(cur) + \"\\n\");\n } else {\n process.stdout.write(String(cur) + \"\\n\");\n }\n}\n\n/**\n * config set <key> <value>\n * Set a single config field. The value is parsed as JSON when it looks like\n * a JSON literal (number, boolean, array, object), otherwise treated as a\n * plain string.\n */\nfunction runConfigSet(args: string[]): void {\n const { positional } = parseFlags(args);\n const [key, rawValue] = positional;\n if (!key || rawValue === undefined) {\n process.stderr.write(\"[code-explainer] Usage: vibe-code-explainer config set <key> <value>\\n\");\n process.exit(1);\n }\n const resolved = resolveConfigPath();\n if (!resolved) {\n process.stderr.write(\"[code-explainer] No config file found. Run 'vibe-code-explainer init' first.\\n\");\n process.exit(1);\n }\n\n let value: unknown = rawValue;\n try {\n value = JSON.parse(rawValue);\n } catch {\n // Use as plain string\n }\n\n // Deep-set the key into the config object.\n const config = loadConfig(resolved.configPath) as Record<string, unknown>;\n const parts = key.split(\".\");\n let cur: Record<string, unknown> = config;\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n if (typeof cur[part] !== \"object\" || cur[part] === null) {\n cur[part] = {};\n }\n cur = cur[part] as Record<string, unknown>;\n }\n cur[parts[parts.length - 1]] = value;\n\n writeFileSync(resolved.configPath, JSON.stringify(config, null, 2) + \"\\n\");\n process.stderr.write(`[code-explainer] Set ${key} = ${JSON.stringify(value)} in ${resolved.configPath}\\n`);\n}\n\nexport async function runConfig(rawArgs: string[] = []): Promise<void> {\n const { flags, positional } = parseFlags(rawArgs);\n const subcommand = positional[0];\n const subArgs = positional.slice(1);\n\n // Non-interactive subcommands for agent-native access.\n if (subcommand === \"show\") { runConfigShow([...subArgs, ...Object.entries(flags).flatMap(([k, v]) => v === true ? [`--${k}`] : [`--${k}=${v}`])]); return; }\n if (subcommand === \"get\") { runConfigGet(subArgs); return; }\n if (subcommand === \"set\") { runConfigSet(subArgs); return; }\n\n // Interactive TUI mode.\n const projectPath = join(process.cwd(), CONFIG_FILENAME);\n const globalPath = getGlobalConfigPath();\n\n let configPath: string;\n let scope: \"project\" | \"global\";\n if (existsSync(projectPath)) {\n configPath = projectPath;\n scope = \"project\";\n } else if (existsSync(globalPath)) {\n configPath = globalPath;\n scope = \"global\";\n } else {\n intro(pc.bold(\"code-explainer config\"));\n cancel(\n `No config file found.\\nSearched: ${pc.cyan(projectPath)}\\n ${pc.cyan(globalPath)}\\nRun ${pc.cyan(\"npx vibe-code-explainer init\")} first.`\n );\n process.exit(1);\n }\n\n intro(pc.bold(`code-explainer config (${scope})`));\n\n // --yes: skip confirmation for non-interactive environments.\n const skipConfirm = flagBool(flags, \"yes\", \"y\");\n\n let config = loadConfig(configPath);\n\n while (true) {\n note(renderCurrent(config), \"Current settings\");\n\n const choice = await select<MenuChoice>({\n message: \"What would you like to change?\",\n options: [\n { label: \"Engine\", value: \"engine\" },\n { label: \"Model\", value: \"model\" },\n { label: \"Ollama URL\", value: \"url\" },\n { label: \"Detail level\", value: \"detail\" },\n { label: \"Language\", value: \"language\" },\n { label: \"Learner level\", value: \"level\" },\n { label: \"Enable/disable hooks\", value: \"hooks\" },\n { label: \"File exclusions\", value: \"exclude\" },\n { label: \"Latency timeout\", value: \"timeout\" },\n { label: \"Back (save and exit)\", value: \"back\" },\n ],\n });\n handleCancel(choice);\n\n if (choice === \"back\") break;\n if (choice === \"engine\") config = await changeEngine(config);\n if (choice === \"model\") config = await changeModel(config);\n if (choice === \"url\") config = await changeUrl(config);\n if (choice === \"detail\") config = await changeDetail(config);\n if (choice === \"language\") config = await changeLanguage(config);\n if (choice === \"level\") config = await changeLevel(config);\n if (choice === \"hooks\") config = await changeHooks(config);\n if (choice === \"exclude\") config = await changeExclude(config);\n if (choice === \"timeout\") config = await changeTimeout(config);\n\n writeFileSync(configPath, JSON.stringify(config, null, 2) + \"\\n\");\n }\n\n if (!skipConfirm) {\n outro(pc.green(\"Settings saved.\"));\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/hooks/post-tool.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
buildClaudePrompt,
|
|
4
|
-
callOllama
|
|
5
|
-
|
|
4
|
+
callOllama,
|
|
5
|
+
parseResponse,
|
|
6
|
+
truncateText
|
|
7
|
+
} from "../chunk-ZZY3IDL2.js";
|
|
6
8
|
import {
|
|
7
9
|
DEFAULT_CONFIG,
|
|
8
10
|
loadConfig
|
|
9
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-GU4Y5ZWY.js";
|
|
10
12
|
import {
|
|
11
13
|
cleanStaleSessionFiles,
|
|
12
14
|
formatDriftAlert,
|
|
@@ -15,10 +17,11 @@ import {
|
|
|
15
17
|
formatSkipNotice,
|
|
16
18
|
getCached,
|
|
17
19
|
getRecentSummaries,
|
|
20
|
+
isSafeSessionId,
|
|
18
21
|
readSession,
|
|
19
22
|
recordEntry,
|
|
20
23
|
setCached
|
|
21
|
-
} from "../chunk-
|
|
24
|
+
} from "../chunk-VJN7Y4SI.js";
|
|
22
25
|
import "../chunk-7OCVIDC7.js";
|
|
23
26
|
|
|
24
27
|
// src/hooks/post-tool.ts
|
|
@@ -26,57 +29,6 @@ import { join } from "path";
|
|
|
26
29
|
|
|
27
30
|
// src/engines/claude.ts
|
|
28
31
|
import { execFile } from "child_process";
|
|
29
|
-
function extractJson(text) {
|
|
30
|
-
const trimmed = text.trim();
|
|
31
|
-
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
32
|
-
return trimmed;
|
|
33
|
-
}
|
|
34
|
-
const fenceMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
35
|
-
if (fenceMatch) {
|
|
36
|
-
return fenceMatch[1].trim();
|
|
37
|
-
}
|
|
38
|
-
const start = trimmed.indexOf("{");
|
|
39
|
-
const end = trimmed.lastIndexOf("}");
|
|
40
|
-
if (start !== -1 && end !== -1 && end > start) {
|
|
41
|
-
return trimmed.slice(start, end + 1);
|
|
42
|
-
}
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
function coerceString(v) {
|
|
46
|
-
return typeof v === "string" ? v : "";
|
|
47
|
-
}
|
|
48
|
-
function coerceDeepDive(v) {
|
|
49
|
-
if (!Array.isArray(v)) return [];
|
|
50
|
-
return v.filter((it) => typeof it === "object" && it !== null).map((it) => ({
|
|
51
|
-
term: coerceString(it.term),
|
|
52
|
-
explanation: coerceString(it.explanation)
|
|
53
|
-
})).filter((it) => it.term.length > 0);
|
|
54
|
-
}
|
|
55
|
-
function parseResponse(rawText) {
|
|
56
|
-
const json = extractJson(rawText);
|
|
57
|
-
if (!json) return null;
|
|
58
|
-
try {
|
|
59
|
-
const parsed = JSON.parse(json);
|
|
60
|
-
const risk = coerceString(parsed.risk);
|
|
61
|
-
if (!["none", "low", "medium", "high"].includes(risk)) return null;
|
|
62
|
-
return {
|
|
63
|
-
impact: coerceString(parsed.impact),
|
|
64
|
-
howItWorks: coerceString(parsed.howItWorks),
|
|
65
|
-
why: coerceString(parsed.why),
|
|
66
|
-
deepDive: coerceDeepDive(parsed.deepDive),
|
|
67
|
-
isSamePattern: parsed.isSamePattern === true,
|
|
68
|
-
samePatternNote: coerceString(parsed.samePatternNote),
|
|
69
|
-
risk,
|
|
70
|
-
riskReason: coerceString(parsed.riskReason)
|
|
71
|
-
};
|
|
72
|
-
} catch {
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
function truncateText(text, max) {
|
|
77
|
-
if (text.length <= max) return text;
|
|
78
|
-
return text.slice(0, max) + "...";
|
|
79
|
-
}
|
|
80
32
|
function runClaude(prompt, timeoutMs) {
|
|
81
33
|
return new Promise((resolve, reject) => {
|
|
82
34
|
const child = execFile(
|
|
@@ -111,14 +63,23 @@ function runClaude(prompt, timeoutMs) {
|
|
|
111
63
|
});
|
|
112
64
|
}
|
|
113
65
|
async function callClaude(inputs) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
66
|
+
let prompt;
|
|
67
|
+
try {
|
|
68
|
+
prompt = buildClaudePrompt(inputs.config.detailLevel, {
|
|
69
|
+
filePath: inputs.filePath,
|
|
70
|
+
diff: inputs.diff,
|
|
71
|
+
language: inputs.config.language,
|
|
72
|
+
learnerLevel: inputs.config.learnerLevel,
|
|
73
|
+
recentSummaries: inputs.recentSummaries
|
|
74
|
+
});
|
|
75
|
+
} catch (err) {
|
|
76
|
+
return {
|
|
77
|
+
kind: "error",
|
|
78
|
+
problem: "Failed to build Claude prompt",
|
|
79
|
+
cause: err.message || String(err),
|
|
80
|
+
fix: "Check detailLevel/learnerLevel/language values via 'npx vibe-code-explainer config'"
|
|
81
|
+
};
|
|
82
|
+
}
|
|
122
83
|
try {
|
|
123
84
|
const result = await runClaude(prompt, inputs.config.skipIfSlowMs);
|
|
124
85
|
if (result.code !== 0) {
|
|
@@ -287,6 +248,20 @@ function extractNewFileDiff(filePath, cwd) {
|
|
|
287
248
|
}
|
|
288
249
|
return { kind: "empty" };
|
|
289
250
|
}
|
|
251
|
+
function looksBinary(buf) {
|
|
252
|
+
const sample = buf.length > 8192 ? buf.subarray(0, 8192) : buf;
|
|
253
|
+
if (sample.length === 0) return false;
|
|
254
|
+
if (sample.indexOf(0) !== -1) return true;
|
|
255
|
+
let nonPrint = 0;
|
|
256
|
+
for (let i = 0; i < sample.length; i++) {
|
|
257
|
+
const b = sample[i];
|
|
258
|
+
if (b === 9 || b === 10 || b === 13 || b >= 32 && b <= 126 || b >= 128) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
nonPrint++;
|
|
262
|
+
}
|
|
263
|
+
return nonPrint / sample.length > 0.3;
|
|
264
|
+
}
|
|
290
265
|
function readFileAsNewDiff(filePath) {
|
|
291
266
|
if (!existsSync(filePath)) {
|
|
292
267
|
return { kind: "skip", reason: `file not found: ${filePath}` };
|
|
@@ -296,13 +271,17 @@ function readFileAsNewDiff(filePath) {
|
|
|
296
271
|
if (stat.size > 2 * 1024 * 1024) {
|
|
297
272
|
return { kind: "skip", reason: `file too large (${Math.round(stat.size / 1024)}KB)` };
|
|
298
273
|
}
|
|
299
|
-
const
|
|
300
|
-
if (
|
|
274
|
+
const buf = readFileSync(filePath);
|
|
275
|
+
if (buf.length === 0) {
|
|
301
276
|
return { kind: "empty" };
|
|
302
277
|
}
|
|
303
|
-
if (
|
|
278
|
+
if (looksBinary(buf)) {
|
|
304
279
|
return { kind: "binary", message: `Binary file created: ${filePath}` };
|
|
305
280
|
}
|
|
281
|
+
const raw = buf.toString("utf-8");
|
|
282
|
+
if (!raw.trim()) {
|
|
283
|
+
return { kind: "empty" };
|
|
284
|
+
}
|
|
306
285
|
const withMarkers = raw.split("\n").map((l) => `+ ${l}`).join("\n");
|
|
307
286
|
const diff = `--- /dev/null
|
|
308
287
|
+++ b/${filePath}
|
|
@@ -359,7 +338,32 @@ var MUTATING_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
359
338
|
"chown",
|
|
360
339
|
"ln",
|
|
361
340
|
"touch",
|
|
362
|
-
"dd"
|
|
341
|
+
"dd",
|
|
342
|
+
"tee",
|
|
343
|
+
"install",
|
|
344
|
+
"truncate",
|
|
345
|
+
"shred",
|
|
346
|
+
"rsync",
|
|
347
|
+
"scp",
|
|
348
|
+
"sftp",
|
|
349
|
+
"mount",
|
|
350
|
+
"umount",
|
|
351
|
+
"kill",
|
|
352
|
+
"killall",
|
|
353
|
+
"pkill",
|
|
354
|
+
"crontab",
|
|
355
|
+
"useradd",
|
|
356
|
+
"userdel",
|
|
357
|
+
"usermod",
|
|
358
|
+
"groupadd",
|
|
359
|
+
"groupdel",
|
|
360
|
+
"passwd",
|
|
361
|
+
"chpasswd",
|
|
362
|
+
"visudo",
|
|
363
|
+
"systemctl",
|
|
364
|
+
"service",
|
|
365
|
+
"launchctl"
|
|
366
|
+
// Note: brew is in CONTEXTUAL_COMMANDS (finer-grained control); do not add here.
|
|
363
367
|
]);
|
|
364
368
|
var CONTEXTUAL_COMMANDS = {
|
|
365
369
|
npm: /\b(install|add|remove|uninstall|update|ci|link|unlink|init|publish)\b/,
|
|
@@ -446,9 +450,12 @@ function subCommandShouldCapture(subCmd) {
|
|
|
446
450
|
const rest = tokens.slice(idx + 1).join(" ");
|
|
447
451
|
return contextPattern.test(rest);
|
|
448
452
|
}
|
|
449
|
-
return
|
|
453
|
+
return true;
|
|
450
454
|
}
|
|
451
|
-
function shouldCaptureBash(command) {
|
|
455
|
+
function shouldCaptureBash(command, capturePatterns = []) {
|
|
456
|
+
if (capturePatterns.length > 0 && capturePatterns.some((p) => command.includes(p))) {
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
452
459
|
const parts = splitCommandChain(command);
|
|
453
460
|
return parts.some((p) => subCommandShouldCapture(p));
|
|
454
461
|
}
|
|
@@ -502,12 +509,11 @@ function shouldAlertDrift(entries) {
|
|
|
502
509
|
const unrelatedFiles = Array.from(
|
|
503
510
|
new Set(entries.filter((e) => e.unrelated).map((e) => e.file))
|
|
504
511
|
);
|
|
505
|
-
const shouldAlert = unrelatedFiles.length > 0 && unrelatedFiles.length % DRIFT_ALERT_THRESHOLD === 0 && entries.filter((e) => e.unrelated).length === entries.filter((e) => e.unrelated).length;
|
|
506
512
|
const lastEntry = entries[entries.length - 1];
|
|
507
513
|
const lastWasUnrelated = lastEntry?.unrelated ?? false;
|
|
508
|
-
const
|
|
514
|
+
const shouldAlert = lastWasUnrelated && unrelatedFiles.length === DRIFT_ALERT_THRESHOLD;
|
|
509
515
|
return {
|
|
510
|
-
shouldAlert
|
|
516
|
+
shouldAlert,
|
|
511
517
|
totalFiles: uniqueFiles.length,
|
|
512
518
|
unrelatedFiles
|
|
513
519
|
};
|
|
@@ -519,11 +525,14 @@ function addOutput(text) {
|
|
|
519
525
|
output.push(text);
|
|
520
526
|
}
|
|
521
527
|
function safeExit() {
|
|
522
|
-
if (output.length
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
528
|
+
if (output.length === 0) {
|
|
529
|
+
process.exit(0);
|
|
530
|
+
}
|
|
531
|
+
const systemMessage = "\n" + output.join("\n");
|
|
532
|
+
const payload = JSON.stringify({ systemMessage }) + "\n";
|
|
533
|
+
process.stdout.write(payload, () => process.exit(0));
|
|
534
|
+
setTimeout(() => process.exit(0), 500);
|
|
535
|
+
throw new Error("unreachable");
|
|
527
536
|
}
|
|
528
537
|
async function readStdin() {
|
|
529
538
|
return new Promise((resolve) => {
|
|
@@ -540,10 +549,11 @@ async function readStdin() {
|
|
|
540
549
|
function parsePayload(raw) {
|
|
541
550
|
try {
|
|
542
551
|
const parsed = JSON.parse(raw);
|
|
543
|
-
if (typeof parsed
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
return null;
|
|
552
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
553
|
+
if (!isSafeSessionId(parsed.session_id)) return null;
|
|
554
|
+
if (typeof parsed.tool_name !== "string") return null;
|
|
555
|
+
if (typeof parsed.tool_input !== "object" || parsed.tool_input === null) return null;
|
|
556
|
+
return parsed;
|
|
547
557
|
} catch {
|
|
548
558
|
return null;
|
|
549
559
|
}
|
|
@@ -562,14 +572,48 @@ function isHookEnabled(toolName, config) {
|
|
|
562
572
|
if (lower === "bash") return config.hooks.bash;
|
|
563
573
|
return false;
|
|
564
574
|
}
|
|
565
|
-
async function runEngine(filePath, diff, config,
|
|
575
|
+
async function runEngine(filePath, diff, config, recentSummaries, signal) {
|
|
566
576
|
if (signal.aborted) {
|
|
567
577
|
return { kind: "skip", reason: "interrupted by user" };
|
|
568
578
|
}
|
|
569
579
|
if (config.engine === "ollama") {
|
|
570
580
|
return callOllama({ filePath, diff, config, recentSummaries });
|
|
571
581
|
}
|
|
572
|
-
return callClaude({ filePath, diff, config,
|
|
582
|
+
return callClaude({ filePath, diff, config, recentSummaries });
|
|
583
|
+
}
|
|
584
|
+
function buildEditWriteDiff(payload, config, cwd) {
|
|
585
|
+
const lowerTool = payload.tool_name.toLowerCase();
|
|
586
|
+
const input = payload.tool_input;
|
|
587
|
+
const target = input.file_path ?? input.filePath;
|
|
588
|
+
if (!target) {
|
|
589
|
+
safeExit();
|
|
590
|
+
}
|
|
591
|
+
const filePath = target;
|
|
592
|
+
if (isExcluded(filePath, config.exclude)) {
|
|
593
|
+
safeExit();
|
|
594
|
+
}
|
|
595
|
+
let result;
|
|
596
|
+
if (lowerTool === "edit") {
|
|
597
|
+
const oldStr = input.old_string ?? input.oldString ?? "";
|
|
598
|
+
const newStr = input.new_string ?? input.newString ?? "";
|
|
599
|
+
result = oldStr || newStr ? buildDiffFromEdit(filePath, oldStr, newStr) : extractEditDiff(filePath, cwd);
|
|
600
|
+
} else if (lowerTool === "multiedit") {
|
|
601
|
+
result = input.edits && input.edits.length > 0 ? buildDiffFromMultiEdit(filePath, input.edits) : extractEditDiff(filePath, cwd);
|
|
602
|
+
} else {
|
|
603
|
+
result = extractNewFileDiff(filePath, cwd);
|
|
604
|
+
}
|
|
605
|
+
if (result.kind === "empty") {
|
|
606
|
+
safeExit();
|
|
607
|
+
}
|
|
608
|
+
if (result.kind === "skip") {
|
|
609
|
+
addOutput(formatSkipNotice(result.reason));
|
|
610
|
+
safeExit();
|
|
611
|
+
}
|
|
612
|
+
if (result.kind === "binary") {
|
|
613
|
+
addOutput(formatSkipNotice(result.message));
|
|
614
|
+
safeExit();
|
|
615
|
+
}
|
|
616
|
+
return { filePath, diff: result.content };
|
|
573
617
|
}
|
|
574
618
|
async function main() {
|
|
575
619
|
const controller = new AbortController();
|
|
@@ -591,48 +635,20 @@ async function main() {
|
|
|
591
635
|
let diff;
|
|
592
636
|
const lowerTool = payload.tool_name.toLowerCase();
|
|
593
637
|
if (lowerTool === "edit" || lowerTool === "multiedit" || lowerTool === "write") {
|
|
594
|
-
const
|
|
595
|
-
const target = input.file_path ?? input.filePath;
|
|
638
|
+
const target = buildEditWriteDiff(payload, config, cwd);
|
|
596
639
|
if (!target) safeExit();
|
|
597
|
-
filePath = target;
|
|
598
|
-
if (isExcluded(filePath, config.exclude)) safeExit();
|
|
599
|
-
let result2;
|
|
600
|
-
if (lowerTool === "edit") {
|
|
601
|
-
const oldStr = input.old_string ?? input.oldString ?? "";
|
|
602
|
-
const newStr = input.new_string ?? input.newString ?? "";
|
|
603
|
-
if (oldStr || newStr) {
|
|
604
|
-
result2 = buildDiffFromEdit(filePath, oldStr, newStr);
|
|
605
|
-
} else {
|
|
606
|
-
result2 = extractEditDiff(filePath, cwd);
|
|
607
|
-
}
|
|
608
|
-
} else if (lowerTool === "multiedit") {
|
|
609
|
-
if (input.edits && input.edits.length > 0) {
|
|
610
|
-
result2 = buildDiffFromMultiEdit(filePath, input.edits);
|
|
611
|
-
} else {
|
|
612
|
-
result2 = extractEditDiff(filePath, cwd);
|
|
613
|
-
}
|
|
614
|
-
} else {
|
|
615
|
-
result2 = extractNewFileDiff(filePath, cwd);
|
|
616
|
-
}
|
|
617
|
-
if (result2.kind === "empty") safeExit();
|
|
618
|
-
if (result2.kind === "skip") {
|
|
619
|
-
addOutput(formatSkipNotice(result2.reason));
|
|
620
|
-
safeExit();
|
|
621
|
-
}
|
|
622
|
-
if (result2.kind === "binary") {
|
|
623
|
-
addOutput(formatSkipNotice(result2.message));
|
|
624
|
-
safeExit();
|
|
625
|
-
}
|
|
626
|
-
diff = result2.content;
|
|
640
|
+
({ filePath, diff } = target);
|
|
627
641
|
} else if (lowerTool === "bash") {
|
|
628
642
|
const input = payload.tool_input;
|
|
629
643
|
const command = input.command ?? "";
|
|
630
|
-
if (!command || !shouldCaptureBash(command)) safeExit();
|
|
644
|
+
if (!command || !shouldCaptureBash(command, config.bashFilter.capturePatterns)) safeExit();
|
|
631
645
|
filePath = "<bash command>";
|
|
632
646
|
diff = command;
|
|
633
647
|
} else {
|
|
634
648
|
safeExit();
|
|
635
649
|
}
|
|
650
|
+
const isBash = filePath === "<bash command>";
|
|
651
|
+
const priorEntries = isBash ? [] : readSession(payload.session_id);
|
|
636
652
|
const cacheKey = `${filePath}
|
|
637
653
|
${diff}`;
|
|
638
654
|
const cached = getCached(payload.session_id, cacheKey);
|
|
@@ -640,15 +656,8 @@ ${diff}`;
|
|
|
640
656
|
if (cached) {
|
|
641
657
|
result = cached;
|
|
642
658
|
} else {
|
|
643
|
-
const recentSummaries = getRecentSummaries(payload.session_id, 3);
|
|
644
|
-
const outcome = await runEngine(
|
|
645
|
-
filePath,
|
|
646
|
-
diff,
|
|
647
|
-
config,
|
|
648
|
-
void 0,
|
|
649
|
-
recentSummaries,
|
|
650
|
-
controller.signal
|
|
651
|
-
);
|
|
659
|
+
const recentSummaries = getRecentSummaries(payload.session_id, 3, priorEntries);
|
|
660
|
+
const outcome = await runEngine(filePath, diff, config, recentSummaries, controller.signal);
|
|
652
661
|
if (outcome.kind === "skip") {
|
|
653
662
|
addOutput(formatSkipNotice(outcome.reason));
|
|
654
663
|
safeExit();
|
|
@@ -661,8 +670,7 @@ ${diff}`;
|
|
|
661
670
|
setCached(payload.session_id, cacheKey, result);
|
|
662
671
|
}
|
|
663
672
|
let driftReason;
|
|
664
|
-
if (
|
|
665
|
-
const priorEntries = readSession(payload.session_id);
|
|
673
|
+
if (!isBash) {
|
|
666
674
|
const analysis = analyzeDrift(filePath, priorEntries);
|
|
667
675
|
if (analysis.isUnrelated) {
|
|
668
676
|
driftReason = analysis.reason;
|
|
@@ -684,8 +692,15 @@ ${diff}`;
|
|
|
684
692
|
summary: summaryForTracking,
|
|
685
693
|
unrelated: !!driftReason
|
|
686
694
|
});
|
|
687
|
-
const
|
|
688
|
-
|
|
695
|
+
const entryJustRecorded = {
|
|
696
|
+
file: filePath,
|
|
697
|
+
timestamp: Date.now(),
|
|
698
|
+
risk: result.risk,
|
|
699
|
+
summary: summaryForTracking,
|
|
700
|
+
unrelated: !!driftReason
|
|
701
|
+
};
|
|
702
|
+
const updatedEntries = [...priorEntries, entryJustRecorded];
|
|
703
|
+
const driftCheck = shouldAlertDrift(updatedEntries);
|
|
689
704
|
if (driftCheck.shouldAlert) {
|
|
690
705
|
addOutput(formatDriftAlert(driftCheck.totalFiles, driftCheck.unrelatedFiles, void 0, config.language));
|
|
691
706
|
}
|