@ai-coders/context 0.2.1 → 0.3.1

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