@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.
@@ -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 { copyTemplates } = require('../utils/copy-templates');
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 Initialization\n'));
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('Using Level 1 (Rapid Development) - from --rapid flag'));
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('Using Level 2 (Balanced) - from --balanced flag'));
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('Using Level 3 (Comprehensive) - from --comprehensive flag'));
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
- // Gather documentation URLs (optional)
62
- const { hasDocs } = await inquirer.prompt([
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
- let documentationFiles = [];
95
- if (hasLocalDocs) {
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
- // Copy framework templates
108
- const copySpinner = ora('Copying framework files...').start();
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
- // Deploy to tool if specified
134
- let deployNow = false;
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
- // Ask if they want to deploy now
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 now?',
147
- default: true
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 deployment tool:',
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
- // Success message
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;