@ai-coders/context 0.2.1 → 0.3.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 (168) hide show
  1. package/README.md +113 -580
  2. package/dist/generators/agents/agentGenerator.d.ts +6 -9
  3. package/dist/generators/agents/agentGenerator.d.ts.map +1 -1
  4. package/dist/generators/agents/agentGenerator.js +89 -32
  5. package/dist/generators/agents/agentGenerator.js.map +1 -1
  6. package/dist/generators/agents/index.d.ts +0 -2
  7. package/dist/generators/agents/index.d.ts.map +1 -1
  8. package/dist/generators/agents/index.js +1 -5
  9. package/dist/generators/agents/index.js.map +1 -1
  10. package/dist/generators/agents/templates/index.d.ts +4 -0
  11. package/dist/generators/agents/templates/index.d.ts.map +1 -0
  12. package/dist/generators/agents/templates/index.js +8 -0
  13. package/dist/generators/agents/templates/index.js.map +1 -0
  14. package/dist/generators/agents/templates/indexTemplate.d.ts +3 -0
  15. package/dist/generators/agents/templates/indexTemplate.d.ts.map +1 -0
  16. package/dist/generators/agents/templates/indexTemplate.js +35 -0
  17. package/dist/generators/agents/templates/indexTemplate.js.map +1 -0
  18. package/dist/generators/agents/templates/playbookTemplate.d.ts +4 -0
  19. package/dist/generators/agents/templates/playbookTemplate.d.ts.map +1 -0
  20. package/dist/generators/agents/templates/playbookTemplate.js +63 -0
  21. package/dist/generators/agents/templates/playbookTemplate.js.map +1 -0
  22. package/dist/generators/agents/templates/types.d.ts +14 -0
  23. package/dist/generators/agents/templates/types.d.ts.map +1 -0
  24. package/dist/generators/agents/templates/types.js +3 -0
  25. package/dist/generators/agents/templates/types.js.map +1 -0
  26. package/dist/generators/documentation/documentationGenerator.d.ts +9 -15
  27. package/dist/generators/documentation/documentationGenerator.d.ts.map +1 -1
  28. package/dist/generators/documentation/documentationGenerator.js +65 -77
  29. package/dist/generators/documentation/documentationGenerator.js.map +1 -1
  30. package/dist/generators/documentation/guideRegistry.d.ts +6 -0
  31. package/dist/generators/documentation/guideRegistry.d.ts.map +1 -0
  32. package/dist/generators/documentation/guideRegistry.js +82 -0
  33. package/dist/generators/documentation/guideRegistry.js.map +1 -0
  34. package/dist/generators/documentation/index.d.ts +0 -6
  35. package/dist/generators/documentation/index.d.ts.map +1 -1
  36. package/dist/generators/documentation/index.js +1 -17
  37. package/dist/generators/documentation/index.js.map +1 -1
  38. package/dist/generators/documentation/templates/architectureTemplate.d.ts +3 -0
  39. package/dist/generators/documentation/templates/architectureTemplate.d.ts.map +1 -0
  40. package/dist/generators/documentation/templates/architectureTemplate.js +66 -0
  41. package/dist/generators/documentation/templates/architectureTemplate.js.map +1 -0
  42. package/dist/generators/documentation/templates/common.d.ts +7 -0
  43. package/dist/generators/documentation/templates/common.d.ts.map +1 -0
  44. package/dist/generators/documentation/templates/common.js +58 -0
  45. package/dist/generators/documentation/templates/common.js.map +1 -0
  46. package/dist/generators/documentation/templates/dataFlowTemplate.d.ts +3 -0
  47. package/dist/generators/documentation/templates/dataFlowTemplate.d.ts.map +1 -0
  48. package/dist/generators/documentation/templates/dataFlowTemplate.js +55 -0
  49. package/dist/generators/documentation/templates/dataFlowTemplate.js.map +1 -0
  50. package/dist/generators/documentation/templates/developmentWorkflowTemplate.d.ts +2 -0
  51. package/dist/generators/documentation/templates/developmentWorkflowTemplate.d.ts.map +1 -0
  52. package/dist/generators/documentation/templates/developmentWorkflowTemplate.js +59 -0
  53. package/dist/generators/documentation/templates/developmentWorkflowTemplate.js.map +1 -0
  54. package/dist/generators/documentation/templates/frontMatter.d.ts +11 -0
  55. package/dist/generators/documentation/templates/frontMatter.d.ts.map +1 -0
  56. package/dist/generators/documentation/templates/frontMatter.js +29 -0
  57. package/dist/generators/documentation/templates/frontMatter.js.map +1 -0
  58. package/dist/generators/documentation/templates/glossaryTemplate.d.ts +3 -0
  59. package/dist/generators/documentation/templates/glossaryTemplate.d.ts.map +1 -0
  60. package/dist/generators/documentation/templates/glossaryTemplate.js +55 -0
  61. package/dist/generators/documentation/templates/glossaryTemplate.js.map +1 -0
  62. package/dist/generators/documentation/templates/index.d.ts +11 -0
  63. package/dist/generators/documentation/templates/index.d.ts.map +1 -0
  64. package/dist/generators/documentation/templates/index.js +22 -0
  65. package/dist/generators/documentation/templates/index.js.map +1 -0
  66. package/dist/generators/documentation/templates/indexTemplate.d.ts +3 -0
  67. package/dist/generators/documentation/templates/indexTemplate.d.ts.map +1 -0
  68. package/dist/generators/documentation/templates/indexTemplate.js +56 -0
  69. package/dist/generators/documentation/templates/indexTemplate.js.map +1 -0
  70. package/dist/generators/documentation/templates/projectOverviewTemplate.d.ts +3 -0
  71. package/dist/generators/documentation/templates/projectOverviewTemplate.d.ts.map +1 -0
  72. package/dist/generators/documentation/templates/projectOverviewTemplate.js +68 -0
  73. package/dist/generators/documentation/templates/projectOverviewTemplate.js.map +1 -0
  74. package/dist/generators/documentation/templates/securityTemplate.d.ts +2 -0
  75. package/dist/generators/documentation/templates/securityTemplate.d.ts.map +1 -0
  76. package/dist/generators/documentation/templates/securityTemplate.js +53 -0
  77. package/dist/generators/documentation/templates/securityTemplate.js.map +1 -0
  78. package/dist/generators/documentation/templates/testingTemplate.d.ts +2 -0
  79. package/dist/generators/documentation/templates/testingTemplate.d.ts.map +1 -0
  80. package/dist/generators/documentation/templates/testingTemplate.js +59 -0
  81. package/dist/generators/documentation/templates/testingTemplate.js.map +1 -0
  82. package/dist/generators/documentation/templates/toolingTemplate.d.ts +2 -0
  83. package/dist/generators/documentation/templates/toolingTemplate.d.ts.map +1 -0
  84. package/dist/generators/documentation/templates/toolingTemplate.js +56 -0
  85. package/dist/generators/documentation/templates/toolingTemplate.js.map +1 -0
  86. package/dist/generators/documentation/templates/types.d.ts +23 -0
  87. package/dist/generators/documentation/templates/types.d.ts.map +1 -0
  88. package/dist/generators/documentation/templates/types.js +3 -0
  89. package/dist/generators/documentation/templates/types.js.map +1 -0
  90. package/dist/generators/documentation/templates.d.ts +31 -0
  91. package/dist/generators/documentation/templates.d.ts.map +1 -0
  92. package/dist/generators/documentation/templates.js +566 -0
  93. package/dist/generators/documentation/templates.js.map +1 -0
  94. package/dist/generators/plans/index.d.ts +2 -0
  95. package/dist/generators/plans/index.d.ts.map +1 -0
  96. package/dist/generators/plans/index.js +6 -0
  97. package/dist/generators/plans/index.js.map +1 -0
  98. package/dist/generators/plans/planGenerator.d.ts +22 -0
  99. package/dist/generators/plans/planGenerator.d.ts.map +1 -0
  100. package/dist/generators/plans/planGenerator.js +109 -0
  101. package/dist/generators/plans/planGenerator.js.map +1 -0
  102. package/dist/generators/plans/templates/indexTemplate.d.ts +3 -0
  103. package/dist/generators/plans/templates/indexTemplate.d.ts.map +1 -0
  104. package/dist/generators/plans/templates/indexTemplate.js +36 -0
  105. package/dist/generators/plans/templates/indexTemplate.js.map +1 -0
  106. package/dist/generators/plans/templates/planTemplate.d.ts +3 -0
  107. package/dist/generators/plans/templates/planTemplate.d.ts.map +1 -0
  108. package/dist/generators/plans/templates/planTemplate.js +83 -0
  109. package/dist/generators/plans/templates/planTemplate.js.map +1 -0
  110. package/dist/generators/plans/templates/types.d.ts +19 -0
  111. package/dist/generators/plans/templates/types.d.ts.map +1 -0
  112. package/dist/generators/plans/templates/types.js +3 -0
  113. package/dist/generators/plans/templates/types.js.map +1 -0
  114. package/dist/generators/shared/contextGenerator.d.ts +2 -7
  115. package/dist/generators/shared/contextGenerator.d.ts.map +1 -1
  116. package/dist/generators/shared/contextGenerator.js +2 -98
  117. package/dist/generators/shared/contextGenerator.js.map +1 -1
  118. package/dist/generators/shared/directoryTemplateHelpers.d.ts +2 -0
  119. package/dist/generators/shared/directoryTemplateHelpers.d.ts.map +1 -0
  120. package/dist/generators/shared/directoryTemplateHelpers.js +12 -0
  121. package/dist/generators/shared/directoryTemplateHelpers.js.map +1 -0
  122. package/dist/generators/shared/index.d.ts +1 -0
  123. package/dist/generators/shared/index.d.ts.map +1 -1
  124. package/dist/generators/shared/index.js +3 -1
  125. package/dist/generators/shared/index.js.map +1 -1
  126. package/dist/index.d.ts +6 -4
  127. package/dist/index.d.ts.map +1 -1
  128. package/dist/index.js +1300 -609
  129. package/dist/index.js.map +1 -1
  130. package/dist/utils/cliUI.d.ts +6 -4
  131. package/dist/utils/cliUI.d.ts.map +1 -1
  132. package/dist/utils/cliUI.js +71 -56
  133. package/dist/utils/cliUI.js.map +1 -1
  134. package/dist/utils/i18n.d.ts +181 -0
  135. package/dist/utils/i18n.d.ts.map +1 -0
  136. package/dist/utils/i18n.js +401 -0
  137. package/dist/utils/i18n.js.map +1 -0
  138. package/package.json +26 -23
  139. package/prompts/update_plan_prompt.md +42 -0
  140. package/prompts/update_scaffold_prompt.md +48 -0
  141. package/dist/generators/agentGenerator.d.ts +0 -23
  142. package/dist/generators/agentGenerator.d.ts.map +0 -1
  143. package/dist/generators/agentGenerator.js +0 -357
  144. package/dist/generators/agentGenerator.js.map +0 -1
  145. package/dist/generators/documentation/enhancedDocumentationGenerator.d.ts +0 -21
  146. package/dist/generators/documentation/enhancedDocumentationGenerator.d.ts.map +0 -1
  147. package/dist/generators/documentation/enhancedDocumentationGenerator.js +0 -216
  148. package/dist/generators/documentation/enhancedDocumentationGenerator.js.map +0 -1
  149. package/dist/generators/documentation/newDocumentationTemplates.d.ts +0 -19
  150. package/dist/generators/documentation/newDocumentationTemplates.d.ts.map +0 -1
  151. package/dist/generators/documentation/newDocumentationTemplates.js +0 -307
  152. package/dist/generators/documentation/newDocumentationTemplates.js.map +0 -1
  153. package/dist/generators/documentationGenerator.d.ts +0 -22
  154. package/dist/generators/documentationGenerator.d.ts.map +0 -1
  155. package/dist/generators/documentationGenerator.js +0 -235
  156. package/dist/generators/documentationGenerator.js.map +0 -1
  157. package/dist/generators/documentationTemplates.d.ts +0 -16
  158. package/dist/generators/documentationTemplates.d.ts.map +0 -1
  159. package/dist/generators/documentationTemplates.js +0 -326
  160. package/dist/generators/documentationTemplates.js.map +0 -1
  161. package/dist/generators/documentationUtils.d.ts +0 -7
  162. package/dist/generators/documentationUtils.d.ts.map +0 -1
  163. package/dist/generators/documentationUtils.js +0 -38
  164. package/dist/generators/documentationUtils.js.map +0 -1
  165. package/dist/generators/incrementalDocumentationGenerator.d.ts +0 -33
  166. package/dist/generators/incrementalDocumentationGenerator.d.ts.map +0 -1
  167. package/dist/generators/incrementalDocumentationGenerator.js +0 -400
  168. package/dist/generators/incrementalDocumentationGenerator.js.map +0 -1
package/dist/index.js CHANGED
@@ -38,739 +38,1430 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  };
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  exports.runGenerate = runGenerate;
41
- exports.runGuidelines = runGuidelines;
42
41
  exports.runAnalyze = runAnalyze;
43
42
  exports.runUpdate = runUpdate;
44
43
  exports.runPreview = runPreview;
44
+ exports.runGuidelines = runGuidelines;
45
+ exports.runInit = runInit;
45
46
  const commander_1 = require("commander");
46
47
  const path = __importStar(require("path"));
47
- const chalk_1 = __importDefault(require("chalk"));
48
+ const fs = __importStar(require("fs-extra"));
49
+ const glob_1 = require("glob");
48
50
  const dotenv = __importStar(require("dotenv"));
49
- // Load environment variables from .env file
50
- dotenv.config();
51
+ const chalk_1 = __importDefault(require("chalk"));
52
+ const inquirer_1 = __importDefault(require("inquirer"));
51
53
  const fileMapper_1 = require("./utils/fileMapper");
52
54
  const documentationGenerator_1 = require("./generators/documentation/documentationGenerator");
53
55
  const agentGenerator_1 = require("./generators/agents/agentGenerator");
54
- const guidelinesGenerator_1 = require("./generators/guidelines/guidelinesGenerator");
55
- const incrementalDocumentationGenerator_1 = require("./generators/documentation/incrementalDocumentationGenerator");
56
+ const planGenerator_1 = require("./generators/plans/planGenerator");
57
+ const shared_1 = require("./generators/shared");
56
58
  const cliUI_1 = require("./utils/cliUI");
