codeflow-hook 2.1.0 ā 2.2.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.
- package/bin/codeflow-hook.js +232 -164
- package/lib/ai-reviewer.cjs +422 -0
- package/lib/cli-integration/src/index.ts +133 -220
- package/lib/cli-integration/src/simulationEngine.ts +118 -4
- package/package.json +2 -2
- package/lib/cli-integration/dist/index.d.ts +0 -128
- package/lib/cli-integration/dist/index.js +0 -585
- package/lib/cli-integration/dist/pipelineConfigs.d.ts +0 -60
- package/lib/cli-integration/dist/pipelineConfigs.js +0 -549
- package/lib/cli-integration/dist/simulationEngine.d.ts +0 -86
- package/lib/cli-integration/dist/simulationEngine.js +0 -475
- package/lib/cli-integration/dist/types.d.ts +0 -156
- package/lib/cli-integration/dist/types.js +0 -15
package/bin/codeflow-hook.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import chalk from 'chalk';
|
|
@@ -11,7 +11,7 @@ import readline from 'readline';
|
|
|
11
11
|
import { orchestrateReview } from './agents.js';
|
|
12
12
|
|
|
13
13
|
// Import CLI integration service
|
|
14
|
-
import { indexProject
|
|
14
|
+
import { indexProject } from '../lib/cli-integration/dist/index.js';
|
|
15
15
|
|
|
16
16
|
// Export for use in agents module
|
|
17
17
|
export { callAIProvider };
|
|
@@ -26,13 +26,16 @@ program
|
|
|
26
26
|
// Configure AI provider settings
|
|
27
27
|
program
|
|
28
28
|
.command('config')
|
|
29
|
-
.description('Configure AI provider settings')
|
|
30
|
-
.option('-p, --provider <provider>', 'AI provider (gemini, openai, claude)', 'gemini')
|
|
31
|
-
.option('-k, --key <key>', 'API key for
|
|
32
|
-
.option('-u, --url <url>', 'Custom API URL (
|
|
33
|
-
.option('-m, --model <model>', 'AI model name (
|
|
29
|
+
.description('Configure AI provider settings (gemini, openai, claude, ollama)')
|
|
30
|
+
.option('-p, --provider <provider>', 'AI provider (gemini, openai, claude, ollama)', 'gemini')
|
|
31
|
+
.option('-k, --key <key>', 'API key for cloud providers (not needed for ollama)')
|
|
32
|
+
.option('-u, --url <url>', 'Custom API URL (for ollama: http://localhost:11434)')
|
|
33
|
+
.option('-m, --model <model>', 'AI model name (for ollama: auto-discovered if not specified)')
|
|
34
|
+
.option('--ollama-enable', 'Enable Ollama as the primary provider')
|
|
35
|
+
.option('--ollama-disable', 'Disable Ollama and use cloud provider instead')
|
|
36
|
+
.option('--ollama-url <url>', 'Ollama server URL (default: http://localhost:11434)')
|
|
37
|
+
.option('--list-models', 'List available Ollama models and exit')
|
|
34
38
|
.action(async (options) => {
|
|
35
|
-
// Use USERPROFILE on Windows instead of HOME which might be undefined
|
|
36
39
|
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
37
40
|
const configDir = path.join(homeDir, '.codeflow-hook');
|
|
38
41
|
if (!fs.existsSync(configDir)) {
|
|
@@ -40,164 +43,186 @@ program
|
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
const configPath = path.join(configDir, 'config.json');
|
|
43
|
-
|
|
44
|
-
// Configuration cascade: Check for project-level config first, then fall back to global
|
|
45
|
-
const projectConfigPath = path.join(process.cwd(), '.codeflowrc.json');
|
|
46
|
-
const projectConfig = fs.existsSync(projectConfigPath) ? JSON.parse(fs.readFileSync(projectConfigPath, 'utf8')) : {};
|
|
47
|
-
|
|
48
46
|
const existingConfig = fs.existsSync(configPath) ? JSON.parse(fs.readFileSync(configPath, 'utf8')) : {};
|
|
49
47
|
|
|
50
|
-
//
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
console.log(chalk.
|
|
54
|
-
if (fs.existsSync(configPath)) {
|
|
55
|
-
console.log(chalk.gray(` Global config: ${configPath}`));
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Determine what the new provider and API key should be
|
|
60
|
-
const requestedProvider = (options.provider || existingConfig.provider || 'gemini').toLowerCase();
|
|
61
|
-
const requestedApiKey = options.key || existingConfig.apiKey;
|
|
62
|
-
|
|
63
|
-
// FIX: Explicit boolean coercion to prevent || operator from returning non-boolean values
|
|
64
|
-
// Step by step to ensure proper boolean evaluation
|
|
65
|
-
const hasNewKey = !!options.key;
|
|
66
|
-
const isFirstTimeSetup = !existingConfig.provider && !existingConfig.apiKey;
|
|
67
|
-
const shouldValidate = hasNewKey || isFirstTimeSetup;
|
|
68
|
-
|
|
69
|
-
console.log('VALIDATION DEBUG - provider:', requestedProvider, 'key_exists:', !!options.key, 'shouldValidate:', shouldValidate);
|
|
70
|
-
|
|
71
|
-
if (shouldValidate && requestedApiKey) {
|
|
72
|
-
console.log(chalk.blue('š Validating API key for provider:', requestedProvider));
|
|
73
|
-
const validationSpinner = ora('Checking key permissions...').start();
|
|
74
|
-
|
|
48
|
+
// List Ollama models mode
|
|
49
|
+
if (options.listModels) {
|
|
50
|
+
const ollamaUrl = options.ollamaUrl || existingConfig.ollama?.url || 'http://localhost:11434';
|
|
51
|
+
console.log(chalk.blue(`š Fetching models from Ollama at ${ollamaUrl}...`));
|
|
75
52
|
try {
|
|
76
|
-
await
|
|
77
|
-
|
|
53
|
+
const { listOllamaModels, isOllamaRunning } = await import('../lib/ai-reviewer.cjs');
|
|
54
|
+
const running = await isOllamaRunning(ollamaUrl);
|
|
55
|
+
if (!running) {
|
|
56
|
+
console.log(chalk.red('ā Ollama is not running or not reachable'));
|
|
57
|
+
console.log(chalk.yellow(`š” Start Ollama: ollama serve`));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
const models = await listOllamaModels(ollamaUrl);
|
|
61
|
+
if (models.length === 0) {
|
|
62
|
+
console.log(chalk.yellow('ā ļø No models found. Pull a model first:'));
|
|
63
|
+
console.log(chalk.gray(' ollama pull qwen2.5-coder'));
|
|
64
|
+
console.log(chalk.gray(' ollama pull deepseek-coder'));
|
|
65
|
+
console.log(chalk.gray(' ollama pull codellama'));
|
|
66
|
+
} else {
|
|
67
|
+
console.log(chalk.green(`ā
Found ${models.length} model(s):`));
|
|
68
|
+
models.forEach((m, i) => console.log(chalk.gray(` ${i + 1}. ${m}`)));
|
|
69
|
+
}
|
|
70
|
+
process.exit(0);
|
|
78
71
|
} catch (error) {
|
|
79
|
-
|
|
80
|
-
console.error(chalk.red(`ā ${error.message}`));
|
|
81
|
-
console.error(chalk.red(`š” Make sure you're using a valid ${requestedProvider.toUpperCase()} API key for the ${requestedProvider} provider.`));
|
|
72
|
+
console.log(chalk.red(`ā Failed to connect to Ollama: ${error.message}`));
|
|
82
73
|
process.exit(1);
|
|
83
74
|
}
|
|
84
|
-
} else {
|
|
85
|
-
console.log(chalk.yellow('ā ļø Validation skipped - no API key validation needed'));
|
|
86
75
|
}
|
|
87
76
|
|
|
88
|
-
|
|
77
|
+
const requestedProvider = (options.provider || existingConfig.provider || 'gemini').toLowerCase();
|
|
78
|
+
const ollamaEnabled = options.ollamaEnable === true ||
|
|
79
|
+
(options.ollamaEnable === undefined && options.ollamaDisable === undefined && existingConfig.ollama?.enabled === true) ||
|
|
80
|
+
requestedProvider === 'ollama';
|
|
81
|
+
|
|
82
|
+
// Initialize config structure
|
|
89
83
|
const config = {
|
|
90
|
-
provider: requestedProvider,
|
|
91
|
-
apiKey:
|
|
84
|
+
provider: requestedProvider === 'ollama' ? 'ollama' : (existingConfig.provider || 'gemini'),
|
|
85
|
+
apiKey: options.key || existingConfig.apiKey,
|
|
92
86
|
apiUrl: options.url || existingConfig.apiUrl,
|
|
93
|
-
model: options.model || existingConfig.model
|
|
87
|
+
model: options.model || existingConfig.model,
|
|
88
|
+
ollama: {
|
|
89
|
+
enabled: ollamaEnabled,
|
|
90
|
+
url: options.ollamaUrl || existingConfig.ollama?.url || 'http://localhost:11434'
|
|
91
|
+
}
|
|
94
92
|
};
|
|
95
93
|
|
|
96
|
-
//
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const hasNewKey = !!options.key;
|
|
94
|
+
// If Ollama is the primary provider, discover models
|
|
95
|
+
if (config.ollama.enabled) {
|
|
96
|
+
console.log(chalk.blue('š¦ Ollama provider detected'));
|
|
97
|
+
console.log(chalk.gray(` URL: ${config.ollama.url}`));
|
|
101
98
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const selectedModel = await new Promise(resolve => {
|
|
123
|
-
rl.question(chalk.blue(`Enter the model name (or number): `), (input) => {
|
|
124
|
-
rl.close();
|
|
125
|
-
resolve(input.trim());
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// Check if user entered a number
|
|
130
|
-
const index = parseInt(selectedModel) - 1;
|
|
131
|
-
if (!isNaN(index) && index >= 0 && index < models.length) {
|
|
132
|
-
config.model = models[index];
|
|
99
|
+
try {
|
|
100
|
+
const { listOllamaModels, isOllamaRunning } = await import('../lib/ai-reviewer.cjs');
|
|
101
|
+
const running = await isOllamaRunning(config.ollama.url);
|
|
102
|
+
|
|
103
|
+
if (!running) {
|
|
104
|
+
console.log(chalk.yellow('ā ļø Ollama is not running or not reachable'));
|
|
105
|
+
console.log(chalk.yellow(' Models will not be auto-discovered'));
|
|
106
|
+
if (!config.model) {
|
|
107
|
+
console.log(chalk.gray(' Using default model: qwen2.5-coder'));
|
|
108
|
+
config.model = 'qwen2.5-coder';
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
const models = await listOllamaModels(config.ollama.url);
|
|
112
|
+
if (models.length === 0) {
|
|
113
|
+
console.log(chalk.yellow('ā ļø No Ollama models found'));
|
|
114
|
+
console.log(chalk.gray(' Pull a model: ollama pull qwen2.5-coder'));
|
|
115
|
+
if (!config.model) {
|
|
116
|
+
config.model = 'qwen2.5-coder';
|
|
117
|
+
}
|
|
133
118
|
} else {
|
|
134
|
-
|
|
119
|
+
console.log(chalk.green(`ā
Found ${models.length} model(s):`));
|
|
120
|
+
models.forEach((m, i) => console.log(chalk.gray(` ${i + 1}. ${m}`)));
|
|
121
|
+
|
|
122
|
+
// Auto-select or prompt
|
|
123
|
+
if (!options.model) {
|
|
124
|
+
const defaultModel = models.find(m => m.includes('coder') || m.includes('code')) || models[0];
|
|
125
|
+
console.log(chalk.blue(`\nš” Recommended: ${defaultModel}`));
|
|
126
|
+
|
|
127
|
+
const rl = readline.createInterface({
|
|
128
|
+
input: process.stdin,
|
|
129
|
+
output: process.stdout
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const input = await new Promise(resolve => {
|
|
133
|
+
rl.question(chalk.blue(`Select model (name or number, Enter for ${defaultModel}): `), (answer) => {
|
|
134
|
+
rl.close();
|
|
135
|
+
resolve(answer.trim());
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (input === '') {
|
|
140
|
+
config.model = defaultModel;
|
|
141
|
+
} else {
|
|
142
|
+
const idx = parseInt(input) - 1;
|
|
143
|
+
config.model = (!isNaN(idx) && idx >= 0 && idx < models.length) ? models[idx] : input;
|
|
144
|
+
}
|
|
145
|
+
console.log(chalk.green(`ā Selected: ${config.model}`));
|
|
146
|
+
}
|
|
135
147
|
}
|
|
148
|
+
}
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.log(chalk.yellow(`ā ļø Could not connect to Ollama: ${error.message}`));
|
|
151
|
+
if (!config.model) {
|
|
152
|
+
config.model = 'qwen2.5-coder';
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} else if (options.key || !existingConfig.apiKey) {
|
|
156
|
+
// Cloud provider setup with API key validation
|
|
157
|
+
const hasNewKey = !!options.key;
|
|
158
|
+
const isFirstTime = !existingConfig.provider && !existingConfig.apiKey;
|
|
159
|
+
const shouldValidate = hasNewKey || isFirstTime;
|
|
136
160
|
|
|
137
|
-
|
|
138
|
-
|
|
161
|
+
if (shouldValidate && config.apiKey) {
|
|
162
|
+
console.log(chalk.blue(`š Validating API key for ${config.provider}...`));
|
|
163
|
+
try {
|
|
164
|
+
await validateApiKey(config.provider, config.apiKey);
|
|
165
|
+
console.log(chalk.green('ā
API key validated'));
|
|
139
166
|
} catch (error) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
console.log(chalk.yellow('ā ļø Using fallback model list:'));
|
|
146
|
-
fallbackModels.forEach((model, index) => {
|
|
147
|
-
console.log(chalk.gray(` ${index + 1}. ${model}`));
|
|
148
|
-
});
|
|
167
|
+
console.log(chalk.red(`ā ${error.message}`));
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
149
171
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
172
|
+
// Set default API URL for cloud providers
|
|
173
|
+
if (!config.apiUrl) {
|
|
174
|
+
switch (config.provider) {
|
|
175
|
+
case 'openai':
|
|
176
|
+
config.apiUrl = 'https://api.openai.com/v1/chat/completions';
|
|
177
|
+
break;
|
|
178
|
+
case 'claude':
|
|
179
|
+
config.apiUrl = 'https://api.anthropic.com/v1/messages';
|
|
180
|
+
break;
|
|
181
|
+
case 'gemini':
|
|
182
|
+
default:
|
|
183
|
+
config.apiUrl = 'https://generativelanguage.googleapis.com/v1beta/models';
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
154
187
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
188
|
+
// Interactive model selection for cloud providers
|
|
189
|
+
if (!options.model) {
|
|
190
|
+
const shouldFetch = !existingConfig.model || existingConfig.provider !== config.provider || hasNewKey;
|
|
191
|
+
if (shouldFetch) {
|
|
192
|
+
console.log(chalk.blue('š Fetching available models...'));
|
|
193
|
+
try {
|
|
194
|
+
const models = await fetchModels(config.provider, config.apiKey);
|
|
195
|
+
console.log(chalk.blue('Available models:'));
|
|
196
|
+
models.forEach((m, i) => console.log(chalk.gray(` ${i + 1}. ${m}`)));
|
|
197
|
+
|
|
198
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
199
|
+
const input = await new Promise(resolve => {
|
|
200
|
+
rl.question(chalk.blue('Enter model name or number: '), (a) => { rl.close(); resolve(a.trim()); });
|
|
159
201
|
});
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
202
|
+
const idx = parseInt(input) - 1;
|
|
203
|
+
config.model = (!isNaN(idx) && idx >= 0 && idx < models.length) ? models[idx] : input;
|
|
204
|
+
} catch {
|
|
205
|
+
const fallbacks = getFallbackModels(config.provider);
|
|
206
|
+
fallbacks.forEach((m, i) => console.log(chalk.gray(` ${i + 1}. ${m}`)));
|
|
207
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
208
|
+
const input = await new Promise(resolve => {
|
|
209
|
+
rl.question(chalk.blue('Select model: '), (a) => { rl.close(); resolve(a.trim()); });
|
|
210
|
+
});
|
|
211
|
+
const idx = parseInt(input) - 1;
|
|
212
|
+
config.model = (!isNaN(idx) && idx >= 0 && idx < fallbacks.length) ? fallbacks[idx] : input;
|
|
167
213
|
}
|
|
168
|
-
|
|
169
|
-
console.log(chalk.green(`ā Selected fallback model: ${config.model}`));
|
|
170
214
|
}
|
|
171
|
-
} else {
|
|
172
|
-
// Reuse existing model and config
|
|
173
|
-
console.log(chalk.blue(`š Using existing configuration (provider: ${existingConfig.provider}, model: ${existingConfig.model})`));
|
|
174
|
-
}
|
|
175
|
-
} else {
|
|
176
|
-
// User explicitly provided model
|
|
177
|
-
console.log(chalk.green(`ā Using specified model: ${config.model}`));
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Set default API URL if not provided by options or existing config
|
|
181
|
-
if (!config.apiUrl) {
|
|
182
|
-
switch (config.provider) {
|
|
183
|
-
case 'openai':
|
|
184
|
-
config.apiUrl = 'https://api.openai.com/v1/chat/completions';
|
|
185
|
-
break;
|
|
186
|
-
case 'claude':
|
|
187
|
-
config.apiUrl = 'https://api.anthropic.com/v1/messages';
|
|
188
|
-
break;
|
|
189
|
-
case 'gemini':
|
|
190
|
-
default:
|
|
191
|
-
// Updated Gemini API URL to v1 - using a base URL
|
|
192
|
-
config.apiUrl = 'https://generativelanguage.googleapis.com/v1/models';
|
|
193
|
-
break;
|
|
194
215
|
}
|
|
195
216
|
}
|
|
196
217
|
|
|
218
|
+
// Save config
|
|
197
219
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
198
|
-
console.log(chalk.green(
|
|
199
|
-
|
|
200
|
-
|
|
220
|
+
console.log(chalk.green('\nā
Configuration saved'));
|
|
221
|
+
console.log(chalk.gray(` Provider: ${config.provider}`));
|
|
222
|
+
console.log(chalk.gray(` Model: ${config.model}`));
|
|
223
|
+
console.log(chalk.gray(` Ollama: ${config.ollama.enabled ? 'enabled (' + config.ollama.url + ')' : 'disabled'}`));
|
|
224
|
+
if (config.apiKey) {
|
|
225
|
+
console.log(chalk.gray(` API Key: ${config.apiKey.substring(0, 8)}...`));
|
|
201
226
|
}
|
|
202
227
|
});
|
|
203
228
|
|
|
@@ -293,12 +318,13 @@ exit 0
|
|
|
293
318
|
}
|
|
294
319
|
});
|
|
295
320
|
|
|
296
|
-
// Analyze diff with
|
|
321
|
+
// Analyze diff with AI review + heuristic fallback
|
|
297
322
|
program
|
|
298
323
|
.command('analyze-diff')
|
|
299
|
-
.description('Analyze git diff
|
|
324
|
+
.description('Analyze git diff with AI code review (Gemini API) and heuristic fallback')
|
|
300
325
|
.argument('[diff]', 'Git diff content')
|
|
301
|
-
.option('--
|
|
326
|
+
.option('--min-score <score>', 'Minimum score to pass (1-10, default: 3)', '3')
|
|
327
|
+
.option('--json', 'Output results as JSON only')
|
|
302
328
|
.action(async (diff, options) => {
|
|
303
329
|
try {
|
|
304
330
|
// Read diff content from stdin or argument
|
|
@@ -316,24 +342,48 @@ program
|
|
|
316
342
|
return;
|
|
317
343
|
}
|
|
318
344
|
|
|
319
|
-
|
|
345
|
+
// Guard against huge diffs
|
|
346
|
+
if (diffContent.length > 20000) {
|
|
347
|
+
console.log(chalk.yellow('ā ļø Diff too large for AI review (>20KB), using heuristic'));
|
|
348
|
+
}
|
|
320
349
|
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
outputFormat: 'console'
|
|
324
|
-
});
|
|
350
|
+
const minScore = parseInt(options.minScore, 10);
|
|
351
|
+
const { reviewDiff } = await import('../lib/ai-reviewer.cjs');
|
|
325
352
|
|
|
326
|
-
|
|
327
|
-
console.log(chalk.green(`ā
${result.message}`));
|
|
328
|
-
displayEKGAnalysisResults(result.analysis);
|
|
353
|
+
const result = await reviewDiff(diffContent, { minScore });
|
|
329
354
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
console.log(chalk.gray(`š„ Similar Repos Found: ${result.stats.similar_repos_found}`));
|
|
333
|
-
console.log(chalk.gray(`ā±ļø Analysis Time: ${result.stats.analysis_time}ms`));
|
|
334
|
-
}
|
|
355
|
+
if (options.json) {
|
|
356
|
+
console.log(JSON.stringify(result, null, 2));
|
|
335
357
|
} else {
|
|
336
|
-
|
|
358
|
+
const icon = result.success ? 'ā
' : 'ā';
|
|
359
|
+
const color = result.success ? chalk.green : chalk.red;
|
|
360
|
+
const providerLabel = result.provider === 'ollama' ? 'š¦ Ollama' :
|
|
361
|
+
result.provider === 'heuristic' ? 'š Heuristic' :
|
|
362
|
+
result.provider === 'gemini' ? 'š Gemini' :
|
|
363
|
+
result.provider === 'openai' ? 'šµ OpenAI' :
|
|
364
|
+
result.provider === 'claude' ? 'š£ Claude' : result.provider;
|
|
365
|
+
console.log(color(`${icon} ${result.message}`));
|
|
366
|
+
console.log(chalk.gray(` Provider: ${providerLabel}${result.usedFallback ? ' (fallback)' : ''}`));
|
|
367
|
+
|
|
368
|
+
if (result.result.score) {
|
|
369
|
+
console.log(chalk.blue(`š Score: ${result.result.score}/10 (threshold: ${minScore}/10)`));
|
|
370
|
+
}
|
|
371
|
+
if (result.result.summary) {
|
|
372
|
+
console.log(chalk.gray(`š ${result.result.summary}`));
|
|
373
|
+
}
|
|
374
|
+
if (result.result.files && result.result.files.length > 0) {
|
|
375
|
+
for (const file of result.result.files) {
|
|
376
|
+
if (file.issues && file.issues.length > 0) {
|
|
377
|
+
console.log(chalk.yellow(`\nš ${file.fileName}:`));
|
|
378
|
+
for (const issue of file.issues) {
|
|
379
|
+
console.log(chalk.red(` - [${issue.type}] ${issue.description}`));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (!result.success) {
|
|
337
387
|
process.exit(1);
|
|
338
388
|
}
|
|
339
389
|
|
|
@@ -433,11 +483,10 @@ program
|
|
|
433
483
|
program
|
|
434
484
|
.command('status')
|
|
435
485
|
.description('Show installation and configuration status')
|
|
436
|
-
.action(() => {
|
|
486
|
+
.action(async () => {
|
|
437
487
|
console.log(chalk.blue('š Codeflow Hook Status'));
|
|
438
488
|
console.log();
|
|
439
489
|
|
|
440
|
-
// Check configuration cascade
|
|
441
490
|
const globalConfigPath = path.join(os.homedir(), '.codeflow-hook', 'config.json');
|
|
442
491
|
const projectConfigPath = path.join(process.cwd(), '.codeflowrc.json');
|
|
443
492
|
|
|
@@ -451,16 +500,35 @@ program
|
|
|
451
500
|
}
|
|
452
501
|
|
|
453
502
|
if (hasGlobalConfig) {
|
|
503
|
+
const config = JSON.parse(fs.readFileSync(globalConfigPath, 'utf8'));
|
|
454
504
|
console.log(chalk.green('ā
Global Configuration: Found'));
|
|
505
|
+
console.log(chalk.gray(` Provider: ${config.provider || 'gemini'}`));
|
|
506
|
+
console.log(chalk.gray(` Model: ${config.model || '(not set)'}`));
|
|
507
|
+
if (config.ollama?.enabled) {
|
|
508
|
+
console.log(chalk.green(` Ollama: enabled (${config.ollama.url || 'http://localhost:11434'})`));
|
|
509
|
+
try {
|
|
510
|
+
const { isOllamaRunning } = await import('../lib/ai-reviewer.cjs');
|
|
511
|
+
const running = await isOllamaRunning(config.ollama.url);
|
|
512
|
+
console.log(running ? chalk.green(' Ollama Status: running') : chalk.yellow(' Ollama Status: not running'));
|
|
513
|
+
} catch {
|
|
514
|
+
console.log(chalk.yellow(' Ollama Status: unknown'));
|
|
515
|
+
}
|
|
516
|
+
} else {
|
|
517
|
+
console.log(chalk.gray(' Ollama: disabled'));
|
|
518
|
+
}
|
|
519
|
+
if (config.apiKey) {
|
|
520
|
+
console.log(chalk.gray(` API Key: ${config.apiKey.substring(0, 8)}...`));
|
|
521
|
+
}
|
|
455
522
|
} else {
|
|
456
523
|
console.log(chalk.red('ā Global Configuration: Not found (run: codeflow-hook config)'));
|
|
457
524
|
}
|
|
458
525
|
|
|
459
526
|
if (!hasGlobalConfig && !hasProjectConfig) {
|
|
460
|
-
console.log(chalk.red('ā No configuration found. Run: codeflow-hook config
|
|
527
|
+
console.log(chalk.red('ā No configuration found. Run: codeflow-hook config'));
|
|
528
|
+
console.log(chalk.gray(' Cloud: codeflow-hook config -k <api-key> -p gemini'));
|
|
529
|
+
console.log(chalk.gray(' Local: codeflow-hook config --ollama-enable'));
|
|
461
530
|
}
|
|
462
531
|
|
|
463
|
-
// Check git hooks
|
|
464
532
|
const hooksDir = '.git/hooks';
|
|
465
533
|
const preCommitHook = path.join(hooksDir, 'pre-commit');
|
|
466
534
|
const prePushHook = path.join(hooksDir, 'pre-push');
|
|
@@ -479,9 +547,9 @@ program
|
|
|
479
547
|
|
|
480
548
|
console.log();
|
|
481
549
|
console.log(chalk.blue('š” Tips:'));
|
|
482
|
-
console.log(chalk.gray(' ā¢
|
|
483
|
-
console.log(chalk.gray(' ā¢
|
|
484
|
-
console.log(chalk.gray(' ā¢
|
|
550
|
+
console.log(chalk.gray(' ⢠Use --ollama-enable for local AI (no API key needed)'));
|
|
551
|
+
console.log(chalk.gray(' ⢠List Ollama models: codeflow-hook config --list-models'));
|
|
552
|
+
console.log(chalk.gray(' ⢠Large diffs (>20KB) use heuristic fallback'));
|
|
485
553
|
});
|
|
486
554
|
|
|
487
555
|
|