lovecode-ai 0.1.9 → 0.2.1

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,3284 @@
1
+ import {
2
+ commit,
3
+ createBranch,
4
+ formatBranches,
5
+ formatLog,
6
+ formatStatus,
7
+ getBranches,
8
+ getCurrentBranch,
9
+ getFullDiff,
10
+ getLog,
11
+ getStagedDiff,
12
+ getStatus,
13
+ isGitAvailable,
14
+ stageAll,
15
+ switchBranch
16
+ } from "./chunk-7CT3XDH6.js";
17
+ import {
18
+ click,
19
+ formatDOMElement,
20
+ formatScreenshotResult,
21
+ goto,
22
+ inspect,
23
+ isBrowserRunning,
24
+ screenshot,
25
+ type
26
+ } from "./chunk-IVAMLKMS.js";
27
+ import {
28
+ disablePlugin,
29
+ enablePlugin,
30
+ getAllPluginTools,
31
+ listPlugins,
32
+ loadBuiltinPlugins
33
+ } from "./chunk-MOZHR2QY.js";
34
+
35
+ // src/core/tools.ts
36
+ import * as fs14 from "fs";
37
+ import * as path13 from "path";
38
+ import { execSync as execSync3 } from "child_process";
39
+ import chalk13 from "chalk";
40
+
41
+ // src/fs/scanner.ts
42
+ import * as fs2 from "fs";
43
+ import * as path2 from "path";
44
+ import chalk from "chalk";
45
+
46
+ // src/fs/ignore.ts
47
+ import * as fs from "fs";
48
+ import * as path from "path";
49
+ function parseGitignore(content) {
50
+ const patterns = [];
51
+ const negations = [];
52
+ for (const line of content.split("\n")) {
53
+ const trimmed = line.trim();
54
+ if (!trimmed || trimmed.startsWith("#")) continue;
55
+ if (trimmed.startsWith("!")) {
56
+ negations.push(trimmed.slice(1));
57
+ } else {
58
+ patterns.push(trimmed);
59
+ }
60
+ }
61
+ return { patterns, negations };
62
+ }
63
+ function loadFile(dir, filename) {
64
+ const filePath = path.join(dir, filename);
65
+ try {
66
+ const content = fs.readFileSync(filePath, "utf-8");
67
+ return parseGitignore(content);
68
+ } catch {
69
+ return null;
70
+ }
71
+ }
72
+ function loadIgnoreRules(rootDir) {
73
+ const rules2 = { patterns: [], negations: [] };
74
+ const gitignore = loadFile(rootDir, ".gitignore");
75
+ if (gitignore) {
76
+ rules2.patterns.push(...gitignore.patterns);
77
+ rules2.negations.push(...gitignore.negations);
78
+ }
79
+ const lovecodeignore = loadFile(rootDir, ".lovecodeignore");
80
+ if (lovecodeignore) {
81
+ rules2.patterns.push(...lovecodeignore.patterns);
82
+ rules2.negations.push(...lovecodeignore.negations);
83
+ }
84
+ rules2.patterns.push("node_modules/**", ".git/**", "dist/**", "build/**", ".next/**");
85
+ return rules2;
86
+ }
87
+ function isIgnored(relativePath, rules2) {
88
+ const normalized = relativePath.replace(/\\/g, "/");
89
+ for (const negation of rules2.negations) {
90
+ if (matchPattern(normalized, negation)) return false;
91
+ }
92
+ for (const pattern of rules2.patterns) {
93
+ if (matchPattern(normalized, pattern)) return true;
94
+ }
95
+ return false;
96
+ }
97
+ function matchPattern(filePath, pattern) {
98
+ if (pattern.endsWith("/")) {
99
+ if (filePath.startsWith(pattern) || filePath === pattern.slice(0, -1)) return true;
100
+ }
101
+ if (pattern.startsWith("/")) {
102
+ return matchSimple(filePath, pattern.slice(1));
103
+ }
104
+ if (matchSimple(filePath, pattern)) return true;
105
+ const basename6 = path.basename(filePath);
106
+ if (isGlobless(pattern) && basename6 === pattern) return true;
107
+ return false;
108
+ }
109
+ function isGlobless(pattern) {
110
+ return !pattern.includes("*") && !pattern.includes("?") && !pattern.includes("[");
111
+ }
112
+ function matchSimple(filePath, pattern) {
113
+ const regex = globToRegex(pattern);
114
+ return regex.test(filePath.split("/").slice(-pattern.split("/").length).join("/"));
115
+ }
116
+ function globToRegex(pattern) {
117
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "___DOUBLESTAR___").replace(/\*/g, "[^/]*").replace(/___DOUBLESTAR___/g, ".*").replace(/\?/g, "[^/]");
118
+ return new RegExp(`^${escaped}$`);
119
+ }
120
+
121
+ // src/fs/scanner.ts
122
+ var sourceExtensions = /* @__PURE__ */ new Set([
123
+ ".ts",
124
+ ".tsx",
125
+ ".js",
126
+ ".jsx",
127
+ ".mjs",
128
+ ".cjs",
129
+ ".mts",
130
+ ".cts",
131
+ ".py",
132
+ ".rs",
133
+ ".go",
134
+ ".rb",
135
+ ".java",
136
+ ".kt",
137
+ ".swift",
138
+ ".c",
139
+ ".cpp",
140
+ ".h",
141
+ ".hpp",
142
+ ".cs",
143
+ ".php",
144
+ ".r",
145
+ ".scala",
146
+ ".ex",
147
+ ".exs"
148
+ ]);
149
+ var configExtensions = /* @__PURE__ */ new Set([
150
+ ".json",
151
+ ".yaml",
152
+ ".yml",
153
+ ".toml",
154
+ ".ini",
155
+ ".cfg",
156
+ ".conf",
157
+ ".env",
158
+ ".env.example",
159
+ ".editorconfig",
160
+ ".prettierrc",
161
+ ".eslintrc",
162
+ "tsconfig.json",
163
+ "package.json",
164
+ "Dockerfile",
165
+ "Makefile"
166
+ ]);
167
+ var docExtensions = /* @__PURE__ */ new Set([
168
+ ".md",
169
+ ".mdx",
170
+ ".txt",
171
+ ".rst",
172
+ ".adoc",
173
+ ".wiki",
174
+ ".org"
175
+ ]);
176
+ var scriptExtensions = /* @__PURE__ */ new Set([
177
+ ".sh",
178
+ ".bash",
179
+ ".zsh",
180
+ ".fish",
181
+ ".bat",
182
+ ".cmd",
183
+ ".ps1"
184
+ ]);
185
+ var dataExtensions = /* @__PURE__ */ new Set([
186
+ ".csv",
187
+ ".tsv",
188
+ ".jsonl",
189
+ ".xml",
190
+ ".sql",
191
+ ".graphql",
192
+ ".proto"
193
+ ]);
194
+ function categorize(ext, basename6) {
195
+ if (sourceExtensions.has(ext)) return "source";
196
+ if (configExtensions.has(ext) || configExtensions.has(basename6)) return "config";
197
+ if (docExtensions.has(ext)) return "doc";
198
+ if (scriptExtensions.has(ext)) return "script";
199
+ if (dataExtensions.has(ext)) return "data";
200
+ return "other";
201
+ }
202
+ function scanDirectory(options) {
203
+ const { rootDir, maxDepth = 20, maxFiles = 1e4, includeHidden = false } = options;
204
+ const rules2 = loadIgnoreRules(rootDir);
205
+ const results = [];
206
+ function walk(dir, depth) {
207
+ if (depth > maxDepth || results.length >= maxFiles) return;
208
+ let entries;
209
+ try {
210
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
211
+ } catch {
212
+ return;
213
+ }
214
+ for (const entry of entries) {
215
+ const fullPath = path2.join(dir, entry.name);
216
+ const relativePath = path2.relative(rootDir, fullPath);
217
+ if (relativePath.startsWith("..")) continue;
218
+ const isHidden = entry.name.startsWith(".") && entry.name !== "." && entry.name !== "..";
219
+ if (isHidden && !includeHidden) {
220
+ if (entry.isDirectory()) continue;
221
+ const skipHiddenConfig = entry.name === ".gitignore" || entry.name === ".env";
222
+ if (!skipHiddenConfig) continue;
223
+ }
224
+ if (isIgnored(relativePath, rules2)) continue;
225
+ if (entry.isDirectory()) {
226
+ walk(fullPath, depth + 1);
227
+ } else if (entry.isFile()) {
228
+ const ext = path2.extname(entry.name).toLowerCase();
229
+ const stats = fs2.statSync(fullPath);
230
+ const relative5 = relativePath.replace(/\\/g, "/");
231
+ results.push({
232
+ path: fullPath,
233
+ relativePath: relative5,
234
+ size: stats.size,
235
+ modifiedAt: stats.mtime,
236
+ extension: ext,
237
+ category: categorize(ext, entry.name)
238
+ });
239
+ }
240
+ }
241
+ }
242
+ walk(rootDir, 0);
243
+ if (options.categories && options.categories.length > 0) {
244
+ return results.filter((f) => options.categories.includes(f.category));
245
+ }
246
+ return results;
247
+ }
248
+ function getFilesByCategory(files) {
249
+ const grouped = {
250
+ source: [],
251
+ config: [],
252
+ doc: [],
253
+ script: [],
254
+ data: [],
255
+ other: []
256
+ };
257
+ for (const file of files) {
258
+ grouped[file.category].push(file);
259
+ }
260
+ return grouped;
261
+ }
262
+ function printScanSummary(files) {
263
+ const grouped = getFilesByCategory(files);
264
+ const total = files.length;
265
+ const categoryColors = {
266
+ source: chalk.cyan,
267
+ config: chalk.yellow,
268
+ doc: chalk.green,
269
+ script: chalk.magenta,
270
+ data: chalk.blue,
271
+ other: chalk.gray
272
+ };
273
+ const lines = [chalk.bold(`
274
+ Scanned ${total} files`)];
275
+ for (const [category, catFiles] of Object.entries(grouped)) {
276
+ if (catFiles.length > 0) {
277
+ const color = categoryColors[category] || chalk.gray;
278
+ lines.push(` ${color(category.padEnd(10))} ${catFiles.length} files`);
279
+ }
280
+ }
281
+ return lines.join("\n");
282
+ }
283
+
284
+ // src/fs/operations.ts
285
+ import * as fs3 from "fs";
286
+ import * as path3 from "path";
287
+ function renameFile(oldPath, newPath) {
288
+ try {
289
+ const dir = path3.dirname(newPath);
290
+ if (!fs3.existsSync(dir)) {
291
+ fs3.mkdirSync(dir, { recursive: true });
292
+ }
293
+ fs3.renameSync(oldPath, newPath);
294
+ return { success: true, message: `Renamed to ${newPath}` };
295
+ } catch (err) {
296
+ return { success: false, message: "", error: String(err) };
297
+ }
298
+ }
299
+ function duplicateFile(sourcePath, destPath) {
300
+ try {
301
+ const dir = path3.dirname(destPath);
302
+ if (!fs3.existsSync(dir)) {
303
+ fs3.mkdirSync(dir, { recursive: true });
304
+ }
305
+ fs3.copyFileSync(sourcePath, destPath);
306
+ return { success: true, message: `Duplicated to ${destPath}` };
307
+ } catch (err) {
308
+ return { success: false, message: "", error: String(err) };
309
+ }
310
+ }
311
+ function getDirectoryTree(dirPath, prefix = "", maxDepth = 3, currentDepth = 0) {
312
+ if (currentDepth > maxDepth) return `${prefix} ...
313
+ `;
314
+ let result = "";
315
+ let entries;
316
+ try {
317
+ entries = fs3.readdirSync(dirPath, { withFileTypes: true });
318
+ } catch {
319
+ return `${prefix} (error reading)
320
+ `;
321
+ }
322
+ const filtered = entries.filter((e) => !e.name.startsWith(".") || e.name === ".gitignore");
323
+ const sorted = filtered.sort((a, b) => {
324
+ if (a.isDirectory() && !b.isDirectory()) return -1;
325
+ if (!a.isDirectory() && b.isDirectory()) return 1;
326
+ return a.name.localeCompare(b.name);
327
+ });
328
+ for (let i = 0; i < sorted.length; i++) {
329
+ const entry = sorted[i];
330
+ const isLast = i === sorted.length - 1;
331
+ const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
332
+ const nextPrefix = isLast ? " " : "\u2502 ";
333
+ if (entry.isDirectory()) {
334
+ result += `${prefix}${connector}${entry.name}/
335
+ `;
336
+ result += getDirectoryTree(
337
+ path3.join(dirPath, entry.name),
338
+ prefix + nextPrefix,
339
+ maxDepth,
340
+ currentDepth + 1
341
+ );
342
+ } else {
343
+ const stats = fs3.statSync(path3.join(dirPath, entry.name));
344
+ const size = formatSize(stats.size);
345
+ result += `${prefix}${connector}${entry.name} ${size}
346
+ `;
347
+ }
348
+ }
349
+ return result;
350
+ }
351
+ function formatSize(bytes) {
352
+ const units = ["B", "KB", "MB", "GB"];
353
+ let size = bytes;
354
+ let unitIdx = 0;
355
+ while (size >= 1024 && unitIdx < units.length - 1) {
356
+ size /= 1024;
357
+ unitIdx++;
358
+ }
359
+ return `(${size.toFixed(1)} ${units[unitIdx]})`;
360
+ }
361
+
362
+ // src/fs/search.ts
363
+ import * as fs4 from "fs";
364
+ import * as path4 from "path";
365
+ import { execSync } from "child_process";
366
+ function normalizeQuery(query) {
367
+ return query.toLowerCase().split(/[\s_-]+/).filter((w) => w.length > 0);
368
+ }
369
+ function scoreByName(filename, query) {
370
+ const lower = filename.toLowerCase();
371
+ const qLower = query.toLowerCase();
372
+ let score = 0;
373
+ if (lower === qLower) score += 100;
374
+ else if (lower.includes(qLower)) score += 50;
375
+ else if (lower.startsWith(qLower)) score += 40;
376
+ const queryWords = normalizeQuery(query);
377
+ const nameWords = normalizeQuery(filename.replace(/[.-]/g, " "));
378
+ for (const qw of queryWords) {
379
+ for (const nw of nameWords) {
380
+ if (nw === qw) score += 20;
381
+ else if (nw.startsWith(qw) || nw.endsWith(qw)) score += 10;
382
+ else if (nw.includes(qw)) score += 5;
383
+ }
384
+ }
385
+ return score;
386
+ }
387
+ function collectAllFiles(dir, maxFiles = 5e3) {
388
+ const results = [];
389
+ function walk(dir2) {
390
+ if (results.length >= maxFiles) return;
391
+ let entries;
392
+ try {
393
+ entries = fs4.readdirSync(dir2, { withFileTypes: true });
394
+ } catch {
395
+ return;
396
+ }
397
+ for (const entry of entries) {
398
+ if (results.length >= maxFiles) return;
399
+ if (entry.name.startsWith(".") && entry.name !== ".gitignore") continue;
400
+ const fullPath = path4.join(dir2, entry.name);
401
+ if (entry.isDirectory()) {
402
+ if (entry.name === "node_modules" || entry.name === ".git") continue;
403
+ walk(fullPath);
404
+ } else {
405
+ results.push(fullPath);
406
+ }
407
+ }
408
+ }
409
+ walk(dir);
410
+ return results;
411
+ }
412
+ function findFiles(options) {
413
+ const { rootDir, query, maxResults = 20 } = options;
414
+ const allFiles = collectAllFiles(rootDir);
415
+ const scored = [];
416
+ for (const filePath of allFiles) {
417
+ const relativePath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
418
+ const basename6 = path4.basename(filePath);
419
+ const score = scoreByName(basename6, query) + scoreByName(relativePath, query) * 0.5;
420
+ if (score > 0) {
421
+ scored.push({
422
+ filePath,
423
+ relativePath,
424
+ score
425
+ });
426
+ }
427
+ }
428
+ scored.sort((a, b) => b.score - a.score);
429
+ const results = scored.slice(0, maxResults);
430
+ if (options.includeContent) {
431
+ for (const result of results) {
432
+ result.matches = searchFileContent(result.filePath, query);
433
+ }
434
+ }
435
+ return results;
436
+ }
437
+ function searchFileContent(filePath, query) {
438
+ try {
439
+ const content = fs4.readFileSync(filePath, "utf-8");
440
+ const lines = content.split("\n");
441
+ const matches = [];
442
+ const lowerQuery = query.toLowerCase();
443
+ for (let i = 0; i < lines.length; i++) {
444
+ if (lines[i].toLowerCase().includes(lowerQuery)) {
445
+ matches.push({ line: i + 1, content: lines[i].trim() });
446
+ if (matches.length >= 10) break;
447
+ }
448
+ }
449
+ return matches;
450
+ } catch {
451
+ return [];
452
+ }
453
+ }
454
+ function findWithRipgrep(query, rootDir) {
455
+ try {
456
+ const cmd = `rg -l -i '${query.replace(/'/g, "'\\''")}' 2>/dev/null`;
457
+ const output = execSync(cmd, { cwd: rootDir, encoding: "utf-8", maxBuffer: 1024 * 1024 });
458
+ const files = output.trim().split("\n").filter(Boolean);
459
+ return files.slice(0, 30).map((filePath) => ({
460
+ filePath: path4.join(rootDir, filePath),
461
+ relativePath: filePath,
462
+ score: 50
463
+ }));
464
+ } catch {
465
+ return [];
466
+ }
467
+ }
468
+ function semanticSearch(query, rootDir) {
469
+ const nameResults = findFiles({ rootDir, query, maxResults: 15 });
470
+ const namePaths = new Set(nameResults.map((r) => r.relativePath));
471
+ let contentResults = [];
472
+ try {
473
+ contentResults = findWithRipgrep(query, rootDir);
474
+ } catch {
475
+ const textFiles = collectAllFiles(rootDir, 2e3).filter((f) => {
476
+ const ext = path4.extname(f);
477
+ return [".ts", ".js", ".py", ".rs", ".go", ".md", ".json", ".txt", ".yaml", ".yml"].includes(ext);
478
+ });
479
+ for (const filePath of textFiles.slice(0, 100)) {
480
+ const relativePath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
481
+ if (namePaths.has(relativePath)) continue;
482
+ const matches = searchFileContent(filePath, query);
483
+ if (matches.length > 0) {
484
+ contentResults.push({
485
+ filePath,
486
+ relativePath,
487
+ matches,
488
+ score: 10 + matches.length
489
+ });
490
+ }
491
+ }
492
+ }
493
+ const combined = [...nameResults];
494
+ for (const cr of contentResults) {
495
+ if (!namePaths.has(cr.relativePath)) {
496
+ combined.push(cr);
497
+ }
498
+ }
499
+ combined.sort((a, b) => b.score - a.score);
500
+ return combined.slice(0, 20);
501
+ }
502
+
503
+ // src/fs/rank.ts
504
+ import * as path5 from "path";
505
+ import chalk2 from "chalk";
506
+ var CATEGORY_WEIGHTS = {
507
+ source: 100,
508
+ config: 70,
509
+ script: 60,
510
+ doc: 40,
511
+ data: 30,
512
+ other: 10
513
+ };
514
+ var EXTENSION_BOOSTS = {
515
+ ".ts": 15,
516
+ ".tsx": 15,
517
+ ".js": 10,
518
+ ".jsx": 10,
519
+ ".py": 12,
520
+ ".rs": 12,
521
+ ".go": 12,
522
+ ".md": 5
523
+ };
524
+ var IMPORTANT_FILES = {
525
+ "package.json": 50,
526
+ "tsconfig.json": 40,
527
+ "Cargo.toml": 40,
528
+ "go.mod": 40,
529
+ "pyproject.toml": 40,
530
+ "Dockerfile": 30,
531
+ "docker-compose.yml": 30,
532
+ "Makefile": 25,
533
+ "README.md": 20,
534
+ "index.ts": 20,
535
+ "main.ts": 20,
536
+ "app.ts": 20,
537
+ "server.ts": 20,
538
+ "cli.ts": 15,
539
+ "main.py": 20,
540
+ "main.rs": 20,
541
+ "main.go": 20,
542
+ "mod.rs": 15,
543
+ "lib.rs": 15,
544
+ "index.js": 20,
545
+ "main.js": 20
546
+ };
547
+ function rankFiles(files, taskDescription, options = {}) {
548
+ const {
549
+ boostSource = true,
550
+ boostRecent = true,
551
+ boostShallow = true,
552
+ boostConfig = true,
553
+ recentHours = 48
554
+ } = options;
555
+ const taskTokens = taskDescription ? taskDescription.toLowerCase().split(/[\s_-]+/).filter((w) => w.length > 2) : [];
556
+ const now = Date.now();
557
+ const recentCutoff = now - recentHours * 60 * 60 * 1e3;
558
+ const results = files.map((file) => {
559
+ let score = 0;
560
+ const reasons = [];
561
+ const basename6 = path5.basename(file.path);
562
+ score += CATEGORY_WEIGHTS[file.category] || 10;
563
+ reasons.push(`category:${file.category}=${CATEGORY_WEIGHTS[file.category] || 10}`);
564
+ if (boostSource && file.category === "source") {
565
+ const extBoost = EXTENSION_BOOSTS[file.extension] || 0;
566
+ if (extBoost > 0) {
567
+ score += extBoost;
568
+ reasons.push(`ext-boost:${file.extension}=${extBoost}`);
569
+ }
570
+ }
571
+ if (IMPORTANT_FILES[basename6] !== void 0) {
572
+ score += IMPORTANT_FILES[basename6];
573
+ reasons.push(`important-file:${basename6}=${IMPORTANT_FILES[basename6]}`);
574
+ }
575
+ if (boostConfig && file.category === "config") {
576
+ score += 15;
577
+ reasons.push("config-boost=15");
578
+ }
579
+ if (boostRecent && file.modifiedAt.getTime() > recentCutoff) {
580
+ const recencyBoost = 20;
581
+ score += recencyBoost;
582
+ reasons.push(`recently-modified=${recencyBoost}`);
583
+ }
584
+ if (boostShallow) {
585
+ const depth = file.relativePath.split("/").length;
586
+ const depthBoost = Math.max(0, 20 - depth * 2);
587
+ score += depthBoost;
588
+ if (depthBoost > 0) reasons.push(`shallow-depth=${depthBoost}`);
589
+ }
590
+ if (file.size < 100) {
591
+ score -= 5;
592
+ reasons.push("small-file=-5");
593
+ }
594
+ if (file.size > 5e5) {
595
+ score -= 10;
596
+ reasons.push("large-file=-10");
597
+ }
598
+ if (taskTokens.length > 0) {
599
+ const lowerPath = file.relativePath.toLowerCase();
600
+ const lowerName = basename6.toLowerCase();
601
+ let taskMatchScore = 0;
602
+ for (const token of taskTokens) {
603
+ if (lowerName.includes(token)) taskMatchScore += 15;
604
+ if (lowerPath.includes(token)) taskMatchScore += 8;
605
+ }
606
+ if (taskMatchScore > 0) {
607
+ score += taskMatchScore;
608
+ reasons.push(`task-match=${taskMatchScore}`);
609
+ }
610
+ }
611
+ return { ...file, rankScore: score, rankReasons: reasons };
612
+ });
613
+ results.sort((a, b) => b.rankScore - a.rankScore);
614
+ return results;
615
+ }
616
+ function printRankedFiles(ranked) {
617
+ const lines = [""];
618
+ lines.push(chalk2.bold(" File Context Rankings"));
619
+ const maxScore = ranked.length > 0 ? ranked[0].rankScore : 1;
620
+ for (const file of ranked) {
621
+ const barLength = Math.round(file.rankScore / maxScore * 20);
622
+ const bar = chalk2.cyan("\u2588".repeat(barLength)) + chalk2.dim("\u2591".repeat(20 - barLength));
623
+ const pct = Math.round(file.rankScore / maxScore * 100);
624
+ lines.push(` ${chalk2.dim(file.relativePath)}`);
625
+ lines.push(` ${bar} ${chalk2.bold(String(file.rankScore))} ${chalk2.dim(`(${pct}%)`)}`);
626
+ }
627
+ lines.push("");
628
+ return lines.join("\n");
629
+ }
630
+
631
+ // src/editor/patch.ts
632
+ import * as fs5 from "fs";
633
+ import chalk3 from "chalk";
634
+ function generateDiff(oldLines, newLines) {
635
+ const result = [];
636
+ const maxLen = Math.max(oldLines.length, newLines.length);
637
+ let hasChanges = false;
638
+ for (let i = 0; i < maxLen; i++) {
639
+ const oldLine = i < oldLines.length ? oldLines[i] : void 0;
640
+ const newLine = i < newLines.length ? newLines[i] : void 0;
641
+ if (oldLine === newLine) {
642
+ result.push(` ${oldLine}`);
643
+ } else {
644
+ hasChanges = true;
645
+ if (oldLine !== void 0) {
646
+ result.push(chalk3.red(`- ${oldLine}`));
647
+ }
648
+ if (newLine !== void 0) {
649
+ result.push(chalk3.green(`+ ${newLine}`));
650
+ }
651
+ }
652
+ }
653
+ if (!hasChanges) return "(no changes)";
654
+ return result.join("\n");
655
+ }
656
+ function applyInlinePatch(filePath, searchBlock, replaceBlock) {
657
+ try {
658
+ if (!fs5.existsSync(filePath)) {
659
+ return { success: false, applied: false, output: "", error: `File not found: ${filePath}` };
660
+ }
661
+ const content = fs5.readFileSync(filePath, "utf-8");
662
+ const searchNormalized = searchBlock.trim();
663
+ const contentNormalized = content;
664
+ const idx = contentNormalized.indexOf(searchNormalized);
665
+ if (idx === -1) {
666
+ return {
667
+ success: false,
668
+ applied: false,
669
+ output: "",
670
+ error: "Search block not found in file. The context may have changed."
671
+ };
672
+ }
673
+ const before = contentNormalized.slice(0, idx);
674
+ const after = contentNormalized.slice(idx + searchNormalized.length);
675
+ const newContent = before + replaceBlock + after;
676
+ const oldLines = searchBlock.split("\n");
677
+ const newLines = replaceBlock.split("\n");
678
+ const diff = generateDiff(oldLines, newLines);
679
+ fs5.writeFileSync(filePath, newContent, "utf-8");
680
+ return { success: true, applied: true, output: `Patch applied:
681
+ ${diff}` };
682
+ } catch (err) {
683
+ return { success: false, applied: false, output: "", error: String(err) };
684
+ }
685
+ }
686
+
687
+ // src/editor/ast.ts
688
+ var PAIR_MATCH = {
689
+ "{": "}",
690
+ "[": "]",
691
+ "(": ")"
692
+ };
693
+ var OPENERS = new Set(Object.keys(PAIR_MATCH));
694
+ var CLOSERS = new Set(Object.values(PAIR_MATCH));
695
+ function checkBraceBalance(code, _filePath) {
696
+ const errors = [];
697
+ const stack = [];
698
+ const lines = code.split("\n");
699
+ let inBlockComment = false;
700
+ let inString = false;
701
+ let stringChar = "";
702
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
703
+ const line = lines[lineNum];
704
+ for (let col = 0; col < line.length; col++) {
705
+ const char = line[col];
706
+ const nextChar = line[col + 1] || "";
707
+ if (inBlockComment) {
708
+ if (char === "*" && nextChar === "/") {
709
+ inBlockComment = false;
710
+ col++;
711
+ }
712
+ continue;
713
+ }
714
+ if (inString) {
715
+ if (char === "\\") {
716
+ col++;
717
+ continue;
718
+ }
719
+ if (char === stringChar) {
720
+ inString = false;
721
+ }
722
+ continue;
723
+ }
724
+ if (char === "/" && nextChar === "*" || char === "/" && nextChar === "/") {
725
+ if (char === "/" && nextChar === "*") {
726
+ inBlockComment = true;
727
+ col++;
728
+ }
729
+ break;
730
+ }
731
+ if (char === "#" && (col === 0 || line[col - 1] === " " || line[col - 1] === " ")) {
732
+ break;
733
+ }
734
+ if (char === '"' || char === "'" || char === "`") {
735
+ inString = true;
736
+ stringChar = char;
737
+ continue;
738
+ }
739
+ if (OPENERS.has(char)) {
740
+ stack.push({ char, line: lineNum + 1, column: col + 1 });
741
+ } else if (CLOSERS.has(char)) {
742
+ const expected = Object.entries(PAIR_MATCH).find(([, v]) => v === char)?.[0];
743
+ if (stack.length === 0) {
744
+ errors.push({
745
+ line: lineNum + 1,
746
+ column: col + 1,
747
+ message: `Unexpected closing '${char}' without opening '${expected}'`
748
+ });
749
+ } else {
750
+ const last = stack[stack.length - 1];
751
+ if (PAIR_MATCH[last.char] !== char) {
752
+ errors.push({
753
+ line: lineNum + 1,
754
+ column: col + 1,
755
+ message: `Expected '${PAIR_MATCH[last.char]}' but found '${char}'`
756
+ });
757
+ } else {
758
+ stack.pop();
759
+ }
760
+ }
761
+ }
762
+ }
763
+ }
764
+ for (const remaining of stack) {
765
+ errors.push({
766
+ line: remaining.line,
767
+ column: remaining.column,
768
+ message: `Unclosed '${remaining.char}' \u2014 expected '${PAIR_MATCH[remaining.char]}'`
769
+ });
770
+ }
771
+ return { valid: errors.length === 0, errors };
772
+ }
773
+ function hasValidSyntax(code, lang) {
774
+ const balance = checkBraceBalance(code);
775
+ const ext = lang?.toLowerCase() || "";
776
+ if (["ts", "tsx", "js", "jsx", "rs", "go", "c", "cpp", "java", "kt"].some((e) => ext.includes(e) || ext === e)) {
777
+ if (!balance.valid) {
778
+ return false;
779
+ }
780
+ }
781
+ return true;
782
+ }
783
+ function detectLanguage(filePath) {
784
+ const ext = filePath.split(".").pop()?.toLowerCase() || "";
785
+ const langMap = {
786
+ ts: "typescript",
787
+ tsx: "typescript",
788
+ js: "javascript",
789
+ jsx: "javascript",
790
+ py: "python",
791
+ rs: "rust",
792
+ go: "go",
793
+ rb: "ruby",
794
+ java: "java",
795
+ kt: "kotlin",
796
+ c: "c",
797
+ cpp: "cpp",
798
+ h: "c",
799
+ cs: "csharp",
800
+ php: "php",
801
+ swift: "swift"
802
+ };
803
+ return langMap[ext] || "unknown";
804
+ }
805
+
806
+ // src/editor/undo.ts
807
+ import * as fs6 from "fs";
808
+ import * as path6 from "path";
809
+ var UNDO_DIR = ".lovecode/undo";
810
+ function getUndoDir(rootDir) {
811
+ return path6.join(rootDir, UNDO_DIR);
812
+ }
813
+ function ensureUndoDir(rootDir) {
814
+ const dir = getUndoDir(rootDir);
815
+ fs6.mkdirSync(dir, { recursive: true });
816
+ return dir;
817
+ }
818
+ function getUndoFilePath(rootDir, id) {
819
+ return path6.join(getUndoDir(rootDir), `${id}.json`);
820
+ }
821
+ function getIndexPath(rootDir) {
822
+ return path6.join(getUndoDir(rootDir), "index.json");
823
+ }
824
+ function saveUndoPoint(rootDir, filePath, label = "edit") {
825
+ try {
826
+ if (!fs6.existsSync(filePath)) return null;
827
+ const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
828
+ const content = fs6.readFileSync(filePath, "utf-8");
829
+ const entry = {
830
+ id,
831
+ filePath,
832
+ timestamp: Date.now(),
833
+ originalContent: content,
834
+ label
835
+ };
836
+ ensureUndoDir(rootDir);
837
+ const entryFile = getUndoFilePath(rootDir, id);
838
+ fs6.writeFileSync(entryFile, JSON.stringify(entry, null, 2), "utf-8");
839
+ const indexPath = getIndexPath(rootDir);
840
+ let index = [];
841
+ if (fs6.existsSync(indexPath)) {
842
+ try {
843
+ index = JSON.parse(fs6.readFileSync(indexPath, "utf-8"));
844
+ } catch {
845
+ index = [];
846
+ }
847
+ }
848
+ index.unshift(entry);
849
+ if (index.length > 100) index = index.slice(0, 100);
850
+ fs6.writeFileSync(indexPath, JSON.stringify(index, null, 2), "utf-8");
851
+ return entry;
852
+ } catch {
853
+ return null;
854
+ }
855
+ }
856
+ function undoLast(rootDir, filePath) {
857
+ try {
858
+ const indexPath = getIndexPath(rootDir);
859
+ if (!fs6.existsSync(indexPath)) return null;
860
+ const index = JSON.parse(fs6.readFileSync(indexPath, "utf-8"));
861
+ const targetIdx = filePath ? index.findIndex((e) => e.filePath === filePath) : 0;
862
+ if (targetIdx === -1) return null;
863
+ const entry = index[targetIdx];
864
+ if (!fs6.existsSync(entry.filePath)) {
865
+ index.splice(targetIdx, 1);
866
+ fs6.writeFileSync(indexPath, JSON.stringify(index, null, 2), "utf-8");
867
+ return null;
868
+ }
869
+ fs6.writeFileSync(entry.filePath, entry.originalContent, "utf-8");
870
+ index.splice(targetIdx, 1);
871
+ fs6.writeFileSync(indexPath, JSON.stringify(index, null, 2), "utf-8");
872
+ const entryFile = getUndoFilePath(rootDir, entry.id);
873
+ if (fs6.existsSync(entryFile)) fs6.unlinkSync(entryFile);
874
+ return entry;
875
+ } catch {
876
+ return null;
877
+ }
878
+ }
879
+ function getUndoHistory(rootDir) {
880
+ try {
881
+ const indexPath = getIndexPath(rootDir);
882
+ if (!fs6.existsSync(indexPath)) return [];
883
+ return JSON.parse(fs6.readFileSync(indexPath, "utf-8"));
884
+ } catch {
885
+ return [];
886
+ }
887
+ }
888
+ function undoForFile(rootDir, filePath) {
889
+ return undoLast(rootDir, filePath);
890
+ }
891
+
892
+ // src/editor/snapshot.ts
893
+ import * as fs7 from "fs";
894
+ import * as path7 from "path";
895
+ var SNAPSHOT_DIR = ".lovecode/snapshots";
896
+ function getSnapshotDir(rootDir) {
897
+ return path7.join(rootDir, SNAPSHOT_DIR);
898
+ }
899
+ function ensureSnapshotDir(rootDir) {
900
+ const dir = getSnapshotDir(rootDir);
901
+ fs7.mkdirSync(dir, { recursive: true });
902
+ return dir;
903
+ }
904
+ function createSnapshot(rootDir, filePath, label = "") {
905
+ try {
906
+ if (!fs7.existsSync(filePath)) return null;
907
+ const content = fs7.readFileSync(filePath, "utf-8");
908
+ const relativePath = path7.relative(rootDir, filePath).replace(/\\/g, "/");
909
+ const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
910
+ const snapshotDir = ensureSnapshotDir(rootDir);
911
+ const snapshot = {
912
+ id,
913
+ filePath,
914
+ relativePath,
915
+ timestamp: Date.now(),
916
+ label,
917
+ size: content.length
918
+ };
919
+ fs7.writeFileSync(path7.join(snapshotDir, `${id}.snap`), content, "utf-8");
920
+ fs7.writeFileSync(path7.join(snapshotDir, `${id}.meta`), JSON.stringify(snapshot, null, 2), "utf-8");
921
+ return snapshot;
922
+ } catch {
923
+ return null;
924
+ }
925
+ }
926
+ function restoreSnapshot(rootDir, id) {
927
+ try {
928
+ const snapshotDir = getSnapshotDir(rootDir);
929
+ const metaPath = path7.join(snapshotDir, `${id}.meta`);
930
+ const snapPath = path7.join(snapshotDir, `${id}.snap`);
931
+ if (!fs7.existsSync(metaPath) || !fs7.existsSync(snapPath)) return false;
932
+ const meta = JSON.parse(fs7.readFileSync(metaPath, "utf-8"));
933
+ const content = fs7.readFileSync(snapPath, "utf-8");
934
+ if (fs7.existsSync(meta.filePath)) {
935
+ fs7.writeFileSync(meta.filePath, content, "utf-8");
936
+ }
937
+ return true;
938
+ } catch {
939
+ return false;
940
+ }
941
+ }
942
+ function listSnapshots(rootDir) {
943
+ try {
944
+ const snapshotDir = getSnapshotDir(rootDir);
945
+ if (!fs7.existsSync(snapshotDir)) return [];
946
+ const files = fs7.readdirSync(snapshotDir);
947
+ const metaFiles = files.filter((f) => f.endsWith(".meta"));
948
+ const snapshots = [];
949
+ for (const metaFile of metaFiles) {
950
+ try {
951
+ const meta = JSON.parse(fs7.readFileSync(path7.join(snapshotDir, metaFile), "utf-8"));
952
+ snapshots.push(meta);
953
+ } catch {
954
+ }
955
+ }
956
+ snapshots.sort((a, b) => b.timestamp - a.timestamp);
957
+ return snapshots;
958
+ } catch {
959
+ return [];
960
+ }
961
+ }
962
+ function createBatchSnapshot(rootDir, filePaths, label = "batch") {
963
+ const snapshots = [];
964
+ for (const fp of filePaths) {
965
+ const snap = createSnapshot(rootDir, fp, label);
966
+ if (snap) snapshots.push(snap);
967
+ }
968
+ return snapshots;
969
+ }
970
+
971
+ // src/editor/refactor.ts
972
+ import * as fs8 from "fs";
973
+ import chalk4 from "chalk";
974
+ async function executeRefactor(plan) {
975
+ const snapshots = [];
976
+ const errors = [];
977
+ let applied = 0;
978
+ const filesToSnapshot = [...new Set(plan.edits.map((e) => e.filePath))];
979
+ const batchSnaps = createBatchSnapshot(plan.rootDir, filesToSnapshot, "refactor");
980
+ snapshots.push(...batchSnaps);
981
+ for (const edit of plan.edits) {
982
+ const result = applyInlinePatch(edit.filePath, edit.search, edit.replace);
983
+ if (result.success && result.applied) {
984
+ if (plan.validateSyntax) {
985
+ try {
986
+ const content = fs8.readFileSync(edit.filePath, "utf-8");
987
+ const lang = detectLanguage(edit.filePath);
988
+ if (!hasValidSyntax(content, lang)) {
989
+ errors.push({
990
+ filePath: edit.filePath,
991
+ error: "Syntax validation failed after edit. Use undo or snapshot to revert."
992
+ });
993
+ }
994
+ } catch {
995
+ }
996
+ }
997
+ applied++;
998
+ } else {
999
+ errors.push({
1000
+ filePath: edit.filePath,
1001
+ error: result.error || "Patch failed to apply"
1002
+ });
1003
+ }
1004
+ }
1005
+ const total = plan.edits.length;
1006
+ const allSuccess = errors.length === 0;
1007
+ const successCount = applied;
1008
+ const output = [
1009
+ chalk4.bold("\n Refactor Summary"),
1010
+ chalk4.dim(` ${successCount}/${total} edits applied`),
1011
+ ...snapshots.length > 0 ? [chalk4.dim(` Snapshots: ${snapshots.length} created`)] : [],
1012
+ ...errors.map((e) => chalk4.red(` \u2717 ${e.filePath}: ${e.error}`)),
1013
+ allSuccess ? chalk4.green(" \u2713 All edits applied successfully\n") : ""
1014
+ ].filter(Boolean).join("\n");
1015
+ return {
1016
+ success: allSuccess,
1017
+ applied: successCount,
1018
+ failed: errors.length,
1019
+ snapshots,
1020
+ errors,
1021
+ output
1022
+ };
1023
+ }
1024
+ function planRefactor(edits, rootDir, validateSyntax = true) {
1025
+ return { edits, rootDir, validateSyntax };
1026
+ }
1027
+
1028
+ // src/shell/executor.ts
1029
+ import { spawn } from "child_process";
1030
+ import { EventEmitter } from "events";
1031
+
1032
+ // src/core/approval.ts
1033
+ import * as readline from "readline";
1034
+ import chalk6 from "chalk";
1035
+
1036
+ // src/core/modes.ts
1037
+ import chalk5 from "chalk";
1038
+ var modeConfigs = {
1039
+ assist: {
1040
+ label: "Assist",
1041
+ icon: "\u{1F3AF}",
1042
+ description: "User controls every step \u2014 you approve each action before execution",
1043
+ color: (t) => chalk5.blue(t),
1044
+ autoApproveSafe: false,
1045
+ autoApproveWarning: false,
1046
+ maxRetries: 1,
1047
+ maxSteps: 15
1048
+ },
1049
+ smart: {
1050
+ label: "Smart",
1051
+ icon: "\u{1F9E0}",
1052
+ description: "Semi-autonomous \u2014 safe actions run auto, warnings ask approval, dangerous blocked",
1053
+ color: (t) => chalk5.yellow(t),
1054
+ autoApproveSafe: true,
1055
+ autoApproveWarning: false,
1056
+ maxRetries: 3,
1057
+ maxSteps: 30
1058
+ },
1059
+ yolo: {
1060
+ label: "YOLO",
1061
+ icon: "\u{1F680}",
1062
+ description: "Full autonomy \u2014 all actions execute without user intervention",
1063
+ color: (t) => chalk5.magenta(t),
1064
+ autoApproveSafe: true,
1065
+ autoApproveWarning: true,
1066
+ maxRetries: 5,
1067
+ maxSteps: 50
1068
+ }
1069
+ };
1070
+ function getModeConfig(mode) {
1071
+ return modeConfigs[mode];
1072
+ }
1073
+ function createContext(mode, workingDir) {
1074
+ const cfg = modeConfigs[mode];
1075
+ return {
1076
+ mode,
1077
+ workingDir,
1078
+ maxRetries: cfg.maxRetries,
1079
+ maxSteps: cfg.maxSteps
1080
+ };
1081
+ }
1082
+ function listModes() {
1083
+ return Object.entries(modeConfigs).map(([, cfg]) => ` ${cfg.color(`${cfg.icon} ${cfg.label}`.padEnd(15))} ${chalk5.dim(cfg.description)}`).join("\n");
1084
+ }
1085
+
1086
+ // src/core/approval.ts
1087
+ var dangerousPatterns = [
1088
+ /^rm\s+-rf\s+(\/|\/\w+|\.)/,
1089
+ /^sudo\s+/,
1090
+ /^chmod\s+777/,
1091
+ /^dd\s+/,
1092
+ /^mkfs/,
1093
+ /^fdisk/,
1094
+ /^>\/dev/,
1095
+ /^:\(\)\s*{/,
1096
+ /^wget\s+.*\||^curl\s+.*\|/,
1097
+ /^eval\s+/,
1098
+ /^exec\s+/
1099
+ ];
1100
+ var warningPatterns = [
1101
+ /^rm\s+/,
1102
+ /^chmod\s+/,
1103
+ /^chown\s+/,
1104
+ /^mv\s+/,
1105
+ /^cp\s+/,
1106
+ /^kill\s+/,
1107
+ /^pkill\s+/,
1108
+ /^systemctl/,
1109
+ /^service/,
1110
+ /^apt\s+(install|remove|purge)/,
1111
+ /^npm\s+(install|uninstall|publish)/,
1112
+ /^pip\s+(install|uninstall)/,
1113
+ /^docker\s+(rm|rmi|kill|stop)/,
1114
+ /^git\s+push/,
1115
+ /^git\s+reset/,
1116
+ /^git\s+revert/,
1117
+ /^git\s+merge/,
1118
+ /^git\s+rebase/,
1119
+ /^gh\s+/
1120
+ ];
1121
+ function classifyCommand(command) {
1122
+ const trimmed = command.trim();
1123
+ for (const pattern of dangerousPatterns) {
1124
+ if (pattern.test(trimmed)) return "dangerous";
1125
+ }
1126
+ for (const pattern of warningPatterns) {
1127
+ if (pattern.test(trimmed)) return "warning";
1128
+ }
1129
+ return "safe";
1130
+ }
1131
+ function classifyTool(toolName, args) {
1132
+ if (toolName === "execute_command" && args?.command) {
1133
+ return classifyCommand(args.command);
1134
+ }
1135
+ const safeTools = /* @__PURE__ */ new Set(["read_file", "grep_search", "glob_search", "list_dir", "get_cwd"]);
1136
+ const warningTools = /* @__PURE__ */ new Set(["write_file", "edit_file", "create_file", "append_file"]);
1137
+ const dangerousTools = /* @__PURE__ */ new Set(["delete_file", "delete_dir", "overwrite_dir"]);
1138
+ if (safeTools.has(toolName)) return "safe";
1139
+ if (dangerousTools.has(toolName)) return "dangerous";
1140
+ if (warningTools.has(toolName)) return "warning";
1141
+ return "warning";
1142
+ }
1143
+ function askApproval(description, toolName, level) {
1144
+ return new Promise((resolve3) => {
1145
+ const label = level === "dangerous" ? chalk6.bgRed.white(" DANGEROUS ") : chalk6.bgYellow.black(" WARNING ");
1146
+ console.log(`
1147
+ ${label} ${chalk6.dim(toolName)}: ${description}`);
1148
+ const rl = readline.createInterface({
1149
+ input: process.stdin,
1150
+ output: process.stdout
1151
+ });
1152
+ rl.question(chalk6.dim(" Allow? (y/N): "), (answer) => {
1153
+ rl.close();
1154
+ const allowed = answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
1155
+ if (allowed) {
1156
+ console.log(chalk6.green(" \u2713 Approved\n"));
1157
+ } else {
1158
+ console.log(chalk6.yellow(" \u2717 Skipped\n"));
1159
+ }
1160
+ resolve3(allowed);
1161
+ });
1162
+ });
1163
+ }
1164
+ async function getApproval(mode, toolName, description, args) {
1165
+ const level = classifyTool(toolName, args);
1166
+ const config = getModeConfig(mode);
1167
+ if (level === "dangerous") {
1168
+ if (mode === "yolo") {
1169
+ console.log(chalk6.bgRed.white(` ${toolName}: ${description}`));
1170
+ if (!config.autoApproveWarning) {
1171
+ const allowed2 = await askApproval(description, toolName, level);
1172
+ return { allowed: allowed2, reason: allowed2 ? "approved by user" : "blocked by user" };
1173
+ }
1174
+ return { allowed: true, reason: "yolo mode" };
1175
+ }
1176
+ console.log(chalk6.bgRed.white(` BLOCKED: ${toolName} - ${description}`));
1177
+ console.log(chalk6.dim(" This action is classified as dangerous.\n"));
1178
+ return { allowed: false, reason: "dangerous action blocked" };
1179
+ }
1180
+ if (level === "warning") {
1181
+ if (config.autoApproveWarning) {
1182
+ console.log(chalk6.green(` \u2713 ${toolName}: ${chalk6.dim(description)}`));
1183
+ return { allowed: true, reason: `${mode} mode auto-approve` };
1184
+ }
1185
+ const allowed2 = await askApproval(description, toolName, level);
1186
+ return { allowed: allowed2, reason: allowed2 ? "approved by user" : "skipped by user" };
1187
+ }
1188
+ if (config.autoApproveSafe) {
1189
+ return { allowed: true, reason: `${mode} mode auto-approve` };
1190
+ }
1191
+ const allowed = await askApproval(description, toolName, level);
1192
+ return { allowed, reason: allowed ? "approved by user" : "skipped by user" };
1193
+ }
1194
+
1195
+ // src/shell/executor.ts
1196
+ var CommandExecutor = class extends EventEmitter {
1197
+ process = null;
1198
+ aborted = false;
1199
+ stdoutBuffer = "";
1200
+ stderrBuffer = "";
1201
+ startTime = 0;
1202
+ cwd;
1203
+ constructor(cwd = process.cwd()) {
1204
+ super();
1205
+ this.cwd = cwd;
1206
+ }
1207
+ async execute(options) {
1208
+ const { command, timeout = 12e4, env, signal } = options;
1209
+ this.aborted = false;
1210
+ this.stdoutBuffer = "";
1211
+ this.stderrBuffer = "";
1212
+ this.startTime = Date.now();
1213
+ const isDangerous = classifyCommand(command);
1214
+ if (isDangerous === "dangerous") {
1215
+ return {
1216
+ success: false,
1217
+ exitCode: null,
1218
+ stdout: "",
1219
+ stderr: "Command blocked by sandbox: classified as dangerous",
1220
+ duration: 0,
1221
+ cancelled: false
1222
+ };
1223
+ }
1224
+ return new Promise((resolve3) => {
1225
+ const shell = process.platform === "win32" ? "cmd.exe" : "/bin/bash";
1226
+ const shellFlag = process.platform === "win32" ? "/c" : "-c";
1227
+ try {
1228
+ this.process = spawn(shell, [shellFlag, command], {
1229
+ cwd: this.cwd,
1230
+ env: { ...process.env, ...env, FORCE_COLOR: "1" },
1231
+ stdio: ["pipe", "pipe", "pipe"],
1232
+ signal
1233
+ });
1234
+ } catch (err) {
1235
+ resolve3({
1236
+ success: false,
1237
+ exitCode: null,
1238
+ stdout: "",
1239
+ stderr: String(err),
1240
+ duration: 0,
1241
+ cancelled: false
1242
+ });
1243
+ return;
1244
+ }
1245
+ const timer = setTimeout(() => {
1246
+ this.kill();
1247
+ resolve3({
1248
+ success: false,
1249
+ exitCode: null,
1250
+ stdout: this.stdoutBuffer,
1251
+ stderr: this.stderrBuffer + "\n[Timeout] Command timed out",
1252
+ duration: Date.now() - this.startTime,
1253
+ cancelled: true
1254
+ });
1255
+ }, timeout);
1256
+ this.process.stdout?.on("data", (data) => {
1257
+ const text = data.toString();
1258
+ this.stdoutBuffer += text;
1259
+ options.onStdout?.(text);
1260
+ this.emit("stdout", text);
1261
+ });
1262
+ this.process.stderr?.on("data", (data) => {
1263
+ const text = data.toString();
1264
+ this.stderrBuffer += text;
1265
+ options.onStderr?.(text);
1266
+ this.emit("stderr", text);
1267
+ });
1268
+ this.process.on("exit", (exitCode) => {
1269
+ clearTimeout(timer);
1270
+ const duration = Date.now() - this.startTime;
1271
+ options.onExit?.(exitCode);
1272
+ this.emit("exit", exitCode);
1273
+ resolve3({
1274
+ success: exitCode === 0 && !this.aborted,
1275
+ exitCode,
1276
+ stdout: this.stdoutBuffer,
1277
+ stderr: this.stderrBuffer,
1278
+ duration,
1279
+ cancelled: this.aborted
1280
+ });
1281
+ });
1282
+ this.process.on("error", (err) => {
1283
+ clearTimeout(timer);
1284
+ this.emit("error", err);
1285
+ resolve3({
1286
+ success: false,
1287
+ exitCode: null,
1288
+ stdout: this.stdoutBuffer,
1289
+ stderr: String(err),
1290
+ duration: Date.now() - this.startTime,
1291
+ cancelled: this.aborted
1292
+ });
1293
+ });
1294
+ });
1295
+ }
1296
+ kill() {
1297
+ this.aborted = true;
1298
+ if (this.process && !this.process.killed) {
1299
+ if (process.platform === "win32") {
1300
+ spawn("taskkill", ["/pid", String(this.process.pid), "/f", "/t"]);
1301
+ } else {
1302
+ this.process.kill("SIGTERM");
1303
+ setTimeout(() => {
1304
+ if (this.process && !this.process.killed) {
1305
+ this.process.kill("SIGKILL");
1306
+ }
1307
+ }, 3e3);
1308
+ }
1309
+ }
1310
+ }
1311
+ get isRunning() {
1312
+ return this.process !== null && !this.process.killed;
1313
+ }
1314
+ };
1315
+ function execCommand(command, cwd = process.cwd(), timeout = 12e4) {
1316
+ const executor = new CommandExecutor(cwd);
1317
+ return executor.execute({ command, cwd, timeout });
1318
+ }
1319
+
1320
+ // src/shell/stream.ts
1321
+ import chalk7 from "chalk";
1322
+
1323
+ // src/shell/interactive.ts
1324
+ import { spawn as spawn2 } from "child_process";
1325
+ import * as readline2 from "readline";
1326
+ import chalk8 from "chalk";
1327
+
1328
+ // src/shell/sandbox.ts
1329
+ import chalk9 from "chalk";
1330
+ var SAFE_PREFIXES = [
1331
+ "npm",
1332
+ "node",
1333
+ "npx",
1334
+ "yarn",
1335
+ "pnpm",
1336
+ "bun",
1337
+ "python",
1338
+ "python3",
1339
+ "pip",
1340
+ "pip3",
1341
+ "go",
1342
+ "cargo",
1343
+ "rustc",
1344
+ "tsc",
1345
+ "eslint",
1346
+ "prettier",
1347
+ "git",
1348
+ "ls",
1349
+ "cat",
1350
+ "head",
1351
+ "tail",
1352
+ "wc",
1353
+ "sort",
1354
+ "uniq",
1355
+ "echo",
1356
+ "printf",
1357
+ "grep",
1358
+ "rg",
1359
+ "ag",
1360
+ "ack",
1361
+ "find",
1362
+ "tree",
1363
+ "pwd",
1364
+ "which",
1365
+ "whoami",
1366
+ "curl",
1367
+ "wget",
1368
+ "mkdir",
1369
+ "touch",
1370
+ "cp",
1371
+ "mv",
1372
+ "docker",
1373
+ "docker-compose",
1374
+ "make",
1375
+ "cmake",
1376
+ "jq",
1377
+ "yq",
1378
+ "diff",
1379
+ "patch",
1380
+ "test",
1381
+ "["
1382
+ ];
1383
+ var DEFAULT_BLOCKED = [
1384
+ /^rm\s+-rf\s+(\/|\/\w+)/,
1385
+ /^sudo\s+/,
1386
+ /^su\s+/,
1387
+ /^chmod\s+777/,
1388
+ /^dd\s+/,
1389
+ /^mkfs/,
1390
+ /^fdisk/,
1391
+ /^:\(\)/,
1392
+ /^eval\s+/,
1393
+ /^exec\s+/,
1394
+ /^kill\s+-9/,
1395
+ /^>\/dev\/sda/
1396
+ ];
1397
+ var DEFAULT_ALLOWED = [
1398
+ /^npm\s+(install|run|test|build|start|add|remove|update)/,
1399
+ /^node\s+/,
1400
+ /^npx\s+/,
1401
+ /^yarn\s+/,
1402
+ /^python3?\s+/,
1403
+ /^pip3?\s+(install|list|show)/,
1404
+ /^go\s+(build|run|test|mod|fmt)/,
1405
+ /^cargo\s+(build|run|test|check|add)/,
1406
+ /^rustc\s+/,
1407
+ /^tsc\s*/,
1408
+ /^eslint\s+/,
1409
+ /^prettier\s+/,
1410
+ /^git\s+(status|log|diff|show|add|commit|push|pull|branch|checkout|stash|init|clone)/,
1411
+ /^ls\s*/,
1412
+ /^cat\s+/,
1413
+ /^head\s+/,
1414
+ /^tail\s+/,
1415
+ /^grep\s+/,
1416
+ /^rg\s+/,
1417
+ /^find\s+/,
1418
+ /^echo\s+/,
1419
+ /^printf\s+/,
1420
+ /^pwd\s*/,
1421
+ /^which\s+/,
1422
+ /^mkdir\s+/,
1423
+ /^touch\s+/,
1424
+ /^cp\s+/,
1425
+ /^mv\s+/,
1426
+ /^rm\s+(?!-rf\s+\/)/,
1427
+ /^diff\s+/,
1428
+ /^jq\s+/,
1429
+ /^make\s+/,
1430
+ /^docker\s+(ps|images|build|run|compose)/,
1431
+ /^curl\s+/,
1432
+ /^wget\s+/
1433
+ ];
1434
+ function createDefaultPolicy() {
1435
+ return {
1436
+ allowCommands: DEFAULT_ALLOWED,
1437
+ blockCommands: DEFAULT_BLOCKED,
1438
+ allowNetwork: true,
1439
+ allowFileWrite: true,
1440
+ maxProcessTime: 3e5,
1441
+ allowedPrefixes: SAFE_PREFIXES
1442
+ };
1443
+ }
1444
+ function evaluateCommand(command, policy) {
1445
+ const p = policy || createDefaultPolicy();
1446
+ const trimmed = command.trim();
1447
+ for (const block of p.blockCommands) {
1448
+ if (block.test(trimmed)) {
1449
+ return {
1450
+ allowed: false,
1451
+ reason: `Command matches blocked pattern: ${block}`,
1452
+ risk: "blocked"
1453
+ };
1454
+ }
1455
+ }
1456
+ for (const allow of p.allowCommands) {
1457
+ if (allow.test(trimmed)) {
1458
+ return {
1459
+ allowed: true,
1460
+ reason: "Command matches allowed pattern",
1461
+ risk: "safe"
1462
+ };
1463
+ }
1464
+ }
1465
+ const firstWord = trimmed.split(/\s+/)[0]?.toLowerCase();
1466
+ if (p.allowedPrefixes.includes(firstWord)) {
1467
+ return {
1468
+ allowed: true,
1469
+ reason: `Command prefix "${firstWord}" is in safe list`,
1470
+ risk: "low"
1471
+ };
1472
+ }
1473
+ return {
1474
+ allowed: false,
1475
+ reason: `Command "${trimmed.slice(0, 50)}..." is not in the allowed list`,
1476
+ risk: "blocked"
1477
+ };
1478
+ }
1479
+ function printSandboxVerdict(verdict) {
1480
+ const labels = {
1481
+ safe: chalk9.green("SAFE"),
1482
+ low: chalk9.cyan("LOW RISK"),
1483
+ medium: chalk9.yellow("MEDIUM RISK"),
1484
+ high: chalk9.red("HIGH RISK"),
1485
+ blocked: chalk9.bgRed.white(" BLOCKED ")
1486
+ };
1487
+ return ` ${labels[verdict.risk] || chalk9.dim("UNKNOWN")} ${chalk9.dim(verdict.reason)}`;
1488
+ }
1489
+
1490
+ // src/repo/detect.ts
1491
+ import * as fs9 from "fs";
1492
+ import * as path8 from "path";
1493
+ import chalk10 from "chalk";
1494
+ var rules = [
1495
+ {
1496
+ name: "Next.js",
1497
+ icon: "\u25B2",
1498
+ language: "TypeScript",
1499
+ check: (dir) => {
1500
+ const evidence = [];
1501
+ if (fs9.existsSync(path8.join(dir, "next.config.js")) || fs9.existsSync(path8.join(dir, "next.config.mjs"))) {
1502
+ evidence.push("next.config.{js,mjs}");
1503
+ }
1504
+ const pkg = readJson(path8.join(dir, "package.json"));
1505
+ if (pkg?.dependencies?.next || pkg?.devDependencies?.next) {
1506
+ evidence.push("package.json: next dependency");
1507
+ }
1508
+ return { detected: evidence.length > 0, confidence: evidence.length >= 2 ? 95 : 70, evidence };
1509
+ }
1510
+ },
1511
+ {
1512
+ name: "React",
1513
+ icon: "\u269B",
1514
+ language: "TypeScript/JavaScript",
1515
+ check: (dir) => {
1516
+ const evidence = [];
1517
+ const pkg = readJson(path8.join(dir, "package.json"));
1518
+ if (pkg?.dependencies?.react) evidence.push("package.json: react dependency");
1519
+ if (fs9.existsSync(path8.join(dir, "jsconfig.json"))) evidence.push("jsconfig.json");
1520
+ if (findFiles2(dir, ".jsx", 3).length > 0) evidence.push(".jsx files found");
1521
+ if (findFiles2(dir, ".tsx", 3).length > 0) evidence.push(".tsx files found");
1522
+ return { detected: evidence.length > 0, confidence: Math.min(evidence.length * 30, 95), evidence };
1523
+ }
1524
+ },
1525
+ {
1526
+ name: "Node.js",
1527
+ icon: "\u25CF",
1528
+ language: "JavaScript",
1529
+ check: (dir) => {
1530
+ const evidence = [];
1531
+ if (fs9.existsSync(path8.join(dir, "package.json"))) evidence.push("package.json");
1532
+ if (fs9.existsSync(path8.join(dir, "package-lock.json"))) evidence.push("package-lock.json");
1533
+ const pkg = readJson(path8.join(dir, "package.json"));
1534
+ if (pkg && !pkg?.dependencies?.react && !pkg?.dependencies?.next) {
1535
+ evidence.push("Node.js package (no React/Next)");
1536
+ }
1537
+ return { detected: evidence.length > 0, confidence: Math.min(evidence.length * 35, 90), evidence };
1538
+ }
1539
+ },
1540
+ {
1541
+ name: "Go",
1542
+ icon: "\u{1F537}",
1543
+ language: "Go",
1544
+ check: (dir) => {
1545
+ const evidence = [];
1546
+ if (fs9.existsSync(path8.join(dir, "go.mod"))) evidence.push("go.mod");
1547
+ if (fs9.existsSync(path8.join(dir, "go.sum"))) evidence.push("go.sum");
1548
+ if (findFiles2(dir, ".go", 3).length > 0) evidence.push(".go files found");
1549
+ return { detected: evidence.length > 0, confidence: evidence.includes("go.mod") ? 98 : 60, evidence };
1550
+ }
1551
+ },
1552
+ {
1553
+ name: "Django",
1554
+ icon: "\u{1F3AF}",
1555
+ language: "Python",
1556
+ check: (dir) => {
1557
+ const evidence = [];
1558
+ if (fs9.existsSync(path8.join(dir, "manage.py"))) evidence.push("manage.py");
1559
+ if (findFiles2(dir, "settings.py", 5).length > 0) evidence.push("settings.py found");
1560
+ const cfg = readFile(path8.join(dir, "requirements.txt"));
1561
+ if (cfg?.includes("django")) evidence.push("requirements.txt: django");
1562
+ const pipfile = readFile(path8.join(dir, "Pipfile"));
1563
+ if (pipfile?.includes("django")) evidence.push("Pipfile: django");
1564
+ return { detected: evidence.length > 0, confidence: evidence.includes("manage.py") ? 95 : 50, evidence };
1565
+ }
1566
+ },
1567
+ {
1568
+ name: "Flutter",
1569
+ icon: "\u{1F7E6}",
1570
+ language: "Dart",
1571
+ check: (dir) => {
1572
+ const evidence = [];
1573
+ if (fs9.existsSync(path8.join(dir, "pubspec.yaml"))) evidence.push("pubspec.yaml");
1574
+ if (findFiles2(dir, ".dart", 3).length > 0) evidence.push(".dart files found");
1575
+ if (fs9.existsSync(path8.join(dir, "android")) && fs9.existsSync(path8.join(dir, "ios"))) {
1576
+ evidence.push("android/ & ios/ directories");
1577
+ }
1578
+ return { detected: evidence.length > 0, confidence: evidence.includes("pubspec.yaml") ? 95 : 50, evidence };
1579
+ }
1580
+ },
1581
+ {
1582
+ name: "Python",
1583
+ icon: "\u{1F40D}",
1584
+ language: "Python",
1585
+ check: (dir) => {
1586
+ const evidence = [];
1587
+ if (fs9.existsSync(path8.join(dir, "requirements.txt"))) evidence.push("requirements.txt");
1588
+ if (fs9.existsSync(path8.join(dir, "setup.py"))) evidence.push("setup.py");
1589
+ if (fs9.existsSync(path8.join(dir, "pyproject.toml"))) evidence.push("pyproject.toml");
1590
+ if (findFiles2(dir, ".py", 5).length > 0) evidence.push(".py files found");
1591
+ return { detected: evidence.length > 0, confidence: Math.min(evidence.length * 25, 90), evidence };
1592
+ }
1593
+ },
1594
+ {
1595
+ name: "Rust",
1596
+ icon: "\u{1F980}",
1597
+ language: "Rust",
1598
+ check: (dir) => {
1599
+ const evidence = [];
1600
+ if (fs9.existsSync(path8.join(dir, "Cargo.toml"))) evidence.push("Cargo.toml");
1601
+ if (fs9.existsSync(path8.join(dir, "Cargo.lock"))) evidence.push("Cargo.lock");
1602
+ if (findFiles2(dir, ".rs", 3).length > 0) evidence.push(".rs files found");
1603
+ return { detected: evidence.length > 0, confidence: evidence.includes("Cargo.toml") ? 98 : 60, evidence };
1604
+ }
1605
+ },
1606
+ {
1607
+ name: "Vue.js",
1608
+ icon: "\u{1F49A}",
1609
+ language: "JavaScript",
1610
+ check: (dir) => {
1611
+ const evidence = [];
1612
+ const pkg = readJson(path8.join(dir, "package.json"));
1613
+ if (pkg?.dependencies?.vue) evidence.push("package.json: vue");
1614
+ if (findFiles2(dir, ".vue", 3).length > 0) evidence.push(".vue files found");
1615
+ return { detected: evidence.length > 0, confidence: Math.min(evidence.length * 40, 90), evidence };
1616
+ }
1617
+ },
1618
+ {
1619
+ name: "Angular",
1620
+ icon: "\u{1F53A}",
1621
+ language: "TypeScript",
1622
+ check: (dir) => {
1623
+ const evidence = [];
1624
+ if (fs9.existsSync(path8.join(dir, "angular.json"))) evidence.push("angular.json");
1625
+ const pkg = readJson(path8.join(dir, "package.json"));
1626
+ if (pkg?.dependencies?.["@angular/core"]) evidence.push("package.json: @angular/core");
1627
+ return { detected: evidence.length > 0, confidence: Math.min(evidence.length * 40, 95), evidence };
1628
+ }
1629
+ }
1630
+ ];
1631
+ function readJson(filePath) {
1632
+ try {
1633
+ const content = fs9.readFileSync(filePath, "utf-8");
1634
+ return JSON.parse(content);
1635
+ } catch {
1636
+ return null;
1637
+ }
1638
+ }
1639
+ function readFile(filePath) {
1640
+ try {
1641
+ return fs9.readFileSync(filePath, "utf-8");
1642
+ } catch {
1643
+ return null;
1644
+ }
1645
+ }
1646
+ function findFiles2(dir, ext, max) {
1647
+ const results = [];
1648
+ try {
1649
+ const entries = fs9.readdirSync(dir, { withFileTypes: true });
1650
+ for (const entry of entries) {
1651
+ if (results.length >= max) break;
1652
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
1653
+ const fullPath = path8.join(dir, entry.name);
1654
+ if (entry.isDirectory()) {
1655
+ results.push(...findFiles2(fullPath, ext, max - results.length));
1656
+ } else if (entry.name.endsWith(ext)) {
1657
+ results.push(fullPath);
1658
+ }
1659
+ }
1660
+ } catch {
1661
+ }
1662
+ return results;
1663
+ }
1664
+ function detectProject(rootDir) {
1665
+ const types = [];
1666
+ for (const rule of rules) {
1667
+ const result = rule.check(rootDir);
1668
+ if (result.detected) {
1669
+ types.push({
1670
+ name: rule.name,
1671
+ icon: rule.icon,
1672
+ confidence: result.confidence,
1673
+ language: rule.language,
1674
+ detectedBy: result.evidence,
1675
+ configFiles: result.evidence
1676
+ });
1677
+ }
1678
+ }
1679
+ types.sort((a, b) => b.confidence - a.confidence);
1680
+ const languages = [...new Set(types.map((t) => t.language))];
1681
+ const primary = types[0] || null;
1682
+ const pkg = readJson(path8.join(rootDir, "package.json"));
1683
+ let packageManager = null;
1684
+ if (fs9.existsSync(path8.join(rootDir, "yarn.lock"))) packageManager = "yarn";
1685
+ else if (fs9.existsSync(path8.join(rootDir, "pnpm-lock.yaml"))) packageManager = "pnpm";
1686
+ else if (fs9.existsSync(path8.join(rootDir, "package-lock.json"))) packageManager = "npm";
1687
+ else if (fs9.existsSync(path8.join(rootDir, "bun.lockb"))) packageManager = "bun";
1688
+ let buildTool = null;
1689
+ if (pkg?.scripts) {
1690
+ const scripts = pkg.scripts;
1691
+ if (scripts.build) {
1692
+ if (scripts.build.includes("webpack")) buildTool = "webpack";
1693
+ else if (scripts.build.includes("vite")) buildTool = "vite";
1694
+ else if (scripts.build.includes("tsc")) buildTool = "tsc";
1695
+ else if (scripts.build.includes("next")) buildTool = "next";
1696
+ else buildTool = "custom";
1697
+ }
1698
+ }
1699
+ if (!buildTool && fs9.existsSync(path8.join(rootDir, "webpack.config.js"))) buildTool = "webpack";
1700
+ if (!buildTool && fs9.existsSync(path8.join(rootDir, "vite.config.ts"))) buildTool = "vite";
1701
+ if (!buildTool && fs9.existsSync(path8.join(rootDir, "tsconfig.json"))) buildTool = "tsc";
1702
+ return { types, primary, languages, packageManager, buildTool };
1703
+ }
1704
+ function printProjectInfo(info) {
1705
+ const lines = [chalk10.bold("\n Project Detection")];
1706
+ if (info.primary) {
1707
+ lines.push(` ${info.primary.icon} ${chalk10.cyan(info.primary.name)} ${chalk10.dim(`(${info.primary.confidence}% confidence)`)}`);
1708
+ lines.push(` ${chalk10.dim(" Language:")} ${info.languages.join(", ")}`);
1709
+ }
1710
+ if (info.types.length > 0) {
1711
+ lines.push(chalk10.dim("\n All detected:"));
1712
+ for (const t of info.types) {
1713
+ const bar = confidenceBar(t.confidence);
1714
+ lines.push(` ${t.icon} ${chalk10.cyan(t.name.padEnd(12))} ${bar} ${chalk10.dim(`${t.confidence}%`)}`);
1715
+ }
1716
+ } else {
1717
+ lines.push(chalk10.yellow("\n No known project types detected."));
1718
+ }
1719
+ if (info.packageManager) {
1720
+ lines.push(`
1721
+ Package Manager: ${chalk10.cyan(info.packageManager)}`);
1722
+ }
1723
+ if (info.buildTool) {
1724
+ lines.push(` Build Tool: ${chalk10.cyan(info.buildTool)}`);
1725
+ }
1726
+ lines.push("");
1727
+ return lines.join("\n");
1728
+ }
1729
+ function confidenceBar(pct) {
1730
+ const filled = Math.round(pct / 10);
1731
+ const empty = 10 - filled;
1732
+ return chalk10.green("\u2588".repeat(filled)) + chalk10.dim("\u2591".repeat(empty));
1733
+ }
1734
+
1735
+ // src/repo/deps.ts
1736
+ import * as fs10 from "fs";
1737
+ import * as path9 from "path";
1738
+ import chalk11 from "chalk";
1739
+ var IMPORT_PATTERNS = [
1740
+ { regex: /import\s+(?:\*\s+as\s+\w+\s+from\s+)?['"]([^'"]+)['"]/g, type: "import" },
1741
+ { regex: /import\s+(\w+(?:\s*,\s*\w+)?)\s+from\s+['"]([^'"]+)['"]/g, type: "import" },
1742
+ { regex: /import\s+type\s+.*?from\s+['"]([^'"]+)['"]/g, type: "import" },
1743
+ { regex: /from\s+['"]([^'"]+)['"]/g, type: "import" },
1744
+ { regex: /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g, type: "require" },
1745
+ { regex: /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g, type: "dynamic" }
1746
+ ];
1747
+ var PYTHON_PATTERNS = [
1748
+ { regex: /^import\s+(\w+(?:\.\w+)*)/gm, type: "import" },
1749
+ { regex: /^from\s+(\w+(?:\.\w+)*)\s+import/gm, type: "import" }
1750
+ ];
1751
+ var GO_PATTERNS = [
1752
+ { regex: /^import\s+\(([^)]+)\)/gms, type: "import" },
1753
+ { regex: /^import\s+"([^"]+)"/gm, type: "import" }
1754
+ ];
1755
+ var RUST_PATTERNS = [
1756
+ { regex: /^use\s+(\w+(?:::\w+)*)/gm, type: "import" },
1757
+ { regex: /^extern\s+crate\s+(\w+)/gm, type: "import" }
1758
+ ];
1759
+ function analyzeDependencies(rootDir) {
1760
+ const nodes = /* @__PURE__ */ new Map();
1761
+ const edges = [];
1762
+ const sourceFiles = collectSourceFiles(rootDir);
1763
+ for (const filePath of sourceFiles) {
1764
+ const relativePath = path9.relative(rootDir, filePath).replace(/\\/g, "/");
1765
+ const ext = path9.extname(filePath).toLowerCase();
1766
+ let patterns = IMPORT_PATTERNS;
1767
+ if (ext === ".py") patterns = PYTHON_PATTERNS;
1768
+ else if (ext === ".go") patterns = GO_PATTERNS;
1769
+ else if (ext === ".rs") patterns = RUST_PATTERNS;
1770
+ if (!nodes.has(relativePath)) {
1771
+ nodes.set(relativePath, {
1772
+ name: relativePath,
1773
+ filePath,
1774
+ imports: [],
1775
+ importedBy: [],
1776
+ isExternal: false
1777
+ });
1778
+ }
1779
+ try {
1780
+ const content = fs10.readFileSync(filePath, "utf-8");
1781
+ const imports = extractImports(content, patterns);
1782
+ for (const imp of imports) {
1783
+ const resolved = resolveImport(imp, relativePath, rootDir, sourceFiles);
1784
+ edges.push({ source: relativePath, target: resolved, type: "import" });
1785
+ nodes.get(relativePath).imports.push(imp);
1786
+ if (!nodes.has(resolved)) {
1787
+ const isExternal = !sourceFiles.includes(resolved) && !fs10.existsSync(resolved);
1788
+ nodes.set(resolved, {
1789
+ name: resolved,
1790
+ filePath: resolved,
1791
+ imports: [],
1792
+ importedBy: [],
1793
+ isExternal
1794
+ });
1795
+ }
1796
+ nodes.get(resolved).importedBy.push(relativePath);
1797
+ }
1798
+ } catch {
1799
+ }
1800
+ }
1801
+ const entryPoints = detectEntryPoints(rootDir, sourceFiles);
1802
+ const externalDeps = [...nodes.values()].filter((n) => n.isExternal).map((n) => n.name);
1803
+ return { nodes, edges, entryPoints, externalDeps };
1804
+ }
1805
+ function collectSourceFiles(rootDir) {
1806
+ const results = [];
1807
+ const skipDirs = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "__pycache__", "target", "vendor"]);
1808
+ function walk(dir) {
1809
+ try {
1810
+ const entries = fs10.readdirSync(dir, { withFileTypes: true });
1811
+ for (const entry of entries) {
1812
+ if (entry.name.startsWith(".") || skipDirs.has(entry.name)) continue;
1813
+ const fullPath = path9.join(dir, entry.name);
1814
+ if (entry.isDirectory()) {
1815
+ walk(fullPath);
1816
+ } else {
1817
+ const ext = path9.extname(entry.name).toLowerCase();
1818
+ if ([".ts", ".tsx", ".js", ".jsx", ".mjs", ".py", ".go", ".rs", ".rb", ".java"].includes(ext)) {
1819
+ results.push(fullPath);
1820
+ }
1821
+ }
1822
+ }
1823
+ } catch {
1824
+ }
1825
+ }
1826
+ walk(rootDir);
1827
+ return results;
1828
+ }
1829
+ function extractImports(content, patterns) {
1830
+ const imports = [];
1831
+ for (const { regex } of patterns) {
1832
+ const matches = content.matchAll(regex);
1833
+ for (const match of matches) {
1834
+ const imp = (match[2] || match[1] || "").trim();
1835
+ if (imp && !imp.startsWith(".") && !imp.startsWith("/")) {
1836
+ const parts = imp.split("/");
1837
+ imports.push(parts[0].startsWith("@") ? `${parts[0]}/${parts[1] || ""}` : parts[0]);
1838
+ } else if (imp) {
1839
+ imports.push(imp);
1840
+ }
1841
+ }
1842
+ }
1843
+ return [...new Set(imports)];
1844
+ }
1845
+ function resolveImport(imp, relativeFrom, rootDir, allFiles) {
1846
+ if (imp.startsWith(".")) {
1847
+ const dir = path9.dirname(path9.join(rootDir, relativeFrom));
1848
+ const resolved = path9.resolve(dir, imp);
1849
+ const extensions = ["", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".py", ".go", ".rs", "/index.ts", "/index.js"];
1850
+ for (const ext of extensions) {
1851
+ const candidate = resolved + ext;
1852
+ if (allFiles.includes(candidate)) return path9.relative(rootDir, candidate).replace(/\\/g, "/");
1853
+ }
1854
+ return imp;
1855
+ }
1856
+ return imp;
1857
+ }
1858
+ function detectEntryPoints(rootDir, files) {
1859
+ const entries = [];
1860
+ const entryNames = ["index.ts", "index.js", "main.ts", "main.js", "app.ts", "app.js", "server.ts", "server.js"];
1861
+ for (const name of entryNames) {
1862
+ const fp = path9.join(rootDir, name);
1863
+ if (files.includes(fp)) entries.push(name);
1864
+ }
1865
+ if (fs10.existsSync(path9.join(rootDir, "package.json"))) {
1866
+ try {
1867
+ const pkg = JSON.parse(fs10.readFileSync(path9.join(rootDir, "package.json"), "utf-8"));
1868
+ if (pkg.main) {
1869
+ const mainPath = path9.join(rootDir, pkg.main);
1870
+ const relative5 = path9.relative(rootDir, mainPath).replace(/\\/g, "/");
1871
+ if (!entries.includes(relative5)) entries.push(relative5);
1872
+ }
1873
+ } catch {
1874
+ }
1875
+ }
1876
+ return entries;
1877
+ }
1878
+ function printDepGraph(graph) {
1879
+ const lines = [chalk11.bold("\n Dependency Analysis")];
1880
+ lines.push(`
1881
+ ${chalk11.dim("Source files:")} ${graph.nodes.size}`);
1882
+ lines.push(` ${chalk11.dim("External deps:")} ${graph.externalDeps.length}`);
1883
+ if (graph.entryPoints.length > 0) {
1884
+ lines.push(`
1885
+ ${chalk11.bold("Entry Points:")}`);
1886
+ for (const ep of graph.entryPoints) {
1887
+ lines.push(` ${chalk11.green("\u2192")} ${ep}`);
1888
+ }
1889
+ }
1890
+ if (graph.externalDeps.length > 0) {
1891
+ lines.push(`
1892
+ ${chalk11.bold("External Dependencies:")}`);
1893
+ const unique = [...new Set(graph.externalDeps)].sort();
1894
+ for (const dep of unique.slice(0, 20)) {
1895
+ lines.push(` ${chalk11.cyan("\u25A0")} ${dep}`);
1896
+ }
1897
+ if (unique.length > 20) {
1898
+ lines.push(` ${chalk11.dim(` ... and ${unique.length - 20} more`)}`);
1899
+ }
1900
+ }
1901
+ const mostImported = [...graph.nodes.entries()].filter(([, n]) => n.importedBy.length > 0).sort(([, a], [, b]) => b.importedBy.length - a.importedBy.length).slice(0, 10);
1902
+ if (mostImported.length > 0) {
1903
+ lines.push(`
1904
+ ${chalk11.bold("Most Imported Modules:")}`);
1905
+ for (const [name, node] of mostImported) {
1906
+ lines.push(` ${chalk11.yellow("\u2605")} ${name} ${chalk11.dim(`(imported ${node.importedBy.length}x)`)}`);
1907
+ }
1908
+ }
1909
+ lines.push("");
1910
+ return lines.join("\n");
1911
+ }
1912
+ function findCircularDeps(graph) {
1913
+ const visited = /* @__PURE__ */ new Set();
1914
+ const recursionStack = /* @__PURE__ */ new Set();
1915
+ const circles = [];
1916
+ function dfs(node, path14) {
1917
+ visited.add(node);
1918
+ recursionStack.add(node);
1919
+ const nodeData = graph.nodes.get(node);
1920
+ if (!nodeData) {
1921
+ recursionStack.delete(node);
1922
+ return;
1923
+ }
1924
+ for (const imp of nodeData.imports) {
1925
+ const target = resolveImportInGraph(imp, node, graph);
1926
+ if (target && graph.nodes.has(target)) {
1927
+ if (!visited.has(target)) {
1928
+ dfs(target, [...path14, target]);
1929
+ } else if (recursionStack.has(target)) {
1930
+ const cycle = [...path14.slice(path14.indexOf(target)), target];
1931
+ circles.push(cycle);
1932
+ }
1933
+ }
1934
+ }
1935
+ recursionStack.delete(node);
1936
+ }
1937
+ for (const [name] of graph.nodes) {
1938
+ if (!visited.has(name)) {
1939
+ dfs(name, [name]);
1940
+ }
1941
+ }
1942
+ return circles;
1943
+ }
1944
+ function resolveImportInGraph(imp, from, _graph) {
1945
+ if (!imp.startsWith(".")) return null;
1946
+ const dir = path9.dirname(from);
1947
+ const resolved = path9.normalize(path9.join(dir, imp)).replace(/\\/g, "/");
1948
+ return resolved;
1949
+ }
1950
+
1951
+ // src/repo/summary.ts
1952
+ import * as fs11 from "fs";
1953
+ import * as path10 from "path";
1954
+ import chalk12 from "chalk";
1955
+ function countDirStats(dir) {
1956
+ let files = 0;
1957
+ let dirs = 0;
1958
+ let totalLines = 0;
1959
+ const languages = /* @__PURE__ */ new Map();
1960
+ const largest = [];
1961
+ const skipDirs = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "__pycache__", "target", "vendor", "coverage"]);
1962
+ function walk(dir2) {
1963
+ try {
1964
+ const entries = fs11.readdirSync(dir2, { withFileTypes: true });
1965
+ for (const entry of entries) {
1966
+ if (entry.name.startsWith(".") || skipDirs.has(entry.name)) continue;
1967
+ const fullPath = path10.join(dir2, entry.name);
1968
+ if (entry.isDirectory()) {
1969
+ dirs++;
1970
+ walk(fullPath);
1971
+ } else {
1972
+ files++;
1973
+ const ext = path10.extname(entry.name).toLowerCase() || entry.name;
1974
+ const stats = fs11.statSync(fullPath);
1975
+ let lineCount = 0;
1976
+ try {
1977
+ const content = fs11.readFileSync(fullPath, "utf-8");
1978
+ lineCount = content.split("\n").length;
1979
+ totalLines += lineCount;
1980
+ } catch {
1981
+ }
1982
+ const existing = languages.get(ext) || { files: 0, lines: 0 };
1983
+ existing.files++;
1984
+ existing.lines += lineCount;
1985
+ languages.set(ext, existing);
1986
+ largest.push({ path: fullPath, size: stats.size, lines: lineCount });
1987
+ }
1988
+ }
1989
+ } catch {
1990
+ }
1991
+ }
1992
+ walk(dir);
1993
+ largest.sort((a, b) => b.size - a.size);
1994
+ return { files, dirs, lines: totalLines, languages, largest: largest.slice(0, 10) };
1995
+ }
1996
+ function buildDirectoryTree(rootDir, maxDepth = 4) {
1997
+ const skipDirs = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "__pycache__", "target", "vendor", "coverage"]);
1998
+ const result = [];
1999
+ function walk(dir, prefix, depth) {
2000
+ if (depth > maxDepth) return;
2001
+ try {
2002
+ const entries = fs11.readdirSync(dir, { withFileTypes: true });
2003
+ const filtered = entries.filter((e) => !e.name.startsWith(".") && !skipDirs.has(e.name));
2004
+ const sorted = filtered.sort((a, b) => {
2005
+ if (a.isDirectory() && !b.isDirectory()) return -1;
2006
+ if (!a.isDirectory() && b.isDirectory()) return 1;
2007
+ return a.name.localeCompare(b.name);
2008
+ });
2009
+ for (let i = 0; i < sorted.length; i++) {
2010
+ const entry = sorted[i];
2011
+ const isLast = i === sorted.length - 1;
2012
+ const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
2013
+ const nextPrefix = isLast ? " " : "\u2502 ";
2014
+ if (entry.isDirectory()) {
2015
+ result.push(`${prefix}${connector}${chalk12.cyan(entry.name)}/`);
2016
+ walk(path10.join(dir, entry.name), prefix + nextPrefix, depth + 1);
2017
+ } else {
2018
+ const stats = fs11.statSync(path10.join(dir, entry.name));
2019
+ const size = stats.size > 1024 ? `${(stats.size / 1024).toFixed(1)} KB` : `${stats.size} B`;
2020
+ result.push(`${prefix}${connector}${entry.name} ${chalk12.dim(size)}`);
2021
+ }
2022
+ }
2023
+ } catch {
2024
+ }
2025
+ }
2026
+ result.push(chalk12.bold(`${path10.basename(rootDir)}/`));
2027
+ walk(rootDir, "", 1);
2028
+ return result.join("\n");
2029
+ }
2030
+ function languageLabel(ext) {
2031
+ const map = {
2032
+ ".ts": "TypeScript",
2033
+ ".tsx": "TypeScript React",
2034
+ ".js": "JavaScript",
2035
+ ".jsx": "React",
2036
+ ".py": "Python",
2037
+ ".rs": "Rust",
2038
+ ".go": "Go",
2039
+ ".rb": "Ruby",
2040
+ ".java": "Java",
2041
+ ".kt": "Kotlin",
2042
+ ".swift": "Swift",
2043
+ ".c": "C",
2044
+ ".cpp": "C++",
2045
+ ".h": "C/C++ Header",
2046
+ ".cs": "C#",
2047
+ ".php": "PHP",
2048
+ ".vue": "Vue",
2049
+ ".svelte": "Svelte",
2050
+ ".md": "Markdown",
2051
+ ".json": "JSON",
2052
+ ".yaml": "YAML",
2053
+ ".yml": "YAML",
2054
+ ".toml": "TOML",
2055
+ ".html": "HTML",
2056
+ ".css": "CSS",
2057
+ ".scss": "SCSS"
2058
+ };
2059
+ return map[ext] || ext || "(unknown)";
2060
+ }
2061
+ function generateSummary(rootDir) {
2062
+ const projectInfo = detectProject(rootDir);
2063
+ const depGraph = analyzeDependencies(rootDir);
2064
+ const stats = countDirStats(rootDir);
2065
+ const directoryTree = buildDirectoryTree(rootDir);
2066
+ return {
2067
+ projectInfo,
2068
+ depGraph,
2069
+ totalFiles: stats.files,
2070
+ totalDirs: stats.dirs,
2071
+ totalLines: stats.lines,
2072
+ languages: stats.languages,
2073
+ directoryTree,
2074
+ largestFiles: stats.largest,
2075
+ structure: directoryTree
2076
+ };
2077
+ }
2078
+ function printSummary(summary) {
2079
+ const lines = [
2080
+ chalk12.bold("\n \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"),
2081
+ chalk12.bold(" \u2551 Repository Architecture Summary \u2551"),
2082
+ chalk12.bold(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"),
2083
+ ""
2084
+ ];
2085
+ if (summary.projectInfo.primary) {
2086
+ const p = summary.projectInfo.primary;
2087
+ lines.push(` ${p.icon} ${chalk12.bold(p.name)} ${chalk12.dim(`${p.confidence}% confidence`)}`);
2088
+ lines.push(` ${chalk12.dim("Language:")} ${p.language}`);
2089
+ }
2090
+ lines.push("");
2091
+ lines.push(` ${chalk12.bold("Stats")}`);
2092
+ lines.push(` ${chalk12.dim("Files:")} ${summary.totalFiles}`);
2093
+ lines.push(` ${chalk12.dim("Directories:")} ${summary.totalDirs}`);
2094
+ lines.push(` ${chalk12.dim("Lines of code:")} ${summary.totalLines.toLocaleString()}`);
2095
+ if (summary.projectInfo.packageManager) {
2096
+ lines.push(` ${chalk12.dim("Package mgr:")} ${summary.projectInfo.packageManager}`);
2097
+ }
2098
+ if (summary.projectInfo.buildTool) {
2099
+ lines.push(` ${chalk12.dim("Build tool:")} ${summary.projectInfo.buildTool}`);
2100
+ }
2101
+ lines.push("");
2102
+ lines.push(` ${chalk12.bold("Languages")}`);
2103
+ const sortedLangs = [...summary.languages.entries()].sort(([, a], [, b]) => b.lines - a.lines);
2104
+ for (const [ext, info] of sortedLangs.slice(0, 10)) {
2105
+ const label = languageLabel(ext);
2106
+ const pct = summary.totalLines > 0 ? (info.lines / summary.totalLines * 100).toFixed(1) : "0";
2107
+ const bar = chalk12.cyan("\u2588".repeat(Math.round(parseFloat(pct) / 5))) + chalk12.dim("\u2591".repeat(20 - Math.round(parseFloat(pct) / 5)));
2108
+ lines.push(` ${chalk12.cyan(label.padEnd(20))} ${bar} ${chalk12.bold(pct)}% ${chalk12.dim(`(${info.files} files, ${info.lines.toLocaleString()} lines)`)}`);
2109
+ }
2110
+ if (summary.largestFiles.length > 0) {
2111
+ lines.push("");
2112
+ lines.push(` ${chalk12.bold("Largest Files")}`);
2113
+ for (const f of summary.largestFiles.slice(0, 5)) {
2114
+ const relativePath = f.path;
2115
+ const size = f.size > 1024 ? `${(f.size / 1024).toFixed(1)} KB` : `${f.size} B`;
2116
+ lines.push(` ${chalk12.dim(relativePath)} ${chalk12.yellow(size)} ${chalk12.dim(`(${f.lines} lines)`)}`);
2117
+ }
2118
+ }
2119
+ if (summary.depGraph.entryPoints.length > 0) {
2120
+ lines.push("");
2121
+ lines.push(` ${chalk12.bold("Entry Points")}`);
2122
+ for (const ep of summary.depGraph.entryPoints) {
2123
+ lines.push(` ${chalk12.green("\u2192")} ${ep}`);
2124
+ }
2125
+ }
2126
+ lines.push("");
2127
+ lines.push(` ${chalk12.bold("Directory Structure")}`);
2128
+ lines.push(summary.directoryTree);
2129
+ lines.push("");
2130
+ return lines.join("\n");
2131
+ }
2132
+
2133
+ // src/memory/memory.ts
2134
+ import * as fs12 from "fs";
2135
+ import * as path11 from "path";
2136
+ function getMemoryDir(rootDir) {
2137
+ const base = rootDir || process.cwd();
2138
+ return path11.join(base, ".lovecode", "memory");
2139
+ }
2140
+ function getMemoryFilePath(key, rootDir) {
2141
+ return path11.join(getMemoryDir(rootDir), `${key}.json`);
2142
+ }
2143
+ function ensureMemoryDir(rootDir) {
2144
+ const dir = getMemoryDir(rootDir);
2145
+ if (!fs12.existsSync(dir)) {
2146
+ fs12.mkdirSync(dir, { recursive: true });
2147
+ }
2148
+ }
2149
+ function readMemory(key, fallback, rootDir) {
2150
+ const filePath = getMemoryFilePath(key, rootDir);
2151
+ if (!fs12.existsSync(filePath)) return fallback;
2152
+ try {
2153
+ const raw = fs12.readFileSync(filePath, "utf-8");
2154
+ return JSON.parse(raw);
2155
+ } catch {
2156
+ return fallback;
2157
+ }
2158
+ }
2159
+ function writeMemory(key, data, rootDir) {
2160
+ ensureMemoryDir(rootDir);
2161
+ const filePath = getMemoryFilePath(key, rootDir);
2162
+ fs12.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
2163
+ }
2164
+ var DEFAULT_PREFERENCES = {
2165
+ indentStyle: "spaces",
2166
+ indentSize: 2,
2167
+ quoteStyle: "single",
2168
+ semiColons: true,
2169
+ trailingComma: true,
2170
+ preferredTestRunner: "vitest",
2171
+ preferredPackageManager: "npm",
2172
+ preferredLinter: "eslint",
2173
+ custom: {}
2174
+ };
2175
+ var DEFAULT_REPO = {
2176
+ lastScan: 0,
2177
+ totalFiles: 0,
2178
+ totalDirs: 0,
2179
+ primaryLanguage: "",
2180
+ frameworks: [],
2181
+ entryPoints: [],
2182
+ keyDirectories: [],
2183
+ buildCommands: [],
2184
+ testCommands: [],
2185
+ notes: []
2186
+ };
2187
+ var DEFAULT_WORKFLOWS = {
2188
+ workflows: []
2189
+ };
2190
+ function getPreferences(rootDir) {
2191
+ return readMemory("preferences", DEFAULT_PREFERENCES, rootDir);
2192
+ }
2193
+ function savePreferences(prefs, rootDir) {
2194
+ const current = getPreferences(rootDir);
2195
+ const updated = { ...current, ...prefs };
2196
+ writeMemory("preferences", updated, rootDir);
2197
+ return updated;
2198
+ }
2199
+ function getRepoMemory(rootDir) {
2200
+ return readMemory("repo", DEFAULT_REPO, rootDir);
2201
+ }
2202
+ function getWorkflows(rootDir) {
2203
+ return readMemory("workflows", DEFAULT_WORKFLOWS, rootDir);
2204
+ }
2205
+ function saveWorkflow(workflow, rootDir) {
2206
+ const current = getWorkflows(rootDir);
2207
+ const existingIdx = current.workflows.findIndex((w) => w.name === workflow.name);
2208
+ if (existingIdx >= 0) {
2209
+ current.workflows[existingIdx] = workflow;
2210
+ } else {
2211
+ current.workflows.push(workflow);
2212
+ }
2213
+ writeMemory("workflows", current, rootDir);
2214
+ return current;
2215
+ }
2216
+ function deleteWorkflow(name, rootDir) {
2217
+ const current = getWorkflows(rootDir);
2218
+ const before = current.workflows.length;
2219
+ current.workflows = current.workflows.filter((w) => w.name !== name);
2220
+ if (current.workflows.length < before) {
2221
+ writeMemory("workflows", current, rootDir);
2222
+ return true;
2223
+ }
2224
+ return false;
2225
+ }
2226
+ function addRepoNote(note, rootDir) {
2227
+ const current = getRepoMemory(rootDir);
2228
+ current.notes.push(note);
2229
+ if (current.notes.length > 50) current.notes = current.notes.slice(-50);
2230
+ writeMemory("repo", current, rootDir);
2231
+ return current;
2232
+ }
2233
+ function clearAllMemory(rootDir) {
2234
+ const dir = getMemoryDir(rootDir);
2235
+ if (fs12.existsSync(dir)) {
2236
+ for (const file of fs12.readdirSync(dir)) {
2237
+ if (file.endsWith(".json")) {
2238
+ fs12.unlinkSync(path11.join(dir, file));
2239
+ }
2240
+ }
2241
+ }
2242
+ }
2243
+ function formatPreferences(prefs) {
2244
+ const lines = ["Coding Preferences:"];
2245
+ lines.push(` Indent: ${prefs.indentStyle} (${prefs.indentSize})`);
2246
+ lines.push(` Quotes: ${prefs.quoteStyle}`);
2247
+ lines.push(` Semicolons: ${prefs.semiColons ? "yes" : "no"}`);
2248
+ lines.push(` Trailing commas: ${prefs.trailingComma ? "yes" : "no"}`);
2249
+ lines.push(` Test runner: ${prefs.preferredTestRunner}`);
2250
+ lines.push(` Package manager: ${prefs.preferredPackageManager}`);
2251
+ lines.push(` Linter: ${prefs.preferredLinter}`);
2252
+ if (Object.keys(prefs.custom).length > 0) {
2253
+ lines.push(" Custom:");
2254
+ for (const [k, v] of Object.entries(prefs.custom)) {
2255
+ lines.push(` ${k}: ${v}`);
2256
+ }
2257
+ }
2258
+ return lines.join("\n");
2259
+ }
2260
+ function formatRepoMemory(mem) {
2261
+ const lines = ["Repo Memory:"];
2262
+ lines.push(` Last scanned: ${mem.lastScan ? new Date(mem.lastScan).toISOString() : "never"}`);
2263
+ lines.push(` Files: ${mem.totalFiles} Dirs: ${mem.totalDirs}`);
2264
+ if (mem.primaryLanguage) lines.push(` Primary language: ${mem.primaryLanguage}`);
2265
+ if (mem.frameworks.length) lines.push(` Frameworks: ${mem.frameworks.join(", ")}`);
2266
+ if (mem.entryPoints.length) lines.push(` Entry points: ${mem.entryPoints.join(", ")}`);
2267
+ if (mem.keyDirectories.length) lines.push(` Key dirs: ${mem.keyDirectories.join(", ")}`);
2268
+ if (mem.buildCommands.length) lines.push(` Build: ${mem.buildCommands.join(", ")}`);
2269
+ if (mem.testCommands.length) lines.push(` Test: ${mem.testCommands.join(", ")}`);
2270
+ if (mem.notes.length) lines.push(` Notes (${mem.notes.length}):`);
2271
+ for (const note of mem.notes.slice(-5)) {
2272
+ lines.push(` \u2022 ${note}`);
2273
+ }
2274
+ return lines.join("\n");
2275
+ }
2276
+
2277
+ // src/memory/vector.ts
2278
+ import * as fs13 from "fs";
2279
+ import * as path12 from "path";
2280
+
2281
+ // src/ai/embeddings.ts
2282
+ import { execSync as execSync2 } from "child_process";
2283
+ async function getEmbedding(text) {
2284
+ const ollamaBaseUrl = process.env.OLLAMA_URL || "http://localhost:11434";
2285
+ try {
2286
+ const response = await fetch(`${ollamaBaseUrl}/api/embeddings`, {
2287
+ method: "POST",
2288
+ headers: { "Content-Type": "application/json" },
2289
+ body: JSON.stringify({
2290
+ model: "nomic-embed-text",
2291
+ prompt: text
2292
+ })
2293
+ });
2294
+ if (response.ok) {
2295
+ const data = await response.json();
2296
+ if (data.embedding) {
2297
+ return { vector: data.embedding, model: "nomic-embed-text", provider: "ollama" };
2298
+ }
2299
+ }
2300
+ } catch {
2301
+ }
2302
+ try {
2303
+ const response = await fetch(`${ollamaBaseUrl}/api/embeddings`, {
2304
+ method: "POST",
2305
+ headers: { "Content-Type": "application/json" },
2306
+ body: JSON.stringify({
2307
+ model: "all-minilm",
2308
+ prompt: text
2309
+ })
2310
+ });
2311
+ if (response.ok) {
2312
+ const data = await response.json();
2313
+ if (data.embedding) {
2314
+ return { vector: data.embedding, model: "all-minilm", provider: "ollama" };
2315
+ }
2316
+ }
2317
+ } catch {
2318
+ }
2319
+ return getSimpleEmbedding(text);
2320
+ }
2321
+ function getSimpleEmbedding(text) {
2322
+ const words = text.toLowerCase().split(/\s+/).filter(Boolean);
2323
+ const freq = /* @__PURE__ */ new Map();
2324
+ for (const word of words) {
2325
+ freq.set(word, (freq.get(word) || 0) + 1);
2326
+ }
2327
+ const vector = new Array(128).fill(0);
2328
+ const chars = text.split("");
2329
+ for (let i = 0; i < Math.min(chars.length, 128); i++) {
2330
+ vector[i] = chars[i].charCodeAt(0) / 255;
2331
+ }
2332
+ return { vector, model: "simple", provider: "local" };
2333
+ }
2334
+ function cosineSimilarity(a, b) {
2335
+ let dot = 0, magA = 0, magB = 0;
2336
+ for (let i = 0; i < Math.min(a.length, b.length); i++) {
2337
+ dot += a[i] * b[i];
2338
+ magA += a[i] * a[i];
2339
+ magB += b[i] * b[i];
2340
+ }
2341
+ const denom = Math.sqrt(magA) * Math.sqrt(magB);
2342
+ return denom === 0 ? 0 : dot / denom;
2343
+ }
2344
+
2345
+ // src/memory/vector.ts
2346
+ function getVectorDir(rootDir) {
2347
+ const base = rootDir || process.cwd();
2348
+ return path12.join(base, ".lovecode", "memory");
2349
+ }
2350
+ function getVectorFilePath(rootDir) {
2351
+ return path12.join(getVectorDir(rootDir), "vectors.json");
2352
+ }
2353
+ function ensureDir2(rootDir) {
2354
+ const dir = getVectorDir(rootDir);
2355
+ if (!fs13.existsSync(dir)) {
2356
+ fs13.mkdirSync(dir, { recursive: true });
2357
+ }
2358
+ }
2359
+ function loadVectors(rootDir) {
2360
+ const filePath = getVectorFilePath(rootDir);
2361
+ if (!fs13.existsSync(filePath)) return [];
2362
+ try {
2363
+ const raw = fs13.readFileSync(filePath, "utf-8");
2364
+ return JSON.parse(raw);
2365
+ } catch {
2366
+ return [];
2367
+ }
2368
+ }
2369
+ function saveVectors(entries, rootDir) {
2370
+ ensureDir2(rootDir);
2371
+ const filePath = getVectorFilePath(rootDir);
2372
+ fs13.writeFileSync(filePath, JSON.stringify(entries, null, 2), "utf-8");
2373
+ }
2374
+ async function storeVector(text, metadata, rootDir) {
2375
+ const result = await getEmbedding(text);
2376
+ const entry = {
2377
+ id: Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
2378
+ text,
2379
+ vector: result.vector,
2380
+ metadata: metadata || {},
2381
+ timestamp: Date.now()
2382
+ };
2383
+ const entries = loadVectors(rootDir);
2384
+ entries.push(entry);
2385
+ saveVectors(entries, rootDir);
2386
+ return entry;
2387
+ }
2388
+ async function searchVectors(query, options, rootDir) {
2389
+ const entries = loadVectors(rootDir);
2390
+ if (entries.length === 0) return [];
2391
+ const queryResult = await getEmbedding(query);
2392
+ const queryVec = queryResult.vector;
2393
+ const minScore = options?.minScore ?? 0.1;
2394
+ const topK = options?.topK ?? 5;
2395
+ let filtered = entries;
2396
+ if (options?.filter) {
2397
+ const filterKeys = Object.keys(options.filter);
2398
+ if (filterKeys.length > 0) {
2399
+ filtered = entries.filter(
2400
+ (e) => filterKeys.every((k) => e.metadata[k] === options.filter[k])
2401
+ );
2402
+ }
2403
+ }
2404
+ const scored = filtered.map((entry) => ({
2405
+ entry,
2406
+ score: cosineSimilarity(queryVec, entry.vector)
2407
+ }));
2408
+ scored.sort((a, b) => b.score - a.score);
2409
+ return scored.filter((s) => s.score >= minScore).slice(0, topK);
2410
+ }
2411
+ function clearVectors(rootDir) {
2412
+ const filePath = getVectorFilePath(rootDir);
2413
+ if (fs13.existsSync(filePath)) {
2414
+ fs13.unlinkSync(filePath);
2415
+ }
2416
+ }
2417
+ function getVectorCount(rootDir) {
2418
+ return loadVectors(rootDir).length;
2419
+ }
2420
+ function formatVectorResults(results) {
2421
+ if (results.length === 0) return "No matching memories found.";
2422
+ const lines = ["Vector Memory Search Results:"];
2423
+ for (const r of results) {
2424
+ const score = (r.score * 100).toFixed(1);
2425
+ const tags = Object.entries(r.entry.metadata).map(([k, v]) => `${k}=${v}`).join(", ");
2426
+ const meta = tags ? chalkDim(tags) : "";
2427
+ lines.push(` [${score}%] ${r.entry.text.slice(0, 120)}${meta ? ` (${meta})` : ""}`);
2428
+ }
2429
+ return lines.join("\n");
2430
+ }
2431
+ function chalkDim(s) {
2432
+ return `\x1B[2m${s}\x1B[22m`;
2433
+ }
2434
+
2435
+ // src/plugin/index.ts
2436
+ loadBuiltinPlugins();
2437
+
2438
+ // src/core/tools.ts
2439
+ var fileTools = [
2440
+ {
2441
+ name: "read_file",
2442
+ description: "Read the contents of a file",
2443
+ usage: "path=<file path>",
2444
+ execute(workingDir, args) {
2445
+ try {
2446
+ const filePath = path13.resolve(workingDir, args.path || ".");
2447
+ if (!fs14.existsSync(filePath)) {
2448
+ return { success: false, output: "", error: `File not found: ${args.path}` };
2449
+ }
2450
+ const content = fs14.readFileSync(filePath, "utf-8");
2451
+ const lines = content.split("\n");
2452
+ const numbered = lines.map((l, i) => `${String(i + 1).padStart(4, " ")} | ${l}`).join("\n");
2453
+ return { success: true, output: numbered };
2454
+ } catch (err) {
2455
+ return { success: false, output: "", error: String(err) };
2456
+ }
2457
+ }
2458
+ },
2459
+ {
2460
+ name: "write_file",
2461
+ description: "Write content to a file (creates parent dirs)",
2462
+ usage: "path=<file path> content=<file content>",
2463
+ execute(workingDir, args) {
2464
+ try {
2465
+ const filePath = path13.resolve(workingDir, args.path || "");
2466
+ fs14.mkdirSync(path13.dirname(filePath), { recursive: true });
2467
+ fs14.writeFileSync(filePath, args.content || "", "utf-8");
2468
+ return { success: true, output: `Wrote ${filePath}` };
2469
+ } catch (err) {
2470
+ return { success: false, output: "", error: String(err) };
2471
+ }
2472
+ }
2473
+ },
2474
+ {
2475
+ name: "edit_file",
2476
+ description: "Edit a file by replacing text (oldString -> newString)",
2477
+ usage: "path=<file path> oldString=<text to find> newString=<replacement>",
2478
+ execute(workingDir, args) {
2479
+ try {
2480
+ const filePath = path13.resolve(workingDir, args.path || "");
2481
+ if (!fs14.existsSync(filePath)) {
2482
+ return { success: false, output: "", error: `File not found: ${args.path}` };
2483
+ }
2484
+ const content = fs14.readFileSync(filePath, "utf-8");
2485
+ const { oldString, newString } = args;
2486
+ if (!oldString) {
2487
+ return { success: false, output: "", error: "oldString is required" };
2488
+ }
2489
+ const count = (content.match(new RegExp(oldString.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g")) || []).length;
2490
+ if (count === 0) {
2491
+ return { success: false, output: "", error: `oldString not found in ${args.path}` };
2492
+ }
2493
+ const updated = content.replaceAll(oldString, newString || "");
2494
+ fs14.writeFileSync(filePath, updated, "utf-8");
2495
+ return { success: true, output: `Edited ${filePath} (${count} replacement${count > 1 ? "s" : ""})` };
2496
+ } catch (err) {
2497
+ return { success: false, output: "", error: String(err) };
2498
+ }
2499
+ }
2500
+ },
2501
+ {
2502
+ name: "create_file",
2503
+ description: "Create a new empty file",
2504
+ usage: "path=<file path>",
2505
+ execute(workingDir, args) {
2506
+ try {
2507
+ const filePath = path13.resolve(workingDir, args.path || "");
2508
+ if (fs14.existsSync(filePath)) {
2509
+ return { success: false, output: "", error: `File already exists: ${args.path}` };
2510
+ }
2511
+ fs14.mkdirSync(path13.dirname(filePath), { recursive: true });
2512
+ fs14.writeFileSync(filePath, "", "utf-8");
2513
+ return { success: true, output: `Created ${filePath}` };
2514
+ } catch (err) {
2515
+ return { success: false, output: "", error: String(err) };
2516
+ }
2517
+ }
2518
+ },
2519
+ {
2520
+ name: "delete_file",
2521
+ description: "Delete a file",
2522
+ usage: "path=<file path>",
2523
+ execute(workingDir, args) {
2524
+ try {
2525
+ const filePath = path13.resolve(workingDir, args.path || "");
2526
+ if (!fs14.existsSync(filePath)) {
2527
+ return { success: false, output: "", error: `File not found: ${args.path}` };
2528
+ }
2529
+ fs14.unlinkSync(filePath);
2530
+ return { success: true, output: `Deleted ${filePath}` };
2531
+ } catch (err) {
2532
+ return { success: false, output: "", error: String(err) };
2533
+ }
2534
+ }
2535
+ },
2536
+ {
2537
+ name: "append_file",
2538
+ description: "Append content to the end of a file",
2539
+ usage: "path=<file path> content=<content to append>",
2540
+ execute(workingDir, args) {
2541
+ try {
2542
+ const filePath = path13.resolve(workingDir, args.path || "");
2543
+ fs14.mkdirSync(path13.dirname(filePath), { recursive: true });
2544
+ fs14.appendFileSync(filePath, (args.content || "") + "\n", "utf-8");
2545
+ return { success: true, output: `Appended to ${filePath}` };
2546
+ } catch (err) {
2547
+ return { success: false, output: "", error: String(err) };
2548
+ }
2549
+ }
2550
+ }
2551
+ ];
2552
+ var commandTools = [];
2553
+ var searchTools = [
2554
+ {
2555
+ name: "grep_search",
2556
+ description: "Search file contents with a regular expression",
2557
+ usage: "pattern=<regex> [include=<glob pattern>]",
2558
+ execute(workingDir, args) {
2559
+ try {
2560
+ const pattern = args.pattern || "";
2561
+ const include = args.include || "**/*";
2562
+ const cmd = `rg -n '${pattern.replace(/'/g, "'\\''")}' --include '${include}' 2>/dev/null || echo "(no matches)"`;
2563
+ const output = execSync3(cmd, { cwd: workingDir, encoding: "utf-8", maxBuffer: 1024 * 1024 });
2564
+ return { success: true, output: output.trim() };
2565
+ } catch {
2566
+ return { success: true, output: "(no matches)" };
2567
+ }
2568
+ }
2569
+ },
2570
+ {
2571
+ name: "glob_search",
2572
+ description: "Find files matching a glob pattern",
2573
+ usage: "pattern=<glob pattern>",
2574
+ execute(workingDir, args) {
2575
+ try {
2576
+ const pattern = args.pattern || "*";
2577
+ const cmd = `ls -1 ${pattern.replace(/ /g, "\\ ")} 2>/dev/null || echo "(no files found)"`;
2578
+ const output = execSync3(cmd, { cwd: workingDir, encoding: "utf-8", maxBuffer: 1024 * 1024 });
2579
+ return { success: true, output: output.trim() };
2580
+ } catch {
2581
+ return { success: true, output: "(no files found)" };
2582
+ }
2583
+ }
2584
+ },
2585
+ {
2586
+ name: "list_dir",
2587
+ description: "List files and directories",
2588
+ usage: "path=<directory path>",
2589
+ execute(workingDir, args) {
2590
+ try {
2591
+ const dirPath = path13.resolve(workingDir, args.path || ".");
2592
+ if (!fs14.existsSync(dirPath)) {
2593
+ return { success: false, output: "", error: `Directory not found: ${args.path}` };
2594
+ }
2595
+ const entries = fs14.readdirSync(dirPath, { withFileTypes: true });
2596
+ const output = entries.map((e) => e.isDirectory() ? chalk13.cyan(`${e.name}/`) : e.name).join("\n");
2597
+ return { success: true, output };
2598
+ } catch (err) {
2599
+ return { success: false, output: "", error: String(err) };
2600
+ }
2601
+ }
2602
+ },
2603
+ {
2604
+ name: "get_cwd",
2605
+ description: "Get the current working directory",
2606
+ usage: "(no arguments)",
2607
+ execute(_workingDir, _args) {
2608
+ return { success: true, output: process.cwd() };
2609
+ }
2610
+ }
2611
+ ];
2612
+ var fsEngineTools = [
2613
+ {
2614
+ name: "scan_files",
2615
+ description: "Recursively scan files in the project. Returns categorized listing.",
2616
+ usage: "[path=<dir>] [maxDepth=<number>] [category=<source|config|doc|script|data>]",
2617
+ execute(workingDir, args) {
2618
+ try {
2619
+ const rootDir = path13.resolve(workingDir, args.path || ".");
2620
+ const maxDepth = parseInt(args.maxDepth || "10", 10);
2621
+ const category = args.category;
2622
+ const files = scanDirectory({
2623
+ rootDir,
2624
+ maxDepth,
2625
+ maxFiles: 1e4
2626
+ });
2627
+ if (category) {
2628
+ const filtered = files.filter((f) => f.category === category);
2629
+ const summary = printScanSummary(filtered);
2630
+ const fileList = filtered.map((f) => ` ${f.relativePath}`).join("\n");
2631
+ return { success: true, output: `${summary}
2632
+
2633
+ ${fileList}` };
2634
+ }
2635
+ return { success: true, output: printScanSummary(files) };
2636
+ } catch (err) {
2637
+ return { success: false, output: "", error: String(err) };
2638
+ }
2639
+ }
2640
+ },
2641
+ {
2642
+ name: "find_file",
2643
+ description: "Search for files by name/pattern",
2644
+ usage: "query=<name or pattern> [content=<true|false>]",
2645
+ execute(workingDir, args) {
2646
+ try {
2647
+ const query = args.query || "";
2648
+ const includeContent = args.content === "true";
2649
+ const results = findFiles({ rootDir: workingDir, query, maxResults: 20, includeContent });
2650
+ if (results.length === 0) return { success: true, output: "(no files found)" };
2651
+ const lines = results.map((r, i) => {
2652
+ let line = ` ${i + 1}. ${r.relativePath} ${chalk13.dim(`(score: ${r.score})`)}`;
2653
+ if (r.matches && r.matches.length > 0) {
2654
+ const top = r.matches.slice(0, 3);
2655
+ line += top.map((m) => `
2656
+ ${chalk13.dim(`L${m.line}:`)} ${m.content.slice(0, 80)}`).join("");
2657
+ }
2658
+ return line;
2659
+ });
2660
+ return { success: true, output: `Found ${results.length} files:
2661
+ ${lines.join("\n")}` };
2662
+ } catch (err) {
2663
+ return { success: false, output: "", error: String(err) };
2664
+ }
2665
+ }
2666
+ },
2667
+ {
2668
+ name: "semantic_search",
2669
+ description: "Search by file name and content (combined)",
2670
+ usage: "query=<search term>",
2671
+ execute(workingDir, args) {
2672
+ try {
2673
+ const query = args.query || "";
2674
+ const results = semanticSearch(query, workingDir);
2675
+ if (results.length === 0) return { success: true, output: "(no matches)" };
2676
+ const lines = results.map((r, i) => {
2677
+ let line = ` ${i + 1}. ${r.relativePath} ${chalk13.dim(`(score: ${r.score})`)}`;
2678
+ if (r.matches && r.matches.length > 0) {
2679
+ const top = r.matches.slice(0, 2);
2680
+ line += top.map((m) => `
2681
+ ${chalk13.dim(`L${m.line}:`)} ${m.content.slice(0, 80)}`).join("");
2682
+ }
2683
+ return line;
2684
+ });
2685
+ return { success: true, output: `Semantic search results:
2686
+ ${lines.join("\n")}` };
2687
+ } catch (err) {
2688
+ return { success: false, output: "", error: String(err) };
2689
+ }
2690
+ }
2691
+ },
2692
+ {
2693
+ name: "rename_file",
2694
+ description: "Rename or move a file",
2695
+ usage: "oldPath=<current path> newPath=<new path>",
2696
+ execute(workingDir, args) {
2697
+ const oldPath = path13.resolve(workingDir, args.oldPath || "");
2698
+ const newPath = path13.resolve(workingDir, args.newPath || "");
2699
+ const result = renameFile(oldPath, newPath);
2700
+ return { success: result.success, output: result.message, error: result.error };
2701
+ }
2702
+ },
2703
+ {
2704
+ name: "duplicate_file",
2705
+ description: "Copy/duplicate a file",
2706
+ usage: "source=<source path> dest=<destination path>",
2707
+ execute(workingDir, args) {
2708
+ const sourcePath = path13.resolve(workingDir, args.source || "");
2709
+ const destPath = path13.resolve(workingDir, args.dest || "");
2710
+ const result = duplicateFile(sourcePath, destPath);
2711
+ return { success: result.success, output: result.message, error: result.error };
2712
+ }
2713
+ },
2714
+ {
2715
+ name: "file_tree",
2716
+ description: "Show directory tree structure",
2717
+ usage: "[path=<dir>] [maxDepth=<number>]",
2718
+ execute(workingDir, args) {
2719
+ try {
2720
+ const dirPath = path13.resolve(workingDir, args.path || ".");
2721
+ const maxDepth = parseInt(args.maxDepth || "3", 10);
2722
+ const tree = getDirectoryTree(dirPath, "", maxDepth);
2723
+ return { success: true, output: tree };
2724
+ } catch (err) {
2725
+ return { success: false, output: "", error: String(err) };
2726
+ }
2727
+ }
2728
+ },
2729
+ {
2730
+ name: "rank_files",
2731
+ description: "Rank files by importance for context",
2732
+ usage: "[task=<task description>] [limit=<number>]",
2733
+ execute(workingDir, args) {
2734
+ try {
2735
+ const task = args.task || "";
2736
+ const limit = parseInt(args.limit || "10", 10);
2737
+ const files = scanDirectory({ rootDir: workingDir, maxDepth: 10, maxFiles: 5e3 });
2738
+ const ranked = rankFiles(files, task);
2739
+ const top = ranked.slice(0, limit);
2740
+ return { success: true, output: printRankedFiles(top) };
2741
+ } catch (err) {
2742
+ return { success: false, output: "", error: String(err) };
2743
+ }
2744
+ }
2745
+ }
2746
+ ];
2747
+ var editorTools = [
2748
+ {
2749
+ name: "inline_patch",
2750
+ description: "Apply a search/replace patch to a file with context matching",
2751
+ usage: "path=<file> search=<text to find> replace=<replacement>",
2752
+ execute(workingDir, args) {
2753
+ const filePath = path13.resolve(workingDir, args.path || "");
2754
+ const result = applyInlinePatch(filePath, args.search || "", args.replace || "");
2755
+ if (result.success) {
2756
+ saveUndoPoint(workingDir, filePath, "inline_patch");
2757
+ }
2758
+ return { success: result.success, output: result.output, error: result.error };
2759
+ }
2760
+ },
2761
+ {
2762
+ name: "syntax_check",
2763
+ description: "Check a file for basic syntax errors (brace balance)",
2764
+ usage: "path=<file path>",
2765
+ execute(workingDir, args) {
2766
+ try {
2767
+ const filePath = path13.resolve(workingDir, args.path || "");
2768
+ const content = fs14.readFileSync(filePath, "utf-8");
2769
+ const result = checkBraceBalance(content, filePath);
2770
+ if (result.valid) {
2771
+ return { success: true, output: "Syntax check passed: all braces balanced" };
2772
+ }
2773
+ const lines = result.errors.map(
2774
+ (e) => ` Line ${e.line}:${e.column} \u2014 ${e.message}`
2775
+ );
2776
+ return { success: false, output: `Syntax errors found:
2777
+ ${lines.join("\n")}` };
2778
+ } catch (err) {
2779
+ return { success: false, output: "", error: String(err) };
2780
+ }
2781
+ }
2782
+ },
2783
+ {
2784
+ name: "undo",
2785
+ description: "Undo the last file edit",
2786
+ usage: "[path=<file path>]",
2787
+ execute(workingDir, args) {
2788
+ const filePath = args.path ? path13.resolve(workingDir, args.path) : void 0;
2789
+ const result = filePath ? undoForFile(workingDir, filePath) : undoForFile(workingDir, "");
2790
+ if (result) {
2791
+ return { success: true, output: `Undone: ${result.label} on ${result.filePath}` };
2792
+ }
2793
+ return { success: false, output: "", error: "Nothing to undo" };
2794
+ }
2795
+ },
2796
+ {
2797
+ name: "undo_history",
2798
+ description: "Show undo history",
2799
+ usage: "(no arguments)",
2800
+ execute(workingDir, _args) {
2801
+ const history = getUndoHistory(workingDir);
2802
+ if (history.length === 0) return { success: true, output: "(no undo history)" };
2803
+ const lines = history.map(
2804
+ (e, i) => ` ${i + 1}. ${chalk13.dim(e.label)} \u2014 ${e.filePath} ${chalk13.dim(`(${new Date(e.timestamp).toLocaleString()})`)}`
2805
+ );
2806
+ return { success: true, output: `Undo History:
2807
+ ${lines.join("\n")}` };
2808
+ }
2809
+ },
2810
+ {
2811
+ name: "snapshot",
2812
+ description: "Create a snapshot of a file",
2813
+ usage: "path=<file path> [label=<optional label>]",
2814
+ execute(workingDir, args) {
2815
+ const filePath = path13.resolve(workingDir, args.path || "");
2816
+ const label = args.label || "manual";
2817
+ const snap = createSnapshot(workingDir, filePath, label);
2818
+ if (snap) {
2819
+ return { success: true, output: `Snapshot created: ${snap.id} (${snap.size} bytes)` };
2820
+ }
2821
+ return { success: false, output: "", error: "Failed to create snapshot" };
2822
+ }
2823
+ },
2824
+ {
2825
+ name: "snapshot_list",
2826
+ description: "List available snapshots",
2827
+ usage: "(no arguments)",
2828
+ execute(workingDir, _args) {
2829
+ const snaps = listSnapshots(workingDir);
2830
+ if (snaps.length === 0) return { success: true, output: "(no snapshots)" };
2831
+ const lines = snaps.map(
2832
+ (s, i) => ` ${i + 1}. ${chalk13.cyan(s.id)} ${chalk13.dim(s.relativePath)} \u2014 ${s.label}`
2833
+ );
2834
+ return { success: true, output: `Snapshots:
2835
+ ${lines.join("\n")}` };
2836
+ }
2837
+ },
2838
+ {
2839
+ name: "snapshot_restore",
2840
+ description: "Restore a file from a snapshot",
2841
+ usage: "id=<snapshot id>",
2842
+ execute(workingDir, args) {
2843
+ const id = args.id || "";
2844
+ const ok = restoreSnapshot(workingDir, id);
2845
+ if (ok) return { success: true, output: `Restored snapshot: ${id}` };
2846
+ return { success: false, output: "", error: `Snapshot not found: ${id}` };
2847
+ }
2848
+ },
2849
+ {
2850
+ name: "refactor",
2851
+ description: "Execute multi-file refactoring with syntax validation",
2852
+ usage: "edits=<JSON array of {filePath,search,replace,description}>",
2853
+ async execute(workingDir, args) {
2854
+ try {
2855
+ const edits = JSON.parse(args.edits || "[]");
2856
+ if (!Array.isArray(edits) || edits.length === 0) {
2857
+ return { success: false, output: "", error: "edits must be a non-empty JSON array" };
2858
+ }
2859
+ const plan = planRefactor(edits, workingDir, true);
2860
+ const result = await executeRefactor(plan);
2861
+ return { success: result.success, output: result.output };
2862
+ } catch (err) {
2863
+ return { success: false, output: "", error: String(err) };
2864
+ }
2865
+ }
2866
+ }
2867
+ ];
2868
+ var shellTools = [
2869
+ {
2870
+ name: "execute_command",
2871
+ description: "Run a shell command with sandbox, timeout, and output capture",
2872
+ usage: "command=<shell command> [timeout=<ms>]",
2873
+ execute(workingDir, args) {
2874
+ const command = args.command || "";
2875
+ const timeout = parseInt(args.timeout || "120000", 10);
2876
+ const policy = createDefaultPolicy();
2877
+ const verdict = evaluateCommand(command, policy);
2878
+ if (!verdict.allowed) {
2879
+ return {
2880
+ success: false,
2881
+ output: printSandboxVerdict(verdict),
2882
+ error: verdict.reason
2883
+ };
2884
+ }
2885
+ return execCommand(command, workingDir, timeout).then((result) => ({
2886
+ success: result.success,
2887
+ output: result.stdout + (result.stderr ? `
2888
+ ${chalk13.yellow(result.stderr)}` : ""),
2889
+ error: result.success ? void 0 : `Exit code: ${result.exitCode}${result.cancelled ? " (cancelled)" : ""}`
2890
+ }));
2891
+ }
2892
+ }
2893
+ ];
2894
+ var memoryTools = [
2895
+ {
2896
+ name: "store_preference",
2897
+ description: "Store a coding preference",
2898
+ usage: "key=<name> value=<value>",
2899
+ async execute(_workingDir, args) {
2900
+ const key = args.key;
2901
+ const value = args.value;
2902
+ if (!key || !value) return { success: false, output: "", error: "key and value required" };
2903
+ const boolKeys = ["semiColons", "trailingComma"];
2904
+ const numKeys = ["indentSize"];
2905
+ const prefs = {};
2906
+ if (boolKeys.includes(key)) prefs[key] = value === "true" || value === "yes";
2907
+ else if (numKeys.includes(key)) prefs[key] = parseInt(value, 10);
2908
+ else prefs[key] = value;
2909
+ savePreferences(prefs);
2910
+ return { success: true, output: `Stored preference ${key} = ${value}` };
2911
+ }
2912
+ },
2913
+ {
2914
+ name: "recall_preferences",
2915
+ description: "Retrieve stored coding preferences",
2916
+ usage: "",
2917
+ execute() {
2918
+ const prefs = getPreferences();
2919
+ const lines = [
2920
+ `Indent: ${prefs.indentStyle} (${prefs.indentSize})`,
2921
+ `Quotes: ${prefs.quoteStyle}`,
2922
+ `Semicolons: ${prefs.semiColons ? "yes" : "no"}`,
2923
+ `Trailing commas: ${prefs.trailingComma ? "yes" : "no"}`,
2924
+ `Test runner: ${prefs.preferredTestRunner}`,
2925
+ `Package manager: ${prefs.preferredPackageManager}`,
2926
+ `Linter: ${prefs.preferredLinter}`
2927
+ ];
2928
+ return { success: true, output: lines.join("\n") };
2929
+ }
2930
+ },
2931
+ {
2932
+ name: "repo_note",
2933
+ description: "Store a note about the repository structure or conventions",
2934
+ usage: "note=<text>",
2935
+ execute(_workingDir, args) {
2936
+ if (!args.note) return { success: false, output: "", error: "note required" };
2937
+ addRepoNote(args.note);
2938
+ return { success: true, output: `Note saved: ${args.note}` };
2939
+ }
2940
+ },
2941
+ {
2942
+ name: "recall_repo_memory",
2943
+ description: "Retrieve stored repo memory and notes",
2944
+ usage: "",
2945
+ execute() {
2946
+ const mem = getRepoMemory();
2947
+ const lines = [];
2948
+ if (mem.primaryLanguage) lines.push(`Language: ${mem.primaryLanguage}`);
2949
+ if (mem.frameworks.length) lines.push(`Frameworks: ${mem.frameworks.join(", ")}`);
2950
+ if (mem.entryPoints.length) lines.push(`Entry points: ${mem.entryPoints.join(", ")}`);
2951
+ if (mem.buildCommands.length) lines.push(`Build: ${mem.buildCommands.join(", ")}`);
2952
+ if (mem.testCommands.length) lines.push(`Test: ${mem.testCommands.join(", ")}`);
2953
+ if (mem.notes.length) lines.push(`Notes:
2954
+ ${mem.notes.join("\n ")}`);
2955
+ return { success: true, output: lines.join("\n") || "No repo memory stored." };
2956
+ }
2957
+ },
2958
+ {
2959
+ name: "save_workflow",
2960
+ description: "Save a reusable workflow for future tasks",
2961
+ usage: "name=<workflow name> steps=<comma separated steps>",
2962
+ execute(_workingDir, args) {
2963
+ if (!args.name || !args.steps) return { success: false, output: "", error: "name and steps required" };
2964
+ const steps = args.steps.split(",").map((s) => s.trim());
2965
+ saveWorkflow({ name: args.name, description: steps[0] || "", steps, tags: [], created: Date.now(), used: Date.now() });
2966
+ return { success: true, output: `Workflow "${args.name}" saved with ${steps.length} steps.` };
2967
+ }
2968
+ },
2969
+ {
2970
+ name: "list_workflows",
2971
+ description: "List saved workflows",
2972
+ usage: "",
2973
+ execute() {
2974
+ const wf = getWorkflows();
2975
+ if (wf.workflows.length === 0) return { success: true, output: "No saved workflows." };
2976
+ const lines = wf.workflows.map((w) => ` ${w.name} (${w.steps.length} steps): ${w.description}`);
2977
+ return { success: true, output: `Workflows:
2978
+ ${lines.join("\n")}` };
2979
+ }
2980
+ },
2981
+ {
2982
+ name: "vector_store",
2983
+ description: "Store text in vector memory for semantic recall",
2984
+ usage: "text=<content> [label=<category>]",
2985
+ async execute(_workingDir, args) {
2986
+ if (!args.text) return { success: false, output: "", error: "text required" };
2987
+ const metadata = {};
2988
+ if (args.label) metadata.label = args.label;
2989
+ await storeVector(args.text, metadata);
2990
+ return { success: true, output: "Stored in vector memory." };
2991
+ }
2992
+ },
2993
+ {
2994
+ name: "vector_search",
2995
+ description: "Search vector memory by semantic similarity",
2996
+ usage: "query=<search text> [topK=<number>]",
2997
+ async execute(_workingDir, args) {
2998
+ if (!args.query) return { success: false, output: "", error: "query required" };
2999
+ const topK = args.topK ? parseInt(args.topK, 10) : 5;
3000
+ const results = await searchVectors(args.query, { topK });
3001
+ return { success: true, output: formatVectorResults(results) };
3002
+ }
3003
+ }
3004
+ ];
3005
+ var gitTools = [
3006
+ {
3007
+ name: "git_status",
3008
+ description: "Show working tree status",
3009
+ usage: "",
3010
+ execute(_workingDir) {
3011
+ if (!isGitAvailable()) return { success: false, output: "", error: "Git not available" };
3012
+ const status = getStatus();
3013
+ return { success: true, output: formatStatus(status) };
3014
+ }
3015
+ },
3016
+ {
3017
+ name: "git_commit",
3018
+ description: "Stage all changes and commit with a message",
3019
+ usage: "message=<commit message>",
3020
+ execute(_workingDir, args) {
3021
+ if (!isGitAvailable()) return { success: false, output: "", error: "Git not available" };
3022
+ if (!args.message) return { success: false, output: "", error: "message required" };
3023
+ stageAll();
3024
+ const result = commit(args.message);
3025
+ return result.success ? { success: true, output: `Committed: ${result.hash ? result.hash.slice(0, 8) : "ok"} \u2014 ${args.message}` } : { success: false, output: result.output, error: result.output };
3026
+ }
3027
+ },
3028
+ {
3029
+ name: "git_diff",
3030
+ description: "Show current diff (staged + unstaged)",
3031
+ usage: "[staged=<true|false>]",
3032
+ execute(_workingDir, args) {
3033
+ if (!isGitAvailable()) return { success: false, output: "", error: "Git not available" };
3034
+ const diff = args.staged === "true" ? getStagedDiff() : getFullDiff();
3035
+ return { success: true, output: diff || "No changes." };
3036
+ }
3037
+ },
3038
+ {
3039
+ name: "git_branches",
3040
+ description: "List all branches",
3041
+ usage: "",
3042
+ execute() {
3043
+ if (!isGitAvailable()) return { success: false, output: "", error: "Git not available" };
3044
+ const branches = getBranches();
3045
+ return { success: true, output: formatBranches(branches) };
3046
+ }
3047
+ },
3048
+ {
3049
+ name: "git_create_branch",
3050
+ description: "Create and switch to a new branch",
3051
+ usage: "name=<branch name>",
3052
+ execute(_workingDir, args) {
3053
+ if (!isGitAvailable()) return { success: false, output: "", error: "Git not available" };
3054
+ if (!args.name) return { success: false, output: "", error: "name required" };
3055
+ const ok = createBranch(args.name);
3056
+ return ok ? { success: true, output: `Created and switched to branch "${args.name}"` } : { success: false, output: "", error: `Failed to create branch "${args.name}"` };
3057
+ }
3058
+ },
3059
+ {
3060
+ name: "git_switch_branch",
3061
+ description: "Switch to an existing branch",
3062
+ usage: "name=<branch name>",
3063
+ execute(_workingDir, args) {
3064
+ if (!isGitAvailable()) return { success: false, output: "", error: "Git not available" };
3065
+ if (!args.name) return { success: false, output: "", error: "name required" };
3066
+ const result = switchBranch(args.name);
3067
+ return result.success ? { success: true, output: `Switched to branch "${args.name}"` } : { success: false, output: result.output, error: result.output };
3068
+ }
3069
+ },
3070
+ {
3071
+ name: "git_log",
3072
+ description: "Show recent commit log",
3073
+ usage: "[count=<number>]",
3074
+ execute(_workingDir, args) {
3075
+ if (!isGitAvailable()) return { success: false, output: "", error: "Git not available" };
3076
+ const count = args.count ? parseInt(args.count, 10) : 10;
3077
+ const log = getLog(count);
3078
+ return { success: true, output: formatLog(log) };
3079
+ }
3080
+ },
3081
+ {
3082
+ name: "git_branch",
3083
+ description: "Get current branch name",
3084
+ usage: "",
3085
+ execute() {
3086
+ if (!isGitAvailable()) return { success: false, output: "", error: "Git not available" };
3087
+ return { success: true, output: getCurrentBranch() };
3088
+ }
3089
+ }
3090
+ ];
3091
+ var browserTools = [
3092
+ {
3093
+ name: "browser_goto",
3094
+ description: "Navigate the browser to a URL",
3095
+ usage: "url=<URL>",
3096
+ async execute(_wd, args) {
3097
+ if (!args.url) return { success: false, output: "", error: "url required" };
3098
+ try {
3099
+ if (!isBrowserRunning()) {
3100
+ const { launchBrowser } = await import("./browser-UA4QOMPS.js");
3101
+ await launchBrowser();
3102
+ }
3103
+ const result = await goto(args.url);
3104
+ return { success: true, output: result };
3105
+ } catch (e) {
3106
+ return { success: false, output: "", error: String(e) };
3107
+ }
3108
+ }
3109
+ },
3110
+ {
3111
+ name: "browser_click",
3112
+ description: "Click an element on the page",
3113
+ usage: "selector=<CSS selector>",
3114
+ async execute(_wd, args) {
3115
+ if (!args.selector) return { success: false, output: "", error: "selector required" };
3116
+ try {
3117
+ const result = await click(args.selector);
3118
+ return { success: true, output: result };
3119
+ } catch (e) {
3120
+ return { success: false, output: "", error: String(e) };
3121
+ }
3122
+ }
3123
+ },
3124
+ {
3125
+ name: "browser_type",
3126
+ description: "Type text into an input element",
3127
+ usage: "selector=<CSS selector> text=<text>",
3128
+ async execute(_wd, args) {
3129
+ if (!args.selector) return { success: false, output: "", error: "selector required" };
3130
+ if (!args.text) return { success: false, output: "", error: "text required" };
3131
+ try {
3132
+ const result = await type(args.selector, args.text);
3133
+ return { success: true, output: result };
3134
+ } catch (e) {
3135
+ return { success: false, output: "", error: String(e) };
3136
+ }
3137
+ }
3138
+ },
3139
+ {
3140
+ name: "browser_screenshot",
3141
+ description: "Take a browser screenshot",
3142
+ usage: "[name=<filename>]",
3143
+ async execute() {
3144
+ try {
3145
+ const result = await screenshot();
3146
+ return { success: true, output: formatScreenshotResult(result) };
3147
+ } catch (e) {
3148
+ return { success: false, output: "", error: String(e) };
3149
+ }
3150
+ }
3151
+ },
3152
+ {
3153
+ name: "browser_inspect",
3154
+ description: "Inspect a DOM element on the page",
3155
+ usage: "selector=<CSS selector>",
3156
+ async execute(_wd, args) {
3157
+ if (!args.selector) return { success: false, output: "", error: "selector required" };
3158
+ try {
3159
+ const el = await inspect(args.selector);
3160
+ if (!el) return { success: false, output: "", error: "Element not found" };
3161
+ return { success: true, output: formatDOMElement(el) };
3162
+ } catch (e) {
3163
+ return { success: false, output: "", error: String(e) };
3164
+ }
3165
+ }
3166
+ }
3167
+ ];
3168
+ var pluginTools = [
3169
+ {
3170
+ name: "plugin_list",
3171
+ description: "List loaded plugins",
3172
+ usage: "",
3173
+ execute() {
3174
+ const plugins = listPlugins();
3175
+ const lines = plugins.map((p) => ` ${p.enabled ? "\u2713" : "\u2717"} ${p.manifest.name}@${p.manifest.version} \u2014 ${p.manifest.description}`);
3176
+ return { success: true, output: lines.join("\n") || "No plugins loaded." };
3177
+ }
3178
+ },
3179
+ {
3180
+ name: "plugin_enable",
3181
+ description: "Enable a plugin by name",
3182
+ usage: "name=<plugin name>",
3183
+ execute(_wd, args) {
3184
+ if (!args.name) return { success: false, output: "", error: "name required" };
3185
+ const ok = enablePlugin(args.name);
3186
+ return ok ? { success: true, output: `Enabled ${args.name}` } : { success: false, output: "", error: `Plugin "${args.name}" not found` };
3187
+ }
3188
+ },
3189
+ {
3190
+ name: "plugin_disable",
3191
+ description: "Disable a plugin by name",
3192
+ usage: "name=<plugin name>",
3193
+ execute(_wd, args) {
3194
+ if (!args.name) return { success: false, output: "", error: "name required" };
3195
+ const ok = disablePlugin(args.name);
3196
+ return ok ? { success: true, output: `Disabled ${args.name}` } : { success: false, output: "", error: `Plugin "${args.name}" not found` };
3197
+ }
3198
+ }
3199
+ ];
3200
+ var repoTools = [
3201
+ {
3202
+ name: "detect_project",
3203
+ description: "Detect project type, framework, and stack",
3204
+ usage: "[dir=<directory>]",
3205
+ execute(workingDir, args) {
3206
+ const dir = args.dir ? path13.resolve(workingDir, args.dir) : workingDir;
3207
+ const info = detectProject(dir);
3208
+ return { success: true, output: printProjectInfo(info) };
3209
+ }
3210
+ },
3211
+ {
3212
+ name: "analyze_deps",
3213
+ description: "Analyze dependencies and import graph",
3214
+ usage: "[dir=<directory>]",
3215
+ execute(workingDir, args) {
3216
+ const dir = args.dir ? path13.resolve(workingDir, args.dir) : workingDir;
3217
+ const graph = analyzeDependencies(dir);
3218
+ return { success: true, output: printDepGraph(graph) };
3219
+ }
3220
+ },
3221
+ {
3222
+ name: "repo_summary",
3223
+ description: "Generate a full repository architecture summary",
3224
+ usage: "[dir=<directory>]",
3225
+ execute(workingDir, args) {
3226
+ const dir = args.dir ? path13.resolve(workingDir, args.dir) : workingDir;
3227
+ const summary = generateSummary(dir);
3228
+ return { success: true, output: printSummary(summary) };
3229
+ }
3230
+ }
3231
+ ];
3232
+ loadBuiltinPlugins();
3233
+ var allTools = [
3234
+ ...fileTools,
3235
+ ...commandTools,
3236
+ ...searchTools,
3237
+ ...fsEngineTools,
3238
+ ...editorTools,
3239
+ ...shellTools,
3240
+ ...repoTools,
3241
+ ...memoryTools,
3242
+ ...gitTools,
3243
+ ...browserTools,
3244
+ ...pluginTools,
3245
+ ...getAllPluginTools()
3246
+ ];
3247
+ function getTool(name) {
3248
+ return allTools.find((t) => t.name === name);
3249
+ }
3250
+
3251
+ export {
3252
+ getPreferences,
3253
+ savePreferences,
3254
+ getRepoMemory,
3255
+ getWorkflows,
3256
+ saveWorkflow,
3257
+ deleteWorkflow,
3258
+ addRepoNote,
3259
+ clearAllMemory,
3260
+ formatPreferences,
3261
+ formatRepoMemory,
3262
+ getEmbedding,
3263
+ cosineSimilarity,
3264
+ storeVector,
3265
+ searchVectors,
3266
+ clearVectors,
3267
+ getVectorCount,
3268
+ formatVectorResults,
3269
+ getModeConfig,
3270
+ createContext,
3271
+ listModes,
3272
+ getApproval,
3273
+ scanDirectory,
3274
+ printScanSummary,
3275
+ detectProject,
3276
+ printProjectInfo,
3277
+ analyzeDependencies,
3278
+ printDepGraph,
3279
+ findCircularDeps,
3280
+ generateSummary,
3281
+ printSummary,
3282
+ allTools,
3283
+ getTool
3284
+ };