@ai-coders/context 0.1.0 → 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 (234) hide show
  1. package/README.md +113 -580
  2. package/dist/generators/agents/agentConfig.d.ts +4 -0
  3. package/dist/generators/agents/agentConfig.d.ts.map +1 -0
  4. package/dist/generators/agents/agentConfig.js +138 -0
  5. package/dist/generators/agents/agentConfig.js.map +1 -0
  6. package/dist/generators/agents/agentGenerator.d.ts +10 -0
  7. package/dist/generators/agents/agentGenerator.d.ts.map +1 -0
  8. package/dist/generators/agents/agentGenerator.js +137 -0
  9. package/dist/generators/agents/agentGenerator.js.map +1 -0
  10. package/dist/generators/agents/agentTypes.d.ts +4 -0
  11. package/dist/generators/agents/agentTypes.d.ts.map +1 -0
  12. package/dist/generators/agents/agentTypes.js +22 -0
  13. package/dist/generators/agents/agentTypes.js.map +1 -0
  14. package/dist/generators/agents/contextUtils.d.ts +8 -0
  15. package/dist/generators/agents/contextUtils.d.ts.map +1 -0
  16. package/dist/generators/agents/contextUtils.js +15 -0
  17. package/dist/generators/agents/contextUtils.js.map +1 -0
  18. package/dist/generators/agents/index.d.ts +4 -0
  19. package/dist/generators/agents/index.d.ts.map +1 -0
  20. package/dist/generators/agents/index.js +12 -0
  21. package/dist/generators/agents/index.js.map +1 -0
  22. package/dist/generators/agents/promptFormatter.d.ts +9 -0
  23. package/dist/generators/agents/promptFormatter.d.ts.map +1 -0
  24. package/dist/generators/agents/promptFormatter.js +84 -0
  25. package/dist/generators/agents/promptFormatter.js.map +1 -0
  26. package/dist/generators/agents/templates/index.d.ts +4 -0
  27. package/dist/generators/agents/templates/index.d.ts.map +1 -0
  28. package/dist/generators/agents/templates/index.js +8 -0
  29. package/dist/generators/agents/templates/index.js.map +1 -0
  30. package/dist/generators/agents/templates/indexTemplate.d.ts +3 -0
  31. package/dist/generators/agents/templates/indexTemplate.d.ts.map +1 -0
  32. package/dist/generators/agents/templates/indexTemplate.js +35 -0
  33. package/dist/generators/agents/templates/indexTemplate.js.map +1 -0
  34. package/dist/generators/agents/templates/playbookTemplate.d.ts +4 -0
  35. package/dist/generators/agents/templates/playbookTemplate.d.ts.map +1 -0
  36. package/dist/generators/agents/templates/playbookTemplate.js +63 -0
  37. package/dist/generators/agents/templates/playbookTemplate.js.map +1 -0
  38. package/dist/generators/agents/templates/types.d.ts +14 -0
  39. package/dist/generators/agents/templates/types.d.ts.map +1 -0
  40. package/dist/generators/agents/templates/types.js +3 -0
  41. package/dist/generators/agents/templates/types.js.map +1 -0
  42. package/dist/generators/analyzers/codebaseAnalyzer.d.ts +45 -0
  43. package/dist/generators/analyzers/codebaseAnalyzer.d.ts.map +1 -0
  44. package/dist/generators/analyzers/codebaseAnalyzer.js +293 -0
  45. package/dist/generators/analyzers/codebaseAnalyzer.js.map +1 -0
  46. package/dist/generators/analyzers/index.d.ts +3 -0
  47. package/dist/generators/analyzers/index.d.ts.map +1 -0
  48. package/dist/generators/analyzers/index.js +6 -0
  49. package/dist/generators/analyzers/index.js.map +1 -0
  50. package/dist/generators/documentation/documentationGenerator.d.ts +13 -0
  51. package/dist/generators/documentation/documentationGenerator.d.ts.map +1 -0
  52. package/dist/generators/documentation/documentationGenerator.js +115 -0
  53. package/dist/generators/documentation/documentationGenerator.js.map +1 -0
  54. package/dist/generators/documentation/documentationTemplates.d.ts +21 -0
  55. package/dist/generators/documentation/documentationTemplates.d.ts.map +1 -0
  56. package/dist/generators/documentation/documentationTemplates.js +359 -0
  57. package/dist/generators/documentation/documentationTemplates.js.map +1 -0
  58. package/dist/generators/documentation/documentationTypes.d.ts +11 -0
  59. package/dist/generators/documentation/documentationTypes.d.ts.map +1 -0
  60. package/dist/generators/documentation/documentationTypes.js +22 -0
  61. package/dist/generators/documentation/documentationTypes.js.map +1 -0
  62. package/dist/generators/documentation/documentationUtils.d.ts +7 -0
  63. package/dist/generators/documentation/documentationUtils.d.ts.map +1 -0
  64. package/dist/generators/documentation/documentationUtils.js +28 -0
  65. package/dist/generators/documentation/documentationUtils.js.map +1 -0
  66. package/dist/generators/documentation/guideRegistry.d.ts +6 -0
  67. package/dist/generators/documentation/guideRegistry.d.ts.map +1 -0
  68. package/dist/generators/documentation/guideRegistry.js +82 -0
  69. package/dist/generators/documentation/guideRegistry.js.map +1 -0
  70. package/dist/generators/{incrementalDocumentationGenerator.d.ts → documentation/incrementalDocumentationGenerator.d.ts} +4 -4
  71. package/dist/generators/documentation/incrementalDocumentationGenerator.d.ts.map +1 -0
  72. package/dist/generators/documentation/incrementalDocumentationGenerator.js.map +1 -0
  73. package/dist/generators/documentation/index.d.ts +2 -0
  74. package/dist/generators/documentation/index.d.ts.map +1 -0
  75. package/dist/generators/documentation/index.js +6 -0
  76. package/dist/generators/documentation/index.js.map +1 -0
  77. package/dist/generators/documentation/templates/architectureTemplate.d.ts +3 -0
  78. package/dist/generators/documentation/templates/architectureTemplate.d.ts.map +1 -0
  79. package/dist/generators/documentation/templates/architectureTemplate.js +66 -0
  80. package/dist/generators/documentation/templates/architectureTemplate.js.map +1 -0
  81. package/dist/generators/documentation/templates/common.d.ts +7 -0
  82. package/dist/generators/documentation/templates/common.d.ts.map +1 -0
  83. package/dist/generators/documentation/templates/common.js +58 -0
  84. package/dist/generators/documentation/templates/common.js.map +1 -0
  85. package/dist/generators/documentation/templates/dataFlowTemplate.d.ts +3 -0
  86. package/dist/generators/documentation/templates/dataFlowTemplate.d.ts.map +1 -0
  87. package/dist/generators/documentation/templates/dataFlowTemplate.js +55 -0
  88. package/dist/generators/documentation/templates/dataFlowTemplate.js.map +1 -0
  89. package/dist/generators/documentation/templates/developmentWorkflowTemplate.d.ts +2 -0
  90. package/dist/generators/documentation/templates/developmentWorkflowTemplate.d.ts.map +1 -0
  91. package/dist/generators/documentation/templates/developmentWorkflowTemplate.js +59 -0
  92. package/dist/generators/documentation/templates/developmentWorkflowTemplate.js.map +1 -0
  93. package/dist/generators/documentation/templates/frontMatter.d.ts +11 -0
  94. package/dist/generators/documentation/templates/frontMatter.d.ts.map +1 -0
  95. package/dist/generators/documentation/templates/frontMatter.js +29 -0
  96. package/dist/generators/documentation/templates/frontMatter.js.map +1 -0
  97. package/dist/generators/documentation/templates/glossaryTemplate.d.ts +3 -0
  98. package/dist/generators/documentation/templates/glossaryTemplate.d.ts.map +1 -0
  99. package/dist/generators/documentation/templates/glossaryTemplate.js +55 -0
  100. package/dist/generators/documentation/templates/glossaryTemplate.js.map +1 -0
  101. package/dist/generators/documentation/templates/index.d.ts +11 -0
  102. package/dist/generators/documentation/templates/index.d.ts.map +1 -0
  103. package/dist/generators/documentation/templates/index.js +22 -0
  104. package/dist/generators/documentation/templates/index.js.map +1 -0
  105. package/dist/generators/documentation/templates/indexTemplate.d.ts +3 -0
  106. package/dist/generators/documentation/templates/indexTemplate.d.ts.map +1 -0
  107. package/dist/generators/documentation/templates/indexTemplate.js +56 -0
  108. package/dist/generators/documentation/templates/indexTemplate.js.map +1 -0
  109. package/dist/generators/documentation/templates/projectOverviewTemplate.d.ts +3 -0
  110. package/dist/generators/documentation/templates/projectOverviewTemplate.d.ts.map +1 -0
  111. package/dist/generators/documentation/templates/projectOverviewTemplate.js +68 -0
  112. package/dist/generators/documentation/templates/projectOverviewTemplate.js.map +1 -0
  113. package/dist/generators/documentation/templates/securityTemplate.d.ts +2 -0
  114. package/dist/generators/documentation/templates/securityTemplate.d.ts.map +1 -0
  115. package/dist/generators/documentation/templates/securityTemplate.js +53 -0
  116. package/dist/generators/documentation/templates/securityTemplate.js.map +1 -0
  117. package/dist/generators/documentation/templates/testingTemplate.d.ts +2 -0
  118. package/dist/generators/documentation/templates/testingTemplate.d.ts.map +1 -0
  119. package/dist/generators/documentation/templates/testingTemplate.js +59 -0
  120. package/dist/generators/documentation/templates/testingTemplate.js.map +1 -0
  121. package/dist/generators/documentation/templates/toolingTemplate.d.ts +2 -0
  122. package/dist/generators/documentation/templates/toolingTemplate.d.ts.map +1 -0
  123. package/dist/generators/documentation/templates/toolingTemplate.js +56 -0
  124. package/dist/generators/documentation/templates/toolingTemplate.js.map +1 -0
  125. package/dist/generators/documentation/templates/types.d.ts +23 -0
  126. package/dist/generators/documentation/templates/types.d.ts.map +1 -0
  127. package/dist/generators/documentation/templates/types.js +3 -0
  128. package/dist/generators/documentation/templates/types.js.map +1 -0
  129. package/dist/generators/documentation/templates.d.ts +31 -0
  130. package/dist/generators/documentation/templates.d.ts.map +1 -0
  131. package/dist/generators/documentation/templates.js +566 -0
  132. package/dist/generators/documentation/templates.js.map +1 -0
  133. package/dist/generators/guidelines/agentIntegration.d.ts +43 -0
  134. package/dist/generators/guidelines/agentIntegration.d.ts.map +1 -0
  135. package/dist/generators/guidelines/agentIntegration.js +157 -0
  136. package/dist/generators/guidelines/agentIntegration.js.map +1 -0
  137. package/dist/generators/guidelines/guidelineTypes.d.ts +40 -0
  138. package/dist/generators/guidelines/guidelineTypes.d.ts.map +1 -0
  139. package/dist/generators/guidelines/guidelineTypes.js +144 -0
  140. package/dist/generators/guidelines/guidelineTypes.js.map +1 -0
  141. package/dist/generators/guidelines/guidelinesAnalyzer.d.ts +30 -0
  142. package/dist/generators/guidelines/guidelinesAnalyzer.d.ts.map +1 -0
  143. package/dist/generators/guidelines/guidelinesAnalyzer.js +263 -0
  144. package/dist/generators/guidelines/guidelinesAnalyzer.js.map +1 -0
  145. package/dist/generators/guidelines/guidelinesGenerator.d.ts +30 -0
  146. package/dist/generators/guidelines/guidelinesGenerator.d.ts.map +1 -0
  147. package/dist/generators/guidelines/guidelinesGenerator.js +249 -0
  148. package/dist/generators/guidelines/guidelinesGenerator.js.map +1 -0
  149. package/dist/generators/guidelines/guidelinesTemplates.d.ts +23 -0
  150. package/dist/generators/guidelines/guidelinesTemplates.d.ts.map +1 -0
  151. package/dist/generators/guidelines/guidelinesTemplates.js +304 -0
  152. package/dist/generators/guidelines/guidelinesTemplates.js.map +1 -0
  153. package/dist/generators/guidelines/index.d.ts +6 -0
  154. package/dist/generators/guidelines/index.d.ts.map +1 -0
  155. package/dist/generators/guidelines/index.js +16 -0
  156. package/dist/generators/guidelines/index.js.map +1 -0
  157. package/dist/generators/moduleGrouper.d.ts +14 -0
  158. package/dist/generators/moduleGrouper.d.ts.map +1 -0
  159. package/dist/generators/moduleGrouper.js +82 -0
  160. package/dist/generators/moduleGrouper.js.map +1 -0
  161. package/dist/generators/plans/index.d.ts +2 -0
  162. package/dist/generators/plans/index.d.ts.map +1 -0
  163. package/dist/generators/plans/index.js +6 -0
  164. package/dist/generators/plans/index.js.map +1 -0
  165. package/dist/generators/plans/planGenerator.d.ts +22 -0
  166. package/dist/generators/plans/planGenerator.d.ts.map +1 -0
  167. package/dist/generators/plans/planGenerator.js +109 -0
  168. package/dist/generators/plans/planGenerator.js.map +1 -0
  169. package/dist/generators/plans/templates/indexTemplate.d.ts +3 -0
  170. package/dist/generators/plans/templates/indexTemplate.d.ts.map +1 -0
  171. package/dist/generators/plans/templates/indexTemplate.js +36 -0
  172. package/dist/generators/plans/templates/indexTemplate.js.map +1 -0
  173. package/dist/generators/plans/templates/planTemplate.d.ts +3 -0
  174. package/dist/generators/plans/templates/planTemplate.d.ts.map +1 -0
  175. package/dist/generators/plans/templates/planTemplate.js +83 -0
  176. package/dist/generators/plans/templates/planTemplate.js.map +1 -0
  177. package/dist/generators/plans/templates/types.d.ts +19 -0
  178. package/dist/generators/plans/templates/types.d.ts.map +1 -0
  179. package/dist/generators/plans/templates/types.js +3 -0
  180. package/dist/generators/plans/templates/types.js.map +1 -0
  181. package/dist/generators/projectAnalyzer.d.ts +14 -0
  182. package/dist/generators/projectAnalyzer.d.ts.map +1 -0
  183. package/dist/generators/projectAnalyzer.js +217 -0
  184. package/dist/generators/projectAnalyzer.js.map +1 -0
  185. package/dist/generators/shared/contextGenerator.d.ts +7 -0
  186. package/dist/generators/shared/contextGenerator.d.ts.map +1 -0
  187. package/dist/generators/shared/contextGenerator.js +13 -0
  188. package/dist/generators/shared/contextGenerator.js.map +1 -0
  189. package/dist/generators/shared/directoryTemplateHelpers.d.ts +2 -0
  190. package/dist/generators/shared/directoryTemplateHelpers.d.ts.map +1 -0
  191. package/dist/generators/shared/directoryTemplateHelpers.js +12 -0
  192. package/dist/generators/shared/directoryTemplateHelpers.js.map +1 -0
  193. package/dist/generators/shared/generatorUtils.d.ts +16 -0
  194. package/dist/generators/shared/generatorUtils.d.ts.map +1 -0
  195. package/dist/generators/shared/generatorUtils.js +119 -0
  196. package/dist/generators/shared/generatorUtils.js.map +1 -0
  197. package/dist/generators/shared/index.d.ts +4 -0
  198. package/dist/generators/shared/index.d.ts.map +1 -0
  199. package/dist/generators/shared/index.js +10 -0
  200. package/dist/generators/shared/index.js.map +1 -0
  201. package/dist/index.d.ts +6 -3
  202. package/dist/index.d.ts.map +1 -1
  203. package/dist/index.js +1327 -419
  204. package/dist/index.js.map +1 -1
  205. package/dist/utils/cliUI.d.ts +6 -4
  206. package/dist/utils/cliUI.d.ts.map +1 -1
  207. package/dist/utils/cliUI.js +71 -56
  208. package/dist/utils/cliUI.js.map +1 -1
  209. package/dist/utils/i18n.d.ts +181 -0
  210. package/dist/utils/i18n.d.ts.map +1 -0
  211. package/dist/utils/i18n.js +401 -0
  212. package/dist/utils/i18n.js.map +1 -0
  213. package/dist/utils/interactiveMode.d.ts +6 -1
  214. package/dist/utils/interactiveMode.d.ts.map +1 -1
  215. package/dist/utils/interactiveMode.js +375 -70
  216. package/dist/utils/interactiveMode.js.map +1 -1
  217. package/dist/utils/tokenEstimator.d.ts +10 -9
  218. package/dist/utils/tokenEstimator.d.ts.map +1 -1
  219. package/dist/utils/tokenEstimator.js +45 -109
  220. package/dist/utils/tokenEstimator.js.map +1 -1
  221. package/package.json +26 -23
  222. package/prompts/update_plan_prompt.md +42 -0
  223. package/prompts/update_scaffold_prompt.md +48 -0
  224. package/dist/generators/agentGenerator.d.ts +0 -23
  225. package/dist/generators/agentGenerator.d.ts.map +0 -1
  226. package/dist/generators/agentGenerator.js +0 -357
  227. package/dist/generators/agentGenerator.js.map +0 -1
  228. package/dist/generators/documentationGenerator.d.ts +0 -40
  229. package/dist/generators/documentationGenerator.d.ts.map +0 -1
  230. package/dist/generators/documentationGenerator.js +0 -786
  231. package/dist/generators/documentationGenerator.js.map +0 -1
  232. package/dist/generators/incrementalDocumentationGenerator.d.ts.map +0 -1
  233. package/dist/generators/incrementalDocumentationGenerator.js.map +0 -1
  234. /package/dist/generators/{incrementalDocumentationGenerator.js → documentation/incrementalDocumentationGenerator.js} +0 -0
