@iservu-inc/adf-cli 0.14.6 → 0.17.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.
Files changed (37) hide show
  1. package/.project/chats/current/SESSION-STATUS.md +25 -279
  2. package/.project/docs/PHASE-6-ADVANCED-LEARNING.md +46 -0
  3. package/.project/docs/ROADMAP.md +72 -157
  4. package/.project/docs/designs/CUSTOM-ARTIFACT-UPLOAD.md +259 -0
  5. package/.project/docs/designs/LEARNING-RULES-EXCHANGE.md +77 -0
  6. package/CHANGELOG.md +2054 -2995
  7. package/README.md +4 -7
  8. package/bin/adf.js +80 -0
  9. package/conductor/tracks/session_resume_review_20260113/plan.md +18 -0
  10. package/conductor/tracks.md +4 -0
  11. package/gemini.md +3 -0
  12. package/lib/ai/ai-client.js +9 -9
  13. package/lib/commands/deploy.js +14 -0
  14. package/lib/commands/guide.js +32 -0
  15. package/lib/commands/import.js +439 -0
  16. package/lib/commands/init.js +17 -4
  17. package/lib/frameworks/interviewer.js +277 -85
  18. package/lib/frameworks/progress-tracker.js +18 -0
  19. package/lib/generators/a2a-generator.js +289 -0
  20. package/lib/generators/index.js +11 -0
  21. package/lib/learning/learning-manager.js +107 -8
  22. package/lib/learning/rules-exporter.js +103 -0
  23. package/lib/learning/rules-importer.js +141 -0
  24. package/lib/templates/shared/agents/analyst.md +1 -1
  25. package/lib/templates/shared/agents/architect.md +1 -1
  26. package/lib/templates/shared/agents/dev.md +1 -1
  27. package/lib/templates/shared/agents/pm.md +2 -2
  28. package/lib/templates/shared/agents/qa.md +1 -1
  29. package/lib/templates/shared/agents/sm.md +3 -3
  30. package/lib/templates/shared/memory/constitution.md +2 -2
  31. package/lib/templates/shared/templates/README.md +14 -14
  32. package/lib/templates/shared/templates/prd-template.md +1 -1
  33. package/lib/utils/artifact-detector.js +253 -0
  34. package/lib/utils/tool-feature-registry.js +6 -0
  35. package/package.json +1 -1
  36. package/tests/a2a-generator.test.js +288 -0
  37. package/tests/progress-tracker.test.js +16 -0
@@ -159,10 +159,14 @@ async function init(options) {
159
159
  const interviewer = new Interviewer(existingSession.progress.framework || 'balanced', cwd, existingSession, aiConfig);
160
160
  const sessionPath = await interviewer.start();
161
161
 
162
- console.log(chalk.green.bold('\n✨ Requirements gathering complete!\n'));
163
- console.log(chalk.cyan(`šŸ“ Session saved to: ${sessionPath}\n`));
164
-
165
- return;
162
+ if (sessionPath === 'back') {
163
+ // User requested to go back to main menu
164
+ // Fall through to existing content check
165
+ } else {
166
+ console.log(chalk.green.bold('\n✨ Requirements gathering complete!\n'));
167
+ console.log(chalk.cyan(`šŸ“ Session saved to: ${sessionPath}\n`));
168
+ return;
169
+ }
166
170
  }
167
171
 
168
172
  // Check if already initialized with actual content
