quackstack 1.0.7 → 1.0.8

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,109 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { client } from "./database.js";
4
+ export async function detectFileChanges(rootDir, projectName) {
5
+ try {
6
+ const existingSnippets = await client.codeSnippet.findMany({
7
+ where: { projectName },
8
+ select: { filePath: true, updatedAt: true },
9
+ distinct: ['filePath'],
10
+ });
11
+ if (existingSnippets.length === 0) {
12
+ return null;
13
+ }
14
+ const lastIndexTime = existingSnippets.reduce((latest, snippet) => {
15
+ return snippet.updatedAt > latest ? snippet.updatedAt : latest;
16
+ }, new Date(0));
17
+ const indexedFiles = new Set(existingSnippets.map(s => s.filePath));
18
+ const currentFiles = await scanDirectory(rootDir);
19
+ const currentFilesSet = new Set(currentFiles);
20
+ let newFiles = 0;
21
+ let modifiedFiles = 0;
22
+ let deletedFiles = 0;
23
+ for (const filePath of currentFiles) {
24
+ if (!indexedFiles.has(filePath)) {
25
+ newFiles++;
26
+ }
27
+ else {
28
+ try {
29
+ const stats = fs.statSync(filePath);
30
+ if (stats.mtime > lastIndexTime) {
31
+ modifiedFiles++;
32
+ }
33
+ }
34
+ catch (error) {
35
+ continue;
36
+ }
37
+ }
38
+ }
39
+ for (const indexedFile of indexedFiles) {
40
+ if (!currentFilesSet.has(indexedFile)) {
41
+ deletedFiles++;
42
+ }
43
+ }
44
+ const totalChanges = newFiles + modifiedFiles + deletedFiles;
45
+ return {
46
+ newFiles,
47
+ modifiedFiles,
48
+ deletedFiles,
49
+ totalChanges,
50
+ };
51
+ }
52
+ catch (error) {
53
+ console.error("Error detecting file changes:", error);
54
+ return null;
55
+ }
56
+ }
57
+ async function scanDirectory(dir) {
58
+ const files = [];
59
+ const ignoreDirs = new Set([
60
+ 'node_modules',
61
+ '.git',
62
+ 'dist',
63
+ 'build',
64
+ '.next',
65
+ 'coverage',
66
+ '.vscode',
67
+ '.idea'
68
+ ]);
69
+ function walk(currentPath) {
70
+ try {
71
+ const entries = fs.readdirSync(currentPath, { withFileTypes: true });
72
+ for (const entry of entries) {
73
+ const fullPath = path.join(currentPath, entry.name);
74
+ if (entry.isDirectory()) {
75
+ if (!ignoreDirs.has(entry.name) && !entry.name.startsWith('.')) {
76
+ walk(fullPath);
77
+ }
78
+ }
79
+ else if (entry.isFile()) {
80
+ const ext = path.extname(entry.name);
81
+ const codeExtensions = [
82
+ '.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.cpp', '.c',
83
+ '.go', '.rs', '.rb', '.php', '.cs', '.swift', '.kt', '.scala'
84
+ ];
85
+ if (codeExtensions.includes(ext)) {
86
+ files.push(fullPath);
87
+ }
88
+ }
89
+ }
90
+ }
91
+ catch (error) {
92
+ }
93
+ }
94
+ walk(dir);
95
+ return files;
96
+ }
97
+ export function formatChangeMessage(stats) {
98
+ const parts = [];
99
+ if (stats.newFiles > 0) {
100
+ parts.push(`${stats.newFiles} new file${stats.newFiles > 1 ? 's' : ''}`);
101
+ }
102
+ if (stats.modifiedFiles > 0) {
103
+ parts.push(`${stats.modifiedFiles} modified file${stats.modifiedFiles > 1 ? 's' : ''}`);
104
+ }
105
+ if (stats.deletedFiles > 0) {
106
+ parts.push(`${stats.deletedFiles} deleted file${stats.deletedFiles > 1 ? 's' : ''}`);
107
+ }
108
+ return parts.join(', ');
109
+ }
package/dist/repl.js CHANGED
@@ -1,79 +1,83 @@
1
1
  import readline from "readline";
2
2
  import chalk from "chalk";
3
- import { ingest } from "./commands/ingest.js";
4
3
  import { search } from "./commands/search.js";
4
+ import { ingest } from "./commands/ingest.js";
5
5
  import { client } from "./lib/database.js";
6
6
  import path from "path";
7
+ import { detectFileChanges, formatChangeMessage } from "./lib/file-change-detector.js";
7
8
  const PROJECT_NAME = path.basename(process.cwd());