package/dist/index.js CHANGED
@@ -41,519 +41,1427 @@ exports.runGenerate = runGenerate;
41
41
  exports.runAnalyze = runAnalyze;
42
42
  exports.runUpdate = runUpdate;
43
43
  exports.runPreview = runPreview;
44
+ exports.runGuidelines = runGuidelines;
45
+ exports.runInit = runInit;
44
46
  const commander_1 = require("commander");
45
47
  const path = __importStar(require("path"));
46
- const chalk_1 = __importDefault(require("chalk"));
48
+ const fs = __importStar(require("fs-extra"));
49
+ const glob_1 = require("glob");
47
50
  const dotenv = __importStar(require("dotenv"));
48
- // Load environment variables from .env file
49
- dotenv.config();
51
+ const chalk_1 = __importDefault(require("chalk"));
52
+ const inquirer_1 = __importDefault(require("inquirer"));
50
53
  const fileMapper_1 = require("./utils/fileMapper");
51
- const documentationGenerator_1 = require("./generators/documentationGenerator");
52
- const agentGenerator_1 = require("./generators/agentGenerator");
53
- const incrementalDocumentationGenerator_1 = require("./generators/incrementalDocumentationGenerator");
54
+ const documentationGenerator_1 = require("./generators/documentation/documentationGenerator");
55
+ const agentGenerator_1 = require("./generators/agents/agentGenerator");
56
+ const planGenerator_1 = require("./generators/plans/planGenerator");
57
+ const shared_1 = require("./generators/shared");
54
58
  const cliUI_1 = require("./utils/cliUI");