@@ -318,6 +322,15 @@ async function init(options) {
318
322
  console.log(chalk.gray(` āœ“ Files saved to: ${sessionPath}/outputs/`));
319
323
  console.log(chalk.gray(` āœ“ You can review your requirements anytime\n`));
320
324
 
325
+ // Generate A2A agent cards
326
+ try {
327
+ const { generateA2A } = require('../generators');
328
+ await generateA2A(sessionPath, cwd, workflow);
329
+ console.log(chalk.gray(' āœ“ A2A agent cards generated'));
330
+ } catch (error) {
331
+ console.warn(chalk.yellow(` ⚠ Could not generate A2A cards: ${error.message}`));
332
+ }
333
+
321
334
  // Optional: Deploy to tool
322
335
  if (options.tool) {
323
336
  console.log('');
@@ -344,6 +344,14 @@ class Interviewer {
344
344
  const resumeInfo = this.progressTracker.getResumeInfo();
345
345
  console.log(chalk.gray(`Last updated: ${new Date(resumeInfo.lastUpdated).toLocaleString()}`));
346
346
  console.log(chalk.gray(`Progress: ${resumeInfo.completedBlocks}/${resumeInfo.totalBlocks} blocks | ${resumeInfo.totalQuestionsAnswered} questions\n`));
347
+
348
+ // Offer to review existing answers
349
+ const reviewAction = await this.reviewProgress();
350
+ if (reviewAction === 'back') {
351
+ return 'back';
352
+ } else if (reviewAction === 'exit') {
353
+ process.exit(0);
354
+ }
347
355
  }
348
356
 
349
357
  console.log(chalk.cyan(`\nā„¹ļø Total question blocks: ${questionBlocks.length}\n`));
@@ -436,6 +444,158 @@ class Interviewer {
436
444
  return this.sessionPath;
437
445
  }
438
446
 
447
+ async reviewProgress() {
448
+ const answeredIds = Object.keys(this.answers);
449
+ if (answeredIds.length === 0) return 'continue';
450
+
451
+ const { shouldReview } = await inquirer.prompt([
452
+ {
453
+ type: 'confirm',
454
+ name: 'shouldReview',
455
+ message: `Review ${answeredIds.length} previously answered questions?`,
456
+ default: true
457
+ }
458
+ ]);
459
+
460
+ if (!shouldReview) return 'continue';
461
+
462
+ // Collect and format Q/A pairs
463
+ const questions = this.customQuestions || getQuestionsForFramework(this.framework);
464
+ const pairs = [];
465
+
466
+ for (const id of answeredIds) {
467
+ const q = questions.find(q => q.id === id);
468
+ if (q) {
469
+ const answerText = typeof this.answers[id] === 'string' ? this.answers[id] : this.answers[id].text;
470
+ pairs.push({
471
+ id: id,
472
+ question: q.text,
473
+ answer: answerText
474
+ });
475
+ }
476
+ }
477
+
478
+ // Pagination logic
479
+ const terminalHeight = process.stdout.rows || 24;
480
+ const availableHeight = terminalHeight - 6; // Reserve lines for instructions
481
+
482
+ let currentIndex = 0;
483
+
484
+ while (currentIndex < pairs.length) {
485
+ console.clear();
486
+ console.log(chalk.cyan.bold('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
487
+ console.log(chalk.cyan.bold('Previously Answered Questions'));
488
+ console.log(chalk.cyan.bold('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
489
+
490
+ let linesUsed = 0;
491
+ let displayedCount = 0;
492
+
493
+ // Calculate how many pairs fit
494
+ for (let i = currentIndex; i < pairs.length; i++) {
495
+ const pair = pairs[i];
496
+ const qNum = (i + 1).toString().padStart(2, '0');
497
+
498
+ // Estimate lines: Question (2) + Answer lines + Spacing (2)
499
+ const answerLines = Math.ceil(pair.answer.length / (process.stdout.columns || 80));
500
+ const pairHeight = 2 + answerLines + 2;
501
+
502
+ if (linesUsed + pairHeight > availableHeight && displayedCount > 0) {
503
+ break;
504
+ }
505
+
506
+ console.log(chalk.white.bold(`Q-${qNum}: ${pair.question}`));
507
+ console.log(chalk.green(`A-${qNum}: ${pair.answer}`));
508
+ console.log(chalk.cyan.bold('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
509
+
510
+ linesUsed += pairHeight;
511
+ displayedCount++;
512
+ }
513
+
514
+ const nextIndex = currentIndex + displayedCount;
515
+ const isEnd = nextIndex >= pairs.length;
516
+
517
+ console.log('');
518
+ if (isEnd) {
519
+ console.log(chalk.yellow('End of review.'));
520
+ } else {
521
+ console.log(chalk.gray(`... ${pairs.length - nextIndex} more items ...`));
522
+ }
523
+
524
+ // Navigation prompt
525
+ const { action } = await inquirer.prompt([
526
+ {
527
+ type: 'expand',
528
+ name: 'action',
529
+ message: 'Navigation:',
530
+ choices: [
531
+ { key: ' ', name: 'Next page', value: 'next' },
532
+ { key: 'e', name: 'Edit an answer', value: 'edit' },
533
+ { key: 'c', name: 'Continue interview', value: 'continue' },
534
+ { key: 'q', name: 'Quit review (Back to menu)', value: 'back' },
535
+ { key: 'x', name: 'Exit CLI', value: 'exit' }
536
+ ],
537
+ default: isEnd ? 1 : 0 // Default to Continue if end, else Next
538
+ }
539
+ ]);
540
+
541
+ if (action === 'next') {
542
+ if (isEnd) return 'continue'; // 'Next' at end means continue
543
+ currentIndex = nextIndex;
544
+ } else if (action === 'continue') {
545
+ return 'continue';
546
+ } else if (action === 'back') {
547
+ return 'back';
548
+ } else if (action === 'exit') {
549
+ return 'exit';
550
+ } else if (action === 'edit') {
551
+ // Show list of currently visible questions to edit
552
+ const pagePairs = pairs.slice(currentIndex, nextIndex);
553
+ const { questionToEdit } = await inquirer.prompt([
554
+ {
555
+ type: 'list',
556
+ name: 'questionToEdit',
557
+ message: 'Select a question to edit:',
558
+ choices: [
559
+ ...pagePairs.map((p, idx) => ({
560
+ name: p.question.length > 60 ? p.question.substring(0, 57) + '...' : p.question,
561
+ value: currentIndex + idx
562
+ })),
563
+ { name: '← Cancel', value: -1 }
564
+ ]
565
+ }
566
+ ]);
567
+
568
+ if (questionToEdit !== -1) {
569
+ const targetPair = pairs[questionToEdit];
570
+
571
+ const { newAnswer } = await inquirer.prompt([
572
+ {
573
+ type: 'editor',
574
+ name: 'newAnswer',
575
+ message: 'Edit your answer:',
576
+ default: targetPair.answer
577
+ }
578
+ ]);
579
+
580
+ if (newAnswer && newAnswer.trim()) {
581
+ const trimmedAnswer = newAnswer.trim();
582
+ // Update local state
583
+ this.answers[targetPair.id] = trimmedAnswer;
584
+ pairs[questionToEdit].answer = trimmedAnswer;
585
+
586
+ // Save progress
587
+ await this.progressTracker.updateAnswer(targetPair.id, trimmedAnswer);
588
+
589
+ console.log(chalk.green('āœ“ Answer updated'));
590
+ await new Promise(r => setTimeout(r, 1000));
591
+ }
592
+ }
593
+ }
594
+ }
595
+
596
+ return 'continue';
597
+ }
598
+
439
599
  async promptAIConfiguration() {
440
600
  console.log(chalk.cyan.bold('━'.repeat(60)));
441
601
  console.log(chalk.cyan.bold('\nšŸ¤– AI Provider Configuration (Pre-Interview)\n'));
@@ -557,11 +717,14 @@ class Interviewer {
557
717
  ? this.answers[question.id]
558
718
  : this.answers[question.id].text;
559
719
 
560
- console.log(chalk.cyan(`Question ${i + 1}/${block.questions.length}`) + chalk.gray(` (Block ${currentBlock}/${totalBlocks})`) + '\n');
561
- console.log(chalk.gray('━'.repeat(60)));
562
- console.log(chalk.green(`\nāœ“ Already answered: ${question.text}`));
563
- console.log(chalk.gray(` Answer: ${answerText.substring(0, 80)}${answerText.length > 80 ? '...' : ''}\n`));
564
- console.log(chalk.gray('━'.repeat(60)) + '\n');
720
+ const qNum = (i + 1).toString().padStart(2, '0');
721
+
722
+ console.log(chalk.cyan.bold('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
723
+ console.log(chalk.cyan.bold('Previously Answered Questions'));
724
+ console.log(chalk.cyan.bold('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
725
+ console.log(chalk.white.bold(`Q-${qNum}: ${question.text}`));
726
+ console.log(chalk.green(`A-${qNum}: ${answerText}`));
727
+ console.log(chalk.cyan.bold('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
565
728
 
566
729
  questionsAnswered++;
567
730
  continue;
@@ -640,13 +803,15 @@ class Interviewer {
640
803
  console.log(chalk.gray('─'.repeat(60)));
641
804
  console.log(chalk.yellow('šŸ’” Type "skip" to skip • Type "exit" or press Ctrl+C to save & exit (resume with: adf init)'));
642
805
  console.log(chalk.gray('─'.repeat(60)));
806
+ console.log(chalk.cyan('Your answer:'));
643
807
 
644
808
  const { answer } = await inquirer.prompt([
645
809
  {
646
810
  type: 'input',
647
811
  name: 'answer',
648
- message: 'Your answer:',
649
- prefix: ''
812
+ message: ' ',
813
+ prefix: '',
814
+ transformer: (input) => input
650
815
  }
651
816
  ]);
652
817
 
@@ -724,103 +889,130 @@ class Interviewer {
724
889
  });
725
890
  }
726
891
 
727
- // Process answer with Dynamic Pipeline (Phase 4.4)
728
- if (this.dynamicPipeline) {
729
- await this.dynamicPipeline.processAnswer(question.id, question.text, answer);
730
- }
892
+ const spinner = ora('Processing reply...').start();
893
+ const startTime = Date.now();
731
894
 
732
- // Check if answer is comprehensive enough to skip follow-ups
733
- if (qualityMetrics.canSkipFollowUps) {
734
- console.log(chalk.green('\nāœ“ Saved\n'));
735
- return answer;
736
- }
895
+ try {
896
+ // Process answer with Dynamic Pipeline (Phase 4.4)
897
+ if (this.dynamicPipeline) {
898
+ await this.dynamicPipeline.processAnswer(question.id, question.text, answer);
899
+ }
737
900
 
738
- // Check if answer needs follow-up (only if follow-up questions are enabled)
739
- let followUp = null;
740
-
741
- if (this.analysisConfig.features.followUpQuestions) {
742
- // Try AI-generated follow-up first
743
- if (this.aiClient && qualityMetrics.issues && qualityMetrics.issues.length > 0 && qualityMetrics.qualityScore < 70) {
744
- try {
745
- const aiFollowUp = await this.aiClient.generateFollowUp(question.text, answer, qualityMetrics.issues);
746
- if (aiFollowUp) {
747
- followUp = {
748
- message: "Let me ask a more specific question:",
749
- question: aiFollowUp
750
- };
901
+ // Check if answer is comprehensive enough to skip follow-ups
902
+ if (qualityMetrics.canSkipFollowUps) {
903
+ // Ensure minimum 1s duration for UX consistency
904
+ const elapsed = Date.now() - startTime;
905
+ if (elapsed < 1000) {
906
+ await new Promise(resolve => setTimeout(resolve, 1000 - elapsed));
907
+ }
908
+
909
+ spinner.succeed(chalk.green('Saved'));
910
+ console.log('');
911
+ return answer;
912
+ }
913
+
914
+ // Check if answer needs follow-up (only if follow-up questions are enabled)
915
+ let followUp = null;
916
+
917
+ if (this.analysisConfig.features.followUpQuestions) {
918
+ // Try AI-generated follow-up first
919
+ if (this.aiClient && qualityMetrics.issues && qualityMetrics.issues.length > 0 && qualityMetrics.qualityScore < 70) {
920
+ try {
921
+ const aiFollowUp = await this.aiClient.generateFollowUp(question.text, answer, qualityMetrics.issues);
922
+ if (aiFollowUp) {
923
+ followUp = {
924
+ message: "Let me ask a more specific question:",
925
+ question: aiFollowUp
926
+ };
927
+ }
928
+ } catch (error) {
929
+ // If AI fails, use heuristic fallback
930
+ followUp = this.determineFollowUp(question, answer);
751
931
  }
752
- } catch (error) {
753
- // If AI fails, use heuristic fallback
932
+ } else {
933
+ // Use heuristic follow-up
754
934
  followUp = this.determineFollowUp(question, answer);
755
935
  }
756
- } else {
757
- // Use heuristic follow-up
758
- followUp = this.determineFollowUp(question, answer);
759
936
  }
760
- }
761
937
 
762
- if (followUp) {
763
- console.log(chalk.yellow(`\nšŸ¤– ${followUp.message}`));
764
- console.log(chalk.yellow(` ${followUp.question}\n`));
938
+ // Ensure minimum 1s duration for UX consistency
939
+ const elapsed = Date.now() - startTime;
940
+ if (elapsed < 1000) {
941
+ await new Promise(resolve => setTimeout(resolve, 1000 - elapsed));
942
+ }
765
943
 
766
- console.log(chalk.gray('─'.repeat(60)));
767
- console.log(chalk.yellow('šŸ’” Type "exit" or press Ctrl+C to save & exit (resume with: adf init)'));
768
- console.log(chalk.gray('─'.repeat(60)));
944
+ spinner.stop();
769
945
 
770
- const { followUpAnswer } = await inquirer.prompt([
771
- {
772
- type: 'input',
773
- name: 'followUpAnswer',
774
- message: 'Follow-up answer:',
775
- prefix: ''
776
- }
777
- ]);
946
+ if (followUp) {
947
+ console.log(chalk.yellow(`\nšŸ¤– ${followUp.message}`));
948
+ console.log(chalk.yellow(` ${followUp.question}\n`));
778
949
 
779
- // Handle "exit" keyword - save progress and exit gracefully
780
- if (followUpAnswer && followUpAnswer.toLowerCase().trim() === 'exit') {
781
- console.log(chalk.yellow('\nšŸ’¾ Saving progress and exiting...'));
950
+ console.log(chalk.gray('─'.repeat(60)));
951
+ console.log(chalk.yellow('šŸ’” Type "exit" or press Ctrl+C to save & exit (resume with: adf init)'));
952
+ console.log(chalk.gray('─'.repeat(60)));
782
953
 
783
- // Ensure progress is saved
784
- if (this.progressTracker) {
785
- await this.progressTracker.save();
786
- }
954
+ console.log(chalk.cyan('Follow-up answer:'));
955
+ const { followUpAnswer } = await inquirer.prompt([
956
+ {
957
+ type: 'input',
958
+ name: 'followUpAnswer',
959
+ message: ' ',
960
+ prefix: '',
961
+ transformer: (input) => input
962
+ }
963
+ ]);
787
964
 
788
- console.log(chalk.green('āœ“ Progress saved!'));
789
- console.log(chalk.cyan('Resume anytime with: adf init\n'));
790
- process.exit(0);
791
- }
965
+ // Handle "exit" keyword - save progress and exit gracefully
966
+ if (followUpAnswer && followUpAnswer.toLowerCase().trim() === 'exit') {
967
+ console.log(chalk.yellow('\nšŸ’¾ Saving progress and exiting...'));
792
968
 
793
- if (followUpAnswer && followUpAnswer.trim()) {
794
- // Combine answers
795
- const combined = `${answer} | Follow-up: ${followUpAnswer}`;
796
-
797
- // Re-analyze combined answer
798
- let combinedMetrics;
799
- try {
800
- if (this.aiClient) {
801
- const aiAnalysis = await this.aiClient.analyzeAnswerQuality(question.text, combined);
802
- combinedMetrics = {
803
- wordCount: combined.trim().split(/\s+/).length,
804
- qualityScore: aiAnalysis.score,
805
- isComprehensive: aiAnalysis.score >= 70,
806
- canSkipFollowUps: aiAnalysis.score >= 85
807
- };
808
- } else {
809
- combinedMetrics = AnswerQualityAnalyzer.analyze(combined, question);
969
+ // Ensure progress is saved
970
+ if (this.progressTracker) {
971
+ await this.progressTracker.save();
810
972
  }
811
- } catch {
812
- combinedMetrics = AnswerQualityAnalyzer.analyze(combined, question);
973
+
974
+ console.log(chalk.green('āœ“ Progress saved!'));
975
+ console.log(chalk.cyan('Resume anytime with: adf init\n'));
976
+ process.exit(0);
813
977
  }
814
978
 
815
- await this.progressTracker.answerQuestion(question.id, question.text, combined, combinedMetrics);
979
+ if (followUpAnswer && followUpAnswer.trim()) {
980
+ // Combine answers
981
+ const combined = `${answer} | Follow-up: ${followUpAnswer}`;
982
+
983
+ // Re-analyze combined answer
984
+ let combinedMetrics;
985
+ try {
986
+ if (this.aiClient) {
987
+ const aiAnalysis = await this.aiClient.analyzeAnswerQuality(question.text, combined);
988
+ combinedMetrics = {
989
+ wordCount: combined.trim().split(/\s+/).length,
990
+ qualityScore: aiAnalysis.score,
991
+ isComprehensive: aiAnalysis.score >= 70,
992
+ canSkipFollowUps: aiAnalysis.score >= 85
993
+ };
994
+ } else {
995
+ combinedMetrics = AnswerQualityAnalyzer.analyze(combined, question);
996
+ }
997
+ } catch {
998
+ combinedMetrics = AnswerQualityAnalyzer.analyze(combined, question);
999
+ }
1000
+
1001
+ await this.progressTracker.answerQuestion(question.id, question.text, combined, combinedMetrics);
816
1002
 
817
- console.log(chalk.green('\nāœ“ Saved\n'));
818
- return combined;
1003
+ console.log(chalk.green('\nāœ“ Saved\n'));
1004
+ return combined;
1005
+ }
819
1006
  }
820
- }
821
1007
 
822
- console.log(chalk.green('\nāœ“ Saved\n'));
823
- return answer;
1008
+ console.log(chalk.green('\nāœ“ Saved\n'));
1009
+ return answer;
1010
+
1011
+ } catch (err) {
1012
+ spinner.fail('Error processing reply');
1013
+ console.error(err);
1014
+ return answer; // Return original answer on error
1015
+ }
824
1016
  }
825
1017
 
826
1018
  determineFollowUp(question, answer) {
@@ -147,6 +147,24 @@ class ProgressTracker {
147
147
  await this.saveWithBackup();
148
148
  }
149
149
 
150
+ async updateAnswer(questionId, newText) {
151
+ if (this.progress.answers[questionId]) {
152
+ // Update word count stats
153
+ const oldWordCount = this.progress.answers[questionId].quality.wordCount || 0;
154
+ const newWordCount = newText.trim().split(/\s+/).length;
155
+ this.progress.totalWordCount = this.progress.totalWordCount - oldWordCount + newWordCount;
156
+
157
+ this.progress.answers[questionId].text = newText;
158
+ this.progress.answers[questionId].timestamp = new Date().toISOString();
159
+
160
+ // Update word count in quality metrics
161
+ this.progress.answers[questionId].quality.wordCount = newWordCount;
162
+
163
+ this.progress.lastUpdated = new Date().toISOString();
164
+ await this.saveWithBackup();
165
+ }
166
+ }
167
+
150
168
  async saveWithBackup() {
151
169
  try {
152
170
  // Save 1: Main progress file