59
+ const i18n_1 = require("./utils/i18n");
57
60
  const llmClientFactory_1 = require("./services/llmClientFactory");
58
- const gitService_1 = require("./utils/gitService");
59
- const changeAnalyzer_1 = require("./services/changeAnalyzer");
60
- const tokenEstimator_1 = require("./utils/tokenEstimator");
61
- const interactiveMode_1 = require("./utils/interactiveMode");
61
+ const guideRegistry_1 = require("./generators/documentation/guideRegistry");
62
+ const agentTypes_1 = require("./generators/agents/agentTypes");
63
+ dotenv.config();
64
+ const initialLocale = (0, i18n_1.detectLocale)(process.argv.slice(2), process.env.AI_CONTEXT_LANG);
65
+ let currentLocale = initialLocale;
66
+ let translateFn = (0, i18n_1.createTranslator)(initialLocale);
67
+ const t = (key, params) => translateFn(key, params);
68
+ const localeLabelKeys = {
69
+ en: 'prompts.language.option.en',
70
+ 'pt-BR': 'prompts.language.option.pt-BR'
71
+ };
62
72
  const program = new commander_1.Command();
63
- const ui = new cliUI_1.CLIInterface();
73
+ const ui = new cliUI_1.CLIInterface(t);
74
+ const VERSION = '0.3.0';
75
+ const DEFAULT_MODEL = 'x-ai/grok-4-fast:free';
76
+ const DOC_CHOICES = guideRegistry_1.DOCUMENT_GUIDES.map(guide => ({
77
+ name: `${guide.title} (${guide.key})`,
78
+ value: guide.key
79
+ }));
80
+ const AGENT_CHOICES = agentTypes_1.AGENT_TYPES.map(agent => ({
81
+ name: formatAgentLabel(agent),
82
+ value: agent
83
+ }));
64
84
  program
65
85
  .name('ai-context')
66
- .description('AI-powered CLI for generating codebase documentation and agent prompts')
67
- .version('0.1.0');
86
+ .description(t('cli.description'))
87
+ .version(VERSION);
88
+ program.option('-l, --lang <locale>', t('global.options.lang'), initialLocale);
68
89
  program
69
90
  .command('init')
70
- .description('Initialize documentation and agent prompts for a repository')
71
- .argument('<repo-path>', 'Path to the repository to analyze')
72
- .argument('[type]', 'Type to initialize: "docs", "agents", "guidelines", or "both" (default: "both")', 'both')
73
- .option('-o, --output <dir>', 'Output directory', './.context')
74
- .option('-k, --api-key <key>', 'API key for the LLM provider')
75
- .option('-m, --model <model>', 'LLM model to use', 'google/gemini-2.5-flash-preview-05-20')
76
- .option('-p, --provider <provider>', 'LLM provider (openrouter, openai, anthropic, gemini, grok)', 'openrouter')
77
- .option('--exclude <patterns...>', 'Patterns to exclude from analysis')
78
- .option('--include <patterns...>', 'Patterns to include in analysis')
79
- .option('-v, --verbose', 'Verbose output')
91
+ .description(t('commands.init.description'))
92
+ .argument('<repo-path>', t('commands.init.arguments.repoPath'))
93
+ .argument('[type]', t('commands.init.arguments.type'), 'both')
94
+ .option('-o, --output <dir>', t('commands.init.options.output'), './.context')
95
+ .option('--docs <keys...>', t('commands.init.options.docs'))
96
+ .option('--agents <keys...>', t('commands.init.options.agents'))
97
+ .option('--exclude <patterns...>', t('commands.init.options.exclude'))
98
+ .option('--include <patterns...>', t('commands.init.options.include'))
99
+ .option('-v, --verbose', t('commands.init.options.verbose'))
80
100
  .action(async (repoPath, type, options) => {
81
101
  try {
82
- // Validate type argument
83
- if (!['docs', 'agents', 'guidelines', 'both'].includes(type)) {
84
- ui.displayError(`Invalid type "${type}". Must be "docs", "agents", "guidelines", or "both".`);
85
- process.exit(1);
86
- }
87
- // Set options based on type argument
88
- if (type === 'docs') {
89
- options.docsOnly = true;
90
- }
91
- else if (type === 'agents') {
92
- options.agentsOnly = true;
93
- }
94
- else if (type === 'guidelines') {
95
- options.guidelinesOnly = true;
96
- }
97
- // For 'both', neither flag is set
98
- await runGenerate(repoPath, options);
102
+ await runInit(repoPath, type, options);
99
103
  }
100
104
  catch (error) {
101
- ui.displayError('Failed to initialize', error);
105
+ ui.displayError(t('errors.init.scaffoldFailed'), error);
102
106
  process.exit(1);
103
107
  }
104
108
  });
105
109
  program