59
+ const i18n_1 = require("./utils/i18n");
55
60
  const llmClientFactory_1 = require("./services/llmClientFactory");
56
- const gitService_1 = require("./utils/gitService");
57
- const changeAnalyzer_1 = require("./services/changeAnalyzer");
58
- const tokenEstimator_1 = require("./utils/tokenEstimator");
59
- 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
+ };
60
72
  const program = new commander_1.Command();
61
- 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
+ }));
62
84
  program
63
85
  .name('ai-context')
64
- .description('AI-powered CLI for generating codebase documentation and agent prompts')
65
- .version('0.1.0');
86
+ .description(t('cli.description'))
87
+ .version(VERSION);
88
+ program.option('-l, --lang <locale>', t('global.options.lang'), initialLocale);
66
89
  program
67
90
  .command('init')
68
- .description('Initialize documentation and agent prompts for a repository')
69
- .argument('<repo-path>', 'Path to the repository to analyze')
70
- .argument('[type]', 'Type to initialize: "docs", "agents", or "both" (default: "both")', 'both')
71
- .option('-o, --output <dir>', 'Output directory', './.context')
72
- .option('-k, --api-key <key>', 'API key for the LLM provider')
73
- .option('-m, --model <model>', 'LLM model to use', 'google/gemini-2.5-flash-preview-05-20')
74
- .option('-p, --provider <provider>', 'LLM provider (openrouter, openai, anthropic, gemini, grok)', 'openrouter')
75
- .option('--exclude <patterns...>', 'Patterns to exclude from analysis')
76
- .option('--include <patterns...>', 'Patterns to include in analysis')
77
- .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'))
78
100
  .action(async (repoPath, type, options) => {
79
101
  try {
80
- // Validate type argument
81
- if (!['docs', 'agents', 'both'].includes(type)) {
82
- ui.displayError(`Invalid type "${type}". Must be "docs", "agents", or "both".`);
83
- process.exit(1);
84
- }
85
- // Set options based on type argument
86
- if (type === 'docs') {
87
- options.docsOnly = true;
88
- }
89
- else if (type === 'agents') {
90
- options.agentsOnly = true;
91
- }
92
- // For 'both', neither flag is set
93
- await runGenerate(repoPath, options);
102
+ await runInit(repoPath, type, options);
94
103
  }
95
104
  catch (error) {
96
- ui.displayError('Failed to initialize', error);
105
+ ui.displayError(t('errors.init.scaffoldFailed'), error);
97
106
  process.exit(1);
98
107
  }
99
108
  });
