kodevu 0.1.62 → 0.1.64

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/README.md CHANGED
@@ -23,6 +23,16 @@ npx kodevu .
23
23
  Review reports are saved to `~/.kodevu/` by default.
24
24
  Console output is intentionally concise by default; detailed execution logs are written to `~/.kodevu/logs/`.
25
25
 
26
+ ## Install as an AI Agent Skill
27
+
28
+ Kodevu includes a natively supported `SKILL.md` file, which allows it to be installed as a specialized skill in AI agent coding assistants.
29
+
30
+ To install:
31
+
32
+ ```bash
33
+ npx skills add gyteng/kodevu
34
+ ```
35
+
26
36
  ## Usage
27
37
 
28
38
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodevu",
3
- "version": "0.1.62",
3
+ "version": "0.1.64",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Poll SVN revisions or Git commits, send each change diff to a reviewer CLI, and write configurable review reports.",
package/src/config.js CHANGED
@@ -139,7 +139,7 @@ async function resolveAutoReviewers(debug) {
139
139
  }
140
140
 
141
141
  if (availableReviewers.length === 0) {
142
- throw new Error(`No reviewer CLI found in PATH. Install one of: ${AUTO_SUPPORTED_REVIEWERS.join(", ")}`);
142
+ throw mkCliError(`No reviewer CLI found in PATH. Install one of: ${AUTO_SUPPORTED_REVIEWERS.join(", ")}`);
143
143
  }
144
144
 
145
145
  // Shuffle for variety
@@ -335,11 +335,11 @@ export async function resolveConfig(cliArgs = {}) {
335
335
  }
336
336
 
337
337
  if (cliArgs.rev && cliArgs.last) {
338
- throw new Error("Parameters --rev and --last are mutually exclusive. Please specify only one.");
338
+ throw mkCliError("Parameters --rev and --last are mutually exclusive. Please specify only one.");
339
339
  }
340
340
 
341
341
  if (cliArgs.uncommitted && (cliArgs.rev || cliArgs.last)) {
342
- throw new Error("Parameter --uncommitted is mutually exclusive with --rev and --last.");
342
+ throw mkCliError("Parameter --uncommitted is mutually exclusive with --rev and --last.");
343
343
  }
344
344
 
