wdyt 0.1.0 → 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/package.json +4 -4
- package/src/cli.ts +2 -1
- package/src/commands/chat.ts +85 -34
- package/src/commands/init.ts +56 -14
- package/src/state.ts +5 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wdyt",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Code review context builder for LLMs - what do you think?",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,12 +16,12 @@
|
|
|
16
16
|
],
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|
|
19
|
-
"url": "git+https://github.com/Bewinxed/
|
|
19
|
+
"url": "git+https://github.com/Bewinxed/wdyt.git"
|
|
20
20
|
},
|
|
21
21
|
"bugs": {
|
|
22
|
-
"url": "https://github.com/Bewinxed/
|
|
22
|
+
"url": "https://github.com/Bewinxed/wdyt/issues"
|
|
23
23
|
},
|
|
24
|
-
"homepage": "https://github.com/Bewinxed/
|
|
24
|
+
"homepage": "https://github.com/Bewinxed/wdyt#readme",
|
|
25
25
|
"keywords": [
|
|
26
26
|
"code-review",
|
|
27
27
|
"llm",
|
package/src/cli.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { defineCommand, runMain } from "citty";
|
|
17
17
|
import type { CLIFlags } from "./types";
|
|
18
|
+
import pkg from "../package.json";
|
|
18
19
|
import { windowsCommand } from "./commands/windows";
|
|
19
20
|
import { builderCommand } from "./commands/builder";
|
|
20
21
|
import {
|
|
@@ -179,7 +180,7 @@ if (args[0] === "init") {
|
|
|
179
180
|
const main = defineCommand({
|
|
180
181
|
meta: {
|
|
181
182
|
name: "wdyt",
|
|
182
|
-
version:
|
|
183
|
+
version: pkg.version,
|
|
183
184
|
description: "Code review context builder for LLMs",
|
|
184
185
|
},
|
|
185
186
|
args: {
|
package/src/commands/chat.ts
CHANGED
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
* }
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import {
|
|
21
|
+
import { mkdirSync } from "fs";
|
|
22
22
|
import { join, dirname, basename } from "path";
|
|
23
23
|
import { homedir } from "os";
|
|
24
|
+
import { $ } from "bun";
|
|
24
25
|
import { getTab, getWindow } from "../state";
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -74,14 +75,79 @@ function generateUUID(): string {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
/**
|
|
77
|
-
*
|
|
78
|
+
* Check if claude CLI is available
|
|
78
79
|
*/
|
|
79
|
-
function
|
|
80
|
+
async function claudeCliAvailable(): Promise<boolean> {
|
|
80
81
|
try {
|
|
81
|
-
|
|
82
|
+
await $`which claude`.quiet();
|
|
83
|
+
return true;
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Run a review using Claude CLI
|
|
91
|
+
* Returns the review output including verdict
|
|
92
|
+
*/
|
|
93
|
+
async function runClaudeReview(contextPath: string, prompt: string): Promise<string> {
|
|
94
|
+
// Read the context file content first
|
|
95
|
+
const contextFile = Bun.file(contextPath);
|
|
96
|
+
const contextContent = await contextFile.text();
|
|
97
|
+
|
|
98
|
+
const reviewPrompt = `You are reviewing code changes. Analyze the following context and provide a thorough review.
|
|
99
|
+
|
|
100
|
+
Review instructions:
|
|
101
|
+
${prompt}
|
|
102
|
+
|
|
103
|
+
<context>
|
|
104
|
+
${contextContent}
|
|
105
|
+
</context>
|
|
106
|
+
|
|
107
|
+
Analyze the code for:
|
|
108
|
+
- Correctness - Logic errors, bugs, spec compliance
|
|
109
|
+
- Security - Injection risks, auth gaps, data exposure
|
|
110
|
+
- Simplicity - Over-engineering, unnecessary complexity
|
|
111
|
+
- Edge cases - Failure modes, boundary conditions
|
|
112
|
+
|
|
113
|
+
Provide findings organized by severity (Critical > Major > Minor).
|
|
114
|
+
|
|
115
|
+
REQUIRED: End your review with exactly one verdict tag:
|
|
116
|
+
<verdict>SHIP</verdict> - Code is production-ready
|
|
117
|
+
<verdict>NEEDS_WORK</verdict> - Issues must be fixed first
|
|
118
|
+
<verdict>MAJOR_RETHINK</verdict> - Fundamental problems require redesign`;
|
|
119
|
+
|
|
120
|
+
// Write prompt to temp file to avoid shell escaping issues
|
|
121
|
+
const tempPromptPath = join(getChatsDir(), `review-prompt-${Date.now()}.txt`);
|
|
122
|
+
await Bun.write(tempPromptPath, reviewPrompt);
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
// Run claude CLI in print mode, reading from temp file
|
|
126
|
+
const result = await $`cat ${tempPromptPath} | claude -p`.text();
|
|
127
|
+
|
|
128
|
+
// Clean up temp file
|
|
129
|
+
await $`rm ${tempPromptPath}`.quiet();
|
|
130
|
+
|
|
131
|
+
return result.trim();
|
|
132
|
+
} catch (error) {
|
|
133
|
+
// Clean up temp file on error too
|
|
134
|
+
await $`rm ${tempPromptPath}`.quiet();
|
|
135
|
+
|
|
136
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
137
|
+
throw new Error(`Claude CLI review failed: ${message}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Read file content safely using Bun's file API
|
|
143
|
+
*/
|
|
144
|
+
async function readFileSafe(path: string): Promise<{ success: boolean; content?: string; error?: string }> {
|
|
145
|
+
try {
|
|
146
|
+
const file = Bun.file(path);
|
|
147
|
+
if (!(await file.exists())) {
|
|
82
148
|
return { success: false, error: `File not found: ${path}` };
|
|
83
149
|
}
|
|
84
|
-
const content =
|
|
150
|
+
const content = await file.text();
|
|
85
151
|
return { success: true, content };
|
|
86
152
|
} catch (error) {
|
|
87
153
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -218,7 +284,7 @@ export async function chatSendCommand(
|
|
|
218
284
|
const errors: string[] = [];
|
|
219
285
|
|
|
220
286
|
for (const filePath of filePaths) {
|
|
221
|
-
const result = readFileSafe(filePath);
|
|
287
|
+
const result = await readFileSafe(filePath);
|
|
222
288
|
if (result.success && result.content !== undefined) {
|
|
223
289
|
files.push({ path: filePath, content: result.content });
|
|
224
290
|
} else {
|
|
@@ -252,41 +318,26 @@ export async function chatSendCommand(
|
|
|
252
318
|
const chatPath = join(chatsDir, `${chatId}.xml`);
|
|
253
319
|
await Bun.write(chatPath, xmlContent);
|
|
254
320
|
|
|
255
|
-
// Check if this is a review request - if so,
|
|
321
|
+
// Check if this is a review request - if so, run Claude CLI to do the review
|
|
256
322
|
const isReviewMode = payload.mode === "review";
|
|
257
323
|
|
|
258
324
|
if (isReviewMode) {
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
Task(subagent_type="flow-next:quality-auditor", prompt="""
|
|
268
|
-
Review the code changes in this context file: ${chatPath}
|
|
269
|
-
|
|
270
|
-
Read the file, analyze the code for:
|
|
271
|
-
- Correctness - Logic errors, bugs
|
|
272
|
-
- Security - Injection, auth gaps
|
|
273
|
-
- Simplicity - Over-engineering
|
|
274
|
-
- Edge cases - Failure modes
|
|
275
|
-
|
|
276
|
-
Provide findings by severity (Critical > Major > Minor).
|
|
277
|
-
|
|
278
|
-
REQUIRED: End with exactly one verdict tag:
|
|
279
|
-
<verdict>SHIP</verdict> - Code is production-ready
|
|
280
|
-
<verdict>NEEDS_WORK</verdict> - Issues must be fixed first
|
|
281
|
-
""")
|
|
325
|
+
// Check if claude CLI is available
|
|
326
|
+
if (!(await claudeCliAvailable())) {
|
|
327
|
+
return {
|
|
328
|
+
success: false,
|
|
329
|
+
error: "Review mode requires Claude CLI (claude) to be installed and in PATH",
|
|
330
|
+
};
|
|
331
|
+
}
|
|
282
332
|
|
|
283
|
-
|
|
284
|
-
|
|
333
|
+
// Run the review using Claude CLI
|
|
334
|
+
console.error("Running review with Claude CLI...");
|
|
335
|
+
const reviewOutput = await runClaudeReview(chatPath, prompt);
|
|
285
336
|
|
|
286
337
|
return {
|
|
287
338
|
success: true,
|
|
288
|
-
data: { id: chatId, path: chatPath },
|
|
289
|
-
output: reviewOutput
|
|
339
|
+
data: { id: chatId, path: chatPath, review: reviewOutput },
|
|
340
|
+
output: `Chat: \`${chatId}\`\n\n${reviewOutput}`,
|
|
290
341
|
};
|
|
291
342
|
}
|
|
292
343
|
|
package/src/commands/init.ts
CHANGED
|
@@ -8,11 +8,14 @@
|
|
|
8
8
|
* bunx wdyt init --global # Install globally
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { mkdirSync,
|
|
11
|
+
import { mkdirSync, symlinkSync, unlinkSync } from "fs";
|
|
12
12
|
import { join } from "path";
|
|
13
13
|
import { homedir } from "os";
|
|
14
14
|
import { $ } from "bun";
|
|
15
15
|
import * as readline from "readline";
|
|
16
|
+
import pkg from "../../package.json";
|
|
17
|
+
|
|
18
|
+
const CURRENT_VERSION = pkg.version;
|
|
16
19
|
|
|
17
20
|
interface InitOptions {
|
|
18
21
|
rpAlias?: boolean;
|
|
@@ -20,6 +23,30 @@ interface InitOptions {
|
|
|
20
23
|
global?: boolean;
|
|
21
24
|
}
|
|
22
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Get the installed version of wdyt binary
|
|
28
|
+
*/
|
|
29
|
+
async function getInstalledVersion(binPath: string): Promise<string | null> {
|
|
30
|
+
try {
|
|
31
|
+
const result = await $`${binPath} --version 2>/dev/null`.text();
|
|
32
|
+
return result.trim() || null;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Compare semver versions, returns true if v1 > v2
|
|
40
|
+
*/
|
|
41
|
+
function isNewerVersion(v1: string, v2: string): boolean {
|
|
42
|
+
const parse = (v: string) => v.split(".").map(Number);
|
|
43
|
+
const [a1, b1, c1] = parse(v1);
|
|
44
|
+
const [a2, b2, c2] = parse(v2);
|
|
45
|
+
if (a1 !== a2) return a1 > a2;
|
|
46
|
+
if (b1 !== b2) return b1 > b2;
|
|
47
|
+
return c1 > c2;
|
|
48
|
+
}
|
|
49
|
+
|
|
23
50
|
/**
|
|
24
51
|
* Prompt user for yes/no input
|
|
25
52
|
*/
|
|
@@ -102,25 +129,40 @@ export async function initCommand(options: InitOptions): Promise<{
|
|
|
102
129
|
};
|
|
103
130
|
}
|
|
104
131
|
|
|
105
|
-
// 2. Check if already installed globally
|
|
132
|
+
// 2. Check if already installed globally and compare versions
|
|
106
133
|
const alreadyInstalled = await commandExists("wdyt");
|
|
134
|
+
const wdytPath = join(binDir, "wdyt");
|
|
135
|
+
let needsUpdate = false;
|
|
107
136
|
|
|
108
|
-
if (alreadyInstalled
|
|
109
|
-
|
|
110
|
-
|
|
137
|
+
if (alreadyInstalled) {
|
|
138
|
+
const installedVersion = await getInstalledVersion(wdytPath);
|
|
139
|
+
if (installedVersion) {
|
|
140
|
+
if (isNewerVersion(CURRENT_VERSION, installedVersion)) {
|
|
141
|
+
lines.push("");
|
|
142
|
+
lines.push(`⚠️ Installed version: ${installedVersion}, available: ${CURRENT_VERSION}`);
|
|
143
|
+
lines.push(" Updating to latest version...");
|
|
144
|
+
needsUpdate = true;
|
|
145
|
+
} else if (!options.global) {
|
|
146
|
+
lines.push("");
|
|
147
|
+
lines.push(`✓ wdyt ${installedVersion} is already installed and up to date`);
|
|
148
|
+
}
|
|
149
|
+
} else if (!options.global) {
|
|
150
|
+
lines.push("");
|
|
151
|
+
lines.push("✓ wdyt is already available in PATH");
|
|
152
|
+
}
|
|
111
153
|
}
|
|
112
154
|
|
|
113
|
-
// 3. Global install if requested
|
|
114
|
-
if (options.global) {
|
|
115
|
-
|
|
116
|
-
|
|
155
|
+
// 3. Global install if requested OR if update needed
|
|
156
|
+
if (options.global || needsUpdate) {
|
|
157
|
+
if (!needsUpdate) {
|
|
158
|
+
lines.push("");
|
|
159
|
+
lines.push("Installing globally...");
|
|
160
|
+
}
|
|
117
161
|
|
|
118
162
|
try {
|
|
119
163
|
// Ensure bin directory exists
|
|
120
164
|
mkdirSync(binDir, { recursive: true });
|
|
121
165
|
|
|
122
|
-
// Get the path to the current executable or script
|
|
123
|
-
const currentExe = process.argv[1];
|
|
124
166
|
const targetPath = join(binDir, "wdyt");
|
|
125
167
|
|
|
126
168
|
// Build the binary
|
|
@@ -128,7 +170,7 @@ export async function initCommand(options: InitOptions): Promise<{
|
|
|
128
170
|
const srcDir = join(import.meta.dir, "..");
|
|
129
171
|
await $`bun build ${join(srcDir, "cli.ts")} --compile --outfile ${targetPath}`.quiet();
|
|
130
172
|
|
|
131
|
-
lines.push(` ✓ Installed to ${targetPath}`);
|
|
173
|
+
lines.push(` ✓ ${needsUpdate ? "Updated" : "Installed"} to ${targetPath} (v${CURRENT_VERSION})`);
|
|
132
174
|
|
|
133
175
|
// Check if ~/.local/bin is in PATH
|
|
134
176
|
const path = process.env.PATH || "";
|
|
@@ -169,12 +211,12 @@ export async function initCommand(options: InitOptions): Promise<{
|
|
|
169
211
|
mkdirSync(binDir, { recursive: true });
|
|
170
212
|
|
|
171
213
|
// Remove existing symlink if present
|
|
172
|
-
if (
|
|
214
|
+
if (await Bun.file(rpCliPath).exists()) {
|
|
173
215
|
unlinkSync(rpCliPath);
|
|
174
216
|
}
|
|
175
217
|
|
|
176
218
|
// Check if wdyt binary exists
|
|
177
|
-
if (
|
|
219
|
+
if (await Bun.file(secondOpinionPath).exists()) {
|
|
178
220
|
symlinkSync(secondOpinionPath, rpCliPath);
|
|
179
221
|
lines.push(` ✓ Created symlink: rp-cli -> wdyt`);
|
|
180
222
|
} else {
|
package/src/state.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { join } from "path";
|
|
9
9
|
import { homedir } from "os";
|
|
10
|
-
import { mkdirSync,
|
|
10
|
+
import { mkdirSync, renameSync } from "fs";
|
|
11
11
|
import type { StateFile, Window, Tab, TabUpdate } from "./types";
|
|
12
12
|
|
|
13
13
|
const STATE_VERSION = 1;
|
|
@@ -60,14 +60,14 @@ function generateUUID(): string {
|
|
|
60
60
|
export async function loadState(): Promise<StateFile> {
|
|
61
61
|
const statePath = getStatePath();
|
|
62
62
|
|
|
63
|
-
// Check if file exists first
|
|
64
|
-
|
|
63
|
+
// Check if file exists first using Bun's file API
|
|
64
|
+
const stateFile = Bun.file(statePath);
|
|
65
|
+
if (!(await stateFile.exists())) {
|
|
65
66
|
return createDefaultState();
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
try {
|
|
69
|
-
const
|
|
70
|
-
const content = await file.text();
|
|
70
|
+
const content = await stateFile.text();
|
|
71
71
|
const state = JSON.parse(content) as StateFile;
|
|
72
72
|
|
|
73
73
|
// Validate basic structure
|