100
109
  program
101
- .command('update')
102
- .description('Update documentation for changed files since last run or specified commit')
103
- .argument('<repo-path>', 'Path to the repository to analyze')
104
- .option('-o, --output <dir>', 'Output directory', './.context')
105
- .option('-k, --api-key <key>', 'API key for the LLM provider')
106
- .option('-m, --model <model>', 'LLM model to use', 'google/gemini-2.5-flash-preview-05-20')
107
- .option('-p, --provider <provider>', 'LLM provider (openrouter, openai, anthropic, gemini, grok)', 'openrouter')
108
- .option('--since <commit>', 'Compare against specific commit/branch (default: last processed commit)')
109
- .option('--staged', 'Only process staged files (for pre-commit hooks)')
110
- .option('--force', 'Force regeneration even if no changes detected')
111
- .option('--exclude <patterns...>', 'Patterns to exclude from analysis')
112
- .option('--include <patterns...>', 'Patterns to include in analysis')
113
- .option('-v, --verbose', 'Verbose output')
114
- .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) => {
115
121
  try {
116
- await runUpdate(repoPath, options);
122
+ await runInit(repoPath, type, options);
117
123
  }
118
124
  catch (error) {
119
- ui.displayError('Failed to update documentation', error);
125
+ ui.displayError(t('errors.init.scaffoldFailed'), error);
120
126
  process.exit(1);
121
127
  }
122
128
  });
123
129
  program
124
- .command('analyze')
125
- .description('Analyze repository structure without generating content')
126
- .argument('<repo-path>', 'Path to the repository to analyze')
127
- .option('--exclude <patterns...>', 'Patterns to exclude from analysis')
128
- .option('--include <patterns...>', 'Patterns to include in analysis')
129
- .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'))
130
147
  .action(async (repoPath, options) => {
131
148
  try {
132
- await runAnalyze(repoPath, options);
149
+ await runLlmFill(repoPath, options);
133
150
  }
134
151
  catch (error) {
135
- ui.displayError('Failed to analyze repository', error);
152
+ ui.displayError(t('errors.fill.failed'), error);
136
153
  process.exit(1);
137
154
  }
138
155
  });
139
156
  program
140
- .command('preview')
141
- .description('Preview what documentation updates would be made without actually updating')
142
- .argument('<repo-path>', 'Path to the repository to analyze')
143
- .option('--since <commit>', 'Compare against specific commit/branch (default: last processed commit)')
144
- .option('--staged', 'Only analyze staged files (for pre-commit hooks)')
145
- .option('--exclude <patterns...>', 'Patterns to exclude from analysis')
146
- .option('--include <patterns...>', 'Patterns to include in analysis')
147
- .option('-v, --verbose', 'Verbose output with detailed file lists')
148
- .addHelpText('after', `
149
- Examples:
150
- $ ai-context preview ./ # Preview changes since last run
151
- $ ai-context preview ./ --staged # Preview staged files only
152
- $ ai-context preview ./ --since HEAD~3 # Preview changes since 3 commits ago
153
- $ ai-context preview ./ --verbose # Show detailed file change lists`)
154
- .action(async (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(', ') }));
181
+ }
182
+ const docSelection = parseDocSelection(rawOptions.docs);
183
+ if (docSelection.invalid.length > 0) {
184
+ ui.displayWarning(t('warnings.docs.unknown', { values: docSelection.invalid.join(', ') }));
185
+ }
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'));
155
207
  try {
156
- await runPreview(repoPath, 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) }));
157
220
  }
158
221
  catch (error) {
159
- ui.displayError('Failed to preview updates', error);
222
+ ui.updateSpinner(t('spinner.plan.creationFailed'), 'fail');
223
+ ui.displayError(t('errors.plan.creationFailed'), error);
160
224
  process.exit(1);
161
225
  }
