codebakers 1.0.45
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/.vscodeignore +18 -0
- package/LICENSE +21 -0
- package/README.md +88 -0
- package/codebakers-1.0.0.vsix +0 -0
- package/codebakers-1.0.10.vsix +0 -0
- package/codebakers-1.0.11.vsix +0 -0
- package/codebakers-1.0.12.vsix +0 -0
- package/codebakers-1.0.13.vsix +0 -0
- package/codebakers-1.0.14.vsix +0 -0
- package/codebakers-1.0.15.vsix +0 -0
- package/codebakers-1.0.16.vsix +0 -0
- package/codebakers-1.0.17.vsix +0 -0
- package/codebakers-1.0.18.vsix +0 -0
- package/codebakers-1.0.19.vsix +0 -0
- package/codebakers-1.0.20.vsix +0 -0
- package/codebakers-1.0.21.vsix +0 -0
- package/codebakers-1.0.22.vsix +0 -0
- package/codebakers-1.0.23.vsix +0 -0
- package/codebakers-1.0.24.vsix +0 -0
- package/codebakers-1.0.25.vsix +0 -0
- package/codebakers-1.0.26.vsix +0 -0
- package/codebakers-1.0.27.vsix +0 -0
- package/codebakers-1.0.28.vsix +0 -0
- package/codebakers-1.0.29.vsix +0 -0
- package/codebakers-1.0.30.vsix +0 -0
- package/codebakers-1.0.31.vsix +0 -0
- package/codebakers-1.0.32.vsix +0 -0
- package/codebakers-1.0.35.vsix +0 -0
- package/codebakers-1.0.36.vsix +0 -0
- package/codebakers-1.0.37.vsix +0 -0
- package/codebakers-1.0.38.vsix +0 -0
- package/codebakers-1.0.39.vsix +0 -0
- package/codebakers-1.0.40.vsix +0 -0
- package/codebakers-1.0.41.vsix +0 -0
- package/codebakers-1.0.42.vsix +0 -0
- package/codebakers-1.0.43.vsix +0 -0
- package/codebakers-1.0.44.vsix +0 -0
- package/codebakers-1.0.45.vsix +0 -0
- package/dist/extension.js +1394 -0
- package/esbuild.js +63 -0
- package/media/icon.png +0 -0
- package/media/icon.svg +7 -0
- package/nul +1 -0
- package/package.json +127 -0
- package/preview.html +547 -0
- package/src/ChatPanelProvider.ts +1815 -0
- package/src/ChatViewProvider.ts +749 -0
- package/src/CodeBakersClient.ts +1146 -0
- package/src/CodeValidator.ts +645 -0
- package/src/FileOperations.ts +410 -0
- package/src/ProjectContext.ts +526 -0
- package/src/extension.ts +332 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import * as vscode from 'vscode';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
interface ProjectState {
|
|
6
|
+
// From .codebakers.json
|
|
7
|
+
version?: string;
|
|
8
|
+
projectType?: 'new' | 'existing';
|
|
9
|
+
stack?: {
|
|
10
|
+
framework?: string;
|
|
11
|
+
database?: string;
|
|
12
|
+
auth?: string;
|
|
13
|
+
ui?: string;
|
|
14
|
+
payments?: string[];
|
|
15
|
+
};
|
|
16
|
+
decisions?: Record<string, string>;
|
|
17
|
+
currentWork?: {
|
|
18
|
+
lastUpdated?: string;
|
|
19
|
+
activeFeature?: string;
|
|
20
|
+
status?: string;
|
|
21
|
+
summary?: string;
|
|
22
|
+
pendingTasks?: string[];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Computed context
|
|
26
|
+
recentFiles?: string[];
|
|
27
|
+
packageDeps?: string[];
|
|
28
|
+
hasTests?: boolean;
|
|
29
|
+
openFile?: string;
|
|
30
|
+
selectedText?: string;
|
|
31
|
+
fileTree?: string; // Project directory structure
|
|
32
|
+
existingTypes?: string; // Type inventory for reuse
|
|
33
|
+
installedPackages?: string[]; // List of npm packages
|
|
34
|
+
|
|
35
|
+
// Conversation memory
|
|
36
|
+
keyDecisions?: string[];
|
|
37
|
+
completedTasks?: string[];
|
|
38
|
+
blockers?: string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface ProjectUpdate {
|
|
42
|
+
patterns?: string[];
|
|
43
|
+
tasks?: string[];
|
|
44
|
+
decisions?: Record<string, string>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class ProjectContext {
|
|
48
|
+
private _cache: ProjectState | null = null;
|
|
49
|
+
private _cacheTime: number = 0;
|
|
50
|
+
private readonly CACHE_TTL = 30000; // 30 seconds
|
|
51
|
+
|
|
52
|
+
constructor() {}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get current project state for context injection
|
|
56
|
+
* This is the "perfect recall" - we maintain state outside the conversation
|
|
57
|
+
*/
|
|
58
|
+
async getProjectState(): Promise<ProjectState | null> {
|
|
59
|
+
// Check cache
|
|
60
|
+
if (this._cache && Date.now() - this._cacheTime < this.CACHE_TTL) {
|
|
61
|
+
// Still add dynamic context (selected text, open file)
|
|
62
|
+
return {
|
|
63
|
+
...this._cache,
|
|
64
|
+
...this._getDynamicContext()
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
|
69
|
+
if (!workspaceFolder) return null;
|
|
70
|
+
|
|
71
|
+
const rootPath = workspaceFolder.uri.fsPath;
|
|
72
|
+
const state: ProjectState = {};
|
|
73
|
+
|
|
74
|
+
// Read .codebakers.json if exists
|
|
75
|
+
const codebakersPath = path.join(rootPath, '.codebakers.json');
|
|
76
|
+
if (fs.existsSync(codebakersPath)) {
|
|
77
|
+
try {
|
|
78
|
+
const content = fs.readFileSync(codebakersPath, 'utf-8');
|
|
79
|
+
const codebakersJson = JSON.parse(content);
|
|
80
|
+
Object.assign(state, codebakersJson);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error('Failed to read .codebakers.json:', error);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Read package.json for dependencies
|
|
87
|
+
const packagePath = path.join(rootPath, 'package.json');
|
|
88
|
+
if (fs.existsSync(packagePath)) {
|
|
89
|
+
try {
|
|
90
|
+
const content = fs.readFileSync(packagePath, 'utf-8');
|
|
91
|
+
const pkg = JSON.parse(content);
|
|
92
|
+
state.packageDeps = [
|
|
93
|
+
...Object.keys(pkg.dependencies || {}),
|
|
94
|
+
...Object.keys(pkg.devDependencies || {})
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
// Detect stack from dependencies
|
|
98
|
+
if (!state.stack) {
|
|
99
|
+
state.stack = this._detectStack(state.packageDeps);
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error('Failed to read package.json:', error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check for test files
|
|
107
|
+
state.hasTests = await this._hasTestFiles(rootPath);
|
|
108
|
+
|
|
109
|
+
// Get recently modified files
|
|
110
|
+
state.recentFiles = await this._getRecentFiles(rootPath);
|
|
111
|
+
|
|
112
|
+
// Get file tree structure (for knowing where to create files)
|
|
113
|
+
state.fileTree = await this._getFileTree(rootPath);
|
|
114
|
+
|
|
115
|
+
// Get existing types for AI to reuse
|
|
116
|
+
state.existingTypes = await this._scanExistingTypes(rootPath);
|
|
117
|
+
|
|
118
|
+
// Store installed packages list
|
|
119
|
+
state.installedPackages = state.packageDeps?.slice(0, 50);
|
|
120
|
+
|
|
121
|
+
// Read devlog for recent context
|
|
122
|
+
const devlogPath = path.join(rootPath, '.codebakers', 'DEVLOG.md');
|
|
123
|
+
if (fs.existsSync(devlogPath)) {
|
|
124
|
+
try {
|
|
125
|
+
const content = fs.readFileSync(devlogPath, 'utf-8');
|
|
126
|
+
// Extract recent decisions and tasks from devlog
|
|
127
|
+
state.keyDecisions = this._extractFromDevlog(content, 'decisions');
|
|
128
|
+
state.completedTasks = this._extractFromDevlog(content, 'tasks');
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error('Failed to read devlog:', error);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Read blockers file
|
|
135
|
+
const blockedPath = path.join(rootPath, '.codebakers', 'BLOCKED.md');
|
|
136
|
+
if (fs.existsSync(blockedPath)) {
|
|
137
|
+
try {
|
|
138
|
+
const content = fs.readFileSync(blockedPath, 'utf-8');
|
|
139
|
+
state.blockers = this._extractBlockers(content);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error('Failed to read blockers:', error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Cache the result
|
|
146
|
+
this._cache = state;
|
|
147
|
+
this._cacheTime = Date.now();
|
|
148
|
+
|
|
149
|
+
// Add dynamic context
|
|
150
|
+
return {
|
|
151
|
+
...state,
|
|
152
|
+
...this._getDynamicContext()
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Apply updates to project state (called after Claude responses)
|
|
158
|
+
*/
|
|
159
|
+
async applyUpdates(updates: ProjectUpdate): Promise<void> {
|
|
160
|
+
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
|
161
|
+
if (!workspaceFolder) return;
|
|
162
|
+
|
|
163
|
+
const rootPath = workspaceFolder.uri.fsPath;
|
|
164
|
+
const codebakersPath = path.join(rootPath, '.codebakers.json');
|
|
165
|
+
|
|
166
|
+
// Read existing state
|
|
167
|
+
let existing: any = {};
|
|
168
|
+
if (fs.existsSync(codebakersPath)) {
|
|
169
|
+
try {
|
|
170
|
+
existing = JSON.parse(fs.readFileSync(codebakersPath, 'utf-8'));
|
|
171
|
+
} catch {
|
|
172
|
+
existing = {};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Apply updates
|
|
177
|
+
if (updates.decisions) {
|
|
178
|
+
existing.decisions = {
|
|
179
|
+
...existing.decisions,
|
|
180
|
+
...updates.decisions
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (updates.tasks) {
|
|
185
|
+
existing.currentWork = existing.currentWork || {};
|
|
186
|
+
existing.currentWork.pendingTasks = updates.tasks;
|
|
187
|
+
existing.currentWork.lastUpdated = new Date().toISOString();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (updates.patterns) {
|
|
191
|
+
existing.analytics = existing.analytics || {};
|
|
192
|
+
existing.analytics.modulesUsed = existing.analytics.modulesUsed || {};
|
|
193
|
+
updates.patterns.forEach(p => {
|
|
194
|
+
existing.analytics.modulesUsed[p] = (existing.analytics.modulesUsed[p] || 0) + 1;
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Write back
|
|
199
|
+
fs.writeFileSync(codebakersPath, JSON.stringify(existing, null, 2));
|
|
200
|
+
|
|
201
|
+
// Invalidate cache
|
|
202
|
+
this._cache = null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Invalidate cache when files change
|
|
207
|
+
*/
|
|
208
|
+
invalidateCache(): void {
|
|
209
|
+
this._cache = null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get dynamic context (current editor state)
|
|
214
|
+
*/
|
|
215
|
+
private _getDynamicContext(): Partial<ProjectState> {
|
|
216
|
+
const editor = vscode.window.activeTextEditor;
|
|
217
|
+
if (!editor) return {};
|
|
218
|
+
|
|
219
|
+
const context: Partial<ProjectState> = {
|
|
220
|
+
openFile: vscode.workspace.asRelativePath(editor.document.uri)
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const selection = editor.selection;
|
|
224
|
+
if (!selection.isEmpty) {
|
|
225
|
+
context.selectedText = editor.document.getText(selection);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return context;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Detect tech stack from dependencies
|
|
233
|
+
*/
|
|
234
|
+
private _detectStack(deps: string[]): ProjectState['stack'] {
|
|
235
|
+
const stack: ProjectState['stack'] = {};
|
|
236
|
+
|
|
237
|
+
// Framework
|
|
238
|
+
if (deps.includes('next')) stack.framework = 'nextjs';
|
|
239
|
+
else if (deps.includes('react')) stack.framework = 'react';
|
|
240
|
+
else if (deps.includes('vue')) stack.framework = 'vue';
|
|
241
|
+
else if (deps.includes('svelte')) stack.framework = 'svelte';
|
|
242
|
+
|
|
243
|
+
// Database/ORM
|
|
244
|
+
if (deps.includes('drizzle-orm')) stack.database = 'drizzle';
|
|
245
|
+
else if (deps.includes('prisma')) stack.database = 'prisma';
|
|
246
|
+
else if (deps.includes('mongoose')) stack.database = 'mongodb';
|
|
247
|
+
|
|
248
|
+
// Auth
|
|
249
|
+
if (deps.includes('@supabase/supabase-js')) stack.auth = 'supabase';
|
|
250
|
+
else if (deps.includes('next-auth')) stack.auth = 'next-auth';
|
|
251
|
+
else if (deps.includes('@clerk/nextjs')) stack.auth = 'clerk';
|
|
252
|
+
|
|
253
|
+
// UI
|
|
254
|
+
if (deps.some(d => d.includes('@radix-ui'))) stack.ui = 'shadcn';
|
|
255
|
+
else if (deps.includes('@chakra-ui/react')) stack.ui = 'chakra';
|
|
256
|
+
else if (deps.includes('@mui/material')) stack.ui = 'mui';
|
|
257
|
+
|
|
258
|
+
// Payments
|
|
259
|
+
const payments: string[] = [];
|
|
260
|
+
if (deps.includes('stripe')) payments.push('stripe');
|
|
261
|
+
if (deps.includes('@paypal/react-paypal-js')) payments.push('paypal');
|
|
262
|
+
if (payments.length > 0) stack.payments = payments;
|
|
263
|
+
|
|
264
|
+
return stack;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Check if project has test files
|
|
269
|
+
*/
|
|
270
|
+
private async _hasTestFiles(rootPath: string): Promise<boolean> {
|
|
271
|
+
const testPatterns = ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/tests/**'];
|
|
272
|
+
|
|
273
|
+
for (const pattern of testPatterns) {
|
|
274
|
+
const files = await vscode.workspace.findFiles(pattern, '**/node_modules/**', 1);
|
|
275
|
+
if (files.length > 0) return true;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Get recently modified files
|
|
283
|
+
*/
|
|
284
|
+
private async _getRecentFiles(rootPath: string): Promise<string[]> {
|
|
285
|
+
const files = await vscode.workspace.findFiles(
|
|
286
|
+
'**/*.{ts,tsx,js,jsx}',
|
|
287
|
+
'**/node_modules/**',
|
|
288
|
+
50
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
// Sort by modification time and take top 10
|
|
292
|
+
const withStats = await Promise.all(
|
|
293
|
+
files.map(async (f) => {
|
|
294
|
+
try {
|
|
295
|
+
const stat = await vscode.workspace.fs.stat(f);
|
|
296
|
+
return { path: vscode.workspace.asRelativePath(f), mtime: stat.mtime };
|
|
297
|
+
} catch {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
})
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
return withStats
|
|
304
|
+
.filter((f): f is { path: string; mtime: number } => f !== null)
|
|
305
|
+
.sort((a, b) => b.mtime - a.mtime)
|
|
306
|
+
.slice(0, 10)
|
|
307
|
+
.map(f => f.path);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Get file tree structure for AI context
|
|
312
|
+
* This helps the AI understand where files exist and where to create new ones
|
|
313
|
+
*/
|
|
314
|
+
private async _getFileTree(rootPath: string, maxDepth: number = 4): Promise<string> {
|
|
315
|
+
const IGNORE_DIRS = new Set([
|
|
316
|
+
'node_modules', '.git', '.next', 'dist', 'build', '.vercel',
|
|
317
|
+
'.turbo', 'coverage', '.cache', '.nuxt', '.output', '__pycache__'
|
|
318
|
+
]);
|
|
319
|
+
|
|
320
|
+
const IMPORTANT_DIRS = new Set([
|
|
321
|
+
'src', 'app', 'pages', 'components', 'lib', 'utils', 'hooks',
|
|
322
|
+
'api', 'services', 'types', 'styles', 'public', 'tests', '__tests__'
|
|
323
|
+
]);
|
|
324
|
+
|
|
325
|
+
const lines: string[] = [];
|
|
326
|
+
|
|
327
|
+
const scan = (dir: string, prefix: string, depth: number): void => {
|
|
328
|
+
if (depth > maxDepth) return;
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
332
|
+
|
|
333
|
+
// Separate directories and files
|
|
334
|
+
const dirs = entries.filter(e => e.isDirectory() && !IGNORE_DIRS.has(e.name) && !e.name.startsWith('.'));
|
|
335
|
+
const files = entries.filter(e => e.isFile());
|
|
336
|
+
|
|
337
|
+
// Sort: important dirs first, then alphabetically
|
|
338
|
+
dirs.sort((a, b) => {
|
|
339
|
+
const aImportant = IMPORTANT_DIRS.has(a.name);
|
|
340
|
+
const bImportant = IMPORTANT_DIRS.has(b.name);
|
|
341
|
+
if (aImportant && !bImportant) return -1;
|
|
342
|
+
if (!aImportant && bImportant) return 1;
|
|
343
|
+
return a.name.localeCompare(b.name);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// Show directories
|
|
347
|
+
for (let i = 0; i < dirs.length; i++) {
|
|
348
|
+
const entry = dirs[i];
|
|
349
|
+
const isLast = i === dirs.length - 1 && files.length === 0;
|
|
350
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
351
|
+
const childPrefix = isLast ? ' ' : '│ ';
|
|
352
|
+
|
|
353
|
+
lines.push(`${prefix}${connector}${entry.name}/`);
|
|
354
|
+
scan(path.join(dir, entry.name), prefix + childPrefix, depth + 1);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Show key files at depth 0-1, or all files at deeper levels (limited)
|
|
358
|
+
const keyFiles = files.filter(f =>
|
|
359
|
+
depth <= 1 ?
|
|
360
|
+
['package.json', 'tsconfig.json', '.env.example', 'next.config.js', 'next.config.mjs', 'drizzle.config.ts'].includes(f.name) ||
|
|
361
|
+
f.name.endsWith('.ts') || f.name.endsWith('.tsx')
|
|
362
|
+
: true
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
// Limit files shown at each level
|
|
366
|
+
const maxFiles = depth === 0 ? 5 : (depth === 1 ? 10 : 15);
|
|
367
|
+
const filesToShow = keyFiles.slice(0, maxFiles);
|
|
368
|
+
const hiddenCount = keyFiles.length - filesToShow.length;
|
|
369
|
+
|
|
370
|
+
for (let i = 0; i < filesToShow.length; i++) {
|
|
371
|
+
const file = filesToShow[i];
|
|
372
|
+
const isLast = i === filesToShow.length - 1 && hiddenCount === 0;
|
|
373
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
374
|
+
lines.push(`${prefix}${connector}${file.name}`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (hiddenCount > 0) {
|
|
378
|
+
lines.push(`${prefix}└── ... (${hiddenCount} more files)`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
} catch (error) {
|
|
382
|
+
// Ignore permission errors
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
lines.push(path.basename(rootPath) + '/');
|
|
387
|
+
scan(rootPath, '', 0);
|
|
388
|
+
|
|
389
|
+
return lines.join('\n');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Scan project for existing types that AI can reuse
|
|
394
|
+
* This prevents creating duplicate types
|
|
395
|
+
*/
|
|
396
|
+
private async _scanExistingTypes(rootPath: string): Promise<string> {
|
|
397
|
+
const types: { name: string; file: string; kind: string }[] = [];
|
|
398
|
+
|
|
399
|
+
// Find TypeScript files in src/types, src/lib, etc
|
|
400
|
+
const typeDirs = ['src/types', 'src/lib', 'types', 'lib'];
|
|
401
|
+
|
|
402
|
+
for (const dir of typeDirs) {
|
|
403
|
+
const dirPath = path.join(rootPath, dir);
|
|
404
|
+
if (!fs.existsSync(dirPath)) continue;
|
|
405
|
+
|
|
406
|
+
try {
|
|
407
|
+
const files = fs.readdirSync(dirPath).filter(f => f.endsWith('.ts') || f.endsWith('.tsx'));
|
|
408
|
+
|
|
409
|
+
for (const file of files.slice(0, 10)) { // Limit files per dir
|
|
410
|
+
const filePath = path.join(dirPath, file);
|
|
411
|
+
try {
|
|
412
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
413
|
+
const relativePath = path.join(dir, file);
|
|
414
|
+
|
|
415
|
+
// Extract interfaces
|
|
416
|
+
const interfaceRegex = /export\s+interface\s+(\w+)/g;
|
|
417
|
+
let match;
|
|
418
|
+
while ((match = interfaceRegex.exec(content)) !== null) {
|
|
419
|
+
types.push({ name: match[1], file: relativePath, kind: 'interface' });
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Extract type aliases
|
|
423
|
+
const typeRegex = /export\s+type\s+(\w+)/g;
|
|
424
|
+
while ((match = typeRegex.exec(content)) !== null) {
|
|
425
|
+
types.push({ name: match[1], file: relativePath, kind: 'type' });
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Extract enums
|
|
429
|
+
const enumRegex = /export\s+enum\s+(\w+)/g;
|
|
430
|
+
while ((match = enumRegex.exec(content)) !== null) {
|
|
431
|
+
types.push({ name: match[1], file: relativePath, kind: 'enum' });
|
|
432
|
+
}
|
|
433
|
+
} catch {
|
|
434
|
+
// Skip files we can't read
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
} catch {
|
|
438
|
+
// Skip dirs we can't read
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (types.length === 0) {
|
|
443
|
+
return '';
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Format for AI context
|
|
447
|
+
const lines = ['EXISTING TYPES (import these instead of creating new):'];
|
|
448
|
+
const byFile = new Map<string, typeof types>();
|
|
449
|
+
|
|
450
|
+
for (const t of types) {
|
|
451
|
+
const arr = byFile.get(t.file) || [];
|
|
452
|
+
arr.push(t);
|
|
453
|
+
byFile.set(t.file, arr);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
for (const [file, fileTypes] of byFile) {
|
|
457
|
+
lines.push(` ${file}:`);
|
|
458
|
+
for (const t of fileTypes.slice(0, 5)) {
|
|
459
|
+
lines.push(` - ${t.kind} ${t.name}`);
|
|
460
|
+
}
|
|
461
|
+
if (fileTypes.length > 5) {
|
|
462
|
+
lines.push(` ... and ${fileTypes.length - 5} more`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return lines.slice(0, 30).join('\n'); // Limit output size
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Extract information from devlog
|
|
471
|
+
*/
|
|
472
|
+
private _extractFromDevlog(content: string, type: 'decisions' | 'tasks'): string[] {
|
|
473
|
+
const results: string[] = [];
|
|
474
|
+
const lines = content.split('\n');
|
|
475
|
+
|
|
476
|
+
// Look for the most recent entry's relevant section
|
|
477
|
+
let inRecentEntry = false;
|
|
478
|
+
let inSection = false;
|
|
479
|
+
|
|
480
|
+
for (const line of lines) {
|
|
481
|
+
if (line.startsWith('## ')) {
|
|
482
|
+
inRecentEntry = true;
|
|
483
|
+
} else if (line.startsWith('---')) {
|
|
484
|
+
break; // Only process most recent entry
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (inRecentEntry) {
|
|
488
|
+
if (type === 'decisions' && line.toLowerCase().includes('decision')) {
|
|
489
|
+
inSection = true;
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
if (type === 'tasks' && line.toLowerCase().includes('what was done')) {
|
|
493
|
+
inSection = true;
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (inSection) {
|
|
498
|
+
if (line.startsWith('###') || line.startsWith('## ')) {
|
|
499
|
+
inSection = false;
|
|
500
|
+
} else if (line.startsWith('- ')) {
|
|
501
|
+
results.push(line.substring(2).trim());
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return results.slice(0, 5); // Limit to 5 items
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Extract blockers from BLOCKED.md
|
|
512
|
+
*/
|
|
513
|
+
private _extractBlockers(content: string): string[] {
|
|
514
|
+
const blockers: string[] = [];
|
|
515
|
+
const lines = content.split('\n');
|
|
516
|
+
|
|
517
|
+
for (let i = 0; i < lines.length; i++) {
|
|
518
|
+
const line = lines[i];
|
|
519
|
+
if (line.startsWith('**Blocking Issue:**')) {
|
|
520
|
+
blockers.push(line.replace('**Blocking Issue:**', '').trim());
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return blockers;
|
|
525
|
+
}
|
|
526
|
+
}
|