codereviewerai 1.0.0

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,54 @@
1
+ import chalk from 'chalk';
2
+ import figlet from 'figlet';
3
+ import gradient from 'gradient-string';
4
+ export const homePage = {
5
+ /**
6
+ * Displays the high-tech matrix-style header
7
+ */
8
+ displayWelcome: async () => {
9
+ // Ensure the terminal is clear for the professional 'splash' effect
10
+ console.clear();
11
+ // Fix: Store the color in a constant for cleaner reuse
12
+ const matrixGreen = chalk.hex('#00FF41');
13
+ // Your specific ASCII Art - Wrapped in a raw string to protect backslashes
14
+ console.log(matrixGreen.bold(`
15
+ ██████╗ ██████╗ ██████╗ ███████╗██████╗ ███████╗██╗ ██╗██╗███████╗██╗ ██╗███████╗██████╗
16
+ ██╔════╝██╔═══██╗██╔══██╗██╔════╝██╔══██╗██╔════╝██║ ██║██║██╔════╝██║ ██║██╔════╝██╔══██╗
17
+ ██║ ██║ ██║██║ ██║█████╗ ██████╔╝█████╗ ██║ ██║██║█████╗ ██║ █╗ ██║█████╗ ██████╔╝
18
+ ██║ ██║ ██║██║ ██║██╔══╝ ██╔══██╗██╔══╝ ╚██╗ ██╔╝██║██╔══╝ ██║███╗██║██╔══╝ ██╔══██╗
19
+ ╚██████╗╚██████╔╝██████╔╝███████╗██║ ██║███████╗ ╚████╔╝ ██║███████╗╚███╔███╔╝███████╗██║ ██║
20
+ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═══╝ ╚═╝╚══════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝.ai
21
+ `));
22
+ // Adding a subtle subtitle using gradient
23
+ try {
24
+ const subHeader = figlet.textSync('CODEREVIEWER', {
25
+ font: 'Small',
26
+ horizontalLayout: 'default',
27
+ verticalLayout: 'default',
28
+ });
29
+ console.log(gradient(['#00FF41', '#008F11']).multiline(subHeader));
30
+ }
31
+ catch (err) {
32
+ // Fallback if figlet fails
33
+ console.log(matrixGreen.bold(' --- CODEREVIEWER.AI --- '));
34
+ }
35
+ console.log(chalk.gray('————————————————————————————————————————————————————————————————————'));
36
+ console.log(chalk.white(' Advanced AI Analysis • Performance Optimization • Security Audit'));
37
+ console.log(chalk.gray('————————————————————————————————————————————————————————————————————\n'));
38
+ },
39
+ /**
40
+ * Shows a clean list of available commands
41
+ */
42
+ displayQuickHelp: () => {
43
+ console.log(chalk.bold.white('📂 GETTING STARTED'));
44
+ console.log(` ${chalk.green('awd init')} ${chalk.gray('→ Setup your API keys and provider')}`);
45
+ console.log(` ${chalk.green('awd review')} ${chalk.gray('→ Review staged changes in Git')}`);
46
+ console.log(` ${chalk.green('awd watch')} ${chalk.gray('→ Enable real-time auto-review mode')}`);
47
+ console.log(` ${chalk.green('awd chat')} ${chalk.gray('→ Discuss results with the AI assistant')}\n`);
48
+ console.log(chalk.bold.white('⚙️ SYSTEM'));
49
+ console.log(` ${chalk.green('awd status')} ${chalk.gray('→ Show current configuration')}`);
50
+ console.log(` ${chalk.green('awd history')} ${chalk.gray('→ View previous review scores')}\n`);
51
+ console.log(chalk.hex('#00FF41')('Ready for input...'));
52
+ }
53
+ };
54
+ //# sourceMappingURL=home.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"home.js","sourceRoot":"","sources":["../../src/ui/home.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,QAAQ,MAAM,iBAAiB,CAAC;AAEvC,MAAM,CAAC,MAAM,QAAQ,GAAG;IACpB;;OAEG;IACH,cAAc,EAAE,KAAK,IAAI,EAAE;QACvB,oEAAoE;QACpE,OAAO,CAAC,KAAK,EAAE,CAAC;QAEhB,uDAAuD;QACvD,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAEzC,2EAA2E;QAC3E,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC;;;;;;;SAO5B,CAAC,CAAC,CAAC;QAEJ,0CAA0C;QAC1C,IAAI,CAAC;YACD,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,cAAc,EAAE;gBAC9C,IAAI,EAAE,OAAO;gBACb,gBAAgB,EAAE,SAAS;gBAC3B,cAAc,EAAE,SAAS;aAC5B,CAAC,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC;QACvE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,2BAA2B;YAC3B,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC,CAAC;QACzE,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,sEAAsE,CAAC,CAAC,CAAC;QAChG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,oEAAoE,CAAC,CAAC,CAAC;QAC/F,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,wEAAwE,CAAC,CAAC,CAAC;IACtG,CAAC;IAED;;OAEG;IACH,gBAAgB,EAAE,GAAG,EAAE;QACnB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC,CAAC;QACpD,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,WAAW,KAAK,CAAC,IAAI,CAAC,oCAAoC,CAAC,EAAE,CAAC,CAAC;QACvG,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,KAAK,CAAC,IAAI,CAAC,gCAAgC,CAAC,EAAE,CAAC,CAAC;QACnG,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,UAAU,KAAK,CAAC,IAAI,CAAC,qCAAqC,CAAC,EAAE,CAAC,CAAC;QACxG,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,WAAW,KAAK,CAAC,IAAI,CAAC,yCAAyC,CAAC,IAAI,CAAC,CAAC;QAE9G,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;QAC5C,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,KAAK,CAAC,IAAI,CAAC,8BAA8B,CAAC,EAAE,CAAC,CAAC;QACjG,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,QAAQ,KAAK,CAAC,IAAI,CAAC,+BAA+B,CAAC,IAAI,CAAC,CAAC;QAEpG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAC5D,CAAC;CACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "codereviewerai",
3
+ "version": "1.0.0",
4
+ "description": "codereviewer.ai - AI-powered code review tool for developers",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "awd": "./bin/awesomediagns.js",
9
+ "awesomediagns": "./bin/awesomediagns.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "dev": "ts-node src/index.ts",
14
+ "start": "node dist/index.js",
15
+ "watch": "tsc --watch",
16
+ "prepublishOnly": "npm run build"
17
+ },
18
+ "keywords": [
19
+ "code-review",
20
+ "ai",
21
+ "cli",
22
+ "productivity",
23
+ "git",
24
+ "llm",
25
+ "automation"
26
+ ],
27
+ "author": "CodeReviewer.ai Team",
28
+ "license": "MIT",
29
+ "engines": {
30
+ "node": ">=18.0.0"
31
+ },
32
+ "dependencies": {
33
+ "axios": "^1.6.2",
34
+ "boxen": "^8.0.1",
35
+ "chalk": "^5.3.0",
36
+ "chokidar": "^4.0.1",
37
+ "commander": "^12.1.0",
38
+ "conf": "^13.0.2",
39
+ "dotenv": "^16.4.5",
40
+ "figlet": "^1.8.0",
41
+ "gradient-string": "^3.0.0",
42
+ "inquirer": "^12.0.0",
43
+ "ora": "^8.1.0",
44
+ "simple-git": "^3.27.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/figlet": "^1.7.0",
48
+ "@types/node": "^22.0.0",
49
+ "ts-node": "^10.9.2",
50
+ "typescript": "^5.6.0"
51
+ }
52
+ }
@@ -0,0 +1,170 @@
1
+ import axios from 'axios';
2
+ import chalk from 'chalk';
3
+ import { ChatMessage } from '../core/context.js';
4
+
5
+ export class AIProvider {
6
+ private config: any;
7
+
8
+ constructor(configManager: any) {
9
+ this.config = configManager.getConfig();
10
+ }
11
+
12
+ /**
13
+ * The main function to send code to the AI
14
+ */
15
+ async reviewCode(code: string, filePath: string, context?: any): Promise<any> {
16
+ const prompt = this.buildReviewPrompt(code, filePath, context);
17
+
18
+ try {
19
+ switch (this.config.provider) {
20
+ case 'gemini':
21
+ return await this.reviewWithGemini(prompt);
22
+ case 'openai':
23
+ return await this.reviewWithOpenAI(prompt);
24
+ case 'claude':
25
+ return await this.reviewWithClaude(prompt);
26
+ default:
27
+ throw new Error('No AI provider configured. Run "awd init"');
28
+ }
29
+ } catch (error: any) {
30
+ throw new Error(`AI Request Failed: ${error.response?.data?.error?.message || error.message}`);
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Chat with the AI using conversation history
36
+ */
37
+ async chat(message: string, history: ChatMessage[]): Promise<string> {
38
+ try {
39
+ switch (this.config.provider) {
40
+ case 'gemini':
41
+ return await this.chatWithGemini(message, history);
42
+ case 'openai':
43
+ return await this.chatWithOpenAI(message, history);
44
+ case 'claude':
45
+ return await this.chatWithClaude(message, history);
46
+ default:
47
+ throw new Error('No AI provider configured. Run "awd init"');
48
+ }
49
+ } catch (error: any) {
50
+ throw new Error(`AI Chat Failed: ${error.response?.data?.error?.message || error.message}`);
51
+ }
52
+ }
53
+
54
+ private buildReviewPrompt(code: string, filePath: string, context?: any): string {
55
+ return `
56
+ You are "codereviewer.ai", an expert senior developer.
57
+ Review the following code for file: ${filePath}
58
+
59
+ CRITERIA:
60
+ 1. Identify bugs or security flaws.
61
+ 2. Suggest performance optimizations.
62
+ 3. Check for best practices and readability.
63
+
64
+ CONTEXT FROM PREVIOUS CHATS:
65
+ ${JSON.stringify(context || "None")}
66
+
67
+ CODE TO REVIEW:
68
+ \`\`\`
69
+ ${code}
70
+ \`\`\`
71
+
72
+ RESPONSE FORMAT:
73
+ You MUST respond ONLY with a valid JSON object. Do not include markdown formatting or prose.
74
+ {
75
+ "summary": "Brief overall thought",
76
+ "score": 1-10,
77
+ "issues": [{"line": number, "type": "bug|style|security", "msg": "description", "fix": "suggested code"}],
78
+ "optimizations": ["list of tips"]
79
+ }
80
+ `;
81
+ }
82
+
83
+ private async reviewWithGemini(prompt: string) {
84
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=${this.config.apiKey}`;
85
+ const response = await axios.post(url, {
86
+ contents: [{ parts: [{ text: prompt }] }]
87
+ });
88
+ const text = response.data.candidates[0].content.parts[0].text;
89
+ return JSON.parse(this.cleanJSON(text));
90
+ }
91
+
92
+ private async reviewWithOpenAI(prompt: string) {
93
+ const response = await axios.post('https://api.openai.com/v1/chat/completions', {
94
+ model: 'gpt-4-turbo-preview',
95
+ messages: [{ role: 'user', content: prompt }],
96
+ response_format: { type: "json_object" }
97
+ }, {
98
+ headers: { Authorization: `Bearer ${this.config.apiKey}` }
99
+ });
100
+ return JSON.parse(response.data.choices[0].message.content);
101
+ }
102
+
103
+ private async reviewWithClaude(prompt: string) {
104
+ // Logic for Claude API (Anthropic)
105
+ const response = await axios.post('https://api.anthropic.com/v1/messages', {
106
+ model: 'claude-3-sonnet-20240229',
107
+ max_tokens: 1024,
108
+ messages: [{ role: 'user', content: prompt }]
109
+ }, {
110
+ headers: {
111
+ 'x-api-key': this.config.apiKey,
112
+ 'anthropic-version': '2023-06-01'
113
+ }
114
+ });
115
+ return JSON.parse(this.cleanJSON(response.data.content[0].text));
116
+ }
117
+
118
+ private async chatWithGemini(message: string, history: ChatMessage[]): Promise<string> {
119
+ const prompt = this.buildChatPrompt(message, history);
120
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=${this.config.apiKey}`;
121
+ const response = await axios.post(url, {
122
+ contents: [{ parts: [{ text: prompt }] }]
123
+ });
124
+ return response.data.candidates[0].content.parts[0].text;
125
+ }
126
+
127
+ private async chatWithOpenAI(message: string, history: ChatMessage[]): Promise<string> {
128
+ const messages = history.map(h => ({ role: h.role, content: h.content }));
129
+ messages.push({ role: 'user', content: message });
130
+ const response = await axios.post('https://api.openai.com/v1/chat/completions', {
131
+ model: 'gpt-4-turbo-preview',
132
+ messages: messages
133
+ }, {
134
+ headers: { Authorization: `Bearer ${this.config.apiKey}` }
135
+ });
136
+ return response.data.choices[0].message.content;
137
+ }
138
+
139
+ private async chatWithClaude(message: string, history: ChatMessage[]): Promise<string> {
140
+ const messages = history.map(h => ({ role: h.role, content: h.content }));
141
+ messages.push({ role: 'user', content: message });
142
+ const response = await axios.post('https://api.anthropic.com/v1/messages', {
143
+ model: 'claude-3-sonnet-20240229',
144
+ max_tokens: 1024,
145
+ messages: messages
146
+ }, {
147
+ headers: {
148
+ 'x-api-key': this.config.apiKey,
149
+ 'anthropic-version': '2023-06-01'
150
+ }
151
+ });
152
+ return response.data.content[0].text;
153
+ }
154
+
155
+ private buildChatPrompt(message: string, history: ChatMessage[]): string {
156
+ let prompt = "You are codereviewer.ai, an expert developer assistant. Help with code-related questions.\n\n";
157
+ for (const msg of history) {
158
+ prompt += `${msg.role}: ${msg.content}\n`;
159
+ }
160
+ prompt += `user: ${message}\nassistant:`;
161
+ return prompt;
162
+ }
163
+
164
+ /**
165
+ * Removes Markdown code blocks if the AI accidentally includes them
166
+ */
167
+ private cleanJSON(text: string): string {
168
+ return text.replace(/```json|```/g, "").trim();
169
+ }
170
+ }
@@ -0,0 +1,120 @@
1
+ import Conf from 'conf';
2
+ import chalk from 'chalk';
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+
6
+ // 1. Defined a strict schema for the config to prevent type errors
7
+ export interface AppConfig {
8
+ provider: 'openai' | 'gemini' | 'claude';
9
+ apiKey: string;
10
+ model: string;
11
+ reviewDepth: 'quick' | 'deep';
12
+ mode: 'manual' | 'auto';
13
+ includeContext: boolean;
14
+ maxContextMessages: number;
15
+ projectPath?: string;
16
+ }
17
+
18
+ export class ConfigManager {
19
+ // We cast the Conf instance to allow better flexibility with the 'conf' library versions
20
+ private config: Conf<AppConfig>;
21
+
22
+ constructor() {
23
+ this.config = new Conf<AppConfig>({
24
+ projectName: 'codereviewerai',
25
+ // Default settings if the user hasn't run 'init'
26
+ defaults: {
27
+ provider: 'gemini',
28
+ apiKey: '',
29
+ model: 'gemini-1.5-pro',
30
+ reviewDepth: 'deep',
31
+ mode: 'manual',
32
+ includeContext: true,
33
+ maxContextMessages: 10
34
+ }
35
+ });
36
+ }
37
+
38
+ isConfigured(): boolean {
39
+ const key = this.config.get('apiKey');
40
+ return typeof key === 'string' && key.length > 0;
41
+ }
42
+
43
+ // Fixed: Explicitly return AppConfig to ensure UI elements can read keys safely
44
+ getConfig(): AppConfig {
45
+ return this.config.store as AppConfig;
46
+ }
47
+
48
+ // Fixed: Standardized update method
49
+ setKey<K extends keyof AppConfig>(key: K, value: AppConfig[K]): void {
50
+ this.config.set(key, value);
51
+ }
52
+
53
+ // Helper for saving multiple answers from Inquirer at once
54
+ setFullConfig(answers: Partial<AppConfig>): void {
55
+ for (const [key, value] of Object.entries(answers)) {
56
+ this.config.set(key as keyof AppConfig, value);
57
+ }
58
+ }
59
+
60
+ displayConfig(): void {
61
+ const store = this.getConfig();
62
+ console.log(chalk.cyan.bold('\n🛠️ Current Configuration:'));
63
+
64
+ console.log(`
65
+ ${chalk.yellow('Provider:')} ${store.provider}
66
+ ${chalk.yellow('Model:')} ${store.model}
67
+ ${chalk.yellow('Mode:')} ${store.mode}
68
+ ${chalk.yellow('Depth:')} ${store.reviewDepth}
69
+ ${chalk.yellow('API Key:')} ${store.apiKey ? '********' + store.apiKey.slice(-4) : chalk.red('Not Set')}
70
+ `);
71
+ }
72
+
73
+ /**
74
+ * LOCAL PROJECT LOGIC
75
+ * This creates the .awesomediagns folder in the CURRENT working directory
76
+ */
77
+ initProject(projectPath: string): void {
78
+ this.setKey('projectPath', projectPath);
79
+
80
+ const localDir = path.join(projectPath, '.awesomediagns');
81
+ const historyDir = path.join(localDir, 'history');
82
+
83
+ if (!fs.existsSync(localDir)) {
84
+ fs.mkdirSync(localDir, { recursive: true });
85
+ fs.mkdirSync(historyDir, { recursive: true });
86
+
87
+ // Auto-ignore the local metadata from Git
88
+ const gitignore = path.join(projectPath, '.gitignore');
89
+ const ignoreEntry = '\n# AwesomeDiagns metadata\n.awesomediagns/\n';
90
+
91
+ if (fs.existsSync(gitignore)) {
92
+ const content = fs.readFileSync(gitignore, 'utf8');
93
+ if (!content.includes('.awesomediagns')) {
94
+ fs.appendFileSync(gitignore, ignoreEntry);
95
+ }
96
+ } else {
97
+ fs.writeFileSync(gitignore, ignoreEntry);
98
+ }
99
+ console.log(chalk.green('✅ Project initialized with local context folder.'));
100
+ }
101
+ }
102
+
103
+ // Check if the current folder is an AWD project
104
+ isProjectInitialized(currentPath: string = process.cwd()): boolean {
105
+ return fs.existsSync(path.join(currentPath, '.awesomediagns'));
106
+ }
107
+
108
+ // Useful for a fallback to find files if needed
109
+ getProjectConfig() {
110
+ return {
111
+ watchedFiles: ['*.ts', '*.js', '*.jsx', '*.tsx', '*.py', '*.go'],
112
+ ignoredPatterns: ['node_modules', 'dist', '.git']
113
+ };
114
+ }
115
+
116
+ clearConfig(): void {
117
+ this.config.clear();
118
+ console.log(chalk.red('🗑️ Global configuration wiped.'));
119
+ }
120
+ }
@@ -0,0 +1,93 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+ import { ConfigManager, AppConfig } from './manager.js';
4
+
5
+ export class ConfigWizard {
6
+ private manager: ConfigManager;
7
+
8
+ constructor() {
9
+ this.manager = new ConfigManager();
10
+ }
11
+
12
+ /**
13
+ * The main setup flow for a new user
14
+ */
15
+ async runSetup(): Promise<void> {
16
+ console.log(chalk.cyan.bold('\n✨ codereviewer.ai Setup Wizard ✨'));
17
+ console.log(chalk.gray('Let\'s configure your AI preferences.\n'));
18
+
19
+ const answers = await inquirer.prompt([
20
+ {
21
+ type: 'list',
22
+ name: 'provider',
23
+ message: 'Select your preferred AI Provider:',
24
+ choices: [
25
+ { name: 'Google Gemini (Fast & Large Context)', value: 'gemini' },
26
+ { name: 'OpenAI (GPT-4o)', value: 'openai' },
27
+ { name: 'Anthropic (Claude 3.5 Sonnet)', value: 'claude' }
28
+ ]
29
+ },
30
+ {
31
+ type: 'password',
32
+ name: 'apiKey',
33
+ message: 'Enter your API Key:',
34
+ mask: '*',
35
+ validate: (input: string) => input.length > 0 || 'API Key is required.'
36
+ },
37
+ {
38
+ type: 'list',
39
+ name: 'reviewDepth',
40
+ message: 'Default Review Depth:',
41
+ choices: [
42
+ { name: 'Quick (Focus on critical bugs)', value: 'quick' },
43
+ { name: 'Deep (Full architectural analysis)', value: 'deep' }
44
+ ]
45
+ },
46
+ {
47
+ type: 'list',
48
+ name: 'mode',
49
+ message: 'Operation Mode:',
50
+ choices: [
51
+ { name: 'Manual (Review when I run the command)', value: 'manual' },
52
+ { name: 'Auto (Review every time I save a file)', value: 'auto' }
53
+ ]
54
+ }
55
+ ]);
56
+
57
+ // Map models based on the provider chosen
58
+ let defaultModel = 'gemini-1.5-pro';
59
+ if (answers.provider === 'openai') defaultModel = 'gpt-4o';
60
+ if (answers.provider === 'claude') defaultModel = 'claude-3-5-sonnet-20240620';
61
+
62
+ // Save everything to the manager
63
+ this.manager.setFullConfig({
64
+ ...answers,
65
+ model: defaultModel,
66
+ includeContext: true,
67
+ maxContextMessages: 10
68
+ });
69
+
70
+ console.log(chalk.green.bold('\n✅ Configuration successful!'));
71
+ console.log(chalk.gray('You can now run ') + chalk.white.bold('awd review') + chalk.gray(' to start your first review.\n'));
72
+ }
73
+
74
+ /**
75
+ * Allows reconfiguring a specific setting without a full reset
76
+ */
77
+ async reconfigure(type: 'provider' | 'mode'): Promise<void> {
78
+ if (type === 'provider') {
79
+ const { provider, apiKey } = await inquirer.prompt([
80
+ { type: 'list', name: 'provider', message: 'New Provider:', choices: ['gemini', 'openai', 'claude'] },
81
+ { type: 'password', name: 'apiKey', message: 'New API Key:' }
82
+ ]);
83
+ this.manager.setKey('provider', provider);
84
+ this.manager.setKey('apiKey', apiKey);
85
+ } else {
86
+ const { mode } = await inquirer.prompt([
87
+ { type: 'list', name: 'mode', message: 'Switch Mode:', choices: ['manual', 'auto'] }
88
+ ]);
89
+ this.manager.setKey('mode', mode);
90
+ }
91
+ console.log(chalk.green(`✅ ${type} updated successfully.`));
92
+ }
93
+ }
@@ -0,0 +1,139 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+
5
+ export interface ReviewEntry {
6
+ file: string;
7
+ timestamp: string;
8
+ code: string;
9
+ review: any;
10
+ score: number;
11
+ }
12
+
13
+ export interface ChatMessage {
14
+ role: 'user' | 'assistant' | 'system';
15
+ content: string;
16
+ }
17
+
18
+ export class ContextManager {
19
+ private contextDir: string;
20
+ private historyFile: string;
21
+ private chatFile: string;
22
+
23
+ constructor() {
24
+ // This looks for the local project folder created by ConfigManager
25
+ this.contextDir = path.join(process.cwd(), '.awesomediagns');
26
+ this.historyFile = path.join(this.contextDir, 'history', 'reviews.json');
27
+ this.chatFile = path.join(this.contextDir, 'history', 'chat.json');
28
+ this.ensureDirectories();
29
+ }
30
+
31
+ /**
32
+ * Ensures the local .awesomediagns folder exists before writing
33
+ */
34
+ private ensureDirectories(): void {
35
+ const historyDir = path.join(this.contextDir, 'history');
36
+
37
+ if (!fs.existsSync(historyDir)) {
38
+ try {
39
+ fs.mkdirSync(historyDir, { recursive: true });
40
+ } catch (err) {
41
+ // If not in a project, we don't crash, but we can't save context
42
+ return;
43
+ }
44
+ }
45
+
46
+ if (!fs.existsSync(this.historyFile)) {
47
+ fs.writeFileSync(this.historyFile, JSON.stringify([], null, 2));
48
+ }
49
+ if (!fs.existsSync(this.chatFile)) {
50
+ fs.writeFileSync(this.chatFile, JSON.stringify([], null, 2));
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Gets previous review data for a specific file to give AI "memory"
56
+ */
57
+ async getContext(filePath: string): Promise<any> {
58
+ try {
59
+ const history = this.loadHistory();
60
+ const fileHistory = history
61
+ .filter((entry: ReviewEntry) => entry.file === filePath)
62
+ .slice(-3); // Last 3 reviews for this specific file
63
+
64
+ return {
65
+ previousReviews: fileHistory.map((entry: ReviewEntry) => ({
66
+ timestamp: entry.timestamp,
67
+ score: entry.score,
68
+ summary: entry.review.summary
69
+ }))
70
+ };
71
+ } catch (error) {
72
+ return { previousReviews: [] };
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Saves a new review into the local project history
78
+ */
79
+ async addToContext(filePath: string, code: string, review: any): Promise<void> {
80
+ try {
81
+ const history = this.loadHistory();
82
+ const entry: ReviewEntry = {
83
+ file: filePath,
84
+ timestamp: new Date().toISOString(),
85
+ code: code.length > 500 ? code.substring(0, 500) + '...' : code,
86
+ review,
87
+ score: review.score || 0
88
+ };
89
+
90
+ history.push(entry);
91
+ // Limit to last 50 reviews to keep file size small
92
+ const limitedHistory = history.slice(-50);
93
+ fs.writeFileSync(this.historyFile, JSON.stringify(limitedHistory, null, 2));
94
+ } catch (error) {
95
+ console.error(chalk.red('Failed to save review context locally.'));
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Conversation History for the AI Chat mode
101
+ */
102
+ async getChatHistory(): Promise<ChatMessage[]> {
103
+ if (!fs.existsSync(this.chatFile)) return [];
104
+ try {
105
+ const data = fs.readFileSync(this.chatFile, 'utf-8');
106
+ return JSON.parse(data);
107
+ } catch {
108
+ return [];
109
+ }
110
+ }
111
+
112
+ async saveChatHistory(messages: ChatMessage[]): Promise<void> {
113
+ try {
114
+ fs.writeFileSync(this.chatFile, JSON.stringify(messages, null, 2));
115
+ } catch (error) {
116
+ console.error(chalk.red('Failed to save chat history.'));
117
+ }
118
+ }
119
+
120
+ async getHistory(limit: number = 10): Promise<ReviewEntry[]> {
121
+ const history = this.loadHistory();
122
+ return history.slice(-limit).reverse();
123
+ }
124
+
125
+ async clearHistory(): Promise<void> {
126
+ fs.writeFileSync(this.historyFile, JSON.stringify([], null, 2));
127
+ fs.writeFileSync(this.chatFile, JSON.stringify([], null, 2));
128
+ }
129
+
130
+ private loadHistory(): ReviewEntry[] {
131
+ try {
132
+ if (!fs.existsSync(this.historyFile)) return [];
133
+ const data = fs.readFileSync(this.historyFile, 'utf-8');
134
+ return JSON.parse(data);
135
+ } catch (error) {
136
+ return [];
137
+ }
138
+ }
139
+ }