226
+ finally {
227
+ ui.stopSpinner();
228
+ }
162
229
  });
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(', ') }));
236
+ }
237
+ if (agentSelection.invalid.length > 0) {
238
+ ui.displayWarning(t('warnings.agents.unknown', { values: agentSelection.invalid.join(', ') }));
239
+ }
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
250
+ };
251
+ if (!options.scaffoldDocs && !options.scaffoldAgents) {
252
+ ui.displayWarning(t('warnings.scaffold.noneSelected'));
253
+ return;
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
+ let docsGenerated = 0;
267
+ let agentsGenerated = 0;
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
+ }
163
306
  async function runGenerate(repoPath, options) {
164
- const provider = options.provider || llmClientFactory_1.LLMClientFactory.detectProviderFromModel(options.model);
165
- // Get API key from options or environment variables
166
- let apiKey = options.apiKey;
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
+ }
350
+ }
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
+ }
362
+ }
363
+ }
364
+ if (!provider) {
365
+ if (model) {
366
+ provider = llmClientFactory_1.LLMClientFactory.detectProviderFromModel(model);
367
+ }
368
+ else if (apiKey) {
369
+ provider = llmClientFactory_1.LLMClientFactory.getProviderFromApiKey(apiKey);
370
+ }
371
+ }
372
+ if (!model) {
373
+ if (provider === 'openrouter' && process.env.OPENROUTER_MODEL) {
374
+ model = process.env.OPENROUTER_MODEL;
375
+ }
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);
382
+ }
383
+ }
384
+ if (!provider) {
385
+ provider = llmClientFactory_1.LLMClientFactory.detectProviderFromModel(model || defaults.fallbackModel);
386
+ }
167
387
  if (!apiKey) {
168
- const envVars = llmClientFactory_1.LLMClientFactory.getEnvironmentVariables()[provider];
169
- for (const envVar of envVars) {
170
- apiKey = process.env[envVar];
171
- if (apiKey)
388
+ for (const envVar of providerEnvMap[provider]) {
389
+ const value = process.env[envVar];
390
+ if (value) {
391
+ apiKey = value;
172
392
  break;
393
+ }
173
394
  }
174
395
  }
175
- const cliOptions = {
176
- repoPath: path.resolve(repoPath),
177
- outputDir: path.resolve(options.output),
178
- model: options.model,
179
- 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 {
180
404
  provider,
181
- exclude: options.exclude || [],
182
- include: options.include,
183
- verbose: options.verbose || false
405
+ model: model || defaults.fallbackModel,
406
+ apiKey,
407
+ promptPath,
408
+ baseUrl: rawOptions.baseUrl
184
409
  };
185
- if (!cliOptions.apiKey) {
186
- const envVars = llmClientFactory_1.LLMClientFactory.getEnvironmentVariables()[provider];
187
- ui.displayError(`${provider.toUpperCase()} API key is required. Set one of these environment variables: ${envVars.join(', ')} or use --api-key option.`);
188
- 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(', ') }));
189
422
  }
190
- // Display welcome message
191
- ui.displayWelcome('0.1.0');
192
- ui.displayProjectInfo(cliOptions.repoPath, cliOptions.outputDir, cliOptions.model, cliOptions.provider);
193
- // Show usage warning for expensive models
194
- if (cliOptions.model && ['anthropic/claude-3-opus', 'openai/gpt-4'].includes(cliOptions.model)) {
195
- ui.displayUsageWarning(2.0); // Estimate high cost for warning
196
- }
197
- // Initialize components
198
- const fileMapper = new fileMapper_1.FileMapper(cliOptions.exclude);
199
- const llmConfig = {
200
- apiKey: cliOptions.apiKey,
201
- model: cliOptions.model || 'google/gemini-2.5-flash-preview-05-20',
202
- provider: cliOptions.provider || 'openrouter'
423
+ if (agentSelection.invalid.length > 0) {
424
+ ui.displayWarning(t('warnings.agents.unknown', { values: agentSelection.invalid.join(', ') }));
425
+ }
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
203
454
  };
204
- const llmClient = llmClientFactory_1.LLMClientFactory.createClient(llmConfig);
205
- const docGenerator = new documentationGenerator_1.DocumentationGenerator(fileMapper, llmClient);
206
- const agentGenerator = new agentGenerator_1.AgentGenerator(fileMapper, llmClient);
207
- // Step 1: Map repository structure
208
- ui.displayStep(1, 4, 'Analyzing repository structure');
209
- ui.startSpinner('Scanning files and directories...');
210
- const repoStructure = await fileMapper.mapRepository(cliOptions.repoPath, cliOptions.include);
211
- ui.updateSpinner(`Found ${repoStructure.totalFiles} files in ${repoStructure.directories.length} directories`, 'success');
212
- // Display analysis results
213
- if (cliOptions.verbose) {
214
- ui.displayAnalysisResults(repoStructure.totalFiles, repoStructure.directories.length, ui.formatBytes(repoStructure.totalSize));
215
- // Show file distribution
216
- const extensions = new Map();
217
- repoStructure.files.forEach(file => {
218
- const ext = file.extension || 'no-extension';
219
- extensions.set(ext, (extensions.get(ext) || 0) + 1);
220
- });
221
- ui.displayFileTypeDistribution(extensions, repoStructure.totalFiles);
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;
222
476
  }
223
- let docsGenerated = 0;
224
- let agentsGenerated = 0;
225
- // Step 2: Generate documentation
226
- if (!options.agentsOnly) {
227
- ui.displayStep(2, 4, 'Generating documentation');
228
- ui.startSpinner('Creating comprehensive documentation...');
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 }));
229
483
  try {
230
- await docGenerator.generateDocumentation(repoStructure, cliOptions.outputDir, false // We'll handle our own progress display
231
- );
232
- docsGenerated = 10; // Number of doc files generated (README, STRUCTURE, DEVELOPMENT, API, DEPLOYMENT, TROUBLESHOOTING, configuration + modules)
233
- ui.updateSpinner('Documentation generated successfully', 'success');
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' });
234
503
  }
235
504
  catch (error) {
236
- ui.updateSpinner('Failed to generate documentation', 'fail');
237
- throw 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
+ });
238
511
  }
239
512
  }