106
- .command('update')
107
- .description('Update documentation for changed files since last run or specified commit')
108
- .argument('<repo-path>', 'Path to the repository to analyze')
109
- .option('-o, --output <dir>', 'Output directory', './.context')
110
- .option('-k, --api-key <key>', 'API key for the LLM provider')
111
- .option('-m, --model <model>', 'LLM model to use', 'google/gemini-2.5-flash-preview-05-20')
112
- .option('-p, --provider <provider>', 'LLM provider (openrouter, openai, anthropic, gemini, grok)', 'openrouter')
113
- .option('--since <commit>', 'Compare against specific commit/branch (default: last processed commit)')
114
- .option('--staged', 'Only process staged files (for pre-commit hooks)')
115
- .option('--force', 'Force regeneration even if no changes detected')
116
- .option('--exclude <patterns...>', 'Patterns to exclude from analysis')
117
- .option('--include <patterns...>', 'Patterns to include in analysis')
118
- .option('-v, --verbose', 'Verbose output')
119
- .action(async (repoPath, options) => {
110
+ .command('scaffold')
111
+ .description(t('commands.scaffold.description'))
112
+ .argument('<repo-path>', t('commands.init.arguments.repoPath'))
113
+ .argument('[type]', t('commands.init.arguments.type'), 'both')
114
+ .option('-o, --output <dir>', t('commands.init.options.output'), './.context')
115
+ .option('--docs <keys...>', t('commands.init.options.docs'))
116
+ .option('--agents <keys...>', t('commands.init.options.agents'))
117
+ .option('--exclude <patterns...>', t('commands.init.options.exclude'))
118
+ .option('--include <patterns...>', t('commands.init.options.include'))
119
+ .option('-v, --verbose', t('commands.init.options.verbose'))
120
+ .action(async (repoPath, type, options) => {
120
121
  try {
121
- await runUpdate(repoPath, options);
122
+ await runInit(repoPath, type, options);
122
123
  }
123
124
  catch (error) {
124
- ui.displayError('Failed to update documentation', error);
125
+ ui.displayError(t('errors.init.scaffoldFailed'), error);
125
126
  process.exit(1);
126
127
  }
127
128
  });
128
129
  program
129
- .command('analyze')
130
- .description('Analyze repository structure without generating content')
131
- .argument('<repo-path>', 'Path to the repository to analyze')
132
- .option('--exclude <patterns...>', 'Patterns to exclude from analysis')
133
- .option('--include <patterns...>', 'Patterns to include in analysis')
134
- .option('--input-price <price>', 'Input token price per 1M tokens (e.g., 2.50)', parseFloat)
135
- .option('--output-price <price>', 'Output token price per 1M tokens (e.g., 10.00)', parseFloat)
136
- .option('-v, --verbose', 'Verbose output')
130
+ .command('fill')
131
+ .description(t('commands.fill.description'))
132
+ .argument('<repo-path>', t('commands.fill.arguments.repoPath'))
133
+ .option('-o, --output <dir>', t('commands.fill.options.output'), './.context')
134
+ .option('-k, --api-key <key>', t('commands.fill.options.apiKey'))
135
+ .option('-m, --model <model>', t('commands.fill.options.model'), DEFAULT_MODEL)
136
+ .option('-p, --provider <provider>', t('commands.fill.options.provider'))
137
+ .option('--base-url <url>', t('commands.fill.options.baseUrl'))
138
+ .option('--prompt <file>', t('commands.fill.options.prompt'), path.join(__dirname, '../prompts/update_scaffold_prompt.md'))
139
+ .option('--dry-run', t('commands.fill.options.dryRun'), false)
140
+ .option('--all', t('commands.fill.options.all'), false)
141
+ .option('--limit <number>', t('commands.fill.options.limit'), (value) => parseInt(value, 10))
142
+ .option('--docs <keys...>', t('commands.fill.options.docs'))
143
+ .option('--agents <keys...>', t('commands.fill.options.agents'))
144
+ .option('--exclude <patterns...>', t('commands.fill.options.exclude'))
145
+ .option('--include <patterns...>', t('commands.fill.options.include'))
146
+ .option('-v, --verbose', t('commands.fill.options.verbose'))
137
147
  .action(async (repoPath, options) => {
138
148
  try {
139
- await runAnalyze(repoPath, options);
149
+ await runLlmFill(repoPath, options);
140
150
  }
141
151
  catch (error) {
142
- ui.displayError('Failed to analyze repository', error);
152
+ ui.displayError(t('errors.fill.failed'), error);
143
153
  process.exit(1);
144
154
  }
145
155
  });
146
156
  program
147
- .command('preview')
148
- .description('Preview what documentation updates would be made without actually updating')
149
- .argument('<repo-path>', 'Path to the repository to analyze')
150
- .option('--since <commit>', 'Compare against specific commit/branch (default: last processed commit)')
151
- .option('--staged', 'Only analyze staged files (for pre-commit hooks)')
152
- .option('--exclude <patterns...>', 'Patterns to exclude from analysis')
153
- .option('--include <patterns...>', 'Patterns to include in analysis')
154
- .option('--input-price <price>', 'Input token price per 1M tokens (e.g., 2.50)', parseFloat)
155
- .option('--output-price <price>', 'Output token price per 1M tokens (e.g., 10.00)', parseFloat)
156
- .option('-v, --verbose', 'Verbose output with detailed file lists')
157
- .addHelpText('after', `
158
- Examples:
159
- $ ai-context preview ./ # Preview changes since last run
160
- $ ai-context preview ./ --staged # Preview staged files only
161
- $ ai-context preview ./ --since HEAD~3 # Preview changes since 3 commits ago
162
- $ ai-context preview ./ --verbose # Show detailed file change lists
163
- $ ai-context preview ./ --input-price 2.50 --output-price 10.00`)
164
- .action(async (repoPath, options) => {
165
- try {
166
- await runPreview(repoPath, options);
157
+ .command('plan')
158
+ .description(t('commands.plan.description'))
159
+ .argument('<plan-name>', t('commands.plan.arguments.planName'))
160
+ .option('-o, --output <dir>', t('commands.plan.options.output'), './.context')
161
+ .option('--title <title>', t('commands.plan.options.title'))
162
+ .option('--summary <text>', t('commands.plan.options.summary'))
163
+ .option('--agents <types...>', t('commands.plan.options.agents'))
164
+ .option('--docs <keys...>', t('commands.plan.options.docs'))
165
+ .option('-f, --force', t('commands.plan.options.force'))
166
+ .option('--fill', t('commands.plan.options.fill'))
167
+ .option('-r, --repo <path>', t('commands.plan.options.repo'))
168
+ .option('-k, --api-key <key>', t('commands.plan.options.apiKey'))
169
+ .option('-m, --model <model>', t('commands.plan.options.model'), DEFAULT_MODEL)
170
+ .option('-p, --provider <provider>', t('commands.plan.options.provider'))
171
+ .option('--base-url <url>', t('commands.plan.options.baseUrl'))
172
+ .option('--prompt <file>', t('commands.plan.options.prompt'), path.join(__dirname, '../prompts/update_plan_prompt.md'))
173
+ .option('--dry-run', t('commands.plan.options.dryRun'), false)
174
+ .option('--include <patterns...>', t('commands.plan.options.include'))
175
+ .option('--exclude <patterns...>', t('commands.plan.options.exclude'))
176
+ .option('-v, --verbose', t('commands.plan.options.verbose'))
177
+ .action(async (planName, rawOptions) => {
178
+ const agentSelection = parseAgentSelection(rawOptions.agents);
179
+ if (agentSelection.invalid.length > 0) {
180
+ ui.displayWarning(t('warnings.agents.unknown', { values: agentSelection.invalid.join(', ') }));
167
181
  }
168
- catch (error) {
169
- ui.displayError('Failed to preview updates', error);
170
- process.exit(1);
182
+ const docSelection = parseDocSelection(rawOptions.docs);
183
+ if (docSelection.invalid.length > 0) {
184
+ ui.displayWarning(t('warnings.docs.unknown', { values: docSelection.invalid.join(', ') }));
171
185
  }
172
- });
173
- program
174
- .command('guidelines')
175
- .description('Generate software development guidelines for a repository')
176
- .argument('<repo-path>', 'Path to the repository to analyze')
177
- .argument('[categories...]', 'Specific guideline categories to generate (testing, frontend, backend, database, security, performance, code-style, git-workflow, deployment, monitoring, documentation, architecture)')
178
- .option('-o, --output <dir>', 'Output directory', './.context')
179
- .option('-k, --api-key <key>', 'API key for the LLM provider')
180
- .option('-m, --model <model>', 'LLM model to use', 'google/gemini-2.5-flash-preview-05-20')
181
- .option('-p, --provider <provider>', 'LLM provider (openrouter, openai, anthropic, gemini, grok)', 'openrouter')
182
- .option('--project-type <type>', 'Project type (frontend, backend, fullstack, mobile, desktop, library)', 'auto')
183
- .option('--complexity <level>', 'Project complexity (simple, moderate, complex)', 'auto')
184
- .option('--team-size <size>', 'Team size (small, medium, large)', 'auto')
185
- .option('--include-examples', 'Include code examples in guidelines', false)
186
- .option('--include-tools', 'Include tool recommendations in guidelines', false)
187
- .option('--exclude <patterns...>', 'Patterns to exclude from analysis')
188
- .option('--include <patterns...>', 'Patterns to include in analysis')
189
- .option('-v, --verbose', 'Verbose output')
190
- .addHelpText('after', `
191
- Examples:
192
- $ ai-context guidelines ./ # Generate comprehensive guidelines
193
- $ ai-context guidelines ./ testing security # Generate only testing and security guidelines
194
- $ ai-context guidelines ./ --project-type frontend # Generate guidelines for frontend project
195
- $ ai-context guidelines ./ --include-examples # Include code examples in guidelines`)
196
- .action(async (repoPath, categories, options) => {
186
+ const outputDir = path.resolve(rawOptions.output || './.context');
187
+ if (rawOptions.fill) {
188
+ try {
189
+ await scaffoldPlanIfNeeded(planName, outputDir, {
190
+ title: rawOptions.title,
191
+ summary: rawOptions.summary,
192
+ agentSelection,
193
+ docSelection,
194
+ force: Boolean(rawOptions.force),
195
+ verbose: Boolean(rawOptions.verbose)
196
+ });
197
+ await runPlanFill(planName, { ...rawOptions, output: outputDir });
198
+ }
199
+ catch (error) {
200
+ ui.displayError(t('errors.plan.fillFailed'), error);
201
+ process.exit(1);
202
+ }
203
+ return;
204
+ }
205
+ const generator = new planGenerator_1.PlanGenerator();
206
+ ui.startSpinner(t('spinner.plan.creating'));
197
207
  try {
198
- await runGuidelines(repoPath, categories, options);
208
+ const result = await generator.generatePlan({
209
+ planName,
210
+ outputDir,
211
+ title: rawOptions.title,
212
+ summary: rawOptions.summary,
213
+ selectedAgentTypes: agentSelection.explicitNone ? null : agentSelection.selected,
214
+ selectedDocKeys: docSelection.explicitNone ? null : docSelection.selected,
215
+ force: Boolean(rawOptions.force),
216
+ verbose: Boolean(rawOptions.verbose)
217
+ });
218
+ ui.updateSpinner(t('spinner.plan.created'), 'success');
219
+ ui.displaySuccess(t('success.plan.createdAt', { path: chalk_1.default.cyan(result.relativePath) }));
199
220
  }
200
221
  catch (error) {
201
- ui.displayError('Failed to generate guidelines', error);
222
+ ui.updateSpinner(t('spinner.plan.creationFailed'), 'fail');
223
+ ui.displayError(t('errors.plan.creationFailed'), error);
202
224
  process.exit(1);
203
225
  }
226
+ finally {
227
+ ui.stopSpinner();
228
+ }
204
229
  });
205
- async function runGenerate(repoPath, options) {
206
- const provider = options.provider || llmClientFactory_1.LLMClientFactory.detectProviderFromModel(options.model);
207
- // Get API key from options or environment variables
208
- let apiKey = options.apiKey;
209
- if (!apiKey) {
210
- const envVars = llmClientFactory_1.LLMClientFactory.getEnvironmentVariables()[provider];
211
- for (const envVar of envVars) {
212
- apiKey = process.env[envVar];
213
- if (apiKey)
214
- break;
215
- }
230
+ async function runInit(repoPath, type, rawOptions) {
231
+ const resolvedType = resolveScaffoldType(type, rawOptions);
232
+ const docSelection = parseDocSelection(rawOptions.docs);
233
+ const agentSelection = parseAgentSelection(rawOptions.agents);
234
+ if (docSelection.invalid.length > 0) {
235
+ ui.displayWarning(t('warnings.docs.unknown', { values: docSelection.invalid.join(', ') }));
216
236
  }
217
- const cliOptions = {
218
- repoPath: path.resolve(repoPath),
219
- outputDir: path.resolve(options.output),
220
- model: options.model,
221
- apiKey,
222
- provider,
223
- exclude: options.exclude || [],
224
- include: options.include,
225
- verbose: options.verbose || false
226
- };
227
- if (!cliOptions.apiKey) {
228
- const envVars = llmClientFactory_1.LLMClientFactory.getEnvironmentVariables()[provider];
229
- ui.displayError(`${provider.toUpperCase()} API key is required. Set one of these environment variables: ${envVars.join(', ')} or use --api-key option.`);
230
- process.exit(1);
237
+ if (agentSelection.invalid.length > 0) {
238
+ ui.displayWarning(t('warnings.agents.unknown', { values: agentSelection.invalid.join(', ') }));
231
239
  }
232
- // Display welcome message
233
- ui.displayWelcome('0.1.0');
234
- ui.displayProjectInfo(cliOptions.repoPath, cliOptions.outputDir, cliOptions.model, cliOptions.provider);
235
- // Show usage warning for expensive models
236
- if (cliOptions.model && ['anthropic/claude-3-opus', 'openai/gpt-4'].includes(cliOptions.model)) {
237
- ui.displayUsageWarning(2.0); // Estimate high cost for warning
238
- }
239
- // Initialize components
240
- const fileMapper = new fileMapper_1.FileMapper(cliOptions.exclude);
241
- const llmConfig = {
242
- apiKey: cliOptions.apiKey,
243
- model: cliOptions.model || 'google/gemini-2.5-flash-preview-05-20',
244
- provider: cliOptions.provider || 'openrouter'
240
+ const options = {
241
+ repoPath: path.resolve(repoPath),
242
+ outputDir: path.resolve(rawOptions.output || './.context'),
243
+ include: rawOptions.include,
244
+ exclude: rawOptions.exclude || [],
245
+ verbose: rawOptions.verbose || false,
246
+ scaffoldDocs: shouldGenerateDocs(resolvedType, docSelection),
247
+ scaffoldAgents: shouldGenerateAgents(resolvedType, agentSelection),
248
+ selectedDocKeys: docSelection.selected,
249
+ selectedAgentTypes: agentSelection.selected
245
250
  };
246
- const llmClient = llmClientFactory_1.LLMClientFactory.createClient(llmConfig);
247
- const docGenerator = new documentationGenerator_1.DocumentationGenerator(fileMapper, llmClient);
248
- const agentGenerator = new agentGenerator_1.AgentGenerator(fileMapper, llmClient);
249
- const guidelinesGenerator = new guidelinesGenerator_1.GuidelinesGenerator(fileMapper, llmClient);
250
- // Step 1: Map repository structure
251
- ui.displayStep(1, 4, 'Analyzing repository structure');
252
- ui.startSpinner('Scanning files and directories...');
253
- const repoStructure = await fileMapper.mapRepository(cliOptions.repoPath, cliOptions.include);
254
- ui.updateSpinner(`Found ${repoStructure.totalFiles} files in ${repoStructure.directories.length} directories`, 'success');
255
- // Display analysis results
256
- if (cliOptions.verbose) {
257
- ui.displayAnalysisResults(repoStructure.totalFiles, repoStructure.directories.length, ui.formatBytes(repoStructure.totalSize));
258
- // Show file distribution
259
- const extensions = new Map();
260
- repoStructure.files.forEach(file => {
261
- const ext = file.extension || 'no-extension';
262
- extensions.set(ext, (extensions.get(ext) || 0) + 1);
263
- });
264
- ui.displayFileTypeDistribution(extensions, repoStructure.totalFiles);
251
+ if (!options.scaffoldDocs && !options.scaffoldAgents) {
252
+ ui.displayWarning(t('warnings.scaffold.noneSelected'));
253
+ return;
265
254
  }
255
+ await ensurePaths(options);
256
+ ui.displayWelcome(VERSION);
257
+ ui.displayProjectInfo(options.repoPath, options.outputDir, resolvedType);
258
+ const fileMapper = new fileMapper_1.FileMapper(options.exclude);
259
+ ui.displayStep(1, 3, t('steps.init.analyze'));
260
+ ui.startSpinner(t('spinner.repo.scanning'));
261
+ const repoStructure = await fileMapper.mapRepository(options.repoPath, options.include);
262
+ ui.updateSpinner(t('spinner.repo.scanComplete', {
263
+ fileCount: repoStructure.totalFiles,
264
+ directoryCount: repoStructure.directories.length
265
+ }), 'success');
266
266
  let docsGenerated = 0;
267
267
  let agentsGenerated = 0;
268
- let guidelinesGenerated = 0;
269
- // Step 2: Generate documentation
270
- if (!options.agentsOnly && !options.guidelinesOnly) {
271
- ui.displayStep(2, 4, 'Generating documentation');
272
- ui.startSpinner('Creating comprehensive documentation...');
273
- try {
274
- await docGenerator.generateDocumentation(repoStructure, cliOptions.outputDir, {}, // Default config
275
- false // We'll handle our own progress display
276
- );
277
- docsGenerated = 10; // Number of doc files generated (README, STRUCTURE, DEVELOPMENT, API, DEPLOYMENT, TROUBLESHOOTING, configuration + modules)
278
- ui.updateSpinner('Documentation generated successfully', 'success');
268
+ const docGenerator = new documentationGenerator_1.DocumentationGenerator();
269
+ const agentGenerator = new agentGenerator_1.AgentGenerator();
270
+ if (options.scaffoldDocs) {
271
+ ui.displayStep(2, 3, t('steps.init.docs'));
272
+ ui.startSpinner(t('spinner.docs.creating'));
273
+ docsGenerated = await docGenerator.generateDocumentation(repoStructure, options.outputDir, { selectedDocs: options.selectedDocKeys }, options.verbose);
274
+ ui.updateSpinner(t('spinner.docs.created', { count: docsGenerated }), 'success');
275
+ }
276
+ if (options.scaffoldAgents) {
277
+ ui.displayStep(3, options.scaffoldDocs ? 3 : 2, t('steps.init.agents'));
278
+ ui.startSpinner(t('spinner.agents.creating'));
279
+ agentsGenerated = await agentGenerator.generateAgentPrompts(repoStructure, options.outputDir, options.selectedAgentTypes, options.verbose);
280
+ ui.updateSpinner(t('spinner.agents.created', { count: agentsGenerated }), 'success');
281
+ }
282
+ ui.displayGenerationSummary(docsGenerated, agentsGenerated);
283
+ ui.displaySuccess(t('success.scaffold.ready', { path: chalk_1.default.cyan(options.outputDir) }));
284
+ }
285
+ function resolveScaffoldType(type, rawOptions) {
286
+ const normalized = (type || 'both').toLowerCase();
287
+ const allowed = ['docs', 'agents', 'both'];
288
+ if (!allowed.includes(normalized)) {
289
+ throw new Error(t('errors.init.invalidType', { value: type, allowed: allowed.join(', ') }));
290
+ }
291
+ if (rawOptions.docsOnly) {
292
+ return 'docs';
293
+ }
294
+ if (rawOptions.agentsOnly) {
295
+ return 'agents';
296
+ }
297
+ return normalized;
298
+ }
299
+ async function ensurePaths(options) {
300
+ const exists = await fs.pathExists(options.repoPath);
301
+ if (!exists) {
302
+ throw new Error(t('errors.common.repoMissing', { path: options.repoPath }));
303
+ }
304
+ await fs.ensureDir(options.outputDir);
305
+ }
306
+ async function runGenerate(repoPath, options) {
307
+ const type = options?.docsOnly ? 'docs' : options?.agentsOnly ? 'agents' : (options?.type || 'both');
308
+ await runInit(repoPath, type, {
309
+ output: options?.output ?? options?.outputDir ?? './.context',
310
+ include: options?.include,
311
+ exclude: options?.exclude,
312
+ verbose: options?.verbose,
313
+ docs: options?.docs,
314
+ agents: options?.agents,
315
+ docsOnly: options?.docsOnly,
316
+ agentsOnly: options?.agentsOnly
317
+ });
318
+ }
319
+ async function runAnalyze(..._args) {
320
+ throw new Error(t('errors.commands.analyzeRemoved'));
321
+ }
322
+ async function runUpdate(..._args) {
323
+ throw new Error(t('errors.commands.updateRemoved'));
324
+ }
325
+ async function runPreview(..._args) {
326
+ throw new Error(t('errors.commands.previewRemoved'));
327
+ }
328
+ async function runGuidelines(..._args) {
329
+ throw new Error(t('errors.commands.guidelinesRemoved'));
330
+ }
331
+ async function resolveLlmConfig(rawOptions, defaults) {
332
+ const promptPath = path.resolve(rawOptions.prompt || defaults.promptPath);
333
+ if (!(await fs.pathExists(promptPath))) {
334
+ throw new Error(t('errors.fill.promptMissing', { path: promptPath }));
335
+ }
336
+ const providerEnvMap = llmClientFactory_1.LLMClientFactory.getEnvironmentVariables();
337
+ const defaultModels = llmClientFactory_1.LLMClientFactory.getDefaultModels();
338
+ let provider = rawOptions.provider;
339
+ let model = rawOptions.model;
340
+ let apiKey = rawOptions.apiKey;
341
+ if (!apiKey) {
342
+ if (provider) {
343
+ for (const envVar of providerEnvMap[provider]) {
344
+ const value = process.env[envVar];
345
+ if (value) {
346
+ apiKey = value;
347
+ break;
348
+ }
349
+ }
279
350
  }
280
- catch (error) {
281
- ui.updateSpinner('Failed to generate documentation', 'fail');
282
- throw error;
351
+ else {
352
+ outer: for (const [prov, envVars] of Object.entries(providerEnvMap)) {
353
+ for (const envVar of envVars) {
354
+ const value = process.env[envVar];
355
+ if (value) {
356
+ apiKey = value;
357
+ provider = prov;
358
+ break outer;
359
+ }
360
+ }
361
+ }
283
362
  }
284
363
  }
285
- // Step 3: Generate agent prompts
286
- if (!options.docsOnly && !options.guidelinesOnly) {
287
- ui.displayStep(3, 4, 'Generating AI agent prompts');
288
- ui.startSpinner('Creating specialized agent prompts...');
289
- try {
290
- await agentGenerator.generateAgentPrompts(repoStructure, cliOptions.outputDir, false // We'll handle our own progress display
291
- );
292
- agentsGenerated = 9; // Number of agent files generated
293
- ui.updateSpinner('Agent prompts generated successfully', 'success');
364
+ if (!provider) {
365
+ if (model) {
366
+ provider = llmClientFactory_1.LLMClientFactory.detectProviderFromModel(model);
294
367
  }
295
- catch (error) {
296
- ui.updateSpinner('Failed to generate agent prompts', 'fail');
297
- throw error;
368
+ else if (apiKey) {
369
+ provider = llmClientFactory_1.LLMClientFactory.getProviderFromApiKey(apiKey);
298
370
  }
299
371
  }
300
- // Step 3/4: Generate guidelines (if guidelines-only or as part of comprehensive generation)
301
- if (options.guidelinesOnly) {
302
- ui.displayStep(3, 4, 'Generating software development guidelines');
303
- ui.startSpinner('Creating comprehensive development guidelines...');
304
- try {
305
- await guidelinesGenerator.generateGuidelines(repoStructure, cliOptions.outputDir, {}, // Default config
306
- false // We'll handle our own progress display
307
- );
308
- guidelinesGenerated = 12; // Number of guideline categories
309
- ui.updateSpinner('Guidelines generated successfully', 'success');
372
+ if (!model) {
373
+ if (provider === 'openrouter' && process.env.OPENROUTER_MODEL) {
374
+ model = process.env.OPENROUTER_MODEL;
310
375
  }
311
- catch (error) {
312
- ui.updateSpinner('Failed to generate guidelines', 'fail');
313
- throw error;
376
+ else if (provider && defaultModels[provider]?.length) {
377
+ model = defaultModels[provider][0];
378
+ }
379
+ else {
380
+ model = defaults.fallbackModel;
381
+ provider = llmClientFactory_1.LLMClientFactory.detectProviderFromModel(model);
314
382
  }
315
383
  }
316
- // Step 4: Complete
317
- ui.displayStep(4, 4, 'Finalizing output');
318
- // Get usage statistics from the LLM client
319
- const usageStats = llmClient.getUsageStats();
320
- ui.displayGenerationSummary(docsGenerated, agentsGenerated, usageStats);
321
- if (options.guidelinesOnly && guidelinesGenerated > 0) {
322
- ui.displaySuccess(`Guidelines generated! Output saved to: ${cliOptions.outputDir}`);
323
- }
324
- else {
325
- ui.displaySuccess(`Output saved to: ${cliOptions.outputDir}`);
384
+ if (!provider) {
385
+ provider = llmClientFactory_1.LLMClientFactory.detectProviderFromModel(model || defaults.fallbackModel);
326
386
  }
327
- }
328
- async function runGuidelines(repoPath, categories, options) {
329
- const provider = options.provider || llmClientFactory_1.LLMClientFactory.detectProviderFromModel(options.model);
330
- // Get API key from options or environment variables
331
- let apiKey = options.apiKey;
332
387
  if (!apiKey) {
333
- const envVars = llmClientFactory_1.LLMClientFactory.getEnvironmentVariables()[provider];
334
- for (const envVar of envVars) {
335
- apiKey = process.env[envVar];
336
- if (apiKey)
388
+ for (const envVar of providerEnvMap[provider]) {
389
+ const value = process.env[envVar];
390
+ if (value) {
391
+ apiKey = value;
337
392
  break;
393
+ }
338
394
  }
339
395
  }
340
- const cliOptions = {
341
- repoPath: path.resolve(repoPath),
342
- outputDir: path.resolve(options.output),
343
- model: options.model,
344
- apiKey,
396
+ if (!apiKey) {
397
+ const envVars = providerEnvMap[provider];
398
+ throw new Error(t('errors.fill.apiKeyMissing', {
399
+ provider: provider.toUpperCase(),
400
+ envVars: envVars.join(', ')
401
+ }));
402
+ }
403
+ return {
345
404
  provider,
346
- exclude: options.exclude || [],
347
- include: options.include,
348
- verbose: options.verbose || false
405
+ model: model || defaults.fallbackModel,
406
+ apiKey,
407
+ promptPath,
408
+ baseUrl: rawOptions.baseUrl
349
409
  };
350
- if (!cliOptions.apiKey) {
351
- const envVars = llmClientFactory_1.LLMClientFactory.getEnvironmentVariables()[provider];
352
- ui.displayError(`${provider.toUpperCase()} API key is required. Set one of these environment variables: ${envVars.join(', ')} or use --api-key option.`);
353
- process.exit(1);
410
+ }
411
+ async function runLlmFill(repoPath, rawOptions) {
412
+ const resolvedRepo = path.resolve(repoPath);
413
+ const outputDir = path.resolve(rawOptions.output || './.context');
414
+ const docsDir = path.join(outputDir, 'docs');
415
+ const agentsDir = path.join(outputDir, 'agents');
416
+ await ensureDirectoryExists(docsDir, t('errors.fill.missingDocsScaffold'));
417
+ await ensureDirectoryExists(agentsDir, t('errors.fill.missingAgentsScaffold'));
418
+ const docSelection = parseDocSelection(rawOptions.docs);
419
+ const agentSelection = parseAgentSelection(rawOptions.agents);
420
+ if (docSelection.invalid.length > 0) {
421
+ ui.displayWarning(t('warnings.docs.unknown', { values: docSelection.invalid.join(', ') }));
422
+ }
423
+ if (agentSelection.invalid.length > 0) {
424
+ ui.displayWarning(t('warnings.agents.unknown', { values: agentSelection.invalid.join(', ') }));
354
425
  }
355
- // Display welcome message
356
- ui.displayWelcome('0.1.0');
357
- ui.displayProjectInfo(cliOptions.repoPath, cliOptions.outputDir, cliOptions.model, cliOptions.provider);
358
- // Show usage warning for expensive models
359
- if (cliOptions.model && ['anthropic/claude-3-opus', 'openai/gpt-4'].includes(cliOptions.model)) {
360
- ui.displayUsageWarning(1.0); // Lower estimate for guidelines only
361
- }
362
- // Initialize components
363
- const fileMapper = new fileMapper_1.FileMapper(cliOptions.exclude);
364
- const llmConfig = {
365
- apiKey: cliOptions.apiKey,
366
- model: cliOptions.model || 'google/gemini-2.5-flash-preview-05-20',
367
- provider: cliOptions.provider || 'openrouter'
426
+ const { provider, model, apiKey, promptPath, baseUrl } = await resolveLlmConfig(rawOptions, {
427
+ promptPath: path.join(__dirname, '../prompts/update_scaffold_prompt.md'),
428
+ fallbackModel: DEFAULT_MODEL
429
+ });
430
+ const docAllowlist = docSelection.explicitNone
431
+ ? new Set()
432
+ : (0, guideRegistry_1.getDocFilesByKeys)(docSelection.selected);
433
+ const agentAllowlist = agentSelection.explicitNone
434
+ ? new Set()
435
+ : getAgentFilesByTypes(agentSelection.selected);
436
+ const options = {
437
+ repoPath: resolvedRepo,
438
+ outputDir,
439
+ promptPath,
440
+ provider,
441
+ model,
442
+ apiKey,
443
+ baseUrl,
444
+ include: rawOptions.include,
445
+ exclude: rawOptions.exclude,
446
+ verbose: rawOptions.verbose || false,
447
+ dryRun: rawOptions.dryRun || false,
448
+ processAll: rawOptions.all || false,
449
+ limit: rawOptions.limit,
450
+ selectedDocKeys: docSelection.selected,
451
+ selectedAgentTypes: agentSelection.selected,
452
+ selectedDocFiles: docAllowlist,
453
+ selectedAgentFiles: agentAllowlist
368
454
  };
369
- const llmClient = llmClientFactory_1.LLMClientFactory.createClient(llmConfig);
370
- const guidelinesGenerator = new guidelinesGenerator_1.GuidelinesGenerator(fileMapper, llmClient);
371
- // Step 1: Map repository structure
372
- ui.displayStep(1, 4, 'Analyzing repository structure');
373
- ui.startSpinner('Scanning files and directories...');
374
- const repoStructure = await fileMapper.mapRepository(cliOptions.repoPath, cliOptions.include);
375
- ui.updateSpinner(`Found ${repoStructure.totalFiles} files in ${repoStructure.directories.length} directories`, 'success');
376
- // Display analysis results
377
- if (cliOptions.verbose) {
378
- ui.displayAnalysisResults(repoStructure.totalFiles, repoStructure.directories.length, ui.formatBytes(repoStructure.totalSize));
379
- }
380
- // Step 2: Analyze codebase for guidelines
381
- ui.displayStep(2, 4, 'Analyzing codebase for guideline recommendations');
382
- ui.startSpinner('Detecting technologies and patterns...');
455
+ ui.displayWelcome(VERSION);
456
+ ui.displayProjectInfo(options.repoPath, options.outputDir, `fill:${options.provider}`);
457
+ const fileMapper = new fileMapper_1.FileMapper(options.exclude);
458
+ ui.displayStep(1, 3, t('steps.fill.analyze'));
459
+ ui.startSpinner(t('spinner.repo.scanning'));
460
+ const repoStructure = await fileMapper.mapRepository(options.repoPath, options.include);
461
+ ui.updateSpinner(t('spinner.repo.scanComplete', {
462
+ fileCount: repoStructure.totalFiles,
463
+ directoryCount: repoStructure.directories.length
464
+ }), 'success');
465
+ const systemPrompt = await fs.readFile(options.promptPath, 'utf-8');
466
+ const llmClient = llmClientFactory_1.LLMClientFactory.createClient({
467
+ apiKey: options.apiKey,
468
+ model: options.model,
469
+ provider: options.provider,
470
+ baseUrl: options.baseUrl
471
+ });
472
+ const targets = await collectTargets(docsDir, agentsDir, options.processAll, options.limit, options.selectedDocFiles, options.selectedAgentFiles);
473
+ if (targets.length === 0) {
474
+ ui.displayWarning(t('warnings.fill.noTargets'));
475
+ return;
476
+ }
477
+ const contextSummary = buildContextSummary(repoStructure);
478
+ const results = [];
479
+ ui.displayStep(2, 3, t('steps.fill.processFiles', { count: targets.length, model: options.model }));
480
+ for (const target of targets) {
481
+ const relativePath = path.relative(options.outputDir, target.fullPath);
482
+ ui.startSpinner(t('spinner.fill.processing', { path: relativePath }));
483
+ try {
484
+ const currentContent = await fs.readFile(target.fullPath, 'utf-8');
485
+ const userPrompt = buildUserPrompt(relativePath, currentContent, contextSummary, target.isAgent);
486
+ const updatedContent = await llmClient.generateText(userPrompt, systemPrompt);
487
+ if (!updatedContent || !updatedContent.trim()) {
488
+ ui.updateSpinner(t('spinner.fill.noContent', { path: relativePath }), 'warn');
489
+ results.push({ file: relativePath, status: 'skipped', message: t('messages.fill.emptyResponse') });
490
+ continue;
491
+ }
492
+ if (options.dryRun) {
493
+ ui.updateSpinner(t('spinner.fill.dryRunPreview', { path: relativePath }), 'info');
494
+ console.log(chalk_1.default.gray(`\n${t('messages.fill.previewStart')}`));
495
+ console.log(updatedContent.trim());
496
+ console.log(chalk_1.default.gray(`${t('messages.fill.previewEnd')}\n`));
497
+ }
498
+ else {
499
+ await fs.writeFile(target.fullPath, ensureTrailingNewline(updatedContent));
500
+ ui.updateSpinner(t('spinner.fill.updated', { path: relativePath }), 'success');
501
+ }
502
+ results.push({ file: relativePath, status: options.dryRun ? 'skipped' : 'updated' });
503
+ }
504
+ catch (error) {
505
+ ui.updateSpinner(t('spinner.fill.failed', { path: relativePath }), 'fail');
506
+ results.push({
507
+ file: relativePath,
508
+ status: 'failed',
509
+ message: error instanceof Error ? error.message : String(error)
510
+ });
511
+ }
512
+ }
513
+ ui.displayStep(3, 3, t('steps.fill.summary'));
514
+ printLlmSummary(llmClient.getUsageStats(), results, options.dryRun);
515
+ ui.displaySuccess(t('success.fill.completed'));
516
+ }
517
+ async function scaffoldPlanIfNeeded(planName, outputDir, options) {
518
+ const resolvedOutput = path.resolve(outputDir);
519
+ const plansDir = path.join(resolvedOutput, 'plans');
520
+ const normalizedInput = planName.replace(/\.md$/i, '');
521
+ const slug = shared_1.GeneratorUtils.slugify(normalizedInput);
522
+ if (!slug) {
523
+ throw new Error(t('errors.plan.invalidName'));
524
+ }
525
+ const planPath = path.join(plansDir, `${slug}.md`);
526
+ const planExists = await fs.pathExists(planPath);
527
+ if (planExists && !options.force) {
528
+ return;
529
+ }
530
+ const generator = new planGenerator_1.PlanGenerator();
531
+ const result = await generator.generatePlan({
532
+ planName,
533
+ outputDir: resolvedOutput,
534
+ title: options.title,
535
+ summary: options.summary,
536
+ selectedAgentTypes: options.agentSelection
537
+ ? options.agentSelection.explicitNone
538
+ ? null
539
+ : options.agentSelection.selected
540
+ : undefined,
541
+ selectedDocKeys: options.docSelection
542
+ ? options.docSelection.explicitNone
543
+ ? null
544
+ : options.docSelection.selected
545
+ : undefined,
546
+ force: Boolean(options.force),
547
+ verbose: Boolean(options.verbose)
548
+ });
549
+ const relativePath = result.relativePath;
550
+ const message = planExists && options.force
551
+ ? t('messages.plan.regenerated', { path: relativePath })
552
+ : t('messages.plan.created', { path: relativePath });
553
+ ui.displayInfo(t('info.plan.scaffolded.title'), message);
554
+ }
555
+ async function runPlanFill(planName, rawOptions) {
556
+ const outputDir = path.resolve(rawOptions.output || './.context');
557
+ const plansDir = path.join(outputDir, 'plans');
558
+ await ensureDirectoryExists(plansDir, t('errors.plan.missingPlansDir'));
559
+ const normalizedInput = planName.replace(/\.md$/i, '');
560
+ const slug = shared_1.GeneratorUtils.slugify(normalizedInput);
561
+ if (!slug) {
562
+ throw new Error(t('errors.plan.invalidName'));
563
+ }
564
+ const candidateFiles = new Set();
565
+ candidateFiles.add(path.join(plansDir, `${slug}.md`));
566
+ if (planName.toLowerCase().endsWith('.md')) {
567
+ candidateFiles.add(path.join(plansDir, planName));
568
+ }
569
+ let planPath;
570
+ for (const candidate of candidateFiles) {
571
+ if (await fs.pathExists(candidate)) {
572
+ planPath = candidate;
573
+ break;
574
+ }
575
+ }
576
+ if (!planPath) {
577
+ const expected = Array.from(candidateFiles).map(file => path.relative(process.cwd(), file)).join(' or ');
578
+ throw new Error(t('errors.plan.notFound', { expected }));
579
+ }
580
+ const docsDir = path.join(outputDir, 'docs');
581
+ const agentsDir = path.join(outputDir, 'agents');
582
+ await ensureDirectoryExists(docsDir, t('errors.fill.missingDocsScaffold'));
583
+ await ensureDirectoryExists(agentsDir, t('errors.fill.missingAgentsScaffold'));
584
+ const repoPath = path.resolve(rawOptions.repo || process.cwd());
585
+ if (!(await fs.pathExists(repoPath))) {
586
+ throw new Error(t('errors.common.repoMissing', { path: repoPath }));
587
+ }
588
+ const { provider, model, apiKey, promptPath, baseUrl } = await resolveLlmConfig(rawOptions, {
589
+ promptPath: path.join(__dirname, '../prompts/update_plan_prompt.md'),
590
+ fallbackModel: DEFAULT_MODEL
591
+ });
592
+ const planContent = await fs.readFile(planPath, 'utf-8');
593
+ const docsIndexPath = path.join(docsDir, 'README.md');
594
+ const agentsIndexPath = path.join(agentsDir, 'README.md');
595
+ const docsIndex = (await fs.pathExists(docsIndexPath)) ? await fs.readFile(docsIndexPath, 'utf-8') : undefined;
596
+ const agentsIndex = (await fs.pathExists(agentsIndexPath)) ? await fs.readFile(agentsIndexPath, 'utf-8') : undefined;
597
+ const referencedDocs = await loadReferencedMarkdown(docsDir, extractPlanReferences(planContent, 'docs'));
598
+ const referencedAgents = await loadReferencedMarkdown(agentsDir, extractPlanReferences(planContent, 'agents'));
599
+ ui.displayWelcome(VERSION);
600
+ ui.displayProjectInfo(repoPath, outputDir, `plan-fill:${provider}`);
601
+ const fileMapper = new fileMapper_1.FileMapper(rawOptions.exclude);
602
+ ui.displayStep(1, 3, t('steps.plan.summary'));
603
+ ui.startSpinner(t('spinner.planFill.analyzingRepo'));
604
+ const repoStructure = await fileMapper.mapRepository(repoPath, rawOptions.include);
605
+ const contextSummary = buildContextSummary(repoStructure);
606
+ ui.updateSpinner(t('spinner.planFill.summaryReady'), 'success');
607
+ const systemPrompt = await fs.readFile(promptPath, 'utf-8');
608
+ const llmClient = llmClientFactory_1.LLMClientFactory.createClient({
609
+ apiKey,
610
+ model,
611
+ provider,
612
+ baseUrl
613
+ });
614
+ const planRelativePath = path.relative(outputDir, planPath);
615
+ const results = [];
616
+ ui.displayStep(2, 3, t('steps.plan.update', { path: planRelativePath, model }));
617
+ ui.startSpinner(t('spinner.planFill.updating', { path: planRelativePath }));
383
618
  try {
384
- const analysis = await guidelinesGenerator.analyzeCodebaseOnly(repoStructure, cliOptions.verbose);
385
- ui.updateSpinner('Technology analysis complete', 'success');
386
- if (cliOptions.verbose) {
387
- console.log(chalk_1.default.bold('\nšŸ“Š Analysis Results:'));
388
- console.log(chalk_1.default.gray('─'.repeat(50)));
389
- console.log(`${chalk_1.default.blue('Project Type:')} ${analysis.projectType}`);
390
- console.log(`${chalk_1.default.blue('Complexity:')} ${analysis.complexity}`);
391
- console.log(`${chalk_1.default.blue('Technologies:')} ${analysis.technologies.map(t => t.name).join(', ')}`);
392
- console.log(`${chalk_1.default.blue('Recommended Categories:')} ${analysis.recommendedCategories.join(', ')}`);
619
+ const userPrompt = buildPlanUserPrompt({
620
+ relativePath: planRelativePath,
621
+ planContent,
622
+ contextSummary,
623
+ docsIndex,
624
+ agentsIndex,
625
+ docs: referencedDocs,
626
+ agents: referencedAgents
627
+ });
628
+ const updatedContent = await llmClient.generateText(userPrompt, systemPrompt);
629
+ if (!updatedContent || !updatedContent.trim()) {
630
+ ui.updateSpinner(t('spinner.planFill.noContent'), 'warn');
631
+ results.push({ file: planRelativePath, status: 'skipped', message: t('messages.fill.emptyResponse') });
632
+ }
633
+ else if (rawOptions.dryRun) {
634
+ ui.updateSpinner(t('spinner.planFill.dryRun'), 'info');
635
+ console.log(chalk_1.default.gray(`\n${t('messages.fill.previewStart')}`));
636
+ console.log(updatedContent.trim());
637
+ console.log(chalk_1.default.gray(`${t('messages.fill.previewEnd')}\n`));
638
+ results.push({ file: planRelativePath, status: 'skipped', message: 'dry-run' });
639
+ }
640
+ else {
641
+ await fs.writeFile(planPath, ensureTrailingNewline(updatedContent));
642
+ ui.updateSpinner(t('spinner.planFill.updated', { path: planRelativePath }), 'success');
643
+ results.push({ file: planRelativePath, status: 'updated' });
393
644
  }
394
645
  }
395
646
  catch (error) {
396
- ui.updateSpinner('Failed to analyze codebase', 'fail');
397
- throw error;
647
+ ui.updateSpinner(t('spinner.planFill.failed'), 'fail');
648
+ results.push({
649
+ file: planRelativePath,
650
+ status: 'failed',
651
+ message: error instanceof Error ? error.message : String(error)
652
+ });
398
653
  }
399
- // Step 3: Generate guidelines
400
- ui.displayStep(3, 4, 'Generating software development guidelines');
401
- ui.startSpinner('Creating comprehensive development guidelines...');
402
- try {
403
- // Build configuration from CLI options
404
- const guidelineConfig = {};
405
- // Set specific categories if provided
406
- if (categories && categories.length > 0) {
407
- // Validate categories
408
- const validCategories = ['testing', 'frontend', 'backend', 'database', 'security', 'performance', 'code-style', 'git-workflow', 'deployment', 'monitoring', 'documentation', 'architecture'];
409
- const invalidCategories = categories.filter(cat => !validCategories.includes(cat));
410
- if (invalidCategories.length > 0) {
411
- ui.displayError(`Invalid categories: ${invalidCategories.join(', ')}. Valid categories are: ${validCategories.join(', ')}`);
412
- process.exit(1);
654
+ finally {
655
+ ui.stopSpinner();
656
+ }
657
+ ui.displayStep(3, 3, t('steps.plan.summaryResults'));
658
+ printLlmSummary(llmClient.getUsageStats(), results, Boolean(rawOptions.dryRun));
659
+ ui.displaySuccess(t('success.plan.filled'));
660
+ }
661
+ async function ensureDirectoryExists(dir, message) {
662
+ const exists = await fs.pathExists(dir);
663
+ if (!exists) {
664
+ throw new Error(message);
665
+ }
666
+ }
667
+ async function collectTargets(docsDir, agentsDir, processAll, limit, docAllowlist, agentAllowlist) {
668
+ const docFiles = await (0, glob_1.glob)('**/*.md', { cwd: docsDir, absolute: true });
669
+ const agentFiles = await (0, glob_1.glob)('**/*.md', { cwd: agentsDir, absolute: true });
670
+ const candidates = [...docFiles, ...agentFiles];
671
+ const targets = [];
672
+ for (const fullPath of candidates) {
673
+ const content = await fs.readFile(fullPath, 'utf-8');
674
+ const hasMarkers = /<!--\s*ai-task:/.test(content) || /<!--\s*ai-slot:/.test(content) || /TODO/.test(content);
675
+ const isAgent = fullPath.includes(`${path.sep}agents${path.sep}`);
676
+ const fileName = path.basename(fullPath);
677
+ if (isAgent) {
678
+ if (agentAllowlist && !agentAllowlist.has(fileName)) {
679
+ continue;
680
+ }
681
+ }
682
+ else {
683
+ if (docAllowlist && !docAllowlist.has(fileName)) {
684
+ continue;
413
685
  }
414
- guidelineConfig.categories = categories;
415
686
  }
416
- // Set project configuration from CLI options
417
- if (options.projectType && options.projectType !== 'auto') {
418
- guidelineConfig.projectType = options.projectType;
687
+ const explicitSelection = isAgent ? !!agentAllowlist : !!docAllowlist;
688
+ const shouldInclude = processAll ||
689
+ hasMarkers ||
690
+ (explicitSelection && (isAgent ? agentAllowlist.has(fileName) : docAllowlist.has(fileName)));
691
+ if (!shouldInclude) {
692
+ continue;
693
+ }
694
+ targets.push({ fullPath, hasMarkers, isAgent });
695
+ if (limit && targets.length >= limit) {
696
+ break;
697
+ }
698
+ }
699
+ return targets;
700
+ }
701
+ function buildContextSummary(repoStructure) {
702
+ const directories = new Set();
703
+ repoStructure.directories.forEach(dir => {
704
+ const [first] = dir.relativePath.split(/[\\/]/).filter(Boolean);
705
+ if (first) {
706
+ directories.add(first);
419
707
  }
420
- if (options.complexity && options.complexity !== 'auto') {
421
- guidelineConfig.complexity = options.complexity;
708
+ });
709
+ const topDirs = Array.from(directories).sort().slice(0, 12);
710
+ const totalSizeMb = (repoStructure.totalSize / (1024 * 1024)).toFixed(2);
711
+ return [
712
+ `Top-level directories: ${topDirs.length ? topDirs.join(', ') : 'n/a'}`,
713
+ `Total files scanned: ${repoStructure.totalFiles}`,
714
+ `Repository size (approx.): ${totalSizeMb} MB`
715
+ ].join('\n');
716
+ }
717
+ function buildUserPrompt(relativePath, currentContent, contextSummary, isAgent) {
718
+ const guidance = [
719
+ '- Preserve YAML front matter and existing `ai-task` sections.',
720
+ '- Replace TODOs and resolve `ai-slot` placeholders with concrete information.',
721
+ '- Ensure success criteria in the front matter are satisfied.',
722
+ '- Return only the full updated Markdown for this file.'
723
+ ];
724
+ if (isAgent) {
725
+ guidance.push('- Keep agent responsibilities, best practices, and documentation touchpoints aligned with the latest docs.');
726
+ }
727
+ else {
728
+ guidance.push('- Maintain accurate cross-links between docs and referenced resources.');
729
+ }
730
+ return [
731
+ `Target file: ${relativePath}`,
732
+ 'Repository summary:',
733
+ contextSummary,
734
+ '',
735
+ 'Guidance:',
736
+ ...guidance,
737
+ '',
738
+ 'Current content:',
739
+ '<file>',
740
+ currentContent,
741
+ '</file>'
742
+ ].join('\n');
743
+ }
744
+ function buildPlanUserPrompt(context) {
745
+ const guidance = [
746
+ '- Preserve the YAML front matter and `ai-task` wrapper already in the plan.',
747
+ '- Replace TODOs with concrete steps that align with the provided documentation and agent playbooks.',
748
+ '- Keep the Agent Lineup and Documentation Touchpoints tables accurate and sorted.',
749
+ '- Ensure stages include owners, deliverables, and evidence expectations.',
750
+ '- Return only the full updated Markdown for this plan.'
751
+ ];
752
+ const sections = [
753
+ `Target file: ${context.relativePath}`,
754
+ 'Repository summary:',
755
+ context.contextSummary,
756
+ '',
757
+ 'Guidance:',
758
+ ...guidance,
759
+ '',
760
+ 'Current plan:',
761
+ '<plan>',
762
+ context.planContent,
763
+ '</plan>'
764
+ ];
765
+ if (context.docsIndex) {
766
+ sections.push('', 'Documentation index (docs/README.md):', '<docs-index>', context.docsIndex, '</docs-index>');
767
+ }
768
+ if (context.agentsIndex) {
769
+ sections.push('', 'Agent handbook (agents/README.md):', '<agents-index>', context.agentsIndex, '</agents-index>');
770
+ }
771
+ context.docs.forEach(doc => {
772
+ sections.push('', `Referenced documentation (${doc.path}):`, '<doc>', doc.content, '</doc>');
773
+ });
774
+ context.agents.forEach(agent => {
775
+ sections.push('', `Referenced agent playbook (${agent.path}):`, '<agent>', agent.content, '</agent>');
776
+ });
777
+ return sections.join('\n');
778
+ }
779
+ function extractPlanReferences(content, type) {
780
+ const regex = type === 'docs'
781
+ ? /\]\(\.\.\/docs\/([^)#]+)(?:#[^)]*)?\)/g
782
+ : /\]\(\.\.\/agents\/([^)#]+)(?:#[^)]*)?\)/g;
783
+ const references = [];
784
+ let match;
785
+ while ((match = regex.exec(content)) !== null) {
786
+ const rawPath = match[1].trim();
787
+ if (!rawPath)
788
+ continue;
789
+ const normalized = rawPath.replace(/^\.\//, '').replace(/#.*$/, '');
790
+ if (!normalized || normalized.includes('..'))
791
+ continue;
792
+ if (!references.includes(normalized)) {
793
+ references.push(normalized);
422
794
  }
423
- if (options.teamSize && options.teamSize !== 'auto') {
424
- guidelineConfig.teamSize = options.teamSize;
795
+ }
796
+ return references;
797
+ }
798
+ async function loadReferencedMarkdown(baseDir, fileNames) {
799
+ const results = [];
800
+ const seen = new Set();
801
+ for (const name of fileNames) {
802
+ const cleanName = name.replace(/#.*$/, '');
803
+ if (!cleanName || seen.has(cleanName)) {
804
+ continue;
425
805
  }
426
- // Set feature flags
427
- if (options.includeExamples) {
428
- guidelineConfig.includeExamples = true;
806
+ const normalized = path.normalize(cleanName).replace(/^\.\//, '');
807
+ if (normalized.includes('..')) {
808
+ continue;
429
809
  }
430
- if (options.includeTools) {
431
- guidelineConfig.includeTools = true;
810
+ const fullPath = path.join(baseDir, normalized);
811
+ if (!(await fs.pathExists(fullPath))) {
812
+ continue;
432
813
  }
433
- await guidelinesGenerator.generateGuidelines(repoStructure, cliOptions.outputDir, guidelineConfig, cliOptions.verbose);
434
- ui.updateSpinner('Guidelines generated successfully', 'success');
814
+ const content = await fs.readFile(fullPath, 'utf-8');
815
+ results.push({ path: normalized, content });
816
+ seen.add(cleanName);
435
817
  }
436
- catch (error) {
437
- ui.updateSpinner('Failed to generate guidelines', 'fail');
438
- throw error;
439
- }
440
- // Step 4: Complete
441
- ui.displayStep(4, 4, 'Finalizing output');
442
- // Get usage statistics from the LLM client
443
- const usageStats = llmClient.getUsageStats();
444
- ui.displayGenerationSummary(0, 0, usageStats); // No docs or agents generated
445
- const guidelinesPath = path.join(cliOptions.outputDir, 'guidelines');
446
- ui.displaySuccess(`Guidelines generated! Output saved to: ${guidelinesPath}`);
447
- // Display helpful next steps
448
- console.log(chalk_1.default.bold('\nšŸ’” Next Steps:'));
818
+ return results;
819
+ }
820
+ function ensureTrailingNewline(content) {
821
+ return content.endsWith('\n') ? content : `${content}\n`;
822
+ }
823
+ function printLlmSummary(usage, results, dryRun) {
824
+ const updated = results.filter(r => r.status === 'updated').length;
825
+ const skipped = results.filter(r => r.status === 'skipped').length;
826
+ const failed = results.filter(r => r.status === 'failed');
827
+ console.log('\n' + chalk_1.default.bold('šŸ“„ LLM Fill Summary'));
449
828
  console.log(chalk_1.default.gray('─'.repeat(50)));
450
- console.log(`${chalk_1.default.blue('1. Review:')} Check the generated guidelines in ${guidelinesPath}`);
451
- console.log(`${chalk_1.default.blue('2. Customize:')} Modify guidelines to match your team's specific needs`);
452
- console.log(`${chalk_1.default.blue('3. Share:')} Distribute guidelines to your development team`);
453
- console.log(`${chalk_1.default.blue('4. Integrate:')} Include guidelines in your project documentation`);
454
- }
455
- async function runAnalyze(repoPath, options) {
456
- const resolvedPath = path.resolve(repoPath);
457
- // Display welcome
458
- ui.displayWelcome('0.1.0');
459
- ui.startSpinner('Analyzing repository structure...');
460
- const fileMapper = new fileMapper_1.FileMapper(options.exclude || []);
461
- const repoStructure = await fileMapper.mapRepository(resolvedPath, options.include);
462
- ui.stopSpinner();
463
- // Display analysis results
464
- ui.displayAnalysisResults(repoStructure.totalFiles, repoStructure.directories.length, ui.formatBytes(repoStructure.totalSize));
465
- // File type distribution
466
- const extensions = new Map();
467
- repoStructure.files.forEach(file => {
468
- const ext = file.extension || 'no-extension';
469
- extensions.set(ext, (extensions.get(ext) || 0) + 1);
470
- });
471
- ui.displayFileTypeDistribution(extensions, repoStructure.totalFiles);
472
- // Directory structure (top level)
473
- const topDirs = repoStructure.directories
474
- .filter(dir => !dir.relativePath.includes('/'))
475
- .slice(0, 10);
476
- if (topDirs.length > 0) {
477
- console.log(chalk_1.default.bold('\nšŸ“‚ Top-level Directories:'));
829
+ console.log(`${chalk_1.default.blue('Updated files:')} ${chalk_1.default.white(updated.toString())}`);
830
+ console.log(`${chalk_1.default.blue('Skipped files:')} ${chalk_1.default.white(skipped.toString())}${dryRun ? chalk_1.default.gray(' (dry run)') : ''}`);
831
+ console.log(`${chalk_1.default.blue('Failures:')} ${failed.length}`);
832
+ if (usage.totalCalls > 0) {
478
833
  console.log(chalk_1.default.gray('─'.repeat(50)));
479
- topDirs.forEach(dir => {
480
- console.log(` ${chalk_1.default.blue('ā–ø')} ${chalk_1.default.white(dir.relativePath)}`);
834
+ console.log(`${chalk_1.default.blue('LLM calls:')} ${usage.totalCalls}`);
835
+ console.log(`${chalk_1.default.blue('Prompt tokens:')} ${usage.totalPromptTokens}`);
836
+ console.log(`${chalk_1.default.blue('Completion tokens:')} ${usage.totalCompletionTokens}`);
837
+ console.log(`${chalk_1.default.blue('Estimated cost:')} ${usage.estimatedCost.toFixed(4)}`);
838
+ console.log(`${chalk_1.default.blue('Model:')} ${usage.model}`);
839
+ }
840
+ if (failed.length > 0) {
841
+ console.log(chalk_1.default.gray('─'.repeat(50)));
842
+ failed.forEach(f => {
843
+ console.log(`${chalk_1.default.red('āœ–')} ${chalk_1.default.white(f.file)} — ${chalk_1.default.gray(f.message || 'Unknown error')}`);
481
844
  });
482
845
  }
483
- // Token estimation for full documentation generation
484
- ui.startSpinner('Estimating token usage for full documentation generation...');
485
- // Create pricing if provided (optional for analyze command)
486
- let pricing = undefined;
487
- if (options.inputPrice !== undefined && options.outputPrice !== undefined) {
488
- pricing = {
489
- input: options.inputPrice,
490
- output: options.outputPrice
491
- };
846
+ }
847
+ function parseDocSelection(input) {
848
+ if (input === undefined) {
849
+ return { selected: undefined, invalid: [], provided: false, explicitNone: false };
492
850
  }
493
- else if (options.inputPrice !== undefined || options.outputPrice !== undefined) {
494
- ui.displayError('Pricing requires both options: --input-price and --output-price');
495
- process.exit(1);
851
+ if (Array.isArray(input) && input.length === 0) {
852
+ return { selected: [], invalid: [], provided: true, explicitNone: true };
496
853
  }
497
- const tokenEstimator = new tokenEstimator_1.TokenEstimator(fileMapper, pricing);
498
- const tokenEstimate = await tokenEstimator.estimateTokensForFullGeneration(repoStructure);
499
- ui.stopSpinner();
500
- console.log(tokenEstimator.formatTokenEstimate(tokenEstimate));
501
- ui.displaySuccess('Analysis complete!');
854
+ const values = toStringArray(input);
855
+ const normalized = values.map(value => value.toLowerCase().replace(/\.md$/, ''));
856
+ const valid = Array.from(new Set(normalized.filter(key => guideRegistry_1.DOCUMENT_GUIDE_KEYS.includes(key))));
857
+ const invalid = normalized.filter(key => !guideRegistry_1.DOCUMENT_GUIDE_KEYS.includes(key));
858
+ if (values.length > 0 && valid.length === 0 && invalid.length > 0) {
859
+ return { selected: undefined, invalid, provided: true, explicitNone: false };
860
+ }
861
+ return { selected: valid.length > 0 ? valid : undefined, invalid, provided: true, explicitNone: false };
502
862
  }
503
- async function runUpdate(repoPath, options) {
504
- const provider = options.provider || llmClientFactory_1.LLMClientFactory.detectProviderFromModel(options.model);
505
- // Get API key from options or environment variables
506
- let apiKey = options.apiKey;
507
- if (!apiKey) {
508
- const envVars = llmClientFactory_1.LLMClientFactory.getEnvironmentVariables()[provider];
509
- for (const envVar of envVars) {
510
- apiKey = process.env[envVar];
511
- if (apiKey)
512
- break;
863
+ function parseAgentSelection(input) {
864
+ if (input === undefined) {
865
+ return { selected: undefined, invalid: [], provided: false, explicitNone: false };
866
+ }
867
+ if (Array.isArray(input) && input.length === 0) {
868
+ return { selected: [], invalid: [], provided: true, explicitNone: true };
869
+ }
870
+ const values = toStringArray(input);
871
+ const normalized = values.map(value => value.toLowerCase().replace(/\.md$/, ''));
872
+ const allowed = new Set(agentTypes_1.AGENT_TYPES);
873
+ const valid = Array.from(new Set(normalized.filter(value => allowed.has(value))));
874
+ const invalid = normalized.filter(value => !allowed.has(value));
875
+ if (values.length > 0 && valid.length === 0 && invalid.length > 0) {
876
+ return { selected: undefined, invalid, provided: true, explicitNone: false };
877
+ }
878
+ return { selected: valid.length > 0 ? valid : undefined, invalid, provided: true, explicitNone: false };
879
+ }
880
+ function shouldGenerateDocs(resolvedType, selection) {
881
+ if (selection.explicitNone) {
882
+ return false;
883
+ }
884
+ if (resolvedType === 'agents') {
885
+ return false;
886
+ }
887
+ if (!selection.provided) {
888
+ return resolvedType === 'docs' || resolvedType === 'both';
889
+ }
890
+ if (selection.selected && selection.selected.length === 0) {
891
+ return false;
892
+ }
893
+ return resolvedType === 'docs' || resolvedType === 'both';
894
+ }
895
+ function shouldGenerateAgents(resolvedType, selection) {
896
+ if (selection.explicitNone) {
897
+ return false;
898
+ }
899
+ if (resolvedType === 'docs') {
900
+ return false;
901
+ }
902
+ if (!selection.provided) {
903
+ return resolvedType === 'agents' || resolvedType === 'both';
904
+ }
905
+ if (selection.selected && selection.selected.length === 0) {
906
+ return false;
907
+ }
908
+ return resolvedType === 'agents' || resolvedType === 'both';
909
+ }
910
+ function toStringArray(input) {
911
+ if (Array.isArray(input)) {
912
+ return input.map(item => item.toString().trim()).filter(Boolean);
913
+ }
914
+ if (input === null || input === undefined) {
915
+ return [];
916
+ }
917
+ return input
918
+ .toString()
919
+ .split(',')
920
+ .map((part) => part.trim())
921
+ .filter(Boolean);
922
+ }
923
+ function formatAgentLabel(value) {
924
+ return value
925
+ .split('-')
926
+ .map(segment => segment.charAt(0).toUpperCase() + segment.slice(1))
927
+ .join(' ');
928
+ }
929
+ async function runInteractive() {
930
+ const { locale } = await inquirer_1.default.prompt([
931
+ {
932
+ type: 'list',
933
+ name: 'locale',
934
+ message: t('prompts.language.select'),
935
+ default: currentLocale,
936
+ choices: i18n_1.SUPPORTED_LOCALES.map(option => ({
937
+ value: option,
938
+ name: t(localeLabelKeys[option])
939
+ }))
513
940
  }
941
+ ]);
942
+ const normalizedLocale = (0, i18n_1.normalizeLocale)(locale);
943
+ currentLocale = normalizedLocale;
944
+ translateFn = (0, i18n_1.createTranslator)(normalizedLocale);
945
+ ui.displayWelcome(VERSION);
946
+ const { action } = await inquirer_1.default.prompt([
947
+ {
948
+ type: 'list',
949
+ name: 'action',
950
+ message: t('prompts.main.action'),
951
+ choices: [
952
+ { name: t('prompts.main.choice.scaffold'), value: 'scaffold' },
953
+ { name: t('prompts.main.choice.fill'), value: 'fill' },
954
+ { name: t('prompts.main.choice.plan'), value: 'plan' }
955
+ ]
956
+ }
957
+ ]);
958
+ if (action === 'scaffold') {
959
+ await runInteractiveScaffold();
514
960
  }
515
- const cliOptions = {
516
- repoPath: path.resolve(repoPath),
517
- outputDir: path.resolve(options.output),
518
- model: options.model,
519
- apiKey,
520
- provider,
521
- exclude: options.exclude || [],
522
- include: options.include,
523
- verbose: options.verbose || false,
524
- since: options.since,
525
- staged: options.staged || false,
526
- force: options.force || false
527
- };
528
- if (!cliOptions.apiKey) {
529
- const envVars = llmClientFactory_1.LLMClientFactory.getEnvironmentVariables()[provider];
530
- ui.displayError(`${provider.toUpperCase()} API key is required. Set one of these environment variables: ${envVars.join(', ')} or use --api-key option.`);
531
- process.exit(1);
961
+ else if (action === 'fill') {
962
+ await runInteractiveLlmFill();
532
963
  }
533
- // Initialize git service
534
- const gitService = new gitService_1.GitService(cliOptions.repoPath);
535
- if (!gitService.isGitRepository()) {
536
- ui.displayError('This command requires a Git repository. Initialize git first or use the generate command instead.');
537
- process.exit(1);
964
+ else {
965
+ await runInteractivePlan();
538
966
  }
539
- // Check if context has been initialized
540
- if (!gitService.hasContextBeenInitialized(cliOptions.outputDir)) {
541
- ui.displayError('No documentation context found. You should run analyze and init before updating.');
542
- console.log(chalk_1.default.bold('\nšŸ’” Getting Started:'));
543
- console.log(chalk_1.default.gray('─'.repeat(50)));
544
- console.log(`${chalk_1.default.blue('1. Analyze:')} ai-context analyze ${cliOptions.repoPath}`);
545
- console.log(`${chalk_1.default.blue('2. Initialize:')} ai-context init ${cliOptions.repoPath}`);
546
- console.log(`${chalk_1.default.blue('3. Update:')} ai-context update ${cliOptions.repoPath}`);
547
- console.log(chalk_1.default.gray('─'.repeat(50)));
548
- console.log(chalk_1.default.gray('The analyze command shows token estimates and costs.'));
549
- console.log(chalk_1.default.gray('The init command creates the initial documentation.'));
550
- console.log(chalk_1.default.gray('The update command incrementally updates existing documentation.'));
551
- process.exit(1);
967
+ }
968
+ async function runInteractiveScaffold() {
969
+ const { repoPath } = await inquirer_1.default.prompt([
970
+ {
971
+ type: 'input',
972
+ name: 'repoPath',
973
+ message: t('prompts.scaffold.repoPath'),
974
+ default: process.cwd()
975
+ }
976
+ ]);
977
+ const resolvedRepo = path.resolve(repoPath.trim() || '.');
978
+ const defaultOutput = path.resolve(resolvedRepo, '.context');
979
+ const { outputDir } = await inquirer_1.default.prompt([
980
+ {
981
+ type: 'input',
982
+ name: 'outputDir',
983
+ message: t('commands.init.options.output'),
984
+ default: defaultOutput
985
+ }
986
+ ]);
987
+ const { includeDocs } = await inquirer_1.default.prompt([
988
+ {
989
+ type: 'confirm',
990
+ name: 'includeDocs',
991
+ message: t('prompts.scaffold.includeDocs'),
992
+ default: true
993
+ }
994
+ ]);
995
+ let selectedDocs;
996
+ if (includeDocs) {
997
+ const { docs } = await inquirer_1.default.prompt([
998
+ {
999
+ type: 'checkbox',
1000
+ name: 'docs',
1001
+ message: t('prompts.scaffold.selectDocs'),
1002
+ choices: DOC_CHOICES,
1003
+ default: DOC_CHOICES.map(choice => choice.value)
1004
+ }
1005
+ ]);
1006
+ selectedDocs = docs;
552
1007
  }
553
- // Display welcome message
554
- ui.displayWelcome('0.1.0');
555
- ui.displayProjectInfo(cliOptions.repoPath, cliOptions.outputDir, cliOptions.model, cliOptions.provider);
556
- // Initialize components
557
- const fileMapper = new fileMapper_1.FileMapper(cliOptions.exclude);
558
- const llmConfig = {
559
- apiKey: cliOptions.apiKey,
560
- model: cliOptions.model || 'google/gemini-2.5-flash-preview-05-20',
561
- provider: cliOptions.provider || 'openrouter'
562
- };
563
- const llmClient = llmClientFactory_1.LLMClientFactory.createClient(llmConfig);
564
- const incrementalGenerator = new incrementalDocumentationGenerator_1.IncrementalDocumentationGenerator(fileMapper, llmClient, gitService);
565
- // Step 1: Analyze repository structure
566
- ui.displayStep(1, 4, 'Analyzing repository structure');
567
- ui.startSpinner('Scanning files and directories...');
568
- const repoStructure = await fileMapper.mapRepository(cliOptions.repoPath, cliOptions.include);
569
- ui.updateSpinner(`Found ${repoStructure.totalFiles} files in ${repoStructure.directories.length} directories`, 'success');
570
- // Step 2: Detect changes
571
- ui.displayStep(2, 4, 'Detecting changes');
572
- // Display commit tracking info in verbose mode
573
- if (cliOptions.verbose) {
574
- gitService.displayCommitTrackingInfo(true);
575
- }
576
- ui.startSpinner('Analyzing git changes...');
577
- let changes;
578
- if (cliOptions.staged) {
579
- // For pre-commit hooks - analyze only staged files
580
- changes = gitService.getStagedChanges();
581
- }
582
- else if (cliOptions.since) {
583
- // Compare against specific commit
584
- changes = gitService.getChangedFiles(cliOptions.since);
1008
+ else {
1009
+ selectedDocs = [];
1010
+ }
1011
+ const { includeAgents } = await inquirer_1.default.prompt([
1012
+ {
1013
+ type: 'confirm',
1014
+ name: 'includeAgents',
1015
+ message: t('prompts.scaffold.includeAgents'),
1016
+ default: true
1017
+ }
1018
+ ]);
1019
+ let selectedAgents;
1020
+ if (includeAgents) {
1021
+ const { agents } = await inquirer_1.default.prompt([
1022
+ {
1023
+ type: 'checkbox',
1024
+ name: 'agents',
1025
+ message: t('prompts.scaffold.selectAgents'),
1026
+ choices: AGENT_CHOICES,
1027
+ default: AGENT_CHOICES.map(choice => choice.value)
1028
+ }
1029
+ ]);
1030
+ selectedAgents = agents;
585
1031
  }
586
1032
  else {
587
- // Compare against last processed commit
588
- changes = gitService.getChangedFiles();
1033
+ selectedAgents = [];
589
1034
  }
590
- const totalChanges = changes.added.length + changes.modified.length + changes.deleted.length + changes.renamed.length;
591
- if (totalChanges === 0 && !cliOptions.force) {
592
- ui.updateSpinner('No changes detected since last run', 'info');
593
- ui.displaySuccess('Documentation is up to date!');
1035
+ if ((selectedDocs?.length ?? 0) === 0 && (selectedAgents?.length ?? 0) === 0) {
1036
+ ui.displayWarning(t('warnings.interactive.nothingSelected'));
594
1037
  return;
595
1038
  }
596
- ui.updateSpinner(`Found ${totalChanges} changed files`, 'success');
597
- // Step 3: Update documentation
598
- ui.displayStep(3, 4, 'Updating documentation');
599
- ui.startSpinner('Processing changed files...');
600
- const result = await incrementalGenerator.updateDocumentation(repoStructure, cliOptions.outputDir, changes, cliOptions.verbose);
601
- ui.updateSpinner(`Updated ${result.updated} files, removed ${result.removed} files`, 'success');
602
- // Step 4: Save state (only if documentation was actually updated)
603
- ui.displayStep(4, 4, 'Saving state');
604
- const currentCommit = gitService.getCurrentCommit();
605
- if (result.updated > 0 || result.removed > 0) {
606
- gitService.saveState(currentCommit);
607
- if (cliOptions.verbose) {
608
- console.log(chalk_1.default.gray(`State saved: tracking commit ${currentCommit.substring(0, 8)}`));
1039
+ const { verbose } = await inquirer_1.default.prompt([
1040
+ {
1041
+ type: 'confirm',
1042
+ name: 'verbose',
1043
+ message: t('prompts.common.verbose'),
1044
+ default: false
1045
+ }
1046
+ ]);
1047
+ const scaffoldType = determineScaffoldType(selectedDocs, selectedAgents);
1048
+ await runInit(resolvedRepo, scaffoldType, {
1049
+ output: outputDir,
1050
+ docs: selectedDocs,
1051
+ agents: selectedAgents,
1052
+ verbose
1053
+ });
1054
+ }
1055
+ function determineScaffoldType(docSelection, agentSelection) {
1056
+ const docsSelected = docSelection === undefined ? true : docSelection.length > 0;
1057
+ const agentsSelected = agentSelection === undefined ? true : agentSelection.length > 0;
1058
+ if (docsSelected && agentsSelected)
1059
+ return 'both';
1060
+ if (docsSelected)
1061
+ return 'docs';
1062
+ return 'agents';
1063
+ }
1064
+ async function runInteractiveLlmFill() {
1065
+ const { repoPath } = await inquirer_1.default.prompt([
1066
+ {
1067
+ type: 'input',
1068
+ name: 'repoPath',
1069
+ message: t('prompts.fill.repoPath'),
1070
+ default: process.cwd()
1071
+ }
1072
+ ]);
1073
+ const resolvedRepo = path.resolve(repoPath.trim() || '.');
1074
+ const defaultOutput = path.resolve(resolvedRepo, '.context');
1075
+ const defaultPrompt = path.resolve(process.cwd(), 'prompts/update_scaffold_prompt.md');
1076
+ const { outputDir, promptPath } = await inquirer_1.default.prompt([
1077
+ {
1078
+ type: 'input',
1079
+ name: 'outputDir',
1080
+ message: t('commands.fill.options.output'),
1081
+ default: defaultOutput
1082
+ },
1083
+ {
1084
+ type: 'input',
1085
+ name: 'promptPath',
1086
+ message: t('prompts.fill.promptPath'),
1087
+ default: defaultPrompt
1088
+ }
1089
+ ]);
1090
+ const { dryRun, processAll } = await inquirer_1.default.prompt([
1091
+ {
1092
+ type: 'confirm',
1093
+ name: 'dryRun',
1094
+ message: t('prompts.fill.dryRun'),
1095
+ default: true
1096
+ },
1097
+ {
1098
+ type: 'confirm',
1099
+ name: 'processAll',
1100
+ message: t('prompts.fill.processAll'),
1101
+ default: false
1102
+ }
1103
+ ]);
1104
+ const { limit } = await inquirer_1.default.prompt([
1105
+ {
1106
+ type: 'input',
1107
+ name: 'limit',
1108
+ message: t('prompts.fill.limit'),
1109
+ filter: (value) => value.trim()
1110
+ }
1111
+ ]);
1112
+ const limitValue = limit ? parseInt(limit, 10) : undefined;
1113
+ const parsedLimit = Number.isNaN(limitValue) ? undefined : limitValue;
1114
+ const { includeDocs } = await inquirer_1.default.prompt([
1115
+ {
1116
+ type: 'confirm',
1117
+ name: 'includeDocs',
1118
+ message: t('prompts.fill.includeDocs'),
1119
+ default: true
609
1120
  }
1121
+ ]);
1122
+ let selectedDocs;
1123
+ if (includeDocs) {
1124
+ const { docs } = await inquirer_1.default.prompt([
1125
+ {
1126
+ type: 'checkbox',
1127
+ name: 'docs',
1128
+ message: t('prompts.fill.selectDocs'),
1129
+ choices: DOC_CHOICES,
1130
+ default: DOC_CHOICES.map(choice => choice.value)
1131
+ }
1132
+ ]);
1133
+ selectedDocs = docs;
610
1134
  }
611
1135
  else {
612
- if (cliOptions.verbose) {
613
- console.log(chalk_1.default.gray('No documentation changes made, state not updated'));
614
- }
1136
+ selectedDocs = [];
615
1137
  }
616
- // Display summary of changed files
617
- if (result.updatedFiles.length > 0 || result.removedFiles.length > 0) {
618
- console.log(chalk_1.default.bold('\nšŸ“„ Documentation Files Changed:'));
619
- console.log(chalk_1.default.gray('─'.repeat(50)));
620
- if (result.updatedFiles.length > 0) {
621
- console.log(chalk_1.default.green('\nāœ… Updated/Created:'));
622
- result.updatedFiles.forEach(file => {
623
- console.log(` ${chalk_1.default.green('ā—')} ${file}`);
624
- });
1138
+ const { includeAgents } = await inquirer_1.default.prompt([
1139
+ {
1140
+ type: 'confirm',
1141
+ name: 'includeAgents',
1142
+ message: t('prompts.fill.includeAgents'),
1143
+ default: true
625
1144
  }
626
- if (result.removedFiles.length > 0) {
627
- console.log(chalk_1.default.red('\nšŸ—‘ļø Removed:'));
628
- result.removedFiles.forEach(file => {
629
- console.log(` ${chalk_1.default.red('ā—')} ${file}`);
630
- });
631
- }
632
- console.log(chalk_1.default.gray('─'.repeat(50)));
1145
+ ]);
1146
+ let selectedAgents;
1147
+ if (includeAgents) {
1148
+ const { agents } = await inquirer_1.default.prompt([
1149
+ {
1150
+ type: 'checkbox',
1151
+ name: 'agents',
1152
+ message: t('prompts.fill.selectAgents'),
1153
+ choices: AGENT_CHOICES,
1154
+ default: AGENT_CHOICES.map(choice => choice.value)
1155
+ }
1156
+ ]);
1157
+ selectedAgents = agents;
633
1158
  }
634
- // Get usage statistics
635
- const usageStats = llmClient.getUsageStats();
636
- ui.displayGenerationSummary(result.updated, 0, usageStats, true);
637
- ui.displaySuccess(`Documentation updated! Processed ${result.updated} files.`);
638
- }
639
- async function runPreview(repoPath, options) {
640
- const resolvedPath = path.resolve(repoPath);
641
- // Display welcome
642
- ui.displayWelcome('0.1.0');
643
- ui.startSpinner('Initializing analysis...');
644
- // Initialize services
645
- const fileMapper = new fileMapper_1.FileMapper(options.exclude || []);
646
- const gitService = new gitService_1.GitService(resolvedPath);
647
- const changeAnalyzer = new changeAnalyzer_1.ChangeAnalyzer(gitService, fileMapper);
648
- // Check if it's a git repository
649
- if (!gitService.isGitRepository()) {
650
- ui.updateSpinner('Not a git repository', 'fail');
651
- ui.displayError('The specified path is not a git repository. Preview requires git tracking.');
652
- process.exit(1);
1159
+ else {
1160
+ selectedAgents = [];
653
1161
  }
654
- // Check if context has been initialized
655
- const outputDir = path.resolve(options.output || './.context');
656
- if (!gitService.hasContextBeenInitialized(outputDir)) {
657
- ui.updateSpinner('Context not initialized', 'fail');
658
- ui.displayError('No documentation context found. You should run analyze and init before previewing changes to update.');
659
- console.log(chalk_1.default.bold('\nšŸ’” Getting Started:'));
660
- console.log(chalk_1.default.gray('─'.repeat(50)));
661
- console.log(`${chalk_1.default.blue('1. Analyze:')} ai-context analyze ${repoPath}`);
662
- console.log(`${chalk_1.default.blue('2. Initialize:')} ai-context init ${repoPath}`);
663
- console.log(`${chalk_1.default.blue('3. Preview:')} ai-context preview ${repoPath}`);
664
- console.log(chalk_1.default.gray('─'.repeat(50)));
665
- console.log(chalk_1.default.gray('The analyze command shows token estimates and costs.'));
666
- console.log(chalk_1.default.gray('The init command creates the initial documentation.'));
667
- console.log(chalk_1.default.gray('The preview command shows what would change in updates.'));
668
- process.exit(1);
1162
+ if ((selectedDocs?.length ?? 0) === 0 && (selectedAgents?.length ?? 0) === 0) {
1163
+ ui.displayWarning(t('warnings.interactive.nothingSelected'));
1164
+ return;
669
1165
  }
670
- ui.updateSpinner('Mapping repository structure...');
671
- // Map repository structure
672
- const repoStructure = await fileMapper.mapRepository(resolvedPath, options.include);
673
- ui.updateSpinner('Analyzing changes...');
674
- // Detect changes
675
- let changes;
676
- if (options.staged) {
677
- // For pre-commit hooks - analyze only staged files
678
- changes = gitService.getStagedChanges();
1166
+ const { specifyModel } = await inquirer_1.default.prompt([
1167
+ {
1168
+ type: 'confirm',
1169
+ name: 'specifyModel',
1170
+ message: t('prompts.fill.overrideModel'),
1171
+ default: false
1172
+ }
1173
+ ]);
1174
+ let provider;
1175
+ let model;
1176
+ if (specifyModel) {
1177
+ const providerAnswer = await inquirer_1.default.prompt([
1178
+ {
1179
+ type: 'list',
1180
+ name: 'provider',
1181
+ message: t('prompts.fill.provider'),
1182
+ choices: ['openrouter', 'openai', 'anthropic', 'gemini', 'grok']
1183
+ }
1184
+ ]);
1185
+ provider = providerAnswer.provider;
1186
+ const modelAnswer = await inquirer_1.default.prompt([
1187
+ {
1188
+ type: 'input',
1189
+ name: 'model',
1190
+ message: t('prompts.fill.model'),
1191
+ default: DEFAULT_MODEL
1192
+ }
1193
+ ]);
1194
+ model = modelAnswer.model.trim();
679
1195
  }
680
- else if (options.since) {
681
- // Compare against specific commit
682
- changes = gitService.getChangedFiles(options.since);
1196
+ const { provideApiKey } = await inquirer_1.default.prompt([
1197
+ {
1198
+ type: 'confirm',
1199
+ name: 'provideApiKey',
1200
+ message: t('prompts.fill.provideApiKey'),
1201
+ default: false
1202
+ }
1203
+ ]);
1204
+ let apiKey;
1205
+ if (provideApiKey) {
1206
+ const apiKeyAnswer = await inquirer_1.default.prompt([
1207
+ {
1208
+ type: 'password',
1209
+ name: 'apiKey',
1210
+ message: t('prompts.fill.apiKey'),
1211
+ mask: '*'
1212
+ }
1213
+ ]);
1214
+ apiKey = apiKeyAnswer.apiKey.trim();
683
1215
  }
684
- else {
685
- // Compare against last processed commit
686
- changes = gitService.getChangedFiles();
687
- }
688
- // Analyze the changes
689
- const analysis = await changeAnalyzer.analyzeChanges(repoStructure, changes);
690
- ui.stopSpinner();
691
- // Display the analysis
692
- changeAnalyzer.displayAnalysis(analysis, options.verbose);
693
- // Add token estimation if there are changes to process
694
- if (analysis.affectedModules.length > 0) {
695
- ui.startSpinner('Estimating token usage and costs for affected changes...');
696
- // Create a subset structure with only affected files
697
- const affectedFiles = new Set();
698
- // Add all files from affected modules
699
- for (const module of analysis.affectedModules) {
700
- module.affectedFiles.forEach(filePath => {
701
- // Convert to absolute path to match repoStructure.files format
702
- const absolutePath = path.resolve(resolvedPath, filePath);
703
- if (fileMapper.isTextFile(absolutePath)) {
704
- affectedFiles.add(absolutePath);
1216
+ const { verbose } = await inquirer_1.default.prompt([
1217
+ {
1218
+ type: 'confirm',
1219
+ name: 'verbose',
1220
+ message: t('prompts.common.verbose'),
1221
+ default: false
1222
+ }
1223
+ ]);
1224
+ await runLlmFill(resolvedRepo, {
1225
+ output: outputDir,
1226
+ prompt: promptPath,
1227
+ docs: selectedDocs,
1228
+ agents: selectedAgents,
1229
+ dryRun,
1230
+ all: processAll,
1231
+ limit: parsedLimit,
1232
+ model,
1233
+ provider,
1234
+ apiKey,
1235
+ verbose
1236
+ });
1237
+ }
1238
+ async function runInteractivePlan() {
1239
+ const { planName } = await inquirer_1.default.prompt([
1240
+ {
1241
+ type: 'input',
1242
+ name: 'planName',
1243
+ message: t('prompts.plan.name'),
1244
+ default: 'new-plan'
1245
+ }
1246
+ ]);
1247
+ const defaultOutput = path.resolve(process.cwd(), '.context');
1248
+ const { mode } = await inquirer_1.default.prompt([
1249
+ {
1250
+ type: 'list',
1251
+ name: 'mode',
1252
+ message: t('prompts.plan.mode'),
1253
+ choices: [
1254
+ { name: t('prompts.plan.modeScaffold'), value: 'scaffold' },
1255
+ { name: t('prompts.plan.modeFill'), value: 'fill' }
1256
+ ],
1257
+ default: 'scaffold'
1258
+ }
1259
+ ]);
1260
+ const { outputDir } = await inquirer_1.default.prompt([
1261
+ {
1262
+ type: 'input',
1263
+ name: 'outputDir',
1264
+ message: t('commands.plan.options.output'),
1265
+ default: defaultOutput
1266
+ }
1267
+ ]);
1268
+ if (mode === 'fill') {
1269
+ const { summary } = await inquirer_1.default.prompt([
1270
+ {
1271
+ type: 'input',
1272
+ name: 'summary',
1273
+ message: t('prompts.plan.summary'),
1274
+ filter: (value) => value.trim()
1275
+ }
1276
+ ]);
1277
+ const { includeAgents } = await inquirer_1.default.prompt([
1278
+ {
1279
+ type: 'confirm',
1280
+ name: 'includeAgents',
1281
+ message: t('prompts.plan.includeAgents'),
1282
+ default: true
1283
+ }
1284
+ ]);
1285
+ let selectedAgents = [];
1286
+ if (includeAgents) {
1287
+ const { agents } = await inquirer_1.default.prompt([
1288
+ {
1289
+ type: 'checkbox',
1290
+ name: 'agents',
1291
+ message: t('prompts.plan.selectAgents'),
1292
+ choices: AGENT_CHOICES,
1293
+ default: AGENT_CHOICES.map(choice => choice.value)
705
1294
  }
706
- });
1295
+ ]);
1296
+ selectedAgents = agents;
707
1297
  }
708
- // Also add directly changed files
709
- [...changes.added, ...changes.modified].forEach(filePath => {
710
- const fullPath = path.resolve(resolvedPath, filePath);
711
- if (fileMapper.isTextFile(fullPath)) {
712
- affectedFiles.add(fullPath);
1298
+ const { includeDocs } = await inquirer_1.default.prompt([
1299
+ {
1300
+ type: 'confirm',
1301
+ name: 'includeDocs',
1302
+ message: t('prompts.plan.includeDocs'),
1303
+ default: true
713
1304
  }
714
- });
715
- // Debug: Log the affected files and available files for comparison
716
- if (options.verbose) {
717
- console.log(chalk_1.default.gray(`\nDebug: Found ${affectedFiles.size} affected files:`));
718
- for (const file of Array.from(affectedFiles).slice(0, 5)) {
719
- const relativePath = path.relative(resolvedPath, file);
720
- console.log(chalk_1.default.gray(` - ${relativePath}`));
1305
+ ]);
1306
+ let selectedDocs = [];
1307
+ if (includeDocs) {
1308
+ const { docs } = await inquirer_1.default.prompt([
1309
+ {
1310
+ type: 'checkbox',
1311
+ name: 'docs',
1312
+ message: t('prompts.plan.selectDocs'),
1313
+ choices: DOC_CHOICES,
1314
+ default: DOC_CHOICES.map(choice => choice.value)
1315
+ }
1316
+ ]);
1317
+ selectedDocs = docs;
1318
+ }
1319
+ const agentSelection = parseAgentSelection(selectedAgents);
1320
+ const docSelection = parseDocSelection(selectedDocs);
1321
+ const { repoPath } = await inquirer_1.default.prompt([
1322
+ {
1323
+ type: 'input',
1324
+ name: 'repoPath',
1325
+ message: t('prompts.plan.repoPath'),
1326
+ default: process.cwd()
721
1327
  }
722
- if (affectedFiles.size > 5) {
723
- console.log(chalk_1.default.gray(` ... and ${affectedFiles.size - 5} more`));
1328
+ ]);
1329
+ const { dryRun } = await inquirer_1.default.prompt([
1330
+ {
1331
+ type: 'confirm',
1332
+ name: 'dryRun',
1333
+ message: t('prompts.plan.dryRun'),
1334
+ default: true
724
1335
  }
1336
+ ]);
1337
+ try {
1338
+ const resolvedOutput = path.resolve(outputDir.trim() || defaultOutput);
1339
+ await scaffoldPlanIfNeeded(planName, resolvedOutput, {
1340
+ summary: summary || undefined,
1341
+ agentSelection,
1342
+ docSelection
1343
+ });
1344
+ await runPlanFill(planName, {
1345
+ output: resolvedOutput,
1346
+ repo: repoPath,
1347
+ dryRun
1348
+ });
725
1349
  }
726
- // Create a reduced repo structure for estimation
727
- const matchedFiles = repoStructure.files.filter(file => affectedFiles.has(file.path));
728
- const affectedRepoStructure = {
729
- ...repoStructure,
730
- files: matchedFiles,
731
- totalFiles: matchedFiles.length
732
- };
733
- if (options.verbose) {
734
- console.log(chalk_1.default.gray(`Debug: Matched ${matchedFiles.length} files from repo structure`));
735
- }
736
- // Create pricing if provided (optional for preview command)
737
- let pricing = undefined;
738
- if (options.inputPrice !== undefined && options.outputPrice !== undefined) {
739
- pricing = {
740
- input: options.inputPrice,
741
- output: options.outputPrice
742
- };
743
- }
744
- else if (options.inputPrice !== undefined || options.outputPrice !== undefined) {
745
- ui.displayError('Pricing requires both options: --input-price and --output-price');
746
- process.exit(1);
1350
+ catch (error) {
1351
+ ui.displayError(t('errors.plan.fillFailed'), error);
1352
+ }
1353
+ return;
1354
+ }
1355
+ const { summary } = await inquirer_1.default.prompt([
1356
+ {
1357
+ type: 'input',
1358
+ name: 'summary',
1359
+ message: t('prompts.plan.summary'),
1360
+ filter: (value) => value.trim()
1361
+ }
1362
+ ]);
1363
+ const { includeAgents } = await inquirer_1.default.prompt([
1364
+ {
1365
+ type: 'confirm',
1366
+ name: 'includeAgents',
1367
+ message: t('prompts.plan.includeAgents'),
1368
+ default: true
1369
+ }
1370
+ ]);
1371
+ let selectedAgents = null;
1372
+ if (includeAgents) {
1373
+ const { agents } = await inquirer_1.default.prompt([
1374
+ {
1375
+ type: 'checkbox',
1376
+ name: 'agents',
1377
+ message: t('prompts.plan.selectAgents'),
1378
+ choices: AGENT_CHOICES,
1379
+ default: AGENT_CHOICES.map(choice => choice.value)
1380
+ }
1381
+ ]);
1382
+ selectedAgents = agents.length > 0 ? agents : null;
1383
+ }
1384
+ const { includeDocs } = await inquirer_1.default.prompt([
1385
+ {
1386
+ type: 'confirm',
1387
+ name: 'includeDocs',
1388
+ message: t('prompts.plan.includeDocs'),
1389
+ default: true
747
1390
  }
748
- const tokenEstimator = new tokenEstimator_1.TokenEstimator(fileMapper, pricing);
749
- const tokenEstimate = await tokenEstimator.estimateTokensForFullGeneration(affectedRepoStructure);
1391
+ ]);
1392
+ let selectedDocs = null;
1393
+ if (includeDocs) {
1394
+ const { docs } = await inquirer_1.default.prompt([
1395
+ {
1396
+ type: 'checkbox',
1397
+ name: 'docs',
1398
+ message: t('prompts.plan.selectDocs'),
1399
+ choices: DOC_CHOICES,
1400
+ default: DOC_CHOICES.map(choice => choice.value)
1401
+ }
1402
+ ]);
1403
+ selectedDocs = docs.length > 0 ? docs : null;
1404
+ }
1405
+ const generator = new planGenerator_1.PlanGenerator();
1406
+ ui.startSpinner(t('spinner.plan.creating'));
1407
+ try {
1408
+ const result = await generator.generatePlan({
1409
+ planName,
1410
+ outputDir: path.resolve(outputDir.trim() || defaultOutput),
1411
+ summary: summary || undefined,
1412
+ selectedAgentTypes: selectedAgents,
1413
+ selectedDocKeys: selectedDocs,
1414
+ verbose: false
1415
+ });
1416
+ ui.updateSpinner(t('spinner.plan.created'), 'success');
1417
+ ui.displaySuccess(t('success.plan.createdAt', { path: chalk_1.default.cyan(result.relativePath) }));
1418
+ }
1419
+ catch (error) {
1420
+ ui.updateSpinner(t('spinner.plan.creationFailed'), 'fail');
1421
+ ui.displayError(t('errors.plan.creationFailed'), error);
1422
+ }
1423
+ finally {
750
1424
  ui.stopSpinner();
751
- // Display the token estimate
752
- console.log(chalk_1.default.bold('\nšŸ”® Token & Cost Estimate for Preview Changes:'));
753
- console.log(chalk_1.default.gray('═'.repeat(60)));
754
- console.log(tokenEstimator.formatTokenEstimate(tokenEstimate));
755
1425
  }
756
- // Summary
757
- if (analysis.affectedModules.length > 0) {
758
- console.log(chalk_1.default.green('\nāœ… Ready to proceed with update command'));
759
- console.log(chalk_1.default.gray('Run the same command with "update" instead of "preview" to apply changes'));
1426
+ }
1427
+ function getAgentFilesByTypes(types) {
1428
+ if (!types || types.length === 0) {
1429
+ return undefined;
760
1430
  }
761
- else {
762
- console.log(chalk_1.default.yellow('\nšŸ“„ No updates needed - documentation is up to date'));
1431
+ const allowed = new Set(agentTypes_1.AGENT_TYPES);
1432
+ const files = types
1433
+ .filter(type => allowed.has(type))
1434
+ .map(type => `${type}.md`);
1435
+ return files.length ? new Set(files) : undefined;
1436
+ }
1437
+ function filterOutLocaleArgs(args) {
1438
+ const filtered = [];
1439
+ for (let index = 0; index < args.length; index += 1) {
1440
+ const current = args[index];
1441
+ if (current === '--lang' || current === '--language' || current === '-l') {
1442
+ index += 1;
1443
+ continue;
1444
+ }
1445
+ if (current.startsWith('--lang=') || current.startsWith('--language=')) {
1446
+ continue;
1447
+ }
1448
+ filtered.push(current);
763
1449
  }
1450
+ return filtered;
764
1451
  }
765
- // Check if no arguments were provided (interactive mode)
766
- if (process.argv.length === 2) {
767
- const interactive = new interactiveMode_1.InteractiveMode();
768
- interactive.start().catch((error) => {
769
- ui.displayError('Interactive mode failed', error);
1452
+ async function main() {
1453
+ const userArgs = process.argv.slice(2);
1454
+ const meaningfulArgs = filterOutLocaleArgs(userArgs);
1455
+ if (meaningfulArgs.length === 0) {
1456
+ await runInteractive();
1457
+ return;
1458
+ }
1459
+ await program.parseAsync(process.argv);
1460
+ }
1461
+ if (require.main === module) {
1462
+ main().catch(error => {
1463
+ ui.displayError(t('errors.cli.executionFailed'), error);
770
1464
  process.exit(1);
771
1465
  });
772
1466
  }
773
- else {
774
- program.parse();
775
- }
776
1467
  //# sourceMappingURL=index.js.map