@iservu-inc/adf-cli 0.2.0 → 0.3.6
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/complete/2025-10-03_AGENTS-MD-AND-TOOL-GENERATORS.md +764 -0
- package/.project/chats/current/2025-10-03_AI-PROVIDER-INTEGRATION.md +569 -0
- package/.project/chats/current/2025-10-03_FRAMEWORK-UPDATE-SYSTEM.md +497 -0
- package/.project/chats/current/SESSION-STATUS.md +127 -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/PROJECT-STRUCTURE-EXPLANATION.md +500 -0
- package/.project/docs/architecture/SYSTEM-DESIGN.md +122 -1
- package/.project/docs/goals/PROJECT-VISION.md +33 -28
- package/.project/docs/tool-integrations/RESEARCH-FINDINGS.md +828 -0
- package/CHANGELOG.md +325 -0
- package/README.md +100 -11
- package/bin/adf.js +7 -0
- package/lib/ai/ai-client.js +328 -0
- package/lib/ai/ai-config.js +398 -0
- package/lib/commands/config.js +154 -0
- package/lib/commands/deploy.js +122 -3
- package/lib/commands/init.js +56 -10
- package/lib/frameworks/interviewer.js +89 -11
- package/lib/frameworks/progress-tracker.js +8 -1
- package/lib/generators/agents-md-generator.js +388 -0
- package/lib/generators/cursor-generator.js +374 -0
- package/lib/generators/index.js +98 -0
- package/lib/generators/tool-config-generator.js +188 -0
- package/lib/generators/vscode-generator.js +403 -0
- package/lib/generators/windsurf-generator.js +596 -0
- package/package.json +15 -3
- package/tests/agents-md-generator.test.js +245 -0
- package/tests/cursor-generator.test.js +326 -0
- package/tests/vscode-generator.test.js +436 -0
- package/tests/windsurf-generator.test.js +320 -0
- /package/.project/chats/{current → complete}/2025-10-03_ADF-CLI-QUALITY-BASED-PROGRESS-AND-RESUME.md +0 -0
package/lib/commands/deploy.js
CHANGED
|
@@ -2,6 +2,12 @@ const fs = require('fs-extra');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const ora = require('ora');
|
|
5
|
+
const {
|
|
6
|
+
generateAgentsMd,
|
|
7
|
+
generateWindsurf,
|
|
8
|
+
generateCursor,
|
|
9
|
+
generateVSCode
|
|
10
|
+
} = require('../generators');
|
|
5
11
|
|
|
6
12
|
const TOOLS = {
|
|
7
13
|
windsurf: { name: 'Windsurf', configFile: '.windsurfrules' },
|
|
@@ -15,6 +21,60 @@ const TOOLS = {
|
|
|
15
21
|
'codex-cli': { name: 'Codex CLI', configFile: '.codex/config.json' }
|
|
16
22
|
};
|
|
17
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Find the latest completed session
|
|
26
|
+
*/
|
|
27
|
+
async function findLatestSession(cwd) {
|
|
28
|
+
const sessionsDir = path.join(cwd, '.adf', 'sessions');
|
|
29
|
+
|
|
30
|
+
if (!await fs.pathExists(sessionsDir)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const sessions = await fs.readdir(sessionsDir);
|
|
35
|
+
if (sessions.length === 0) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Find most recent session with outputs
|
|
40
|
+
let latestSession = null;
|
|
41
|
+
let latestTime = 0;
|
|
42
|
+
|
|
43
|
+
for (const sessionId of sessions) {
|
|
44
|
+
const sessionPath = path.join(sessionsDir, sessionId);
|
|
45
|
+
const outputsPath = path.join(sessionPath, 'outputs');
|
|
46
|
+
|
|
47
|
+
if (await fs.pathExists(outputsPath)) {
|
|
48
|
+
const stats = await fs.stat(sessionPath);
|
|
49
|
+
if (stats.mtimeMs > latestTime) {
|
|
50
|
+
latestTime = stats.mtimeMs;
|
|
51
|
+
latestSession = sessionPath;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return latestSession;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get framework type from session metadata
|
|
61
|
+
*/
|
|
62
|
+
async function getFrameworkFromSession(sessionPath) {
|
|
63
|
+
const metadataPath = path.join(sessionPath, '_metadata.json');
|
|
64
|
+
if (await fs.pathExists(metadataPath)) {
|
|
65
|
+
const metadata = await fs.readJson(metadataPath);
|
|
66
|
+
return metadata.framework;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const progressPath = path.join(sessionPath, '_progress.json');
|
|
70
|
+
if (await fs.pathExists(progressPath)) {
|
|
71
|
+
const progress = await fs.readJson(progressPath);
|
|
72
|
+
return progress.framework;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return 'balanced'; // Default fallback
|
|
76
|
+
}
|
|
77
|
+
|
|
18
78
|
async function deployToTool(tool, options = {}) {
|
|
19
79
|
const cwd = process.cwd();
|
|
20
80
|
const adfDir = path.join(cwd, '.adf');
|
|
@@ -26,22 +86,80 @@ async function deployToTool(tool, options = {}) {
|
|
|
26
86
|
process.exit(1);
|
|
27
87
|
}
|
|
28
88
|
|
|
29
|
-
//
|
|
89
|
+
// Find latest session
|
|
90
|
+
const sessionPath = await findLatestSession(cwd);
|
|
91
|
+
if (!sessionPath) {
|
|
92
|
+
console.error(chalk.red('\n❌ Error: No completed sessions found.'));
|
|
93
|
+
console.log(chalk.yellow('Run "adf init" to create requirements first.\n'));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const framework = await getFrameworkFromSession(sessionPath);
|
|
98
|
+
|
|
99
|
+
// Read context to get workflow (legacy support)
|
|
30
100
|
const contextPath = path.join(adfDir, 'context.json');
|
|
31
101
|
let context = {};
|
|
32
102
|
if (await fs.pathExists(contextPath)) {
|
|
33
103
|
context = await fs.readJson(contextPath);
|
|
34
104
|
}
|
|
35
105
|
|
|
36
|
-
const workflow = context.workflow || 'balanced';
|
|
106
|
+
const workflow = framework || context.workflow || 'balanced';
|
|
37
107
|
|
|
38
|
-
// Deploy logic (simplified - will be implemented based on actual deploy script)
|
|
39
108
|
const spinner = options.silent ? null : ora(`Deploying to ${TOOLS[tool]?.name || tool}...`).start();
|
|
40
109
|
|
|
41
110
|
try {
|
|
111
|
+
// Generate AGENTS.md (universal standard)
|
|
112
|
+
if (!options.silent) {
|
|
113
|
+
if (spinner) spinner.text = 'Generating AGENTS.md...';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
await generateAgentsMd(sessionPath, cwd, framework);
|
|
118
|
+
if (!options.silent && !spinner) {
|
|
119
|
+
console.log(chalk.green('✓ Generated AGENTS.md'));
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.warn(chalk.yellow(`\n⚠️ Warning: Could not generate AGENTS.md: ${error.message}`));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Generate tool-specific configurations
|
|
126
|
+
if (spinner) spinner.text = `Generating ${TOOLS[tool]?.name || tool} configurations...`;
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
let generatedFiles = {};
|
|
130
|
+
|
|
131
|
+
if (tool === 'windsurf') {
|
|
132
|
+
generatedFiles = await generateWindsurf(sessionPath, cwd, framework);
|
|
133
|
+
if (!options.silent && !spinner) {
|
|
134
|
+
console.log(chalk.green('✓ Generated Windsurf configurations'));
|
|
135
|
+
console.log(chalk.gray(` - .windsurfrules (legacy)`));
|
|
136
|
+
console.log(chalk.gray(` - .windsurf/rules/*.md (${generatedFiles.rules?.length || 0} files)`));
|
|
137
|
+
console.log(chalk.gray(` - .windsurf/workflows/*.md (${generatedFiles.workflows?.length || 0} files)`));
|
|
138
|
+
}
|
|
139
|
+
} else if (tool === 'cursor') {
|
|
140
|
+
generatedFiles = await generateCursor(sessionPath, cwd, framework);
|
|
141
|
+
if (!options.silent && !spinner) {
|
|
142
|
+
console.log(chalk.green('✓ Generated Cursor configurations'));
|
|
143
|
+
console.log(chalk.gray(` - .cursor/rules`));
|
|
144
|
+
console.log(chalk.gray(` - .cursorrules (deprecation notice)`));
|
|
145
|
+
}
|
|
146
|
+
} else if (tool === 'vscode' || tool === 'vscode-insider') {
|
|
147
|
+
generatedFiles = await generateVSCode(sessionPath, cwd, framework);
|
|
148
|
+
if (!options.silent && !spinner) {
|
|
149
|
+
console.log(chalk.green('✓ Generated VS Code configurations'));
|
|
150
|
+
console.log(chalk.gray(` - .github/copilot-instructions.md`));
|
|
151
|
+
console.log(chalk.gray(` - .vscode/settings.json (custom chat modes)`));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.warn(chalk.yellow(`\n⚠️ Warning: Could not generate ${TOOLS[tool]?.name || tool} configurations: ${error.message}`));
|
|
156
|
+
}
|
|
157
|
+
|
|
42
158
|
// Get agents based on workflow
|
|
43
159
|
const agentsList = getAgentsForWorkflow(workflow);
|
|
44
160
|
|
|
161
|
+
if (spinner) spinner.text = `Deploying to ${TOOLS[tool]?.name || tool}...`;
|
|
162
|
+
|
|
45
163
|
// Copy agents
|
|
46
164
|
const agentsDir = path.join(cwd, '.framework', 'agents');
|
|
47
165
|
await fs.ensureDir(agentsDir);
|
|
@@ -82,6 +200,7 @@ async function deployToTool(tool, options = {}) {
|
|
|
82
200
|
}
|
|
83
201
|
|
|
84
202
|
if (!options.silent) {
|
|
203
|
+
console.log(chalk.gray(` Generated: AGENTS.md (universal AI agent config)`));
|
|
85
204
|
console.log(chalk.gray(` Agents: ${agentsList.join(', ')}`));
|
|
86
205
|
console.log(chalk.gray(` Config: ${toolConfig?.configFile || 'N/A'}\n`));
|
|
87
206
|
}
|
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
|
|
|
@@ -13,9 +13,10 @@ const AnswerQualityAnalyzer = require('./answer-quality-analyzer');
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
class Interviewer {
|
|
16
|
-
constructor(framework, projectPath, existingSession = null) {
|
|
16
|
+
constructor(framework, projectPath, existingSession = null, aiConfig = null) {
|
|
17
17
|
this.framework = framework;
|
|
18
18
|
this.projectPath = projectPath;
|
|
19
|
+
this.aiConfig = aiConfig; // Store AI configuration
|
|
19
20
|
|
|
20
21
|
if (existingSession) {
|
|
21
22
|
// Resuming existing session
|
|
@@ -24,6 +25,8 @@ class Interviewer {
|
|
|
24
25
|
this.answers = existingSession.progress.answers || {};
|
|
25
26
|
this.transcript = existingSession.progress.transcript || [];
|
|
26
27
|
this.isResuming = true;
|
|
28
|
+
// Use stored AI config from session if available
|
|
29
|
+
this.aiConfig = existingSession.progress.aiConfig || aiConfig;
|
|
27
30
|
} else {
|
|
28
31
|
// New session
|
|
29
32
|
this.sessionId = this.generateSessionId();
|
|
@@ -34,6 +37,7 @@ class Interviewer {
|
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
this.progressTracker = null;
|
|
40
|
+
this.aiClient = null; // Will be initialized in start()
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
generateSessionId() {
|
|
@@ -48,6 +52,16 @@ class Interviewer {
|
|
|
48
52
|
console.log(chalk.gray(`Session: ${this.sessionId}`));
|
|
49
53
|
console.log(chalk.gray(`Files will be saved to: .adf/sessions/${this.sessionId}/\n`));
|
|
50
54
|
|
|
55
|
+
// Initialize AI Client
|
|
56
|
+
if (!this.aiClient && this.aiConfig) {
|
|
57
|
+
const AIClient = require('../ai/ai-client');
|
|
58
|
+
this.aiClient = new AIClient(this.aiConfig);
|
|
59
|
+
console.log(chalk.green(`✓ AI Provider: ${this.aiConfig.providerName} (${this.aiConfig.model})\n`));
|
|
60
|
+
} else if (!this.aiConfig) {
|
|
61
|
+
console.log(chalk.red('\n✖ Error: No AI configuration provided. Cannot start interview.\n'));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
51
65
|
// Create session directory
|
|
52
66
|
await fs.ensureDir(this.sessionPath);
|
|
53
67
|
await fs.ensureDir(path.join(this.sessionPath, 'qa-responses'));
|
|
@@ -60,7 +74,7 @@ class Interviewer {
|
|
|
60
74
|
const questionBlocks = this.groupQuestionsIntoBlocks(questions);
|
|
61
75
|
|
|
62
76
|
// Initialize progress tracker
|
|
63
|
-
this.progressTracker = new ProgressTracker(this.sessionPath, questionBlocks.length, this.framework);
|
|
77
|
+
this.progressTracker = new ProgressTracker(this.sessionPath, questionBlocks.length, this.framework, this.aiConfig);
|
|
64
78
|
const isResumable = await this.progressTracker.initialize();
|
|
65
79
|
|
|
66
80
|
if (isResumable && this.isResuming) {
|
|
@@ -250,7 +264,8 @@ class Interviewer {
|
|
|
250
264
|
// Show guidance
|
|
251
265
|
console.log(chalk.gray(`💡 ${question.guidance}`));
|
|
252
266
|
console.log(chalk.green(` ✓ Good: ${question.goodExample}`));
|
|
253
|
-
console.log(chalk.red(` ✗ Bad: ${question.badExample}
|
|
267
|
+
console.log(chalk.red(` ✗ Bad: ${question.badExample}`));
|
|
268
|
+
console.log(chalk.gray(` (Type "skip" to skip remaining questions in this block)\n`));
|
|
254
269
|
|
|
255
270
|
// Get answer
|
|
256
271
|
const { answer } = await inquirer.prompt([
|
|
@@ -260,7 +275,7 @@ class Interviewer {
|
|
|
260
275
|
message: 'Your answer:',
|
|
261
276
|
validate: (input) => {
|
|
262
277
|
if (!input || input.trim().length === 0) {
|
|
263
|
-
return 'Please provide an answer or type "skip"
|
|
278
|
+
return 'Please provide an answer or type "skip"';
|
|
264
279
|
}
|
|
265
280
|
return true;
|
|
266
281
|
}
|
|
@@ -271,13 +286,41 @@ class Interviewer {
|
|
|
271
286
|
return null; // Signal to skip remaining questions
|
|
272
287
|
}
|
|
273
288
|
|
|
274
|
-
// Analyze answer quality
|
|
275
|
-
|
|
289
|
+
// Analyze answer quality (use AI if available, fallback to heuristic)
|
|
290
|
+
let qualityMetrics;
|
|
291
|
+
try {
|
|
292
|
+
if (this.aiClient) {
|
|
293
|
+
const aiAnalysis = await this.aiClient.analyzeAnswerQuality(question.text, answer);
|
|
294
|
+
qualityMetrics = {
|
|
295
|
+
wordCount: answer.trim().split(/\s+/).length,
|
|
296
|
+
qualityScore: aiAnalysis.score,
|
|
297
|
+
isComprehensive: aiAnalysis.score >= 70,
|
|
298
|
+
canSkipFollowUps: aiAnalysis.score >= 85,
|
|
299
|
+
issues: aiAnalysis.issues,
|
|
300
|
+
suggestions: aiAnalysis.suggestions,
|
|
301
|
+
missingElements: aiAnalysis.missingElements
|
|
302
|
+
};
|
|
303
|
+
} else {
|
|
304
|
+
// Fallback to heuristic analysis
|
|
305
|
+
qualityMetrics = AnswerQualityAnalyzer.analyze(answer, question);
|
|
306
|
+
}
|
|
307
|
+
} catch (error) {
|
|
308
|
+
// If AI analysis fails, use heuristic
|
|
309
|
+
console.log(chalk.yellow(`⚠️ AI analysis failed, using fallback method`));
|
|
310
|
+
qualityMetrics = AnswerQualityAnalyzer.analyze(answer, question);
|
|
311
|
+
}
|
|
276
312
|
|
|
277
313
|
// Show quality feedback
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
314
|
+
if (qualityMetrics.issues && qualityMetrics.issues.length > 0 && qualityMetrics.qualityScore < 70) {
|
|
315
|
+
console.log(chalk.yellow(`\n💡 Quality: ${qualityMetrics.qualityScore}/100`));
|
|
316
|
+
if (qualityMetrics.suggestions && qualityMetrics.suggestions.length > 0) {
|
|
317
|
+
console.log(chalk.gray(` Suggestion: ${qualityMetrics.suggestions[0]}`));
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
const feedback = AnswerQualityAnalyzer.getFeedback(qualityMetrics);
|
|
321
|
+
if (feedback) {
|
|
322
|
+
console.log(chalk.cyan(`${feedback}`));
|
|
323
|
+
}
|
|
281
324
|
}
|
|
282
325
|
|
|
283
326
|
// Track answer with quality metrics
|
|
@@ -290,7 +333,26 @@ class Interviewer {
|
|
|
290
333
|
}
|
|
291
334
|
|
|
292
335
|
// Check if answer needs follow-up
|
|
293
|
-
|
|
336
|
+
let followUp = null;
|
|
337
|
+
|
|
338
|
+
// Try AI-generated follow-up first
|
|
339
|
+
if (this.aiClient && qualityMetrics.issues && qualityMetrics.issues.length > 0 && qualityMetrics.qualityScore < 70) {
|
|
340
|
+
try {
|
|
341
|
+
const aiFollowUp = await this.aiClient.generateFollowUp(question.text, answer, qualityMetrics.issues);
|
|
342
|
+
if (aiFollowUp) {
|
|
343
|
+
followUp = {
|
|
344
|
+
message: "Let me ask a more specific question:",
|
|
345
|
+
question: aiFollowUp
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
} catch (error) {
|
|
349
|
+
// If AI fails, use heuristic fallback
|
|
350
|
+
followUp = this.determineFollowUp(question, answer);
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
// Use heuristic follow-up
|
|
354
|
+
followUp = this.determineFollowUp(question, answer);
|
|
355
|
+
}
|
|
294
356
|
|
|
295
357
|
if (followUp) {
|
|
296
358
|
console.log(chalk.yellow(`\n🤖 ${followUp.message}\n`));
|
|
@@ -308,7 +370,23 @@ class Interviewer {
|
|
|
308
370
|
const combined = `${answer} | Follow-up: ${followUpAnswer}`;
|
|
309
371
|
|
|
310
372
|
// Re-analyze combined answer
|
|
311
|
-
|
|
373
|
+
let combinedMetrics;
|
|
374
|
+
try {
|
|
375
|
+
if (this.aiClient) {
|
|
376
|
+
const aiAnalysis = await this.aiClient.analyzeAnswerQuality(question.text, combined);
|
|
377
|
+
combinedMetrics = {
|
|
378
|
+
wordCount: combined.trim().split(/\s+/).length,
|
|
379
|
+
qualityScore: aiAnalysis.score,
|
|
380
|
+
isComprehensive: aiAnalysis.score >= 70,
|
|
381
|
+
canSkipFollowUps: aiAnalysis.score >= 85
|
|
382
|
+
};
|
|
383
|
+
} else {
|
|
384
|
+
combinedMetrics = AnswerQualityAnalyzer.analyze(combined, question);
|
|
385
|
+
}
|
|
386
|
+
} catch {
|
|
387
|
+
combinedMetrics = AnswerQualityAnalyzer.analyze(combined, question);
|
|
388
|
+
}
|
|
389
|
+
|
|
312
390
|
await this.progressTracker.answerQuestion(question.id, question.text, combined, combinedMetrics);
|
|
313
391
|
|
|
314
392
|
console.log(chalk.green('\n✓ Saved\n'));
|
|
@@ -8,7 +8,7 @@ const chalk = require('chalk');
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
class ProgressTracker {
|
|
11
|
-
constructor(sessionPath, totalBlocks, framework = null) {
|
|
11
|
+
constructor(sessionPath, totalBlocks, framework = null, aiConfig = null) {
|
|
12
12
|
this.sessionPath = sessionPath;
|
|
13
13
|
this.progressFile = path.join(sessionPath, '_progress.json');
|
|
14
14
|
this.progressBackupFile = path.join(sessionPath, '_progress.backup.json');
|
|
@@ -17,6 +17,13 @@ class ProgressTracker {
|
|
|
17
17
|
this.progress = {
|
|
18
18
|
sessionId: path.basename(sessionPath),
|
|
19
19
|
framework: framework, // Store framework for resume
|
|
20
|
+
aiConfig: aiConfig ? {
|
|
21
|
+
provider: aiConfig.provider,
|
|
22
|
+
providerName: aiConfig.providerName,
|
|
23
|
+
model: aiConfig.model,
|
|
24
|
+
envVar: aiConfig.envVar
|
|
25
|
+
// Note: We don't store the API key for security
|
|
26
|
+
} : null,
|
|
20
27
|
status: 'in-progress',
|
|
21
28
|
startedAt: new Date().toISOString(),
|
|
22
29
|
lastUpdated: new Date().toISOString(),
|