240
- // Step 3: Generate agent prompts
241
- if (!options.docsOnly) {
242
- ui.displayStep(3, 4, 'Generating AI agent prompts');
243
- ui.startSpinner('Creating specialized agent prompts...');
244
- try {
245
- await agentGenerator.generateAgentPrompts(repoStructure, cliOptions.outputDir, false // We'll handle our own progress display
246
- );
247
- agentsGenerated = 9; // Number of agent files generated
248
- ui.updateSpinner('Agent prompts generated successfully', 'success');
249
- }
250
- catch (error) {
251
- ui.updateSpinner('Failed to generate agent prompts', 'fail');
252
- throw error;
253
- }
254
- }
255
- // Step 4: Complete
256
- ui.displayStep(4, 4, 'Finalizing output');
257
- // Get usage statistics from the LLM client
258
- const usageStats = llmClient.getUsageStats();
259
- ui.displayGenerationSummary(docsGenerated, agentsGenerated, usageStats);
260
- ui.displaySuccess(`Output saved to: ${cliOptions.outputDir}`);
261
- }
262
- async function runAnalyze(repoPath, options) {
263
- const resolvedPath = path.resolve(repoPath);
264
- // Display welcome
265
- ui.displayWelcome('0.1.0');
266
- ui.startSpinner('Analyzing repository structure...');
267
- const fileMapper = new fileMapper_1.FileMapper(options.exclude || []);
268
- const repoStructure = await fileMapper.mapRepository(resolvedPath, options.include);
269
- ui.stopSpinner();
270
- // Display analysis results
271
- ui.displayAnalysisResults(repoStructure.totalFiles, repoStructure.directories.length, ui.formatBytes(repoStructure.totalSize));
272
- // File type distribution
273
- const extensions = new Map();
274
- repoStructure.files.forEach(file => {
275
- const ext = file.extension || 'no-extension';
276
- extensions.set(ext, (extensions.get(ext) || 0) + 1);
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)
277
548
  });
278
- ui.displayFileTypeDistribution(extensions, repoStructure.totalFiles);
279
- // Directory structure (top level)
280
- const topDirs = repoStructure.directories
281
- .filter(dir => !dir.relativePath.includes('/'))
282
- .slice(0, 10);
283
- if (topDirs.length > 0) {
284
- console.log(chalk_1.default.bold('\nšŸ“‚ Top-level Directories:'));
285
- console.log(chalk_1.default.gray('─'.repeat(50)));
286
- topDirs.forEach(dir => {
287
- console.log(` ${chalk_1.default.blue('ā–ø')} ${chalk_1.default.white(dir.relativePath)}`);
288
- });
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'));
289
563
  }
290
- // Token estimation for full documentation generation
291
- ui.startSpinner('Estimating token usage for full documentation generation...');
292
- const tokenEstimator = new tokenEstimator_1.TokenEstimator(fileMapper);
293
- const tokenEstimate = await tokenEstimator.estimateTokensForFullGeneration(repoStructure);
294
- ui.stopSpinner();
295
- console.log(tokenEstimator.formatTokenEstimate(tokenEstimate));
296
- ui.displaySuccess('Analysis complete!');
297
- }
298
- async function runUpdate(repoPath, options) {
299
- const provider = options.provider || llmClientFactory_1.LLMClientFactory.detectProviderFromModel(options.model);
300
- // Get API key from options or environment variables
301
- let apiKey = options.apiKey;
302
- if (!apiKey) {
303
- const envVars = llmClientFactory_1.LLMClientFactory.getEnvironmentVariables()[provider];
304
- for (const envVar of envVars) {
305
- apiKey = process.env[envVar];
306
- if (apiKey)
307
- break;
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;
308
574
  }
309
575
  }
310
- const cliOptions = {
311
- repoPath: path.resolve(repoPath),
312
- outputDir: path.resolve(options.output),
313
- model: options.model,
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({
314
609
  apiKey,
610
+ model,
315
611
  provider,
316
- exclude: options.exclude || [],
317
- include: options.include,
318
- verbose: options.verbose || false,
319
- since: options.since,
320
- staged: options.staged || false,
321
- force: options.force || false
322
- };
323
- if (!cliOptions.apiKey) {
324
- const envVars = llmClientFactory_1.LLMClientFactory.getEnvironmentVariables()[provider];
325
- ui.displayError(`${provider.toUpperCase()} API key is required. Set one of these environment variables: ${envVars.join(', ')} or use --api-key option.`);
326
- process.exit(1);
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 }));
618
+ try {
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' });
644
+ }
327
645
  }
328
- // Initialize git service
329
- const gitService = new gitService_1.GitService(cliOptions.repoPath);
330
- if (!gitService.isGitRepository()) {
331
- ui.displayError('This command requires a Git repository. Initialize git first or use the generate command instead.');
332
- process.exit(1);
646
+ catch (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
+ });
333
653
  }
334
- // Check if context has been initialized
335
- if (!gitService.hasContextBeenInitialized(cliOptions.outputDir)) {
336
- ui.displayError('No documentation context found. You should run analyze and init before updating.');
337
- console.log(chalk_1.default.bold('\nšŸ’” Getting Started:'));
338
- console.log(chalk_1.default.gray('─'.repeat(50)));
339
- console.log(`${chalk_1.default.blue('1. Analyze:')} ai-context analyze ${cliOptions.repoPath}`);
340
- console.log(`${chalk_1.default.blue('2. Initialize:')} ai-context init ${cliOptions.repoPath}`);
341
- console.log(`${chalk_1.default.blue('3. Update:')} ai-context update ${cliOptions.repoPath}`);
342
- console.log(chalk_1.default.gray('─'.repeat(50)));
343
- console.log(chalk_1.default.gray('The analyze command shows token estimates and costs.'));
344
- console.log(chalk_1.default.gray('The init command creates the initial documentation.'));
345
- console.log(chalk_1.default.gray('The update command incrementally updates existing documentation.'));
346
- process.exit(1);
654
+ finally {
655
+ ui.stopSpinner();
347
656
  }
348
- // Display welcome message
349
- ui.displayWelcome('0.1.0');
350
- ui.displayProjectInfo(cliOptions.repoPath, cliOptions.outputDir, cliOptions.model, cliOptions.provider);
351
- // Initialize components
352
- const fileMapper = new fileMapper_1.FileMapper(cliOptions.exclude);
353
- const llmConfig = {
354
- apiKey: cliOptions.apiKey,
355
- model: cliOptions.model || 'google/gemini-2.5-flash-preview-05-20',
356
- provider: cliOptions.provider || 'openrouter'
357
- };
358
- const llmClient = llmClientFactory_1.LLMClientFactory.createClient(llmConfig);
359
- const incrementalGenerator = new incrementalDocumentationGenerator_1.IncrementalDocumentationGenerator(fileMapper, llmClient, gitService);
360
- // Step 1: Analyze repository structure
361
- ui.displayStep(1, 4, 'Analyzing repository structure');
362
- ui.startSpinner('Scanning files and directories...');
363
- const repoStructure = await fileMapper.mapRepository(cliOptions.repoPath, cliOptions.include);
364
- ui.updateSpinner(`Found ${repoStructure.totalFiles} files in ${repoStructure.directories.length} directories`, 'success');
365
- // Step 2: Detect changes
366
- ui.displayStep(2, 4, 'Detecting changes');
367
- // Display commit tracking info in verbose mode
368
- if (cliOptions.verbose) {
369
- gitService.displayCommitTrackingInfo(true);
370
- }
371
- ui.startSpinner('Analyzing git changes...');
372
- let changes;
373
- if (cliOptions.staged) {
374
- // For pre-commit hooks - analyze only staged files
375
- changes = gitService.getStagedChanges();
376
- }
377
- else if (cliOptions.since) {
378
- // Compare against specific commit
379
- changes = gitService.getChangedFiles(cliOptions.since);
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;
685
+ }
686
+ }
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);
707
+ }
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.');
380
726
  }
