@iservu-inc/adf-cli 0.3.0 → 0.4.12
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/.project/chats/{current → complete}/2025-10-03_AGENTS-MD-AND-TOOL-GENERATORS.md +82 -17
- package/.project/chats/complete/2025-10-03_AI-PROVIDER-INTEGRATION.md +568 -0
- package/.project/chats/complete/2025-10-03_FRAMEWORK-UPDATE-SYSTEM.md +497 -0
- package/.project/chats/complete/2025-10-04_CONFIG-COMMAND.md +503 -0
- package/.project/chats/current/2025-10-04_PHASE-4-1-SMART-FILTERING.md +381 -0
- package/.project/chats/current/SESSION-STATUS.md +168 -0
- package/.project/docs/AI-PROVIDER-INTEGRATION.md +600 -0
- package/.project/docs/FRAMEWORK-UPDATE-INTEGRATION.md +421 -0
- package/.project/docs/FRAMEWORK-UPDATE-SYSTEM.md +832 -0
- package/.project/docs/PHASE-4-2-LEARNING-SYSTEM.md +881 -0
- package/.project/docs/PROJECT-STRUCTURE-EXPLANATION.md +500 -0
- package/.project/docs/SMART-FILTERING-SYSTEM.md +385 -0
- package/.project/docs/architecture/SYSTEM-DESIGN.md +122 -1
- package/.project/docs/goals/PROJECT-VISION.md +61 -34
- package/CHANGELOG.md +257 -1
- package/README.md +476 -292
- package/bin/adf.js +7 -0
- package/lib/ai/ai-client.js +328 -0
- package/lib/ai/ai-config.js +398 -0
- package/lib/analyzers/project-analyzer.js +380 -0
- package/lib/commands/config.js +221 -0
- package/lib/commands/init.js +56 -10
- package/lib/filters/question-filter.js +480 -0
- package/lib/frameworks/interviewer.js +271 -12
- package/lib/frameworks/progress-tracker.js +8 -1
- package/lib/learning/learning-manager.js +447 -0
- package/lib/learning/pattern-detector.js +376 -0
- package/lib/learning/rule-generator.js +304 -0
- package/lib/learning/skip-tracker.js +260 -0
- package/lib/learning/storage.js +296 -0
- package/package.json +70 -57
- package/tests/learning-storage.test.js +184 -0
- package/tests/pattern-detector.test.js +297 -0
- package/tests/project-analyzer.test.js +221 -0
- package/tests/question-filter.test.js +297 -0
- package/tests/skip-tracker.test.js +198 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Project Analyzer - Analyzes existing project files for context
|
|
7
|
+
*
|
|
8
|
+
* This module provides intelligent project analysis to:
|
|
9
|
+
* - Detect project type (web app, CLI, library, API, etc.)
|
|
10
|
+
* - Identify tech stack and frameworks
|
|
11
|
+
* - Extract project metadata
|
|
12
|
+
* - Provide context for smart question filtering
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Project types that can be detected
|
|
17
|
+
*/
|
|
18
|
+
const PROJECT_TYPES = {
|
|
19
|
+
WEB_APP: 'web-app',
|
|
20
|
+
API_SERVER: 'api-server',
|
|
21
|
+
CLI_TOOL: 'cli-tool',
|
|
22
|
+
LIBRARY: 'library',
|
|
23
|
+
MOBILE_APP: 'mobile-app',
|
|
24
|
+
DESKTOP_APP: 'desktop-app',
|
|
25
|
+
FULLSTACK: 'fullstack',
|
|
26
|
+
MICROSERVICE: 'microservice',
|
|
27
|
+
UNKNOWN: 'unknown'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Analyze project and extract context
|
|
32
|
+
* @param {string} projectPath - Path to project root
|
|
33
|
+
* @returns {Promise<Object>} Project context object
|
|
34
|
+
*/
|
|
35
|
+
async function analyzeProject(projectPath) {
|
|
36
|
+
const context = {
|
|
37
|
+
type: PROJECT_TYPES.UNKNOWN,
|
|
38
|
+
subtype: null,
|
|
39
|
+
frameworks: [],
|
|
40
|
+
languages: [],
|
|
41
|
+
dependencies: {},
|
|
42
|
+
devDependencies: {},
|
|
43
|
+
scripts: {},
|
|
44
|
+
hasTests: false,
|
|
45
|
+
hasCI: false,
|
|
46
|
+
hasDocker: false,
|
|
47
|
+
teamSize: 'unknown',
|
|
48
|
+
description: null,
|
|
49
|
+
confidence: 0
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Analyze package.json if exists (Node.js project)
|
|
53
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
54
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
55
|
+
const packageData = await analyzePackageJson(packageJsonPath);
|
|
56
|
+
Object.assign(context, packageData);
|
|
57
|
+
context.languages.push('JavaScript/TypeScript');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Analyze Python projects
|
|
61
|
+
const requirementsTxt = path.join(projectPath, 'requirements.txt');
|
|
62
|
+
const pipfile = path.join(projectPath, 'Pipfile');
|
|
63
|
+
if (await fs.pathExists(requirementsTxt) || await fs.pathExists(pipfile)) {
|
|
64
|
+
const pythonData = await analyzePythonProject(projectPath);
|
|
65
|
+
Object.assign(context, pythonData);
|
|
66
|
+
context.languages.push('Python');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Analyze file structure
|
|
70
|
+
const fileStructure = await analyzeFileStructure(projectPath);
|
|
71
|
+
context.hasTests = context.hasTests || fileStructure.hasTests; // Preserve if already detected from package.json
|
|
72
|
+
context.hasCI = fileStructure.hasCI;
|
|
73
|
+
context.hasDocker = fileStructure.hasDocker;
|
|
74
|
+
|
|
75
|
+
// Try to read README for description
|
|
76
|
+
const readmePath = await findReadme(projectPath);
|
|
77
|
+
if (readmePath) {
|
|
78
|
+
context.description = await extractDescriptionFromReadme(readmePath);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Detect project type if still unknown
|
|
82
|
+
if (context.type === PROJECT_TYPES.UNKNOWN) {
|
|
83
|
+
context.type = inferProjectType(context);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Calculate confidence score (0-100)
|
|
87
|
+
context.confidence = calculateConfidence(context);
|
|
88
|
+
|
|
89
|
+
return context;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Analyze package.json for Node.js projects
|
|
94
|
+
*/
|
|
95
|
+
async function analyzePackageJson(packageJsonPath) {
|
|
96
|
+
try {
|
|
97
|
+
const packageJson = await fs.readJSON(packageJsonPath);
|
|
98
|
+
const context = {
|
|
99
|
+
dependencies: packageJson.dependencies || {},
|
|
100
|
+
devDependencies: packageJson.devDependencies || {},
|
|
101
|
+
scripts: packageJson.scripts || {},
|
|
102
|
+
frameworks: [],
|
|
103
|
+
type: PROJECT_TYPES.UNKNOWN
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Detect frameworks
|
|
107
|
+
const deps = { ...context.dependencies, ...context.devDependencies };
|
|
108
|
+
|
|
109
|
+
// Frontend frameworks
|
|
110
|
+
if (deps.react) context.frameworks.push('React');
|
|
111
|
+
if (deps.vue) context.frameworks.push('Vue');
|
|
112
|
+
if (deps.angular || deps['@angular/core']) context.frameworks.push('Angular');
|
|
113
|
+
if (deps.svelte) context.frameworks.push('Svelte');
|
|
114
|
+
if (deps.next) context.frameworks.push('Next.js');
|
|
115
|
+
if (deps.nuxt) context.frameworks.push('Nuxt');
|
|
116
|
+
if (deps.gatsby) context.frameworks.push('Gatsby');
|
|
117
|
+
|
|
118
|
+
// Backend frameworks
|
|
119
|
+
if (deps.express) context.frameworks.push('Express');
|
|
120
|
+
if (deps.koa) context.frameworks.push('Koa');
|
|
121
|
+
if (deps.fastify) context.frameworks.push('Fastify');
|
|
122
|
+
if (deps.hapi || deps['@hapi/hapi']) context.frameworks.push('Hapi');
|
|
123
|
+
if (deps.nestjs || deps['@nestjs/core']) context.frameworks.push('NestJS');
|
|
124
|
+
|
|
125
|
+
// Testing frameworks
|
|
126
|
+
if (deps.jest || deps.mocha || deps.jasmine || deps.vitest) {
|
|
127
|
+
context.hasTests = true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Detect project type from package.json
|
|
131
|
+
if (packageJson.bin) {
|
|
132
|
+
context.type = PROJECT_TYPES.CLI_TOOL;
|
|
133
|
+
context.subtype = 'cli';
|
|
134
|
+
} else if (packageJson.main && !packageJson.private) {
|
|
135
|
+
context.type = PROJECT_TYPES.LIBRARY;
|
|
136
|
+
context.subtype = 'npm-package';
|
|
137
|
+
} else if (context.frameworks.some(f => ['React', 'Vue', 'Angular', 'Svelte'].includes(f))) {
|
|
138
|
+
if (context.frameworks.some(f => ['Express', 'NestJS', 'Fastify'].includes(f))) {
|
|
139
|
+
context.type = PROJECT_TYPES.FULLSTACK;
|
|
140
|
+
context.subtype = 'frontend-backend';
|
|
141
|
+
} else {
|
|
142
|
+
context.type = PROJECT_TYPES.WEB_APP;
|
|
143
|
+
context.subtype = 'frontend';
|
|
144
|
+
}
|
|
145
|
+
} else if (context.frameworks.some(f => ['Express', 'NestJS', 'Fastify', 'Koa', 'Hapi'].includes(f))) {
|
|
146
|
+
// Check if it's an API server
|
|
147
|
+
if (deps.cors || packageJson.name?.includes('api') || packageJson.description?.toLowerCase().includes('api')) {
|
|
148
|
+
context.type = PROJECT_TYPES.API_SERVER;
|
|
149
|
+
context.subtype = 'rest-api';
|
|
150
|
+
} else {
|
|
151
|
+
context.type = PROJECT_TYPES.WEB_APP;
|
|
152
|
+
context.subtype = 'backend';
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
context.description = packageJson.description || null;
|
|
157
|
+
|
|
158
|
+
return context;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.warn(chalk.yellow(`Warning: Could not parse package.json: ${error.message}`));
|
|
161
|
+
return {};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Analyze Python project
|
|
167
|
+
*/
|
|
168
|
+
async function analyzePythonProject(projectPath) {
|
|
169
|
+
const context = {
|
|
170
|
+
frameworks: [],
|
|
171
|
+
type: PROJECT_TYPES.UNKNOWN
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// Check for common Python frameworks
|
|
175
|
+
const requirementsTxt = path.join(projectPath, 'requirements.txt');
|
|
176
|
+
if (await fs.pathExists(requirementsTxt)) {
|
|
177
|
+
const requirements = await fs.readFile(requirementsTxt, 'utf-8');
|
|
178
|
+
|
|
179
|
+
if (requirements.includes('django')) context.frameworks.push('Django');
|
|
180
|
+
if (requirements.includes('flask')) context.frameworks.push('Flask');
|
|
181
|
+
if (requirements.includes('fastapi')) context.frameworks.push('FastAPI');
|
|
182
|
+
if (requirements.includes('streamlit')) context.frameworks.push('Streamlit');
|
|
183
|
+
|
|
184
|
+
// Detect type
|
|
185
|
+
if (context.frameworks.some(f => ['Django', 'Flask', 'FastAPI'].includes(f))) {
|
|
186
|
+
context.type = PROJECT_TYPES.WEB_APP;
|
|
187
|
+
context.subtype = 'python-web';
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return context;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Analyze file structure for indicators
|
|
196
|
+
*/
|
|
197
|
+
async function analyzeFileStructure(projectPath) {
|
|
198
|
+
const structure = {
|
|
199
|
+
hasTests: false,
|
|
200
|
+
hasCI: false,
|
|
201
|
+
hasDocker: false
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const files = await fs.readdir(projectPath);
|
|
206
|
+
|
|
207
|
+
// Check for test directories
|
|
208
|
+
if (files.includes('test') || files.includes('tests') || files.includes('__tests__') || files.includes('spec')) {
|
|
209
|
+
structure.hasTests = true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Check for CI configuration
|
|
213
|
+
if (files.includes('.github') || files.includes('.gitlab-ci.yml') || files.includes('.travis.yml') || files.includes('.circleci')) {
|
|
214
|
+
structure.hasCI = true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Check for Docker
|
|
218
|
+
if (files.includes('Dockerfile') || files.includes('docker-compose.yml')) {
|
|
219
|
+
structure.hasDocker = true;
|
|
220
|
+
}
|
|
221
|
+
} catch (error) {
|
|
222
|
+
// Silently fail - not critical
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return structure;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Find README file (case-insensitive)
|
|
230
|
+
*/
|
|
231
|
+
async function findReadme(projectPath) {
|
|
232
|
+
const readmeNames = ['README.md', 'readme.md', 'README.MD', 'README', 'Readme.md'];
|
|
233
|
+
|
|
234
|
+
for (const name of readmeNames) {
|
|
235
|
+
const readmePath = path.join(projectPath, name);
|
|
236
|
+
if (await fs.pathExists(readmePath)) {
|
|
237
|
+
return readmePath;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Extract description from README (first paragraph)
|
|
246
|
+
*/
|
|
247
|
+
async function extractDescriptionFromReadme(readmePath) {
|
|
248
|
+
try {
|
|
249
|
+
const content = await fs.readFile(readmePath, 'utf-8');
|
|
250
|
+
|
|
251
|
+
// Remove title (first # line)
|
|
252
|
+
const lines = content.split('\n');
|
|
253
|
+
let description = '';
|
|
254
|
+
let foundTitle = false;
|
|
255
|
+
|
|
256
|
+
for (const line of lines) {
|
|
257
|
+
const trimmed = line.trim();
|
|
258
|
+
|
|
259
|
+
// Skip title
|
|
260
|
+
if (!foundTitle && trimmed.startsWith('#')) {
|
|
261
|
+
foundTitle = true;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Skip empty lines
|
|
266
|
+
if (!trimmed) continue;
|
|
267
|
+
|
|
268
|
+
// Stop at next heading or code block
|
|
269
|
+
if (trimmed.startsWith('#') || trimmed.startsWith('```')) break;
|
|
270
|
+
|
|
271
|
+
// Add to description
|
|
272
|
+
description += trimmed + ' ';
|
|
273
|
+
|
|
274
|
+
// Stop after first paragraph (roughly 200 chars)
|
|
275
|
+
if (description.length > 200) break;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return description.trim().substring(0, 300) || null;
|
|
279
|
+
} catch (error) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Infer project type from context when not explicitly detected
|
|
286
|
+
*/
|
|
287
|
+
function inferProjectType(context) {
|
|
288
|
+
// If we have frameworks, make educated guess
|
|
289
|
+
if (context.frameworks.length > 0) {
|
|
290
|
+
const frontend = ['React', 'Vue', 'Angular', 'Svelte', 'Next.js', 'Nuxt', 'Gatsby'];
|
|
291
|
+
const backend = ['Express', 'NestJS', 'Fastify', 'Koa', 'Django', 'Flask', 'FastAPI'];
|
|
292
|
+
|
|
293
|
+
const hasFrontend = context.frameworks.some(f => frontend.includes(f));
|
|
294
|
+
const hasBackend = context.frameworks.some(f => backend.includes(f));
|
|
295
|
+
|
|
296
|
+
if (hasFrontend && hasBackend) return PROJECT_TYPES.FULLSTACK;
|
|
297
|
+
if (hasFrontend) return PROJECT_TYPES.WEB_APP;
|
|
298
|
+
if (hasBackend) return PROJECT_TYPES.API_SERVER;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Check description for hints
|
|
302
|
+
if (context.description) {
|
|
303
|
+
const desc = context.description.toLowerCase();
|
|
304
|
+
if (desc.includes('api') || desc.includes('server')) return PROJECT_TYPES.API_SERVER;
|
|
305
|
+
if (desc.includes('cli') || desc.includes('command-line')) return PROJECT_TYPES.CLI_TOOL;
|
|
306
|
+
if (desc.includes('library') || desc.includes('package')) return PROJECT_TYPES.LIBRARY;
|
|
307
|
+
if (desc.includes('web') || desc.includes('app')) return PROJECT_TYPES.WEB_APP;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return PROJECT_TYPES.UNKNOWN;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Calculate confidence score (0-100) for the analysis
|
|
315
|
+
*/
|
|
316
|
+
function calculateConfidence(context) {
|
|
317
|
+
let score = 0;
|
|
318
|
+
|
|
319
|
+
// Type detection (+30)
|
|
320
|
+
if (context.type !== PROJECT_TYPES.UNKNOWN) score += 30;
|
|
321
|
+
|
|
322
|
+
// Framework detection (+20)
|
|
323
|
+
if (context.frameworks.length > 0) score += 20;
|
|
324
|
+
|
|
325
|
+
// Language detection (+15)
|
|
326
|
+
if (context.languages.length > 0) score += 15;
|
|
327
|
+
|
|
328
|
+
// Dependencies (+10)
|
|
329
|
+
if (Object.keys(context.dependencies).length > 0) score += 10;
|
|
330
|
+
|
|
331
|
+
// Description (+15)
|
|
332
|
+
if (context.description) score += 15;
|
|
333
|
+
|
|
334
|
+
// Additional indicators (+10)
|
|
335
|
+
if (context.hasTests) score += 3;
|
|
336
|
+
if (context.hasCI) score += 3;
|
|
337
|
+
if (context.hasDocker) score += 4;
|
|
338
|
+
|
|
339
|
+
return Math.min(score, 100);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Get human-readable project summary
|
|
344
|
+
*/
|
|
345
|
+
function getProjectSummary(context) {
|
|
346
|
+
const parts = [];
|
|
347
|
+
|
|
348
|
+
// Type
|
|
349
|
+
const typeNames = {
|
|
350
|
+
[PROJECT_TYPES.WEB_APP]: 'Web Application',
|
|
351
|
+
[PROJECT_TYPES.API_SERVER]: 'API Server',
|
|
352
|
+
[PROJECT_TYPES.CLI_TOOL]: 'CLI Tool',
|
|
353
|
+
[PROJECT_TYPES.LIBRARY]: 'Library/Package',
|
|
354
|
+
[PROJECT_TYPES.MOBILE_APP]: 'Mobile App',
|
|
355
|
+
[PROJECT_TYPES.DESKTOP_APP]: 'Desktop App',
|
|
356
|
+
[PROJECT_TYPES.FULLSTACK]: 'Full-stack Application',
|
|
357
|
+
[PROJECT_TYPES.MICROSERVICE]: 'Microservice',
|
|
358
|
+
[PROJECT_TYPES.UNKNOWN]: 'Unknown Type'
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
parts.push(typeNames[context.type] || 'Unknown');
|
|
362
|
+
|
|
363
|
+
// Frameworks
|
|
364
|
+
if (context.frameworks.length > 0) {
|
|
365
|
+
parts.push(`using ${context.frameworks.join(', ')}`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Languages
|
|
369
|
+
if (context.languages.length > 0) {
|
|
370
|
+
parts.push(`(${context.languages.join(', ')})`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return parts.join(' ');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
module.exports = {
|
|
377
|
+
analyzeProject,
|
|
378
|
+
getProjectSummary,
|
|
379
|
+
PROJECT_TYPES
|
|
380
|
+
};
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
const inquirer = require('inquirer');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { configureAIProvider, getEnvFilePath, loadEnvFile } = require('../ai/ai-config');
|
|
6
|
+
const LearningManager = require('../learning/learning-manager');
|
|
7
|
+
const { getLearningStats, getLearningConfig } = require('../learning/storage');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Configuration categories available in ADF CLI
|
|
11
|
+
*/
|
|
12
|
+
const CONFIG_CATEGORIES = {
|
|
13
|
+
AI_PROVIDER: {
|
|
14
|
+
name: 'AI Provider Setup',
|
|
15
|
+
description: 'Configure AI provider (Anthropic, OpenAI, Google Gemini, OpenRouter)',
|
|
16
|
+
value: 'ai-provider'
|
|
17
|
+
},
|
|
18
|
+
LEARNING_SYSTEM: {
|
|
19
|
+
name: 'Learning System',
|
|
20
|
+
description: 'Manage interview learning data and preferences',
|
|
21
|
+
value: 'learning'
|
|
22
|
+
}
|
|
23
|
+
// Future config categories can be added here:
|
|
24
|
+
// PROJECT_SETTINGS: { name: 'Project Settings', description: '...', value: 'project' },
|
|
25
|
+
// DEPLOYMENT: { name: 'Deployment Preferences', description: '...', value: 'deployment' },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Check if AI provider is already configured
|
|
30
|
+
*/
|
|
31
|
+
async function isAIConfigured(projectPath = process.cwd()) {
|
|
32
|
+
const envPath = getEnvFilePath(projectPath);
|
|
33
|
+
|
|
34
|
+
if (!await fs.pathExists(envPath)) {
|
|
35
|
+
return { configured: false };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const envVars = await loadEnvFile(envPath);
|
|
39
|
+
|
|
40
|
+
// Check if any AI provider key exists
|
|
41
|
+
const aiKeys = [
|
|
42
|
+
'ANTHROPIC_API_KEY',
|
|
43
|
+
'OPENAI_API_KEY',
|
|
44
|
+
'GOOGLE_API_KEY',
|
|
45
|
+
'OPENROUTER_API_KEY'
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
for (const key of aiKeys) {
|
|
49
|
+
if (envVars[key] && envVars[key].length > 0) {
|
|
50
|
+
return {
|
|
51
|
+
configured: true,
|
|
52
|
+
provider: key.replace('_API_KEY', '').toLowerCase()
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { configured: false };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if Learning System has data
|
|
62
|
+
*/
|
|
63
|
+
async function getLearningStatus(projectPath = process.cwd()) {
|
|
64
|
+
try {
|
|
65
|
+
const stats = await getLearningStats(projectPath);
|
|
66
|
+
const config = await getLearningConfig(projectPath);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
hasData: stats.totalSessions > 0,
|
|
70
|
+
enabled: config.enabled,
|
|
71
|
+
totalSessions: stats.totalSessions,
|
|
72
|
+
totalPatterns: stats.totalPatterns
|
|
73
|
+
};
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return {
|
|
76
|
+
hasData: false,
|
|
77
|
+
enabled: true,
|
|
78
|
+
totalSessions: 0,
|
|
79
|
+
totalPatterns: 0
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Display configuration status for AI provider
|
|
86
|
+
*/
|
|
87
|
+
function displayAIStatus(status) {
|
|
88
|
+
if (status.configured) {
|
|
89
|
+
const providerName = status.provider.charAt(0).toUpperCase() + status.provider.slice(1);
|
|
90
|
+
return `${chalk.green('✓ Configured')} ${chalk.gray(`(${providerName})`)}`;
|
|
91
|
+
} else {
|
|
92
|
+
return chalk.yellow('○ Not configured');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Display configuration status for Learning System
|
|
98
|
+
*/
|
|
99
|
+
function displayLearningStatus(status) {
|
|
100
|
+
if (status.hasData && status.enabled) {
|
|
101
|
+
return `${chalk.green('✓ Active')} ${chalk.gray(`(${status.totalSessions} sessions, ${status.totalPatterns} patterns)`)}`;
|
|
102
|
+
} else if (status.hasData && !status.enabled) {
|
|
103
|
+
return `${chalk.yellow('○ Disabled')} ${chalk.gray(`(${status.totalSessions} sessions)`)}`;
|
|
104
|
+
} else if (!status.hasData && status.enabled) {
|
|
105
|
+
return chalk.gray('○ No data yet');
|
|
106
|
+
} else {
|
|
107
|
+
return chalk.gray('○ Not configured');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Main config command
|
|
113
|
+
*/
|
|
114
|
+
async function config() {
|
|
115
|
+
console.log(chalk.cyan.bold('\n⚙️ ADF Configuration\n'));
|
|
116
|
+
|
|
117
|
+
const cwd = process.cwd();
|
|
118
|
+
|
|
119
|
+
// Check configuration status for all categories
|
|
120
|
+
const aiStatus = await isAIConfigured(cwd);
|
|
121
|
+
const learningStatus = await getLearningStatus(cwd);
|
|
122
|
+
|
|
123
|
+
// Build choices with status indicators
|
|
124
|
+
const choices = [
|
|
125
|
+
{
|
|
126
|
+
name: `${CONFIG_CATEGORIES.AI_PROVIDER.name} - ${displayAIStatus(aiStatus)}`,
|
|
127
|
+
value: CONFIG_CATEGORIES.AI_PROVIDER.value,
|
|
128
|
+
short: CONFIG_CATEGORIES.AI_PROVIDER.name
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: `${CONFIG_CATEGORIES.LEARNING_SYSTEM.name} - ${displayLearningStatus(learningStatus)}`,
|
|
132
|
+
value: CONFIG_CATEGORIES.LEARNING_SYSTEM.value,
|
|
133
|
+
short: CONFIG_CATEGORIES.LEARNING_SYSTEM.name
|
|
134
|
+
},
|
|
135
|
+
new inquirer.Separator(),
|
|
136
|
+
{
|
|
137
|
+
name: chalk.gray('← Back'),
|
|
138
|
+
value: 'back'
|
|
139
|
+
}
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
const { category } = await inquirer.prompt([
|
|
143
|
+
{
|
|
144
|
+
type: 'list',
|
|
145
|
+
name: 'category',
|
|
146
|
+
message: 'Select configuration category:',
|
|
147
|
+
choices: choices,
|
|
148
|
+
pageSize: 10
|
|
149
|
+
}
|
|
150
|
+
]);
|
|
151
|
+
|
|
152
|
+
if (category === 'back') {
|
|
153
|
+
console.log(chalk.yellow('\n✋ Configuration cancelled.\n'));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Handle selected category
|
|
158
|
+
switch (category) {
|
|
159
|
+
case 'ai-provider':
|
|
160
|
+
await configureAIProviderCategory(cwd, aiStatus);
|
|
161
|
+
break;
|
|
162
|
+
|
|
163
|
+
case 'learning':
|
|
164
|
+
await configureLearningCategory(cwd, learningStatus);
|
|
165
|
+
break;
|
|
166
|
+
|
|
167
|
+
// Future categories will be handled here
|
|
168
|
+
default:
|
|
169
|
+
console.log(chalk.red('\n❌ Configuration category not implemented yet.\n'));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Configure AI Provider category
|
|
175
|
+
*/
|
|
176
|
+
async function configureAIProviderCategory(cwd, aiStatus) {
|
|
177
|
+
console.log(chalk.gray('\n' + '─'.repeat(60) + '\n'));
|
|
178
|
+
|
|
179
|
+
if (aiStatus.configured) {
|
|
180
|
+
const providerName = aiStatus.provider.charAt(0).toUpperCase() + aiStatus.provider.slice(1);
|
|
181
|
+
console.log(chalk.green(`✓ AI Provider already configured: ${providerName}\n`));
|
|
182
|
+
|
|
183
|
+
const { reconfigure } = await inquirer.prompt([
|
|
184
|
+
{
|
|
185
|
+
type: 'confirm',
|
|
186
|
+
name: 'reconfigure',
|
|
187
|
+
message: 'Do you want to reconfigure your AI provider?',
|
|
188
|
+
default: false
|
|
189
|
+
}
|
|
190
|
+
]);
|
|
191
|
+
|
|
192
|
+
if (!reconfigure) {
|
|
193
|
+
console.log(chalk.yellow('\n✋ Configuration unchanged.\n'));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Run AI provider configuration
|
|
199
|
+
console.log('');
|
|
200
|
+
const aiConfig = await configureAIProvider(cwd);
|
|
201
|
+
|
|
202
|
+
if (aiConfig) {
|
|
203
|
+
console.log(chalk.green.bold('\n✅ AI Provider configured successfully!\n'));
|
|
204
|
+
console.log(chalk.gray(` Provider: ${aiConfig.providerName}`));
|
|
205
|
+
console.log(chalk.gray(` Model: ${aiConfig.model}`));
|
|
206
|
+
console.log(chalk.gray(` Config saved to: ${getEnvFilePath(cwd)}\n`));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Configure Learning System category
|
|
212
|
+
*/
|
|
213
|
+
async function configureLearningCategory(cwd, learningStatus) {
|
|
214
|
+
console.log(chalk.gray('\n' + '─'.repeat(60) + '\n'));
|
|
215
|
+
|
|
216
|
+
// Create and show learning manager menu
|
|
217
|
+
const learningManager = new LearningManager(cwd);
|
|
218
|
+
await learningManager.showMenu();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
module.exports = config;
|
package/lib/commands/init.js
CHANGED
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
const Interviewer = require('../frameworks/interviewer');
|
|
11
11
|
const SessionManager = require('../frameworks/session-manager');
|
|
12
12
|
const { deployToTool } = require('./deploy');
|
|
13
|
+
const { configureAIProvider, loadEnvIntoProcess, getEnvFilePath } = require('../ai/ai-config');
|
|
13
14
|
|
|
14
15
|
async function init(options) {
|
|
15
16
|
console.log(chalk.cyan.bold('\n🚀 AgentDevFramework - Software Development Requirements\n'));
|
|
@@ -17,13 +18,41 @@ async function init(options) {
|
|
|
17
18
|
const cwd = process.cwd();
|
|
18
19
|
const adfDir = path.join(cwd, '.adf');
|
|
19
20
|
|
|
21
|
+
// Load .env file if it exists (for API keys)
|
|
22
|
+
const envPath = getEnvFilePath(cwd);
|
|
23
|
+
if (await fs.pathExists(envPath)) {
|
|
24
|
+
loadEnvIntoProcess(envPath);
|
|
25
|
+
}
|
|
26
|
+
|
|
20
27
|
// Check for resumable sessions FIRST (before asking to overwrite)
|
|
21
28
|
const sessionManager = new SessionManager(cwd);
|
|
22
29
|
const existingSession = await sessionManager.promptToResume();
|
|
23
30
|
|
|
24
31
|
if (existingSession) {
|
|
25
32
|
// Resume existing session
|
|
26
|
-
|
|
33
|
+
// Check if session has AI config (from resumed session)
|
|
34
|
+
let aiConfig = existingSession.progress.aiConfig;
|
|
35
|
+
|
|
36
|
+
if (aiConfig) {
|
|
37
|
+
// We have AI config from session, but need to verify API key exists
|
|
38
|
+
const apiKey = process.env[aiConfig.envVar];
|
|
39
|
+
if (!apiKey) {
|
|
40
|
+
console.log(chalk.yellow(`\n⚠️ Previous session used ${aiConfig.providerName}`));
|
|
41
|
+
console.log(chalk.yellow(`Please configure API key to resume...\n`));
|
|
42
|
+
aiConfig = await configureAIProvider(cwd);
|
|
43
|
+
} else {
|
|
44
|
+
// Add API key to config (it's not stored in session for security)
|
|
45
|
+
aiConfig.apiKey = apiKey;
|
|
46
|
+
console.log(chalk.green(`\n✓ Resuming with ${aiConfig.providerName} (${aiConfig.model})\n`));
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
// Old session without AI config, configure now
|
|
50
|
+
console.log(chalk.yellow('\n⚠️ This session was created before AI provider integration.'));
|
|
51
|
+
console.log(chalk.yellow('Please configure AI provider to continue...\n'));
|
|
52
|
+
aiConfig = await configureAIProvider(cwd);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const interviewer = new Interviewer(existingSession.progress.framework || 'balanced', cwd, existingSession, aiConfig);
|
|
27
56
|
const sessionPath = await interviewer.start();
|
|
28
57
|
|
|
29
58
|
console.log(chalk.green.bold('\n✨ Requirements gathering complete!\n'));
|
|
@@ -76,17 +105,34 @@ async function init(options) {
|
|
|
76
105
|
// Create .adf directory
|
|
77
106
|
await fs.ensureDir(adfDir);
|
|
78
107
|
|
|
79
|
-
//
|
|
108
|
+
// Configure AI Provider (OPTIONAL - can be done later with 'adf config')
|
|
109
|
+
let aiConfig = null;
|
|
110
|
+
|
|
111
|
+
const { configureAI } = await inquirer.prompt([
|
|
112
|
+
{
|
|
113
|
+
type: 'confirm',
|
|
114
|
+
name: 'configureAI',
|
|
115
|
+
message: 'Configure AI provider now? (Enables intelligent follow-up questions)',
|
|
116
|
+
default: true
|
|
117
|
+
}
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
if (configureAI) {
|
|
121
|
+
aiConfig = await configureAIProvider(cwd);
|
|
122
|
+
} else {
|
|
123
|
+
console.log(chalk.yellow('\n💡 You can configure AI later by running: adf config\n'));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Start interview (with or without AI)
|
|
80
127
|
console.log(chalk.gray('\n' + '━'.repeat(60)) + '\n');
|
|
81
128
|
|
|
82
|
-
const interviewer = new Interviewer(workflow, cwd);
|
|
129
|
+
const interviewer = new Interviewer(workflow, cwd, null, aiConfig);
|
|
83
130
|
const sessionPath = await interviewer.start();
|
|
84
131
|
|
|
85
|
-
// Show
|
|
86
|
-
console.log(chalk.cyan('📋
|
|
87
|
-
console.log(chalk.gray(`
|
|
88
|
-
console.log(chalk.gray(`
|
|
89
|
-
console.log(chalk.gray(` 3. Start building based on the detailed requirements\n`));
|
|
132
|
+
// Show completion message
|
|
133
|
+
console.log(chalk.cyan('📋 Requirements Complete!\n'));
|
|
134
|
+
console.log(chalk.gray(` ✓ Files saved to: ${sessionPath}/outputs/`));
|
|
135
|
+
console.log(chalk.gray(` ✓ You can review your requirements anytime\n`));
|
|
90
136
|
|
|
91
137
|
// Optional: Deploy to tool
|
|
92
138
|
if (options.tool) {
|
|
@@ -97,8 +143,8 @@ async function init(options) {
|
|
|
97
143
|
{
|
|
98
144
|
type: 'confirm',
|
|
99
145
|
name: 'deployNow',
|
|
100
|
-
message: '
|
|
101
|
-
default:
|
|
146
|
+
message: 'Automatically deploy to your IDE? (I\'ll configure everything for you)',
|
|
147
|
+
default: true
|
|
102
148
|
}
|
|
103
149
|
]);
|
|
104
150
|
|