quackstack 1.0.23 → 1.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -105
- package/dist/cli.cjs +82 -0
- package/dist/commands/ingest.js +67 -3
- package/dist/commands/search.js +115 -11
- package/dist/lib/context-generator.js +106 -12
- package/dist/lib/database.js +104 -36
- package/dist/lib/git-history.js +314 -0
- package/dist/repl.js +1 -1
- package/package.json +1 -1
- package/prisma/schema.prisma +32 -3
package/dist/commands/search.js
CHANGED
|
@@ -2,21 +2,48 @@
|
|
|
2
2
|
import { client } from "../lib/database.js";
|
|
3
3
|
import { localEmbeddings } from "../lib/local-embeddings.js";
|
|
4
4
|
import { getAIClient } from "../lib/ai-provider.js";
|
|
5
|
-
export async function search(query, projectName, provider, model) {
|
|
5
|
+
export async function search(query, projectName, provider, model, options = {}) {
|
|
6
|
+
const { boostRecent = false, boostFrequent = false, filterAuthor, recentDays = 30, } = options;
|
|
7
|
+
const whereClause = { projectName };
|
|
8
|
+
if (filterAuthor) {
|
|
9
|
+
whereClause.OR = [
|
|
10
|
+
{ lastCommitEmail: filterAuthor },
|
|
11
|
+
{ primaryAuthorEmail: filterAuthor },
|
|
12
|
+
];
|
|
13
|
+
}
|
|
6
14
|
const snippets = await client.codeSnippet.findMany({
|
|
7
|
-
where:
|
|
15
|
+
where: whereClause,
|
|
8
16
|
});
|
|
9
17
|
const allContent = snippets.map(s => s.content);
|
|
10
18
|
localEmbeddings.addDocuments(allContent);
|
|
11
19
|
const queryVector = localEmbeddings.getVector(query);
|
|
12
20
|
const ranked = snippets
|
|
13
|
-
.map(snippet =>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
.map(snippet => {
|
|
22
|
+
let score = localEmbeddings.cosineSimilarity(queryVector, snippet.embedding);
|
|
23
|
+
if (boostRecent && snippet.lastCommitDate) {
|
|
24
|
+
const daysSinceCommit = (Date.now() - snippet.lastCommitDate.getTime()) / (1000 * 60 * 60 * 24);
|
|
25
|
+
if (daysSinceCommit <= recentDays) {
|
|
26
|
+
const recencyBoost = 1 + (0.2 * (1 - daysSinceCommit / recentDays));
|
|
27
|
+
score *= recencyBoost;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (boostFrequent && snippet.totalCommits) {
|
|
31
|
+
const commitBoost = 1 + (Math.min(snippet.totalCommits, 50) / 50) * 0.15;
|
|
32
|
+
score *= commitBoost;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
id: snippet.id,
|
|
36
|
+
content: snippet.content,
|
|
37
|
+
filePath: snippet.filePath,
|
|
38
|
+
functionName: snippet.functionName,
|
|
39
|
+
score,
|
|
40
|
+
lastCommitAuthor: snippet.lastCommitAuthor,
|
|
41
|
+
lastCommitDate: snippet.lastCommitDate,
|
|
42
|
+
lastCommitMessage: snippet.lastCommitMessage,
|
|
43
|
+
totalCommits: snippet.totalCommits,
|
|
44
|
+
primaryAuthor: snippet.primaryAuthor,
|
|
45
|
+
};
|
|
46
|
+
})
|
|
20
47
|
.sort((a, b) => b.score - a.score);
|
|
21
48
|
const seenFiles = new Set();
|
|
22
49
|
const uniqueResults = ranked.filter(item => {
|
|
@@ -26,9 +53,86 @@ export async function search(query, projectName, provider, model) {
|
|
|
26
53
|
return true;
|
|
27
54
|
}).slice(0, 5);
|
|
28
55
|
const context = uniqueResults
|
|
29
|
-
.map((r, i) =>
|
|
56
|
+
.map((r, i) => {
|
|
57
|
+
let entry = `[${i + 1}] ${r.filePath}${r.functionName ? ` (${r.functionName})` : ""}`;
|
|
58
|
+
if (r.lastCommitAuthor || r.primaryAuthor) {
|
|
59
|
+
const author = r.primaryAuthor || r.lastCommitAuthor;
|
|
60
|
+
entry += `\nPrimary Author: ${author}`;
|
|
61
|
+
if (r.totalCommits) {
|
|
62
|
+
entry += ` (${r.totalCommits} commits)`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (r.lastCommitDate) {
|
|
66
|
+
const daysAgo = Math.floor((Date.now() - r.lastCommitDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
67
|
+
entry += `\nLast Modified: ${daysAgo} days ago`;
|
|
68
|
+
if (r.lastCommitMessage) {
|
|
69
|
+
entry += `\nLast Commit: "${r.lastCommitMessage.substring(0, 60)}${r.lastCommitMessage.length > 60 ? '...' : ''}"`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
entry += `\n\n${r.content}`;
|
|
73
|
+
return entry;
|
|
74
|
+
})
|
|
30
75
|
.join("\n\n---\n\n");
|
|
31
76
|
const aiClient = getAIClient(provider, model);
|
|
32
|
-
const
|
|
77
|
+
const enhancedPrompt = query +
|
|
78
|
+
"\n\nNote: Code snippets include git history metadata (authors, commit counts, last modified dates). " +
|
|
79
|
+
"Use this information to provide context about code ownership and recency when relevant.";
|
|
80
|
+
const answer = await aiClient.generateAnswer(enhancedPrompt, context);
|
|
33
81
|
return { answer, sources: uniqueResults };
|
|
34
82
|
}
|
|
83
|
+
export async function searchByAuthor(authorEmail, projectName) {
|
|
84
|
+
const snippets = await client.codeSnippet.findMany({
|
|
85
|
+
where: {
|
|
86
|
+
projectName,
|
|
87
|
+
OR: [
|
|
88
|
+
{ lastCommitEmail: authorEmail },
|
|
89
|
+
{ primaryAuthorEmail: authorEmail },
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
select: {
|
|
93
|
+
filePath: true,
|
|
94
|
+
functionName: true,
|
|
95
|
+
totalCommits: true,
|
|
96
|
+
lastCommitDate: true,
|
|
97
|
+
},
|
|
98
|
+
orderBy: {
|
|
99
|
+
totalCommits: "desc",
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
const fileMap = new Map();
|
|
103
|
+
snippets.forEach(s => {
|
|
104
|
+
if (!fileMap.has(s.filePath)) {
|
|
105
|
+
fileMap.set(s.filePath, s);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
return Array.from(fileMap.values());
|
|
109
|
+
}
|
|
110
|
+
export async function getRecentActivity(projectName, days = 7) {
|
|
111
|
+
const since = new Date();
|
|
112
|
+
since.setDate(since.getDate() - days);
|
|
113
|
+
const snippets = await client.codeSnippet.findMany({
|
|
114
|
+
where: {
|
|
115
|
+
projectName,
|
|
116
|
+
lastCommitDate: {
|
|
117
|
+
gte: since,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
select: {
|
|
121
|
+
filePath: true,
|
|
122
|
+
lastCommitAuthor: true,
|
|
123
|
+
lastCommitDate: true,
|
|
124
|
+
lastCommitMessage: true,
|
|
125
|
+
},
|
|
126
|
+
orderBy: {
|
|
127
|
+
lastCommitDate: "desc",
|
|
128
|
+
},
|
|
129
|
+
take: 20,
|
|
130
|
+
});
|
|
131
|
+
const fileMap = new Map();
|
|
132
|
+
snippets.forEach(s => {
|
|
133
|
+
if (!fileMap.has(s.filePath)) {
|
|
134
|
+
fileMap.set(s.filePath, s);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
return Array.from(fileMap.values());
|
|
138
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import os from "os";
|
|
4
|
-
import { search } from "../commands/search.js";
|
|
4
|
+
import { search, getRecentActivity } from "../commands/search.js";
|
|
5
|
+
import { getProjectAuthors } from "../lib/database.js";
|
|
6
|
+
import { gitHistory } from "../lib/git-history.js";
|
|
5
7
|
export async function generateCodebaseDoc(projectName) {
|
|
6
|
-
console.log("
|
|
8
|
+
console.log("Analyzing your codebase...\n");
|
|
7
9
|
const queries = [
|
|
8
10
|
"What is the overall architecture and design patterns used?",
|
|
9
11
|
"What are the main entry points and how does the application start?",
|
|
@@ -15,8 +17,24 @@ export async function generateCodebaseDoc(projectName) {
|
|
|
15
17
|
let doc = `# ${projectName} - Codebase Documentation\n\n`;
|
|
16
18
|
doc += `**Auto-generated by QuackStack** | Last updated: ${new Date().toLocaleString()}\n\n`;
|
|
17
19
|
doc += `This document provides a high-level overview of the codebase architecture, key components, and design decisions.\n\n`;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
if (gitHistory.isRepository()) {
|
|
21
|
+
doc += `## Repository Information\n\n`;
|
|
22
|
+
const currentBranch = gitHistory.getCurrentBranch();
|
|
23
|
+
if (currentBranch) {
|
|
24
|
+
doc += `**Current Branch:** ${currentBranch}\n\n`;
|
|
25
|
+
}
|
|
26
|
+
const recentCommits = gitHistory.getRecentCommits(10);
|
|
27
|
+
if (recentCommits.length > 0) {
|
|
28
|
+
doc += `**Recent Activity:**\n`;
|
|
29
|
+
recentCommits.slice(0, 5).forEach(commit => {
|
|
30
|
+
const date = new Date(commit.date).toLocaleDateString();
|
|
31
|
+
doc += `- ${date} - ${commit.author}: ${commit.message.substring(0, 60)}${commit.message.length > 60 ? '...' : ''}\n`;
|
|
32
|
+
});
|
|
33
|
+
doc += `\n`;
|
|
34
|
+
}
|
|
35
|
+
doc += `---\n\n`;
|
|
36
|
+
}
|
|
37
|
+
doc += `## Table of Contents\n\n`;
|
|
20
38
|
queries.forEach((q, i) => {
|
|
21
39
|
const anchor = q.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
22
40
|
doc += `${i + 1}. [${q}](#${anchor})\n`;
|
|
@@ -28,12 +46,15 @@ export async function generateCodebaseDoc(projectName) {
|
|
|
28
46
|
doc += `## ${query}\n\n`;
|
|
29
47
|
doc += `${answer}\n\n`;
|
|
30
48
|
if (sources.length > 0) {
|
|
31
|
-
doc += `###
|
|
49
|
+
doc += `### Key Files\n\n`;
|
|
32
50
|
sources.slice(0, 5).forEach(s => {
|
|
33
51
|
doc += `- \`${s.filePath}\``;
|
|
34
52
|
if (s.functionName) {
|
|
35
53
|
doc += ` - ${s.functionName}`;
|
|
36
54
|
}
|
|
55
|
+
if (s.primaryAuthor) {
|
|
56
|
+
doc += ` (maintained by ${s.primaryAuthor})`;
|
|
57
|
+
}
|
|
37
58
|
doc += `\n`;
|
|
38
59
|
});
|
|
39
60
|
doc += `\n`;
|
|
@@ -44,7 +65,28 @@ export async function generateCodebaseDoc(projectName) {
|
|
|
44
65
|
console.error(`Error analyzing: ${query}`);
|
|
45
66
|
}
|
|
46
67
|
}
|
|
47
|
-
|
|
68
|
+
if (gitHistory.isRepository()) {
|
|
69
|
+
doc += `## Contributors\n\n`;
|
|
70
|
+
const authors = await getProjectAuthors(projectName);
|
|
71
|
+
if (authors.length > 0) {
|
|
72
|
+
doc += `This project has ${authors.length} contributor${authors.length > 1 ? 's' : ''}:\n\n`;
|
|
73
|
+
authors.slice(0, 10).forEach((author, i) => {
|
|
74
|
+
doc += `${i + 1}. **${author.author}** (${author.email})\n`;
|
|
75
|
+
doc += ` - ${author.totalCommits} commits\n`;
|
|
76
|
+
doc += ` - +${author.linesAdded} / -${author.linesRemoved} lines\n`;
|
|
77
|
+
if (author.recentActivity) {
|
|
78
|
+
const daysAgo = Math.floor((Date.now() - author.recentActivity.getTime()) / (1000 * 60 * 60 * 24));
|
|
79
|
+
doc += ` - Last active ${daysAgo} days ago\n`;
|
|
80
|
+
}
|
|
81
|
+
if (author.filesOwned.length > 0) {
|
|
82
|
+
doc += ` - Primary owner of ${author.filesOwned.length} files\n`;
|
|
83
|
+
}
|
|
84
|
+
doc += `\n`;
|
|
85
|
+
});
|
|
86
|
+
doc += `---\n\n`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
doc += `## Project Structure\n\n`;
|
|
48
90
|
doc += '```\n';
|
|
49
91
|
doc += await getProjectStructure(process.cwd());
|
|
50
92
|
doc += '```\n\n';
|
|
@@ -59,7 +101,7 @@ export async function generateCodebaseDoc(projectName) {
|
|
|
59
101
|
doc += `### Running the Project\n`;
|
|
60
102
|
doc += `Refer to \`package.json\` scripts section for available commands.\n\n`;
|
|
61
103
|
doc += `---\n\n`;
|
|
62
|
-
doc += `##
|
|
104
|
+
doc += `## Updating This Document\n\n`;
|
|
63
105
|
doc += `This documentation is auto-generated. To regenerate:\n\n`;
|
|
64
106
|
doc += '```bash\n';
|
|
65
107
|
doc += 'quack --docs\n';
|
|
@@ -81,6 +123,34 @@ export async function generateContextFiles(projectName) {
|
|
|
81
123
|
let baseContext = `# ${projectName} - Codebase Context\n\n`;
|
|
82
124
|
baseContext += `Generated: ${new Date().toISOString()}\n\n`;
|
|
83
125
|
baseContext += "This file is auto-generated by QuackStack to provide AI assistants with codebase context.\n\n";
|
|
126
|
+
if (gitHistory.isRepository()) {
|
|
127
|
+
baseContext += `## Git Repository Info\n\n`;
|
|
128
|
+
const branch = gitHistory.getCurrentBranch();
|
|
129
|
+
if (branch) {
|
|
130
|
+
baseContext += `**Branch:** ${branch}\n\n`;
|
|
131
|
+
}
|
|
132
|
+
const recentActivity = await getRecentActivity(projectName, 7);
|
|
133
|
+
if (recentActivity.length > 0) {
|
|
134
|
+
baseContext += `**Recent Changes (Last 7 days):**\n\n`;
|
|
135
|
+
recentActivity.slice(0, 5).forEach(item => {
|
|
136
|
+
const daysAgo = Math.floor((Date.now() - item.lastCommitDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
137
|
+
baseContext += `- ${item.filePath}\n`;
|
|
138
|
+
baseContext += ` - Modified by ${item.lastCommitAuthor} ${daysAgo} days ago\n`;
|
|
139
|
+
if (item.lastCommitMessage) {
|
|
140
|
+
baseContext += ` - "${item.lastCommitMessage.substring(0, 60)}${item.lastCommitMessage.length > 60 ? '...' : ''}"\n`;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
baseContext += `\n`;
|
|
144
|
+
}
|
|
145
|
+
const authors = await getProjectAuthors(projectName);
|
|
146
|
+
if (authors.length > 0) {
|
|
147
|
+
baseContext += `**Top Contributors:**\n\n`;
|
|
148
|
+
authors.slice(0, 5).forEach(author => {
|
|
149
|
+
baseContext += `- ${author.author} (${author.totalCommits} commits, owns ${author.filesOwned.length} files)\n`;
|
|
150
|
+
});
|
|
151
|
+
baseContext += `\n`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
84
154
|
baseContext += "---\n\n";
|
|
85
155
|
for (const query of queries) {
|
|
86
156
|
try {
|
|
@@ -89,7 +159,11 @@ export async function generateContextFiles(projectName) {
|
|
|
89
159
|
if (sources.length > 0) {
|
|
90
160
|
baseContext += "**Key files:**\n";
|
|
91
161
|
sources.slice(0, 3).forEach(s => {
|
|
92
|
-
baseContext += `- ${s.filePath}
|
|
162
|
+
baseContext += `- ${s.filePath}`;
|
|
163
|
+
if (s.primaryAuthor) {
|
|
164
|
+
baseContext += ` (${s.primaryAuthor})`;
|
|
165
|
+
}
|
|
166
|
+
baseContext += `\n`;
|
|
93
167
|
});
|
|
94
168
|
baseContext += "\n";
|
|
95
169
|
}
|
|
@@ -100,12 +174,28 @@ export async function generateContextFiles(projectName) {
|
|
|
100
174
|
}
|
|
101
175
|
baseContext += "---\n\n## Project Structure\n\n";
|
|
102
176
|
baseContext += await getProjectStructure(process.cwd());
|
|
177
|
+
if (gitHistory.isRepository()) {
|
|
178
|
+
const authors = await getProjectAuthors(projectName);
|
|
179
|
+
if (authors.length > 0) {
|
|
180
|
+
baseContext += "\n---\n\n## Code Ownership & Expertise\n\n";
|
|
181
|
+
baseContext += "When making changes, consider consulting these experts:\n\n";
|
|
182
|
+
for (const author of authors.slice(0, 5)) {
|
|
183
|
+
if (author.filesOwned.length > 0) {
|
|
184
|
+
baseContext += `**${author.author}** - Expert in:\n`;
|
|
185
|
+
author.filesOwned.slice(0, 5).forEach(file => {
|
|
186
|
+
baseContext += `- ${file}\n`;
|
|
187
|
+
});
|
|
188
|
+
baseContext += `\n`;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
103
193
|
await generateCursorRules(baseContext);
|
|
104
194
|
await generateWindsurfContext(baseContext);
|
|
105
195
|
await generateClineContext(baseContext);
|
|
106
196
|
await generateContinueContext(baseContext);
|
|
107
197
|
await generateAiderContext(baseContext);
|
|
108
|
-
console.log("\n
|
|
198
|
+
console.log("\n Context files generated for:");
|
|
109
199
|
console.log(" - Cursor (.cursorrules)");
|
|
110
200
|
console.log(" - Windsurf (.windsurfrules)");
|
|
111
201
|
console.log(" - Cline (.clinerules)");
|
|
@@ -138,11 +228,9 @@ async function generateAiderContext(context) {
|
|
|
138
228
|
const aiderConfig = `# Aider configuration with QuackStack context
|
|
139
229
|
# Project: ${path.basename(process.cwd())}
|
|
140
230
|
|
|
141
|
-
# Context file
|
|
142
231
|
read:
|
|
143
232
|
- .aider.context.md
|
|
144
233
|
|
|
145
|
-
# Model settings
|
|
146
234
|
model: gpt-4o-mini
|
|
147
235
|
edit-format: whole
|
|
148
236
|
`;
|
|
@@ -166,6 +254,12 @@ export async function updateGlobalContext(projectName) {
|
|
|
166
254
|
topFiles: sources.slice(0, 5).map(s => s.filePath),
|
|
167
255
|
lastUpdated: new Date().toISOString(),
|
|
168
256
|
};
|
|
257
|
+
if (gitHistory.isRepository()) {
|
|
258
|
+
const branch = gitHistory.getCurrentBranch();
|
|
259
|
+
const authors = await getProjectAuthors(projectName);
|
|
260
|
+
contexts[projectName].gitBranch = branch;
|
|
261
|
+
contexts[projectName].contributors = authors.length;
|
|
262
|
+
}
|
|
169
263
|
fs.writeFileSync(contextPath, JSON.stringify(contexts, null, 2), "utf-8");
|
|
170
264
|
}
|
|
171
265
|
export function watchAndUpdateContext(projectName) {
|
|
@@ -180,7 +274,7 @@ export function watchAndUpdateContext(projectName) {
|
|
|
180
274
|
await generateContextFiles(projectName);
|
|
181
275
|
}, 5000);
|
|
182
276
|
});
|
|
183
|
-
console.log("
|
|
277
|
+
console.log("Watching for file changes...");
|
|
184
278
|
}
|
|
185
279
|
async function getProjectStructure(dir, prefix = "", maxDepth = 3, currentDepth = 0) {
|
|
186
280
|
if (currentDepth >= maxDepth)
|
package/dist/lib/database.js
CHANGED
|
@@ -1,41 +1,109 @@
|
|
|
1
1
|
import { PrismaClient } from "@prisma/client";
|
|
2
2
|
export const client = new PrismaClient();
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
export async function saveToDB(data) {
|
|
4
|
+
await client.codeSnippet.create({
|
|
5
|
+
data: {
|
|
6
|
+
content: data.content,
|
|
7
|
+
embedding: data.embedding,
|
|
8
|
+
filePath: data.filePath,
|
|
9
|
+
projectName: data.projectName,
|
|
10
|
+
language: data.language,
|
|
11
|
+
functionName: data.functionName,
|
|
12
|
+
lineStart: data.lineStart,
|
|
13
|
+
lineEnd: data.lineEnd,
|
|
14
|
+
lastCommitHash: data.lastCommitHash,
|
|
15
|
+
lastCommitAuthor: data.lastCommitAuthor,
|
|
16
|
+
lastCommitEmail: data.lastCommitEmail,
|
|
17
|
+
lastCommitDate: data.lastCommitDate,
|
|
18
|
+
lastCommitMessage: data.lastCommitMessage,
|
|
19
|
+
totalCommits: data.totalCommits,
|
|
20
|
+
primaryAuthor: data.primaryAuthor,
|
|
21
|
+
primaryAuthorEmail: data.primaryAuthorEmail,
|
|
22
|
+
fileOwnerCommits: data.fileOwnerCommits,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export async function saveAuthorToDB(data) {
|
|
27
|
+
await client.gitAuthor.upsert({
|
|
28
|
+
where: {
|
|
29
|
+
projectName_email: {
|
|
30
|
+
projectName: data.projectName,
|
|
31
|
+
email: data.email,
|
|
9
32
|
},
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
33
|
+
},
|
|
34
|
+
create: data,
|
|
35
|
+
update: {
|
|
36
|
+
author: data.author,
|
|
37
|
+
totalCommits: data.totalCommits,
|
|
38
|
+
linesAdded: data.linesAdded,
|
|
39
|
+
linesRemoved: data.linesRemoved,
|
|
40
|
+
recentActivity: data.recentActivity,
|
|
41
|
+
filesOwned: data.filesOwned,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export async function clearProject(projectName) {
|
|
46
|
+
await client.codeSnippet.deleteMany({
|
|
47
|
+
where: { projectName },
|
|
48
|
+
});
|
|
49
|
+
await client.gitAuthor.deleteMany({
|
|
50
|
+
where: { projectName },
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export async function getProjectAuthors(projectName) {
|
|
54
|
+
return await client.gitAuthor.findMany({
|
|
55
|
+
where: { projectName },
|
|
56
|
+
orderBy: { totalCommits: "desc" },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export async function getAuthorFiles(projectName, authorEmail) {
|
|
60
|
+
const snippets = await client.codeSnippet.findMany({
|
|
61
|
+
where: {
|
|
62
|
+
projectName,
|
|
63
|
+
primaryAuthorEmail: authorEmail,
|
|
64
|
+
},
|
|
65
|
+
select: {
|
|
66
|
+
filePath: true,
|
|
67
|
+
functionName: true,
|
|
68
|
+
totalCommits: true,
|
|
69
|
+
},
|
|
70
|
+
orderBy: {
|
|
71
|
+
totalCommits: "desc",
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
const fileMap = new Map();
|
|
75
|
+
snippets.forEach(s => {
|
|
76
|
+
if (!fileMap.has(s.filePath)) {
|
|
77
|
+
fileMap.set(s.filePath, s);
|
|
16
78
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
79
|
+
});
|
|
80
|
+
return Array.from(fileMap.values());
|
|
81
|
+
}
|
|
82
|
+
export async function getRecentlyModifiedFiles(projectName, days = 7) {
|
|
83
|
+
const since = new Date();
|
|
84
|
+
since.setDate(since.getDate() - days);
|
|
85
|
+
const snippets = await client.codeSnippet.findMany({
|
|
86
|
+
where: {
|
|
87
|
+
projectName,
|
|
88
|
+
lastCommitDate: {
|
|
89
|
+
gte: since,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
select: {
|
|
93
|
+
filePath: true,
|
|
94
|
+
lastCommitAuthor: true,
|
|
95
|
+
lastCommitDate: true,
|
|
96
|
+
lastCommitMessage: true,
|
|
97
|
+
},
|
|
98
|
+
orderBy: {
|
|
99
|
+
lastCommitDate: "desc",
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
const fileMap = new Map();
|
|
103
|
+
snippets.forEach(s => {
|
|
104
|
+
if (!fileMap.has(s.filePath)) {
|
|
105
|
+
fileMap.set(s.filePath, s);
|
|
38
106
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
107
|
+
});
|
|
108
|
+
return Array.from(fileMap.values());
|
|
109
|
+
}
|