@iservu-inc/adf-cli 0.1.6 ā 0.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/.project/chats/current/2025-10-03_ADF-CLI-QUALITY-BASED-PROGRESS-AND-RESUME.md +399 -0
- package/.project/docs/architecture/SYSTEM-DESIGN.md +369 -0
- package/.project/docs/frameworks/FRAMEWORK-METHODOLOGIES.md +449 -0
- package/.project/docs/goals/PROJECT-VISION.md +112 -0
- package/.project/docs/tool-integrations/IDE-CUSTOMIZATIONS.md +578 -0
- package/CHANGELOG.md +115 -0
- package/jest.config.js +20 -0
- package/lib/commands/init.js +41 -113
- package/lib/frameworks/answer-quality-analyzer.js +216 -0
- package/lib/frameworks/interviewer.js +447 -0
- package/lib/frameworks/output-generators.js +345 -0
- package/lib/frameworks/progress-tracker.js +239 -0
- package/lib/frameworks/questions.js +664 -0
- package/lib/frameworks/session-manager.js +100 -0
- package/package.json +10 -5
- package/tests/answer-quality-analyzer.test.js +173 -0
- package/tests/progress-tracker.test.js +205 -0
- package/tests/session-manager.test.js +162 -0
package/lib/commands/init.js
CHANGED
|
@@ -5,18 +5,33 @@ const chalk = require('chalk');
|
|
|
5
5
|
const ora = require('ora');
|
|
6
6
|
const {
|
|
7
7
|
detectProjectType,
|
|
8
|
-
getWorkflowRecommendation
|
|
9
|
-
generateContextFile
|
|
8
|
+
getWorkflowRecommendation
|
|
10
9
|
} = require('../utils/project-detector');
|
|
11
|
-
const
|
|
10
|
+
const Interviewer = require('../frameworks/interviewer');
|
|
11
|
+
const SessionManager = require('../frameworks/session-manager');
|
|
12
12
|
const { deployToTool } = require('./deploy');
|
|
13
13
|
|
|
14
14
|
async function init(options) {
|
|
15
|
-
console.log(chalk.cyan.bold('\nš AgentDevFramework
|
|
15
|
+
console.log(chalk.cyan.bold('\nš AgentDevFramework - Software Development Requirements\n'));
|
|
16
16
|
|
|
17
17
|
const cwd = process.cwd();
|
|
18
18
|
const adfDir = path.join(cwd, '.adf');
|
|
19
19
|
|
|
20
|
+
// Check for resumable sessions FIRST (before asking to overwrite)
|
|
21
|
+
const sessionManager = new SessionManager(cwd);
|
|
22
|
+
const existingSession = await sessionManager.promptToResume();
|
|
23
|
+
|
|
24
|
+
if (existingSession) {
|
|
25
|
+
// Resume existing session
|
|
26
|
+
const interviewer = new Interviewer(existingSession.progress.framework || 'balanced', cwd, existingSession);
|
|
27
|
+
const sessionPath = await interviewer.start();
|
|
28
|
+
|
|
29
|
+
console.log(chalk.green.bold('\n⨠Requirements gathering complete!\n'));
|
|
30
|
+
console.log(chalk.cyan(`š Session saved to: ${sessionPath}\n`));
|
|
31
|
+
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
20
35
|
// Check if already initialized
|
|
21
36
|
if (await fs.pathExists(adfDir)) {
|
|
22
37
|
const { overwrite } = await inquirer.prompt([
|
|
@@ -41,132 +56,59 @@ async function init(options) {
|
|
|
41
56
|
const projectType = await detectProjectType(cwd);
|
|
42
57
|
spinner.succeed(`Project type: ${chalk.green(projectType.type)}`);
|
|
43
58
|
|
|
44
|
-
// Determine workflow
|
|
59
|
+
// Determine workflow/framework
|
|
45
60
|
let workflow;
|
|
46
61
|
|
|
47
62
|
if (options.rapid) {
|
|
48
63
|
workflow = 'rapid';
|
|
49
|
-
console.log(chalk.blue('
|
|
64
|
+
console.log(chalk.blue('\nUsing: PRP Framework (Rapid Development) - from --rapid flag'));
|
|
50
65
|
} else if (options.balanced) {
|
|
51
66
|
workflow = 'balanced';
|
|
52
|
-
console.log(chalk.blue('
|
|
67
|
+
console.log(chalk.blue('\nUsing: PRP + Spec-Kit (Balanced) - from --balanced flag'));
|
|
53
68
|
} else if (options.comprehensive) {
|
|
54
69
|
workflow = 'comprehensive';
|
|
55
|
-
console.log(chalk.blue('
|
|
70
|
+
console.log(chalk.blue('\nUsing: BMAD Framework (Comprehensive) - from --comprehensive flag'));
|
|
56
71
|
} else {
|
|
57
72
|
// Interactive workflow selection
|
|
58
73
|
workflow = await getWorkflowRecommendation(projectType);
|
|
59
74
|
}
|
|
60
75
|
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
{
|
|
64
|
-
type: 'confirm',
|
|
65
|
-
name: 'hasDocs',
|
|
66
|
-
message: 'Do you have documentation URLs to include?',
|
|
67
|
-
default: false
|
|
68
|
-
}
|
|
69
|
-
]);
|
|
70
|
-
|
|
71
|
-
let documentationUrls = [];
|
|
72
|
-
if (hasDocs) {
|
|
73
|
-
const { urls } = await inquirer.prompt([
|
|
74
|
-
{
|
|
75
|
-
type: 'input',
|
|
76
|
-
name: 'urls',
|
|
77
|
-
message: 'Enter documentation URLs (comma-separated):',
|
|
78
|
-
filter: (input) => input.split(',').map(url => url.trim()).filter(Boolean)
|
|
79
|
-
}
|
|
80
|
-
]);
|
|
81
|
-
documentationUrls = urls;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Gather local documentation files (optional)
|
|
85
|
-
const { hasLocalDocs } = await inquirer.prompt([
|
|
86
|
-
{
|
|
87
|
-
type: 'confirm',
|
|
88
|
-
name: 'hasLocalDocs',
|
|
89
|
-
message: 'Do you have local documentation files to include? (use "." as the project root folder & add like so "./docs/")',
|
|
90
|
-
default: false
|
|
91
|
-
}
|
|
92
|
-
]);
|
|
76
|
+
// Create .adf directory
|
|
77
|
+
await fs.ensureDir(adfDir);
|
|
93
78
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const { files } = await inquirer.prompt([
|
|
97
|
-
{
|
|
98
|
-
type: 'input',
|
|
99
|
-
name: 'files',
|
|
100
|
-
message: 'Enter local documentation paths (comma-separated, e.g., ./docs/, ./README.md):',
|
|
101
|
-
filter: (input) => input.split(',').map(file => file.trim()).filter(Boolean)
|
|
102
|
-
}
|
|
103
|
-
]);
|
|
104
|
-
documentationFiles = files;
|
|
105
|
-
}
|
|
79
|
+
// Start AI-guided interview
|
|
80
|
+
console.log(chalk.gray('\n' + 'ā'.repeat(60)) + '\n');
|
|
106
81
|
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
await copyTemplates(cwd);
|
|
110
|
-
copySpinner.succeed('Framework files copied to .adf/');
|
|
111
|
-
|
|
112
|
-
// Generate context file
|
|
113
|
-
const context = generateContextFile({
|
|
114
|
-
workflow,
|
|
115
|
-
projectType: projectType.type,
|
|
116
|
-
documentationUrls,
|
|
117
|
-
documentationFiles,
|
|
118
|
-
timestamp: new Date().toISOString()
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
await fs.writeJson(path.join(adfDir, 'context.json'), context, { spaces: 2 });
|
|
122
|
-
console.log(chalk.green('ā Created .adf/context.json'));
|
|
123
|
-
|
|
124
|
-
// Copy .env.template to project root
|
|
125
|
-
const envTemplateSrc = path.join(__dirname, '../templates/.env.template');
|
|
126
|
-
const envTemplateDest = path.join(cwd, '.env.template');
|
|
127
|
-
|
|
128
|
-
if (await fs.pathExists(envTemplateSrc)) {
|
|
129
|
-
await fs.copy(envTemplateSrc, envTemplateDest);
|
|
130
|
-
console.log(chalk.green('ā Created .env.template'));
|
|
131
|
-
}
|
|
82
|
+
const interviewer = new Interviewer(workflow, cwd);
|
|
83
|
+
const sessionPath = await interviewer.start();
|
|
132
84
|
|
|
133
|
-
//
|
|
134
|
-
|
|
85
|
+
// Show next steps
|
|
86
|
+
console.log(chalk.cyan('š Next Steps:\n'));
|
|
87
|
+
console.log(chalk.gray(` 1. Review your requirements: ${sessionPath}/outputs/`));
|
|
88
|
+
console.log(chalk.gray(` 2. Share the output files with your AI coding assistant`));
|
|
89
|
+
console.log(chalk.gray(` 3. Start building based on the detailed requirements\n`));
|
|
135
90
|
|
|
91
|
+
// Optional: Deploy to tool
|
|
136
92
|
if (options.tool) {
|
|
137
93
|
console.log('');
|
|
138
94
|
await deployToTool(options.tool, { silent: false });
|
|
139
|
-
deployNow = true;
|
|
140
95
|
} else {
|
|
141
|
-
|
|
142
|
-
const response = await inquirer.prompt([
|
|
96
|
+
const { deployNow } = await inquirer.prompt([
|
|
143
97
|
{
|
|
144
98
|
type: 'confirm',
|
|
145
99
|
name: 'deployNow',
|
|
146
|
-
message: 'Deploy to a development tool
|
|
147
|
-
default:
|
|
100
|
+
message: 'Deploy framework to a development tool?',
|
|
101
|
+
default: false
|
|
148
102
|
}
|
|
149
103
|
]);
|
|
150
104
|
|
|
151
|
-
deployNow = response.deployNow;
|
|
152
|
-
|
|
153
105
|
if (deployNow) {
|
|
154
106
|
const { tool } = await inquirer.prompt([
|
|
155
107
|
{
|
|
156
108
|
type: 'list',
|
|
157
109
|
name: 'tool',
|
|
158
|
-
message: 'Select
|
|
159
|
-
choices: [
|
|
160
|
-
'windsurf',
|
|
161
|
-
'cursor',
|
|
162
|
-
'vscode',
|
|
163
|
-
'vscode-insider',
|
|
164
|
-
'kiro',
|
|
165
|
-
'trae',
|
|
166
|
-
'claude-code',
|
|
167
|
-
'gemini-cli',
|
|
168
|
-
'codex-cli'
|
|
169
|
-
]
|
|
110
|
+
message: 'Select tool:',
|
|
111
|
+
choices: ['windsurf', 'cursor', 'vscode', 'claude-code', 'gemini-cli']
|
|
170
112
|
}
|
|
171
113
|
]);
|
|
172
114
|
|
|
@@ -175,21 +117,7 @@ async function init(options) {
|
|
|
175
117
|
}
|
|
176
118
|
}
|
|
177
119
|
|
|
178
|
-
|
|
179
|
-
console.log(chalk.green.bold('\n⨠Initialization complete!\n'));
|
|
180
|
-
|
|
181
|
-
console.log(chalk.cyan('Next steps:'));
|
|
182
|
-
console.log(chalk.gray(' 1. Review .adf/context.json'));
|
|
183
|
-
console.log(chalk.gray(' 2. Copy .env.template to .env and configure'));
|
|
184
|
-
if (!deployNow) {
|
|
185
|
-
console.log(chalk.gray(' 3. Run "adf deploy <tool>" to deploy to your editor'));
|
|
186
|
-
console.log(chalk.gray(' 4. Check documentation: .adf/shared/templates/\n'));
|
|
187
|
-
} else {
|
|
188
|
-
console.log(chalk.gray(' 3. Check documentation: .adf/shared/templates/\n'));
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
console.log(chalk.blue(`Workflow: ${workflow === 'rapid' ? 'Level 1 (Rapid)' : workflow === 'balanced' ? 'Level 2 (Balanced)' : 'Level 3 (Comprehensive)'}`));
|
|
192
|
-
console.log(chalk.gray('Run "adf deploy --list" to see available tools\n'));
|
|
120
|
+
console.log(chalk.green.bold('\nā
All done! Happy coding! š\n'));
|
|
193
121
|
}
|
|
194
122
|
|
|
195
123
|
module.exports = init;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Answer Quality Analyzer
|
|
3
|
+
* Evaluates answer richness and completeness to determine if follow-ups are needed
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class AnswerQualityAnalyzer {
|
|
7
|
+
/**
|
|
8
|
+
* Analyze answer quality and richness
|
|
9
|
+
* @param {string} answer - User's answer
|
|
10
|
+
* @param {object} question - Question metadata
|
|
11
|
+
* @returns {object} Quality metrics
|
|
12
|
+
*/
|
|
13
|
+
static analyze(answer, question) {
|
|
14
|
+
const metrics = {
|
|
15
|
+
wordCount: this.getWordCount(answer),
|
|
16
|
+
hasKeywords: this.checkKeywords(answer, question.keywords || []),
|
|
17
|
+
hasRequiredElements: this.checkRequiredElements(answer, question.requiredElements || []),
|
|
18
|
+
isDetailed: this.checkDetailLevel(answer),
|
|
19
|
+
hasTechnicalDepth: this.checkTechnicalDepth(answer),
|
|
20
|
+
qualityScore: 0,
|
|
21
|
+
isComprehensive: false,
|
|
22
|
+
canSkipFollowUps: false
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Calculate quality score (0-100)
|
|
26
|
+
let score = 0;
|
|
27
|
+
|
|
28
|
+
// Word count scoring (up to 30 points)
|
|
29
|
+
if (metrics.wordCount >= 50) score += 30;
|
|
30
|
+
else if (metrics.wordCount >= 30) score += 20;
|
|
31
|
+
else if (metrics.wordCount >= 15) score += 10;
|
|
32
|
+
|
|
33
|
+
// Keyword presence (up to 20 points)
|
|
34
|
+
score += metrics.hasKeywords.score;
|
|
35
|
+
|
|
36
|
+
// Required elements (up to 25 points)
|
|
37
|
+
score += metrics.hasRequiredElements.score;
|
|
38
|
+
|
|
39
|
+
// Detail level (up to 15 points)
|
|
40
|
+
if (metrics.isDetailed.hasBulletPoints) score += 5;
|
|
41
|
+
if (metrics.isDetailed.hasMultipleSentences) score += 5;
|
|
42
|
+
if (metrics.isDetailed.hasExamples) score += 5;
|
|
43
|
+
|
|
44
|
+
// Technical depth (up to 10 points)
|
|
45
|
+
if (metrics.hasTechnicalDepth.hasTechStack) score += 5;
|
|
46
|
+
if (metrics.hasTechnicalDepth.hasVersions) score += 5;
|
|
47
|
+
|
|
48
|
+
metrics.qualityScore = Math.min(score, 100);
|
|
49
|
+
|
|
50
|
+
// Determine if answer is comprehensive enough
|
|
51
|
+
metrics.isComprehensive = metrics.qualityScore >= 70;
|
|
52
|
+
metrics.canSkipFollowUps = metrics.qualityScore >= 85;
|
|
53
|
+
|
|
54
|
+
return metrics;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static getWordCount(answer) {
|
|
58
|
+
return answer.trim().split(/\s+/).length;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static checkKeywords(answer, keywords) {
|
|
62
|
+
const lowerAnswer = answer.toLowerCase();
|
|
63
|
+
const matchedKeywords = keywords.filter(kw => lowerAnswer.includes(kw.toLowerCase()));
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
matched: matchedKeywords,
|
|
67
|
+
count: matchedKeywords.length,
|
|
68
|
+
total: keywords.length,
|
|
69
|
+
score: keywords.length > 0 ? Math.round((matchedKeywords.length / keywords.length) * 20) : 0
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static checkRequiredElements(answer, requiredElements) {
|
|
74
|
+
const lowerAnswer = answer.toLowerCase();
|
|
75
|
+
const detectedElements = [];
|
|
76
|
+
|
|
77
|
+
// Platform detection
|
|
78
|
+
if (requiredElements.includes('platform')) {
|
|
79
|
+
const platforms = ['web', 'mobile', 'desktop', 'api', 'backend', 'frontend', 'fullstack', 'cli', 'ios', 'android'];
|
|
80
|
+
if (platforms.some(p => lowerAnswer.includes(p))) {
|
|
81
|
+
detectedElements.push('platform');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Technology detection
|
|
86
|
+
if (requiredElements.includes('technology')) {
|
|
87
|
+
const techs = ['react', 'vue', 'angular', 'node', 'python', 'java', 'typescript', 'javascript', 'next', 'django', 'rails', 'express', 'fastapi', 'spring', 'laravel', '.net', 'go', 'rust'];
|
|
88
|
+
if (techs.some(t => lowerAnswer.includes(t))) {
|
|
89
|
+
detectedElements.push('technology');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// User interaction detection
|
|
94
|
+
if (requiredElements.includes('user-interaction')) {
|
|
95
|
+
const actions = ['click', 'submit', 'add', 'create', 'update', 'delete', 'view', 'search', 'filter', 'upload', 'download', 'form', 'button'];
|
|
96
|
+
if (actions.some(a => lowerAnswer.includes(a))) {
|
|
97
|
+
detectedElements.push('user-interaction');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Problem statement detection
|
|
102
|
+
if (requiredElements.includes('problem-statement')) {
|
|
103
|
+
const problemWords = ['problem', 'issue', 'challenge', 'pain', 'difficult', 'struggle', 'frustrat', 'miss', 'lose', 'lack'];
|
|
104
|
+
if (problemWords.some(p => lowerAnswer.includes(p))) {
|
|
105
|
+
detectedElements.push('problem-statement');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Measurable outcome detection
|
|
110
|
+
if (requiredElements.includes('measurable-outcome')) {
|
|
111
|
+
const metrics = ['%', 'percent', 'increase', 'decrease', 'reduce', 'save', 'improve', 'time', 'hour', 'day', 'week', 'user', 'metric', 'kpi', 'measure'];
|
|
112
|
+
if (metrics.some(m => lowerAnswer.includes(m))) {
|
|
113
|
+
detectedElements.push('measurable-outcome');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// File paths detection
|
|
118
|
+
if (requiredElements.includes('file-paths')) {
|
|
119
|
+
const pathPatterns = [/[./]\w+\/\w+/, /src\//, /app\//, /components\//, /\.ts/, /\.js/, /\.jsx/, /\.tsx/, /\.py/];
|
|
120
|
+
if (pathPatterns.some(p => p.test(answer))) {
|
|
121
|
+
detectedElements.push('file-paths');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Data structure detection
|
|
126
|
+
if (requiredElements.includes('data-structure')) {
|
|
127
|
+
const dataWords = ['table', 'schema', 'entity', 'model', 'field', 'column', 'database', 'collection', 'document', 'id', 'foreign key'];
|
|
128
|
+
if (dataWords.some(d => lowerAnswer.includes(d))) {
|
|
129
|
+
detectedElements.push('data-structure');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// API endpoints detection
|
|
134
|
+
if (requiredElements.includes('api-endpoints')) {
|
|
135
|
+
const apiPatterns = [/GET|POST|PUT|PATCH|DELETE/, /\/api\//, /endpoint/, /route/, /rest/, /graphql/i];
|
|
136
|
+
if (apiPatterns.some(p => p.test(answer))) {
|
|
137
|
+
detectedElements.push('api-endpoints');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
detected: detectedElements,
|
|
143
|
+
count: detectedElements.length,
|
|
144
|
+
total: requiredElements.length,
|
|
145
|
+
score: requiredElements.length > 0 ? Math.round((detectedElements.length / requiredElements.length) * 25) : 0
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static checkDetailLevel(answer) {
|
|
150
|
+
return {
|
|
151
|
+
hasBulletPoints: /[-*ā¢]\s/.test(answer) || /\n\d+\./.test(answer),
|
|
152
|
+
hasMultipleSentences: answer.split(/[.!?]+/).filter(s => s.trim().length > 10).length >= 2,
|
|
153
|
+
hasExamples: /example|e\.g\.|such as|like|for instance/i.test(answer),
|
|
154
|
+
hasCodeReferences: /`[^`]+`|```/.test(answer),
|
|
155
|
+
hasUrls: /https?:\/\//.test(answer)
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
static checkTechnicalDepth(answer) {
|
|
160
|
+
return {
|
|
161
|
+
hasTechStack: /(react|vue|angular|node|python|java|typescript|next|django|rails)/i.test(answer),
|
|
162
|
+
hasVersions: /\d+\.\d+|\bv\d+/i.test(answer),
|
|
163
|
+
hasSpecificTools: /(npm|yarn|pip|maven|gradle|docker|kubernetes|aws|azure|gcp)/i.test(answer),
|
|
164
|
+
hasFileExtensions: /\.(js|ts|jsx|tsx|py|java|go|rs|rb|php)/.test(answer)
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Determine if subsequent questions can be skipped based on answer quality
|
|
170
|
+
* @param {object} metrics - Quality metrics from analyze()
|
|
171
|
+
* @param {array} upcomingQuestions - Questions that might be skipped
|
|
172
|
+
* @returns {array} Questions that can be safely skipped
|
|
173
|
+
*/
|
|
174
|
+
static determineSkippableQuestions(metrics, upcomingQuestions) {
|
|
175
|
+
if (!metrics.isComprehensive) {
|
|
176
|
+
return []; // Not comprehensive enough to skip anything
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const skippable = [];
|
|
180
|
+
const detectedInfo = new Set([
|
|
181
|
+
...metrics.hasKeywords.matched,
|
|
182
|
+
...metrics.hasRequiredElements.detected
|
|
183
|
+
]);
|
|
184
|
+
|
|
185
|
+
upcomingQuestions.forEach(q => {
|
|
186
|
+
// If this question asks for info we already have, it's skippable
|
|
187
|
+
if (q.requiredElements) {
|
|
188
|
+
const alreadyHave = q.requiredElements.every(elem => detectedInfo.has(elem));
|
|
189
|
+
if (alreadyHave && metrics.qualityScore >= 85) {
|
|
190
|
+
skippable.push(q.id);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return skippable;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Generate feedback message for user based on answer quality
|
|
200
|
+
* @param {object} metrics - Quality metrics
|
|
201
|
+
* @returns {string} Feedback message
|
|
202
|
+
*/
|
|
203
|
+
static getFeedback(metrics) {
|
|
204
|
+
if (metrics.qualityScore >= 90) {
|
|
205
|
+
return 'š Excellent! Very comprehensive answer.';
|
|
206
|
+
} else if (metrics.qualityScore >= 70) {
|
|
207
|
+
return 'ā Great! Good level of detail.';
|
|
208
|
+
} else if (metrics.qualityScore >= 50) {
|
|
209
|
+
return 'š Good start.';
|
|
210
|
+
} else {
|
|
211
|
+
return null; // Will trigger follow-up questions
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
module.exports = AnswerQualityAnalyzer;
|