kodevu 0.1.63 → 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 +10 -0
- package/package.json +1 -1
- package/src/logger.js +63 -85
- package/src/utils.js +1 -0
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
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
|
-
|
|
6
|
-
if (value == null) {
|
|
7
|
-
return "";
|
|
8
|
-
}
|
|
5
|
+
// Top-level helpers moved into Logger as private methods (#name).
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
15
|
+
#formatValue(value) {
|
|
16
|
+
if (value == null) return "";
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
return JSON.stringify(value);
|
|
20
|
-
} catch {
|
|
21
|
-
return String(value);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
18
|
+
if (typeof value === "string") return value.trim();
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
const parts = [];
|
|
20
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
27
21
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
try {
|
|
23
|
+
return JSON.stringify(value);
|
|
24
|
+
} catch {
|
|
25
|
+
return String(value);
|
|
31
26
|
}
|
|
27
|
+
}
|
|
32
28
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
29
|
+
#formatMeta(meta = {}) {
|
|
30
|
+
const parts = [];
|
|
37
31
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
32
|
+
for (const [key, rawValue] of Object.entries(meta || {})) {
|
|
33
|
+
if (rawValue == null || rawValue === "") continue;
|
|
42
34
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
35
|
+
const value = this.#formatValue(rawValue);
|
|
36
|
+
if (!value) continue;
|
|
47
37
|
|
|
48
|
-
|
|
49
|
-
|
|
38
|
+
if (typeof rawValue === "string") {
|
|
39
|
+
parts.push(`${key}=${JSON.stringify(value)}`);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
50
42
|
|
|
51
|
-
|
|
52
|
-
}
|
|
43
|
+
if (typeof rawValue === "number" || typeof rawValue === "boolean") {
|
|
44
|
+
parts.push(`${key}=${rawValue}`);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
53
47
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return "";
|
|
57
|
-
}
|
|
48
|
+
parts.push(`${key}=${value}`);
|
|
49
|
+
}
|
|
58
50
|
|
|
59
|
-
|
|
60
|
-
return error.stack || error.message || String(error);
|
|
51
|
+
return parts.join(" ");
|
|
61
52
|
}
|
|
62
53
|
|
|
63
|
-
|
|
64
|
-
return
|
|
65
|
-
}
|
|
54
|
+
#formatErrorDetails(error) {
|
|
55
|
+
if (!error) return "";
|
|
66
56
|
|
|
67
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
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
|
|
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
|
|
91
|
+
this.#log("INFO", message, meta);
|
|
106
92
|
}
|
|
107
93
|
|
|
108
94
|
warn(message, meta) {
|
|
109
|
-
this
|
|
95
|
+
this.#log("WARN", message, { ...meta, console: meta?.console ?? true });
|
|
110
96
|
}
|
|
111
97
|
|
|
112
98
|
error(message, error, meta) {
|
|
113
|
-
this
|
|
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
|
|
107
|
+
this.#log("DEBUG", message, meta);
|
|
122
108
|
}
|
|
123
109
|
|
|
124
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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");
|