@iservu-inc/adf-cli 0.4.35 → 0.5.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.
@@ -41,7 +41,7 @@ class VSCodeGenerator extends ToolConfigGenerator {
41
41
  await this.ensureDir('.github');
42
42
 
43
43
  const content = this.framework === 'rapid' ?
44
- this.generateCopilotPRP() :
44
+ await this.generateCopilotPRP() :
45
45
  this.framework === 'balanced' ?
46
46
  this.generateCopilotBalanced() :
47
47
  this.generateCopilotBMAD();
@@ -49,23 +49,41 @@ class VSCodeGenerator extends ToolConfigGenerator {
49
49
  return await this.writeToProject('.github/copilot-instructions.md', content);
50
50
  }
51
51
 
52
- generateCopilotPRP() {
52
+ async generateCopilotPRP() {
53
53
  const projectName = this.getProjectName();
54
54
  const sections = this.outputs.sections || {};
55
+ const answers = await this.loadSessionAnswers();
56
+
57
+ // Extract content with fallback to answers
58
+ let goal = sections['1._goal_definition'] || sections['goal_definition'];
59
+ let techStack = this.extractTechStack(sections);
60
+ let blueprint = sections['4._implementation_blueprint'] || sections['implementation_blueprint'];
61
+ let validation = sections['5._validation'] || sections['validation'];
62
+
63
+ // Fallback to extracted answers if sections are empty
64
+ if (!goal || goal.length < 20) {
65
+ const whatBuilding = this.extractWhatBuildingFromAnswers(answers);
66
+ if (whatBuilding) goal = whatBuilding;
67
+ }
68
+
69
+ if (!techStack || techStack.includes('See framework')) {
70
+ const techFromAnswers = this.extractTechStackFromAnswers(answers);
71
+ if (techFromAnswers) techStack = techFromAnswers;
72
+ }
55
73
 
56
74
  return `# Copilot Instructions for ${projectName}
57
75
 
58
76
  ## Project Overview
59
77
 
60
- ${sections['1._goal_definition'] || sections['goal_definition'] || 'Software development project'}
78
+ ${goal || 'Software development project'}
61
79
 
62
80
  ## Tech Stack
63
81
 
64
- ${this.extractTechStack(sections)}
82
+ ${techStack || 'See PRP for tech stack'}
65
83
 
66
84
  ## Implementation Blueprint
67
85
 
68
- ${sections['4._implementation_blueprint'] || sections['implementation_blueprint'] || 'See PRP for details'}
86
+ ${blueprint || 'See PRP for implementation details'}
69
87
 
70
88
  ## Code Style
71
89
 
@@ -91,7 +109,7 @@ ${sections['4._implementation_blueprint'] || sections['implementation_blueprint'
91
109
 
92
110
  ## Success Criteria
93
111
 
94
- ${sections['5._validation'] || sections['validation'] || 'See PRP'}
112
+ ${validation || 'See PRP for validation criteria'}
95
113
 
96
114
  Ensure all code meets these criteria before considering it complete.
97
115
 
@@ -398,6 +416,57 @@ ${this.extractSection(prd, 'Performance') || '- Optimize critical paths\n- Monit
398
416
  return '0.3.0';
399
417
  }
400
418
  }
419
+
420
+ /**
421
+ * Load session answers from _progress.json
422
+ */
423
+ async loadSessionAnswers() {
424
+ const progressPath = path.join(this.sessionPath, '_progress.json');
425
+
426
+ try {
427
+ if (await fs.pathExists(progressPath)) {
428
+ const progress = await fs.readJson(progressPath);
429
+ return progress.answers || {};
430
+ }
431
+ } catch (error) {
432
+ // Fall back to empty if can't load
433
+ }
434
+
435
+ return {};
436
+ }
437
+
438
+ /**
439
+ * Extract tech stack from any answer
440
+ */
441
+ extractTechStackFromAnswers(answers) {
442
+ for (const [questionId, answer] of Object.entries(answers)) {
443
+ if (typeof answer === 'string') {
444
+ const lower = answer.toLowerCase();
445
+ if (lower.includes('react') || lower.includes('vue') || lower.includes('angular') ||
446
+ lower.includes('node') || lower.includes('python') || lower.includes('next') ||
447
+ lower.includes('postgres') || lower.includes('mongo') || lower.includes('mysql')) {
448
+ return answer;
449
+ }
450
+ }
451
+ }
452
+ return null;
453
+ }
454
+
455
+ /**
456
+ * Extract "what are you building" from any answer
457
+ */
458
+ extractWhatBuildingFromAnswers(answers) {
459
+ for (const [questionId, answer] of Object.entries(answers)) {
460
+ if (questionId.toLowerCase().includes('goal') ||
461
+ questionId.toLowerCase().includes('building') ||
462
+ questionId.toLowerCase().includes('project')) {
463
+ if (typeof answer === 'string' && answer.length > 20) {
464
+ return answer;
465
+ }
466
+ }
467
+ }
468
+ return null;
469
+ }
401
470
  }
402
471
 
403
472
  module.exports = VSCodeGenerator;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iservu-inc/adf-cli",
3
- "version": "0.4.35",
3
+ "version": "0.5.0",
4
4
  "description": "CLI tool for AgentDevFramework - AI-assisted development framework with multi-provider AI support",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -0,0 +1,262 @@
1
+ const AnswerAnalyzer = require('../lib/analysis/answer-analyzer');
2
+
3
+ describe('AnswerAnalyzer', () => {
4
+ let analyzer;
5
+ let mockAIClient;
6
+
7
+ beforeEach(() => {
8
+ mockAIClient = {
9
+ sendMessage: jest.fn()
10
+ };
11
+ analyzer = new AnswerAnalyzer(mockAIClient);
12
+ });
13
+
14
+ describe('heuristicExtraction', () => {
15
+ it('should extract tech stack from answer', () => {
16
+ const answer = 'I am building a React frontend with PostgreSQL database and Node.js backend';
17
+ const questionId = 'what-building';
18
+
19
+ const extracted = analyzer.heuristicExtraction(answer, questionId);
20
+
21
+ const techStack = extracted.find(e => e.type === 'tech_stack');
22
+ expect(techStack).toBeDefined();
23
+ expect(techStack.extractedTerms).toContain('react');
24
+ expect(techStack.extractedTerms).toContain('postgresql');
25
+ expect(techStack.extractedTerms).toContain('node');
26
+ expect(techStack.confidence).toBeGreaterThan(60);
27
+ });
28
+
29
+ it('should extract architecture patterns', () => {
30
+ const answer = 'I will build a frontend with a separate backend API using microservices';
31
+ const questionId = 'architecture';
32
+
33
+ const extracted = analyzer.heuristicExtraction(answer, questionId);
34
+
35
+ const frontendBackend = extracted.find(e =>
36
+ e.type === 'architecture' && e.pattern === 'frontend-backend-separation'
37
+ );
38
+ const microservices = extracted.find(e =>
39
+ e.type === 'architecture' && e.pattern === 'microservices'
40
+ );
41
+
42
+ expect(frontendBackend).toBeDefined();
43
+ expect(microservices).toBeDefined();
44
+ expect(frontendBackend.confidence).toBeGreaterThan(70);
45
+ });
46
+
47
+ it('should extract platform information', () => {
48
+ const answer = 'This is a mobile app for iOS and Android using React Native';
49
+ const questionId = 'platform';
50
+
51
+ const extracted = analyzer.heuristicExtraction(answer, questionId);
52
+
53
+ const platform = extracted.find(e => e.type === 'platform');
54
+ expect(platform).toBeDefined();
55
+ expect(platform.platform).toBe('mobile');
56
+ expect(platform.confidence).toBeGreaterThan(70);
57
+ });
58
+
59
+ it('should extract project goal from goal questions', () => {
60
+ const answer = 'Create a social media platform for developers to share code snippets';
61
+ const questionId = 'what-is-goal';
62
+
63
+ const extracted = analyzer.heuristicExtraction(answer, questionId);
64
+
65
+ const goal = extracted.find(e => e.type === 'project_goal');
66
+ expect(goal).toBeDefined();
67
+ expect(goal.confidence).toBe(95);
68
+ expect(goal.content).toBe(answer);
69
+ });
70
+
71
+ it('should extract timeline information', () => {
72
+ const answer = 'I need this completed in 3 weeks for a product launch';
73
+ const questionId = 'timeline';
74
+
75
+ const extracted = analyzer.heuristicExtraction(answer, questionId);
76
+
77
+ const timeline = extracted.find(e => e.type === 'timeline');
78
+ expect(timeline).toBeDefined();
79
+ expect(timeline.confidence).toBeGreaterThan(70);
80
+ });
81
+
82
+ it('should extract target users', () => {
83
+ const answer = 'The target audience is small businesses and their customers';
84
+ const questionId = 'users';
85
+
86
+ const extracted = analyzer.heuristicExtraction(answer, questionId);
87
+
88
+ const users = extracted.find(e => e.type === 'target_users');
89
+ expect(users).toBeDefined();
90
+ expect(users.confidence).toBe(70);
91
+ });
92
+
93
+ it('should return empty array for uninformative answers', () => {
94
+ const answer = 'yes';
95
+ const questionId = 'question';
96
+
97
+ const extracted = analyzer.heuristicExtraction(answer, questionId);
98
+
99
+ expect(extracted).toEqual([]);
100
+ });
101
+
102
+ it('should handle multiple tech mentions with higher confidence', () => {
103
+ const answer = 'Using React, Next.js, TypeScript, PostgreSQL, Redis, and AWS';
104
+ const questionId = 'tech';
105
+
106
+ const extracted = analyzer.heuristicExtraction(answer, questionId);
107
+
108
+ const techStack = extracted.find(e => e.type === 'tech_stack');
109
+ expect(techStack).toBeDefined();
110
+ expect(techStack.extractedTerms.length).toBeGreaterThanOrEqual(5);
111
+ expect(techStack.confidence).toBeGreaterThan(80);
112
+ });
113
+ });
114
+
115
+ describe('aiExtraction', () => {
116
+ it('should extract information using AI when available', async () => {
117
+ const mockAIResponse = JSON.stringify([
118
+ {
119
+ type: 'tech_stack',
120
+ content: 'React and PostgreSQL',
121
+ confidence: 95,
122
+ reasoning: 'Explicitly mentioned'
123
+ },
124
+ {
125
+ type: 'architecture',
126
+ content: 'separate backend API',
127
+ confidence: 85,
128
+ reasoning: 'Implies frontend-backend separation'
129
+ }
130
+ ]);
131
+
132
+ mockAIClient.sendMessage.mockResolvedValue(mockAIResponse);
133
+
134
+ const extracted = await analyzer.aiExtraction(
135
+ 'What tech stack?',
136
+ 'React frontend with separate backend API using PostgreSQL',
137
+ 'tech-stack'
138
+ );
139
+
140
+ expect(mockAIClient.sendMessage).toHaveBeenCalled();
141
+ expect(extracted).toHaveLength(2);
142
+ expect(extracted[0].type).toBe('tech_stack');
143
+ expect(extracted[0].method).toBe('ai');
144
+ expect(extracted[0].source).toBe('tech-stack');
145
+ });
146
+
147
+ it('should handle AI extraction errors gracefully', async () => {
148
+ mockAIClient.sendMessage.mockRejectedValue(new Error('AI error'));
149
+
150
+ const extracted = await analyzer.aiExtraction(
151
+ 'What are you building?',
152
+ 'A web app',
153
+ 'goal'
154
+ );
155
+
156
+ expect(extracted).toEqual([]);
157
+ });
158
+
159
+ it('should handle invalid JSON from AI', async () => {
160
+ mockAIClient.sendMessage.mockResolvedValue('This is not JSON');
161
+
162
+ const extracted = await analyzer.aiExtraction(
163
+ 'What tech?',
164
+ 'React and Node',
165
+ 'tech'
166
+ );
167
+
168
+ expect(extracted).toEqual([]);
169
+ });
170
+ });
171
+
172
+ describe('analyzeAnswer', () => {
173
+ it('should combine heuristic and AI results', async () => {
174
+ const mockAIResponse = JSON.stringify([
175
+ {
176
+ type: 'tech_stack',
177
+ content: 'React and Node.js',
178
+ confidence: 90,
179
+ reasoning: 'Explicitly stated'
180
+ }
181
+ ]);
182
+
183
+ mockAIClient.sendMessage.mockResolvedValue(mockAIResponse);
184
+
185
+ const extracted = await analyzer.analyzeAnswer(
186
+ 'What tech stack?',
187
+ 'I will use React for frontend and Node.js for backend',
188
+ 'tech-stack'
189
+ );
190
+
191
+ expect(extracted.length).toBeGreaterThan(0);
192
+ // Should have at least tech_stack from both methods
193
+ const techStackItems = extracted.filter(e => e.type === 'tech_stack');
194
+ expect(techStackItems.length).toBeGreaterThanOrEqual(1);
195
+ });
196
+
197
+ it('should use AI result if confidence is higher', async () => {
198
+ const mockAIResponse = JSON.stringify([
199
+ {
200
+ type: 'tech_stack',
201
+ content: 'Full tech stack analysis',
202
+ confidence: 95,
203
+ reasoning: 'Comprehensive analysis'
204
+ }
205
+ ]);
206
+
207
+ mockAIClient.sendMessage.mockResolvedValue(mockAIResponse);
208
+
209
+ const extracted = await analyzer.analyzeAnswer(
210
+ 'What tech?',
211
+ 'React and Node',
212
+ 'tech'
213
+ );
214
+
215
+ const techStack = extracted.find(e => e.type === 'tech_stack');
216
+ expect(techStack.confidence).toBeGreaterThanOrEqual(85);
217
+ });
218
+
219
+ it('should work without AI client', async () => {
220
+ const analyzerWithoutAI = new AnswerAnalyzer(null);
221
+
222
+ const extracted = await analyzerWithoutAI.analyzeAnswer(
223
+ 'What are you building?',
224
+ 'A React web application with PostgreSQL database',
225
+ 'goal'
226
+ );
227
+
228
+ expect(extracted.length).toBeGreaterThan(0);
229
+ const techStack = extracted.find(e => e.type === 'tech_stack');
230
+ expect(techStack).toBeDefined();
231
+ });
232
+ });
233
+
234
+ describe('getSummary', () => {
235
+ it('should generate human-readable summary', () => {
236
+ const extractedInfo = [
237
+ { type: 'tech_stack', confidence: 90 },
238
+ { type: 'architecture', confidence: 85 },
239
+ { type: 'project_goal', confidence: 95 }
240
+ ];
241
+
242
+ const summary = analyzer.getSummary(extractedInfo);
243
+
244
+ expect(summary).toContain('TECH STACK: 90% confidence');
245
+ expect(summary).toContain('ARCHITECTURE: 85% confidence');
246
+ expect(summary).toContain('PROJECT GOAL: 95% confidence');
247
+ });
248
+
249
+ it('should handle multiple items of same type', () => {
250
+ const extractedInfo = [
251
+ { type: 'tech_stack', confidence: 90 },
252
+ { type: 'tech_stack', confidence: 80 },
253
+ { type: 'architecture', confidence: 85 }
254
+ ];
255
+
256
+ const summary = analyzer.getSummary(extractedInfo);
257
+
258
+ // Should show highest confidence
259
+ expect(summary).toContain('TECH STACK: 90% confidence');
260
+ });
261
+ });
262
+ });