8
- async function ensureIngested(forceReindex = false) {
9
- const count = await client.codeSnippet.count({
10
- where: { projectName: PROJECT_NAME }
9
+ export async function startREPL(forceReindex = false) {
10
+ console.log(chalk.cyan("\nšŸ’” Tip: Press Ctrl+C to exit\n"));
11
+ if (!forceReindex) {
12
+ const changes = await detectFileChanges(process.cwd(), PROJECT_NAME);
13
+ if (changes && changes.totalChanges > 0) {
14
+ console.log(chalk.yellow(`\nāš ļø Detected ${changes.totalChanges} file change${changes.totalChanges > 1 ? 's' : ''} since last index:`));
15
+ console.log(chalk.yellow(` ${formatChangeMessage(changes)}`));
16
+ console.log(chalk.yellow(` Run 'quack --reindex' for best results.\n`));
17
+ const shouldReindex = await promptUser(chalk.yellow("Would you like to reindex now? (y/n) > "));
18
+ if (shouldReindex.toLowerCase() === 'y') {
19
+ forceReindex = true;
20
+ }
21
+ }
22
+ }
23
+ const existingCount = await client.codeSnippet.count({
24
+ where: { projectName: PROJECT_NAME },
11
25
  });
12
- if (count === 0 || forceReindex) {
13
- if (forceReindex && count > 0) {
14
- console.log("šŸ—‘ļø Clearing old index...");
26
+ if (existingCount === 0 || forceReindex) {
27
+ if (forceReindex) {
28
+ console.log(chalk.gray("šŸ—‘ļø Clearing old index..."));
15
29
  await client.codeSnippet.deleteMany({
16
- where: { projectName: PROJECT_NAME }
30
+ where: { projectName: PROJECT_NAME },
17
31
  });
18
32
  }
19
- console.log("šŸ” Indexing your codebase (this may take a moment)...");
33
+ console.log(chalk.gray("šŸ” Indexing your codebase (this may take a moment)..."));
20
34
  await ingest(process.cwd(), PROJECT_NAME, true);
21
- console.log("āœ… Indexing complete!\n");
35
+ console.log(chalk.green("āœ… Indexing complete!"));
22
36
  }
23
- }
24
- function stripMarkdown(text) {
25
- return text
26
- .replace(/\*\*(.+?)\*\*/g, chalk.bold('$1'))
27
- .replace(/\*(.+?)\*/g, chalk.italic('$1'))
28
- .replace(/`(.+?)`/g, chalk.cyan('$1'))
29
- .replace(/^#{1,6}\s+(.+)$/gm, chalk.bold.blue('$1'));
30
- }
31
- export async function startREPL(forceReindex = false) {
32
- await ensureIngested(forceReindex);
33
- console.log("šŸ’” Tip: Press Ctrl+C to exit\n");
34
37
  const rl = readline.createInterface({
35
38
  input: process.stdin,
36
39
  output: process.stdout,
37
- terminal: true
40
+ prompt: chalk.yellow("🐄 Quack! How can I help? > "),
38
41
  });
39
- let waitingForDetails = false;
40
- let currentSources = [];
41
- rl.on("line", async (input) => {
42
- const trimmed = input.trim().toLowerCase();
43
- if (waitingForDetails) {
44
- waitingForDetails = false;
45
- if (trimmed === "y" || trimmed === "yes") {
46
- console.log("\nšŸ“š Relevant Code:\n");
47
- currentSources.slice(0, 3).forEach((r, i) => {
48
- console.log(chalk.dim(`[${i + 1}] ${r.filePath} (relevance: ${(r.score * 100).toFixed(1)}%)`));
49
- console.log(chalk.gray(r.content));
50
- console.log(chalk.dim("---\n"));
51
- });
52
- }
53
- console.log();
54
- rl.prompt();
55
- return;
56
- }
57
- if (!trimmed) {
42
+ rl.prompt();
43
+ rl.on("line", async (line) => {
44
+ const query = line.trim();
45
+ if (!query) {
58
46
  rl.prompt();
59
47
  return;
60
48
  }
61
49
  try {
62
- const { answer, sources } = await search(input, PROJECT_NAME);
63
- currentSources = sources;
64
- console.log("\n" + stripMarkdown(answer) + "\n");
65
- waitingForDetails = true;
66
- process.stdout.write("šŸ’” Want more details? (y/n) > ");
50
+ const { answer, sources } = await search(query, PROJECT_NAME);
51
+ console.log(chalk.white(`\n${answer}\n`));
52
+ const showDetails = await promptUser(chalk.cyan("šŸ’” Want more details? (y/n) > "));
53
+ if (showDetails.toLowerCase() === "y") {
54
+ console.log(chalk.blue("\nšŸ“š Relevant Code:\n"));
55
+ sources.forEach((src, i) => {
56
+ console.log(chalk.gray(`[${i + 1}] ${src.filePath} (relevance: ${(src.score * 100).toFixed(1)}%)`));
57
+ console.log(chalk.white(src.content));
58
+ console.log(chalk.gray("\n---\n"));
59
+ });
60
+ }
67
61
  }
68
62
  catch (error) {
69
- console.error(chalk.red("āŒ Error:"), error instanceof Error ? error.message : "Unknown error");
70
- rl.prompt();
63
+ console.error(chalk.red(`\nError: ${error.message}\n`));
71
64
  }
65
+ rl.prompt();
72
66
  });
73
67
  rl.on("close", () => {
74
- console.log("\nšŸ‘‹ Happy coding!");
68
+ console.log(chalk.gray("\nšŸ‘‹ Happy coding!"));
75
69
  process.exit(0);
76
70
  });
77
- rl.setPrompt("🐄 Quack! How can I help? > ");
78
- rl.prompt();
71
+ }
72
+ function promptUser(question) {
73
+ const rl = readline.createInterface({
74
+ input: process.stdin,
75
+ output: process.stdout,
76
+ });
77
+ return new Promise((resolve) => {
78
+ rl.question(question, (answer) => {
79
+ rl.close();
80
+ resolve(answer);
81
+ });
82
+ });
79
83
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quackstack",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "Your cracked unpaid intern for all things codebase related! AI-powered codebase search and Q&A.",
5
5
  "type": "module",
6
6
  "main": "dist/cli.cjs",