345
345
  if (!config.target) {
@@ -358,7 +358,7 @@ export async function resolveConfig(cliArgs = {}) {
358
358
  try {
359
359
  config.prompt = await fs.readFile(promptPath, "utf8");
360
360
  } catch (err) {
361
- throw new Error(`Failed to read prompt file: ${promptPath} (${err.message})`);
361
+ throw mkCliError(`Failed to read prompt file: ${promptPath} (${err.message})`);
362
362
  }
363
363
  }
364
364
 
@@ -370,7 +370,7 @@ export async function resolveConfig(cliArgs = {}) {
370
370
  config.fallbackReviewers = availableReviewers.map(r => r.reviewerName).slice(1);
371
371
  config.reviewerWasAutoSelected = true;
372
372
  } else if (!SUPPORTED_REVIEWERS.includes(config.reviewer)) {
373
- throw new Error(`"reviewer" must be one of: ${SUPPORTED_REVIEWERS.join(", ")}, or "auto"`);
373
+ throw mkCliError(`"reviewer" must be one of: ${SUPPORTED_REVIEWERS.join(", ")}, or "auto"`);
374
374
  }
375
375
 
376
376
  config.outputDir = resolvePath(config.outputDir);
@@ -391,7 +391,7 @@ export async function resolveConfig(cliArgs = {}) {
391
391
  }
392
392
 
393
393
  if (config.reviewer === "openai" && !config.openaiApiKey) {
394
- throw new Error('Reviewer "openai" requires an API key. Set KODEVU_OPENAI_API_KEY or pass --openai-api-key.');
394
+ throw mkCliError('Reviewer "openai" requires an API key. Set KODEVU_OPENAI_API_KEY or pass --openai-api-key.');
395
395
  }
396
396
 
397
397
  return config;
package/src/logger.js CHANGED
@@ -2,87 +2,73 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { formatDate } from "./utils.js";
4
4
 
5
- function formatValue(value) {
6
- if (value == null) {
7
- return "";
8
- }
5
+ // Top-level helpers moved into Logger as private methods (#name).
9
6
 
10
- if (typeof value === "string") {
11
- return value.trim();
7
+ class Logger {
8
+ constructor () {
9
+ this.config = null;
10
+ this.logFile = null;
11
+ this.sessionId = null;
12
+ this.initialized = false;
12
13
  }
13
14
 
14
- if (typeof value === "number" || typeof value === "boolean") {
15
- return String(value);
16
- }
15
+ #formatValue(value) {
16
+ if (value == null) return "";
17
17
 
18
- try {
19
- return JSON.stringify(value);
20
- } catch {
21
- return String(value);
22
- }
23
- }
18
+ if (typeof value === "string") return value.trim();
24
19
 
25
- function formatMeta(meta = {}) {
26
- const parts = [];
20
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
27
21
 
28
- for (const [key, rawValue] of Object.entries(meta)) {
29
- if (rawValue == null || rawValue === "") {
30
- continue;
22
+ try {
23
+ return JSON.stringify(value);
24
+ } catch {
25
+ return String(value);
31
26
  }
27
+ }
32
28
 
33
- const value = formatValue(rawValue);
34
- if (!value) {
35
- continue;
36
- }
29
+ #formatMeta(meta = {}) {
30
+ const parts = [];
37
31
 
38
- if (typeof rawValue === "string") {
39
- parts.push(`${key}=${JSON.stringify(value)}`);
40
- continue;
41
- }
32
+ for (const [key, rawValue] of Object.entries(meta || {})) {
33
+ if (rawValue == null || rawValue === "") continue;
42
34
 
43
- if (typeof rawValue === "number" || typeof rawValue === "boolean") {
44
- parts.push(`${key}=${String(rawValue)}`);
45
- continue;
46
- }
35
+ const value = this.#formatValue(rawValue);
36
+ if (!value) continue;
47
37
 
48
- parts.push(`${key}=${value}`);
49
- }
38
+ if (typeof rawValue === "string") {
39
+ parts.push(`${key}=${JSON.stringify(value)}`);
40
+ continue;
41
+ }
50
42
 
51
- return parts.join(" ");
52
- }
43
+ if (typeof rawValue === "number" || typeof rawValue === "boolean") {
44
+ parts.push(`${key}=${rawValue}`);
45
+ continue;
46
+ }
53
47
 
54
- function formatErrorDetails(error) {
55
- if (!error) {
56
- return "";
57
- }
48
+ parts.push(`${key}=${value}`);
49
+ }
58
50
 
59
- if (error instanceof Error) {
60
- return error.stack || error.message || String(error);
51
+ return parts.join(" ");
61
52
  }
62
53
 
63
- if (typeof error === "string") {
64
- return error;
65
- }
54
+ #formatErrorDetails(error) {
55
+ if (!error) return "";
66
56
 
67
- try {
68
- return JSON.stringify(error, null, 2);
69
- } catch {
70
- return String(error);
71
- }
72
- }
57
+ if (error instanceof Error) return error.stack ?? error.message ?? String(error);
73
58
 
74
- class Logger {
75
- constructor() {
76
- this.config = null;
77
- this.logFile = null;
78
- this.sessionId = null;
79
- this.initialized = false;
59
+ if (typeof error === "string") return error;
60
+
61
+ try {
62
+ return JSON.stringify(error, null, 2);
63
+ } catch {
64
+ return String(error);
65
+ }
80
66
  }
81
67
 
82
68
  init(config) {
83
69
  if (this.initialized) return;
84
70
  this.config = config;
85
- this.sessionId = this._createSessionId();
71
+ this.sessionId = this.#createSessionId();
86
72
 
87
73
  if (config.logsDir) {
88
74
  try {
@@ -93,7 +79,7 @@ class Logger {
93
79
  this.logFile = path.join(config.logsDir, `run-${date}.log`);
94
80
 
95
81
  // Simple rotation: Clean up logs older than 7 days
96
- this._cleanupOldLogs(config.logsDir);
82
+ this.#cleanupOldLogs(config.logsDir);
97
83
  this.initialized = true;
98
84
  } catch (err) {
99
85
  console.error(`[logger] Failed to initialize log file: ${err.message}`);
@@ -102,33 +88,33 @@ class Logger {
102
88
  }
103
89
 
104
90
  info(message, meta) {
105
- this._log("INFO", message, meta);
91
+ this.#log("INFO", message, meta);
106
92
  }
107
93
 
108
94
  warn(message, meta) {
109
- this._log("WARN", message, { ...meta, console: meta?.console ?? true });
95
+ this.#log("WARN", message, { ...meta, console: meta?.console ?? true });
110
96
  }
111
97
 
112
98
  error(message, error, meta) {
113
- this._log("ERROR", message, {
99
+ this.#log("ERROR", message, {
114
100
  ...meta,
115
101
  console: meta?.console ?? true,
116
- error: formatErrorDetails(error)
102
+ error: this.#formatErrorDetails(error)
117
103
  });
118
104
  }
119
105
 
120
106
  debug(message, meta) {
121
- this._log("DEBUG", message, meta);
107
+ this.#log("DEBUG", message, meta);
122
108
  }
123
109
 
124
- _log(level, message, meta = {}) {
110
+ #log(level, message, meta = {}) {
125
111
  const timestamp = formatDate(new Date());
126
112
  const { console: consoleMode, ...details } = meta;
127
113
  const fields = {
128
114
  session: this.sessionId || "uninitialized",
129
115
  ...details
130
116
  };
131
- const metaSuffix = formatMeta(fields);
117
+ const metaSuffix = this.#formatMeta(fields);
132
118
  const logLine = `[${timestamp}] [${level}] ${message}${metaSuffix ? ` | ${metaSuffix}` : ""}`;
133
119
 
134
120
  if (this.logFile) {
@@ -139,7 +125,7 @@ class Logger {
139
125
  }
140
126
  }
141
127
 
142
- if (!this._shouldWriteToConsole(level, consoleMode)) {
128
+ if (!this.#shouldWriteToConsole(level, consoleMode)) {
143
129
  return;
144
130
  }
145
131
 
@@ -150,34 +136,24 @@ class Logger {
150
136
  }
151
137
  }
152
138
 
153
- _shouldWriteToConsole(level, consoleMode) {
154
- if (consoleMode === false) {
155
- return false;
156
- }
139
+ #shouldWriteToConsole(level, consoleMode) {
140
+ if (consoleMode === false) return false;
157
141
 
158
- if (level === "ERROR" || level === "WARN") {
159
- return true;
160
- }
142
+ if (level === "ERROR" || level === "WARN") return true;
161
143
 
162
144
  if (level === "DEBUG") {
163
- if (consoleMode === true) {
164
- return Boolean(this.config?.debug);
165
- }
145
+ if (consoleMode === true) return Boolean(this.config?.debug);
166
146
  return false;
167
147
  }
168
148
 
169
- if (consoleMode === true) {
170
- return true;
171
- }
149
+ if (consoleMode === true) return true;
172
150
 
173
- if (consoleMode === "debug") {
174
- return Boolean(this.config?.debug);
175
- }
151
+ if (consoleMode === "debug") return Boolean(this.config?.debug);
176
152
 
177
153
  return false;
178
154
  }
179
155
 
180
- _createSessionId() {
156
+ #createSessionId() {
181
157
  return [
182
158
  Date.now().toString(36),
183
159
  process.pid.toString(36),
@@ -185,7 +161,9 @@ class Logger {
185
161
  ].join("-");
186
162
  }
187
163
 
188
- _cleanupOldLogs(logsDir) {
164
+
165
+
166
+ #cleanupOldLogs(logsDir) {
189
167
  try {
190
168
  const files = fs.readdirSync(logsDir);
191
169
  const now = Date.now();
package/src/utils.js CHANGED
@@ -55,6 +55,7 @@ export function formatDate(dateInput) {
55
55
 
56
56
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds} ${offset}`;
57
57
  }
58
+
58
59
  export function getTimestampPrefix() {
59
60
  const now = new Date();
60
61
  const pad = (n) => String(n).padStart(2, "0");