381
727
  else {
382
- // Compare against last processed commit
383
- changes = gitService.getChangedFiles();
728
+ guidance.push('- Maintain accurate cross-links between docs and referenced resources.');
384
729
  }
385
- const totalChanges = changes.added.length + changes.modified.length + changes.deleted.length + changes.renamed.length;
386
- if (totalChanges === 0 && !cliOptions.force) {
387
- ui.updateSpinner('No changes detected since last run', 'info');
388
- ui.displaySuccess('Documentation is up to date!');
389
- return;
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>');
390
767
  }
391
- ui.updateSpinner(`Found ${totalChanges} changed files`, 'success');
392
- // Step 3: Update documentation
393
- ui.displayStep(3, 4, 'Updating documentation');
394
- ui.startSpinner('Processing changed files...');
395
- const result = await incrementalGenerator.updateDocumentation(repoStructure, cliOptions.outputDir, changes, cliOptions.verbose);
396
- ui.updateSpinner(`Updated ${result.updated} files, removed ${result.removed} files`, 'success');
397
- // Step 4: Save state (only if documentation was actually updated)
398
- ui.displayStep(4, 4, 'Saving state');
399
- const currentCommit = gitService.getCurrentCommit();
400
- if (result.updated > 0 || result.removed > 0) {
401
- gitService.saveState(currentCommit);
402
- if (cliOptions.verbose) {
403
- console.log(chalk_1.default.gray(`State saved: tracking commit ${currentCommit.substring(0, 8)}`));
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);
404
794
  }
405
795
  }
406
- else {
407
- if (cliOptions.verbose) {
408
- console.log(chalk_1.default.gray('No documentation changes made, state not updated'));
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;
409
805
  }
806
+ const normalized = path.normalize(cleanName).replace(/^\.\//, '');
807
+ if (normalized.includes('..')) {
808
+ continue;
809
+ }
810
+ const fullPath = path.join(baseDir, normalized);
811
+ if (!(await fs.pathExists(fullPath))) {
812
+ continue;
813
+ }
814
+ const content = await fs.readFile(fullPath, 'utf-8');
815
+ results.push({ path: normalized, content });
816
+ seen.add(cleanName);
817
+ }
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'));
828
+ console.log(chalk_1.default.gray('─'.repeat(50)));
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) {
833
+ console.log(chalk_1.default.gray('─'.repeat(50)));
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}`);
410
839
  }
411
- // Display summary of changed files
412
- if (result.updatedFiles.length > 0 || result.removedFiles.length > 0) {
413
- console.log(chalk_1.default.bold('\nšŸ“„ Documentation Files Changed:'));
840
+ if (failed.length > 0) {
414
841
  console.log(chalk_1.default.gray('─'.repeat(50)));
415
- if (result.updatedFiles.length > 0) {
416
- console.log(chalk_1.default.green('\nāœ… Updated/Created:'));
417
- result.updatedFiles.forEach(file => {
418
- console.log(` ${chalk_1.default.green('ā—')} ${file}`);
419
- });
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')}`);
844
+ });
845
+ }
846
+ }
847
+ function parseDocSelection(input) {
848
+ if (input === undefined) {
849
+ return { selected: undefined, invalid: [], provided: false, explicitNone: false };
850
+ }
851
+ if (Array.isArray(input) && input.length === 0) {
852
+ return { selected: [], invalid: [], provided: true, explicitNone: true };
853
+ }
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 };
862
+ }
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
+ }))
420
940
  }
421
- if (result.removedFiles.length > 0) {
422
- console.log(chalk_1.default.red('\nšŸ—‘ļø Removed:'));
423
- result.removedFiles.forEach(file => {
424
- console.log(` ${chalk_1.default.red('ā—')} ${file}`);
425
- });
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
+ ]
426
956
  }
427
- console.log(chalk_1.default.gray('─'.repeat(50)));
957
+ ]);
958
+ if (action === 'scaffold') {
959
+ await runInteractiveScaffold();
428
960
  }
