wdyt 0.1.13 → 0.1.15

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.
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Re-review cache-busting module
3
+ *
4
+ * Detects when a chat is a re-review (same chat ID continuing) and prepends
5
+ * cache-busting instructions telling the model to re-read changed files.
6
+ *
7
+ * Compatible with flowctl.py:
8
+ * - build_rereview_preamble() (line 974-998)
9
+ * - is_rereview detection via session_id/chat_id (line 4314-4324)
10
+ */
11
+
12
+ import { $ } from "bun";
13
+
14
+ /**
15
+ * Re-review options
16
+ */
17
+ export interface ReReviewOptions {
18
+ /** Previous chat ID (if continuing a chat) */
19
+ chatId?: string;
20
+ /** Explicit flag indicating this is a re-review */
21
+ isReReview?: boolean;
22
+ /** Base branch/commit for computing changed files */
23
+ baseBranch?: string;
24
+ /** Review type for the preamble message */
25
+ reviewType?: string;
26
+ }
27
+
28
+ /**
29
+ * Re-review state tracking
30
+ * Maps chat IDs to their last review timestamp/commit
31
+ */
32
+ const reReviewState = new Map<string, { timestamp: number; files: string[] }>();
33
+
34
+ /**
35
+ * Build the re-review preamble instructing the model to re-read changed files.
36
+ *
37
+ * Based on flowctl's build_rereview_preamble()
38
+ *
39
+ * @param changedFiles - List of files that have changed since last review
40
+ * @param reviewType - Type of review (e.g., "implementation", "plan")
41
+ * @returns The preamble markdown string
42
+ */
43
+ export function buildReReviewPreamble(changedFiles: string[], reviewType: string = "implementation"): string {
44
+ // Cap at 30 files to avoid overwhelming the preamble
45
+ const MAX_FILES = 30;
46
+ let filesList = changedFiles.slice(0, MAX_FILES).map((f) => `- ${f}`).join("\n");
47
+
48
+ if (changedFiles.length > MAX_FILES) {
49
+ filesList += `\n- ... and ${changedFiles.length - MAX_FILES} more files`;
50
+ }
51
+
52
+ return `## IMPORTANT: Re-review After Fixes
53
+
54
+ This is a RE-REVIEW. Code has been modified since your last review.
55
+
56
+ **You MUST re-read these files before reviewing** - your cached view is stale:
57
+ ${filesList}
58
+
59
+ Use your file reading tools to get the CURRENT content of these files.
60
+ Do NOT rely on what you saw in the previous review - the code has changed.
61
+
62
+ After re-reading, conduct a fresh ${reviewType} review on the updated code.
63
+
64
+ ---
65
+
66
+ `;
67
+ }
68
+
69
+ /**
70
+ * Get changed files using git diff against a base branch/commit
71
+ *
72
+ * @param baseBranch - Base branch or commit to diff against (default: "main")
73
+ * @returns Array of changed file paths
74
+ */
75
+ export async function getChangedFiles(baseBranch: string = "main"): Promise<string[]> {
76
+ try {
77
+ // Get list of changed files (both staged and unstaged)
78
+ const result = await $`git diff --name-only ${baseBranch}...HEAD`.text();
79
+ const files = result
80
+ .trim()
81
+ .split("\n")
82
+ .filter((f) => f.length > 0);
83
+ return files;
84
+ } catch {
85
+ // Fallback: try diffing against base directly (not merge-base)
86
+ try {
87
+ const result = await $`git diff --name-only ${baseBranch}`.text();
88
+ const files = result
89
+ .trim()
90
+ .split("\n")
91
+ .filter((f) => f.length > 0);
92
+ return files;
93
+ } catch {
94
+ return [];
95
+ }
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Detect if this is a re-review scenario
101
+ *
102
+ * A re-review is detected when:
103
+ * 1. chatId is provided AND matches a previous review state
104
+ * 2. isReReview flag is explicitly set to true
105
+ * 3. The payload contains chat_id (indicating continuation)
106
+ *
107
+ * @param options - Re-review detection options
108
+ * @returns True if this is a re-review scenario
109
+ */
110
+ export function detectReReview(options: ReReviewOptions): boolean {
111
+ // Explicit flag takes precedence
112
+ if (options.isReReview === true) {
113
+ return true;
114
+ }
115
+
116
+ // Check if chatId indicates a re-review (continuing same chat)
117
+ if (options.chatId) {
118
+ return reReviewState.has(options.chatId);
119
+ }
120
+
121
+ return false;
122
+ }
123
+
124
+ /**
125
+ * Record that a review was performed for a chat
126
+ *
127
+ * This is used to track review state for detecting re-reviews
128
+ *
129
+ * @param chatId - The chat ID that was reviewed
130
+ * @param files - Files that were included in the review
131
+ */
132
+ export function recordReview(chatId: string, files: string[]): void {
133
+ reReviewState.set(chatId, {
134
+ timestamp: Date.now(),
135
+ files,
136
+ });
137
+ }
138
+
139
+ /**
140
+ * Get previous review state for a chat
141
+ *
142
+ * @param chatId - The chat ID to look up
143
+ * @returns Previous review state or undefined
144
+ */
145
+ export function getPreviousReviewState(chatId: string): { timestamp: number; files: string[] } | undefined {
146
+ return reReviewState.get(chatId);
147
+ }
148
+
149
+ /**
150
+ * Clear review state (useful for testing)
151
+ */
152
+ export function clearReviewState(): void {
153
+ reReviewState.clear();
154
+ }
155
+
156
+ /**
157
+ * Process re-review and prepend preamble if needed
158
+ *
159
+ * This is the main entry point for re-review handling.
160
+ * It detects if this is a re-review, gets changed files, and
161
+ * returns the preamble to prepend to the prompt.
162
+ *
163
+ * @param options - Re-review options
164
+ * @returns Object with isReReview flag and optional preamble
165
+ */
166
+ export async function processReReview(options: ReReviewOptions): Promise<{
167
+ isReReview: boolean;
168
+ preamble?: string;
169
+ changedFiles?: string[];
170
+ }> {
171
+ const isReReview = detectReReview(options);
172
+
173
+ if (!isReReview) {
174
+ return { isReReview: false };
175
+ }
176
+
177
+ // Get changed files
178
+ const baseBranch = options.baseBranch || "main";
179
+ const changedFiles = await getChangedFiles(baseBranch);
180
+
181
+ if (changedFiles.length === 0) {
182
+ // No changed files, but still a re-review - use a simpler preamble
183
+ return {
184
+ isReReview: true,
185
+ preamble: `## IMPORTANT: Re-review After Fixes
186
+
187
+ This is a RE-REVIEW. Please re-read any files you reviewed previously as they may have changed.
188
+
189
+ ---
190
+
191
+ `,
192
+ changedFiles: [],
193
+ };
194
+ }
195
+
196
+ const reviewType = options.reviewType || "implementation";
197
+ const preamble = buildReReviewPreamble(changedFiles, reviewType);
198
+
199
+ return {
200
+ isReReview: true,
201
+ preamble,
202
+ changedFiles,
203
+ };
204
+ }