429
- // Get usage statistics
430
- const usageStats = llmClient.getUsageStats();
431
- ui.displayGenerationSummary(result.updated, 0, usageStats, true);
432
- ui.displaySuccess(`Documentation updated! Processed ${result.updated} files.`);
433
- }
434
- async function runPreview(repoPath, options) {
435
- const resolvedPath = path.resolve(repoPath);
436
- // Display welcome
437
- ui.displayWelcome('0.1.0');
438
- ui.startSpinner('Initializing analysis...');
439
- // Initialize services
440
- const fileMapper = new fileMapper_1.FileMapper(options.exclude || []);
441
- const gitService = new gitService_1.GitService(resolvedPath);
442
- const changeAnalyzer = new changeAnalyzer_1.ChangeAnalyzer(gitService, fileMapper);
443
- // Check if it's a git repository
444
- if (!gitService.isGitRepository()) {
445
- ui.updateSpinner('Not a git repository', 'fail');
446
- ui.displayError('The specified path is not a git repository. Preview requires git tracking.');
447
- process.exit(1);
961
+ else if (action === 'fill') {
962
+ await runInteractiveLlmFill();
448
963
  }
449
- // Check if context has been initialized
450
- const outputDir = path.resolve(options.output || './.context');
451
- if (!gitService.hasContextBeenInitialized(outputDir)) {
452
- ui.updateSpinner('Context not initialized', 'fail');
453
- ui.displayError('No documentation context found. You should run analyze and init before previewing changes to update.');
454
- console.log(chalk_1.default.bold('\nšŸ’” Getting Started:'));
455
- console.log(chalk_1.default.gray('─'.repeat(50)));
456
- console.log(`${chalk_1.default.blue('1. Analyze:')} ai-context analyze ${repoPath}`);
457
- console.log(`${chalk_1.default.blue('2. Initialize:')} ai-context init ${repoPath}`);
458
- console.log(`${chalk_1.default.blue('3. Preview:')} ai-context preview ${repoPath}`);
459
- console.log(chalk_1.default.gray('─'.repeat(50)));
460
- console.log(chalk_1.default.gray('The analyze command shows token estimates and costs.'));
461
- console.log(chalk_1.default.gray('The init command creates the initial documentation.'));
462
- console.log(chalk_1.default.gray('The preview command shows what would change in updates.'));
463
- process.exit(1);
964
+ else {
965
+ await runInteractivePlan();
966
+ }
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;
464
1007
  }
465
- ui.updateSpinner('Mapping repository structure...');
466
- // Map repository structure
467
- const repoStructure = await fileMapper.mapRepository(resolvedPath, options.include);
468
- ui.updateSpinner('Analyzing changes...');
469
- // Detect changes
470
- let changes;
471
- if (options.staged) {
472
- // For pre-commit hooks - analyze only staged files
473
- changes = gitService.getStagedChanges();
1008
+ else {
1009
+ selectedDocs = [];
474
1010
  }
475
- else if (options.since) {
476
- // Compare against specific commit
477
- changes = gitService.getChangedFiles(options.since);
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;
478
1031
  }
479
1032
  else {
480
- // Compare against last processed commit
481
- changes = gitService.getChangedFiles();
482
- }
483
- // Analyze the changes
484
- const analysis = await changeAnalyzer.analyzeChanges(repoStructure, changes);
485
- ui.stopSpinner();
486
- // Display the analysis
487
- changeAnalyzer.displayAnalysis(analysis, options.verbose);
488
- // Add token estimation if there are changes to process
489
- if (analysis.affectedModules.length > 0) {
490
- ui.startSpinner('Estimating token usage and costs for affected changes...');
491
- // Create a subset structure with only affected files
492
- const affectedFiles = new Set();
493
- // Add all files from affected modules
494
- for (const module of analysis.affectedModules) {
495
- module.affectedFiles.forEach(filePath => {
496
- // Convert to absolute path to match repoStructure.files format
497
- const absolutePath = path.resolve(resolvedPath, filePath);
498
- if (fileMapper.isTextFile(absolutePath)) {
499
- affectedFiles.add(absolutePath);
1033
+ selectedAgents = [];
1034
+ }
1035
+ if ((selectedDocs?.length ?? 0) === 0 && (selectedAgents?.length ?? 0) === 0) {
1036
+ ui.displayWarning(t('warnings.interactive.nothingSelected'));
1037
+ return;
1038
+ }
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
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;
1134
+ }
1135
+ else {
1136
+ selectedDocs = [];
1137
+ }
1138
+ const { includeAgents } = await inquirer_1.default.prompt([
1139
+ {
1140
+ type: 'confirm',
1141
+ name: 'includeAgents',
1142
+ message: t('prompts.fill.includeAgents'),
1143
+ default: true
1144
+ }
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;
1158
+ }
1159
+ else {
1160
+ selectedAgents = [];
1161
+ }
1162
+ if ((selectedDocs?.length ?? 0) === 0 && (selectedAgents?.length ?? 0) === 0) {
1163
+ ui.displayWarning(t('warnings.interactive.nothingSelected'));
1164
+ return;
1165
+ }
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();
1195
+ }
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();
1215
+ }
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)
500
1294
  }
501
- });
1295
+ ]);
1296
+ selectedAgents = agents;
502
1297
  }
503
- // Also add directly changed files
504
- [...changes.added, ...changes.modified].forEach(filePath => {
505
- const fullPath = path.resolve(resolvedPath, filePath);
506
- if (fileMapper.isTextFile(fullPath)) {
507
- 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
508
1304
  }
509
- });
510
- // Debug: Log the affected files and available files for comparison
511
- if (options.verbose) {
512
- console.log(chalk_1.default.gray(`\nDebug: Found ${affectedFiles.size} affected files:`));
513
- for (const file of Array.from(affectedFiles).slice(0, 5)) {
514
- const relativePath = path.relative(resolvedPath, file);
515
- 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()
516
1327
  }
517
- if (affectedFiles.size > 5) {
518
- 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
519
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
+ });
520
1349
  }
521
- // Create a reduced repo structure for estimation
522
- const matchedFiles = repoStructure.files.filter(file => affectedFiles.has(file.path));
523
- const affectedRepoStructure = {
524
- ...repoStructure,
525
- files: matchedFiles,
526
- totalFiles: matchedFiles.length
527
- };
528
- if (options.verbose) {
529
- console.log(chalk_1.default.gray(`Debug: Matched ${matchedFiles.length} files from repo structure`));
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
530
1390
  }
531
- const tokenEstimator = new tokenEstimator_1.TokenEstimator(fileMapper);
532
- 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 {
533
1424
  ui.stopSpinner();
534
- // Display the token estimate
535
- console.log(chalk_1.default.bold('\nšŸ”® Token & Cost Estimate for Preview Changes:'));
536
- console.log(chalk_1.default.gray('═'.repeat(60)));
537
- console.log(tokenEstimator.formatTokenEstimate(tokenEstimate));
538
1425
  }
539
- // Summary
540
- if (analysis.affectedModules.length > 0) {
541
- console.log(chalk_1.default.green('\nāœ… Ready to proceed with update command'));
542
- 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;
1430
+ }
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);
543
1449
  }
544
- else {
545
- console.log(chalk_1.default.yellow('\nšŸ“„ No updates needed - documentation is up to date'));
1450
+ return filtered;
1451
+ }
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;
546
1458
  }
1459
+ await program.parseAsync(process.argv);
547
1460
  }
548
- // Check if no arguments were provided (interactive mode)
549
- if (process.argv.length === 2) {
550
- const interactive = new interactiveMode_1.InteractiveMode();
551
- interactive.start().catch((error) => {
552
- ui.displayError('Interactive mode failed', error);
1461
+ if (require.main === module) {
1462
+ main().catch(error => {
1463
+ ui.displayError(t('errors.cli.executionFailed'), error);
553
1464
  process.exit(1);
554
1465
  });
555
1466
  }
556
- else {
557
- program.parse();
558
- }
559
1467
  //# sourceMappingURL=index.js.map