@ai-coders/context 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/README.md +25 -23
  2. package/dist/generators/agents/agentConfig.d.ts.map +1 -1
  3. package/dist/generators/agents/agentConfig.js +42 -0
  4. package/dist/generators/agents/agentConfig.js.map +1 -1
  5. package/dist/generators/agents/agentGenerator.d.ts +0 -1
  6. package/dist/generators/agents/agentGenerator.d.ts.map +1 -1
  7. package/dist/generators/agents/agentGenerator.js +7 -47
  8. package/dist/generators/agents/agentGenerator.js.map +1 -1
  9. package/dist/generators/agents/agentTypes.d.ts +1 -1
  10. package/dist/generators/agents/agentTypes.d.ts.map +1 -1
  11. package/dist/generators/agents/agentTypes.js +4 -1
  12. package/dist/generators/agents/agentTypes.js.map +1 -1
  13. package/dist/generators/agents/templates/indexTemplate.d.ts.map +1 -1
  14. package/dist/generators/agents/templates/indexTemplate.js +2 -1
  15. package/dist/generators/agents/templates/indexTemplate.js.map +1 -1
  16. package/dist/generators/agents/templates/playbookTemplate.d.ts.map +1 -1
  17. package/dist/generators/agents/templates/playbookTemplate.js +39 -3
  18. package/dist/generators/agents/templates/playbookTemplate.js.map +1 -1
  19. package/dist/generators/documentation/documentationGenerator.d.ts +3 -1
  20. package/dist/generators/documentation/documentationGenerator.d.ts.map +1 -1
  21. package/dist/generators/documentation/documentationGenerator.js +88 -15
  22. package/dist/generators/documentation/documentationGenerator.js.map +1 -1
  23. package/dist/generators/documentation/guideRegistry.js +8 -8
  24. package/dist/generators/documentation/guideRegistry.js.map +1 -1
  25. package/dist/generators/documentation/templates/apiReferenceTemplate.d.ts +2 -0
  26. package/dist/generators/documentation/templates/apiReferenceTemplate.d.ts.map +1 -0
  27. package/dist/generators/documentation/templates/apiReferenceTemplate.js +490 -0
  28. package/dist/generators/documentation/templates/apiReferenceTemplate.js.map +1 -0
  29. package/dist/generators/documentation/templates/architectureTemplate.d.ts.map +1 -1
  30. package/dist/generators/documentation/templates/architectureTemplate.js +27 -27
  31. package/dist/generators/documentation/templates/architectureTemplate.js.map +1 -1
  32. package/dist/generators/documentation/templates/common.js +2 -2
  33. package/dist/generators/documentation/templates/common.js.map +1 -1
  34. package/dist/generators/documentation/templates/dataFlowTemplate.d.ts.map +1 -1
  35. package/dist/generators/documentation/templates/dataFlowTemplate.js +5 -20
  36. package/dist/generators/documentation/templates/dataFlowTemplate.js.map +1 -1
  37. package/dist/generators/documentation/templates/developmentWorkflowTemplate.d.ts.map +1 -1
  38. package/dist/generators/documentation/templates/developmentWorkflowTemplate.js +5 -20
  39. package/dist/generators/documentation/templates/developmentWorkflowTemplate.js.map +1 -1
  40. package/dist/generators/documentation/templates/glossaryTemplate.d.ts.map +1 -1
  41. package/dist/generators/documentation/templates/glossaryTemplate.js +9 -23
  42. package/dist/generators/documentation/templates/glossaryTemplate.js.map +1 -1
  43. package/dist/generators/documentation/templates/index.d.ts +4 -0
  44. package/dist/generators/documentation/templates/index.d.ts.map +1 -1
  45. package/dist/generators/documentation/templates/index.js +9 -1
  46. package/dist/generators/documentation/templates/index.js.map +1 -1
  47. package/dist/generators/documentation/templates/indexTemplate.d.ts.map +1 -1
  48. package/dist/generators/documentation/templates/indexTemplate.js +5 -19
  49. package/dist/generators/documentation/templates/indexTemplate.js.map +1 -1
  50. package/dist/generators/documentation/templates/migrationTemplate.d.ts +2 -0
  51. package/dist/generators/documentation/templates/migrationTemplate.d.ts.map +1 -0
  52. package/dist/generators/documentation/templates/migrationTemplate.js +422 -0
  53. package/dist/generators/documentation/templates/migrationTemplate.js.map +1 -0
  54. package/dist/generators/documentation/templates/onboardingTemplate.d.ts +2 -0
  55. package/dist/generators/documentation/templates/onboardingTemplate.d.ts.map +1 -0
  56. package/dist/generators/documentation/templates/onboardingTemplate.js +431 -0
  57. package/dist/generators/documentation/templates/onboardingTemplate.js.map +1 -0
  58. package/dist/generators/documentation/templates/projectOverviewTemplate.d.ts.map +1 -1
  59. package/dist/generators/documentation/templates/projectOverviewTemplate.js +22 -25
  60. package/dist/generators/documentation/templates/projectOverviewTemplate.js.map +1 -1
  61. package/dist/generators/documentation/templates/securityTemplate.d.ts.map +1 -1
  62. package/dist/generators/documentation/templates/securityTemplate.js +5 -19
  63. package/dist/generators/documentation/templates/securityTemplate.js.map +1 -1
  64. package/dist/generators/documentation/templates/testingTemplate.d.ts.map +1 -1
  65. package/dist/generators/documentation/templates/testingTemplate.js +5 -19
  66. package/dist/generators/documentation/templates/testingTemplate.js.map +1 -1
  67. package/dist/generators/documentation/templates/toolingTemplate.d.ts.map +1 -1
  68. package/dist/generators/documentation/templates/toolingTemplate.js +6 -20
  69. package/dist/generators/documentation/templates/toolingTemplate.js.map +1 -1
  70. package/dist/generators/documentation/templates/troubleshootingTemplate.d.ts +2 -0
  71. package/dist/generators/documentation/templates/troubleshootingTemplate.d.ts.map +1 -0
  72. package/dist/generators/documentation/templates/troubleshootingTemplate.js +292 -0
  73. package/dist/generators/documentation/templates/troubleshootingTemplate.js.map +1 -0
  74. package/dist/generators/plans/templates/indexTemplate.d.ts.map +1 -1
  75. package/dist/generators/plans/templates/indexTemplate.js +3 -2
  76. package/dist/generators/plans/templates/indexTemplate.js.map +1 -1
  77. package/dist/generators/plans/templates/planTemplate.d.ts.map +1 -1
  78. package/dist/generators/plans/templates/planTemplate.js +74 -3
  79. package/dist/generators/plans/templates/planTemplate.js.map +1 -1
  80. package/dist/generators/shared/generatorUtils.d.ts +1 -1
  81. package/dist/generators/shared/generatorUtils.d.ts.map +1 -1
  82. package/dist/generators/shared/generatorUtils.js +1 -1
  83. package/dist/generators/shared/generatorUtils.js.map +1 -1
  84. package/dist/index.d.ts +2 -2
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +46 -1000
  87. package/dist/index.js.map +1 -1
  88. package/dist/prompts/defaults.d.ts +2 -2
  89. package/dist/prompts/defaults.d.ts.map +1 -1
  90. package/dist/prompts/defaults.js +11 -11
  91. package/dist/services/baseLLMClient.d.ts +3 -3
  92. package/dist/services/baseLLMClient.d.ts.map +1 -1
  93. package/dist/services/baseLLMClient.js +44 -5
  94. package/dist/services/baseLLMClient.js.map +1 -1
  95. package/dist/services/fill/fillService.d.ts +0 -4
  96. package/dist/services/fill/fillService.d.ts.map +1 -1
  97. package/dist/services/fill/fillService.js +6 -54
  98. package/dist/services/fill/fillService.js.map +1 -1
  99. package/dist/services/init/initService.d.ts +2 -2
  100. package/dist/services/init/initService.d.ts.map +1 -1
  101. package/dist/services/init/initService.js +45 -15
  102. package/dist/services/init/initService.js.map +1 -1
  103. package/dist/services/llmClientFactory.d.ts +2 -8
  104. package/dist/services/llmClientFactory.d.ts.map +1 -1
  105. package/dist/services/llmClientFactory.js +10 -96
  106. package/dist/services/llmClientFactory.js.map +1 -1
  107. package/dist/services/openRouterClient.d.ts +0 -3
  108. package/dist/services/openRouterClient.d.ts.map +1 -1
  109. package/dist/services/openRouterClient.js +2 -49
  110. package/dist/services/openRouterClient.js.map +1 -1
  111. package/dist/services/plan/planService.d.ts +2 -4
  112. package/dist/services/plan/planService.d.ts.map +1 -1
  113. package/dist/services/plan/planService.js +3 -12
  114. package/dist/services/plan/planService.js.map +1 -1
  115. package/dist/services/shared/llmConfig.d.ts.map +1 -1
  116. package/dist/services/shared/llmConfig.js +11 -53
  117. package/dist/services/shared/llmConfig.js.map +1 -1
  118. package/dist/types.d.ts +7 -2
  119. package/dist/types.d.ts.map +1 -1
  120. package/dist/utils/fileMapper.d.ts +1 -1
  121. package/dist/utils/fileMapper.d.ts.map +1 -1
  122. package/dist/utils/fileMapper.js +40 -19
  123. package/dist/utils/fileMapper.js.map +1 -1
  124. package/dist/utils/i18n.d.ts +11 -32
  125. package/dist/utils/i18n.d.ts.map +1 -1
  126. package/dist/utils/i18n.js +22 -64
  127. package/dist/utils/i18n.js.map +1 -1
  128. package/package.json +9 -12
  129. package/prompts/update_plan_prompt.md +4 -5
  130. package/prompts/update_scaffold_prompt.md +7 -8
  131. package/dist/generators/agents/contextUtils.d.ts +0 -8
  132. package/dist/generators/agents/contextUtils.d.ts.map +0 -1
  133. package/dist/generators/agents/contextUtils.js +0 -15
  134. package/dist/generators/agents/contextUtils.js.map +0 -1
  135. package/dist/generators/agents/promptFormatter.d.ts +0 -9
  136. package/dist/generators/agents/promptFormatter.d.ts.map +0 -1
  137. package/dist/generators/agents/promptFormatter.js +0 -84
  138. package/dist/generators/agents/promptFormatter.js.map +0 -1
  139. package/dist/generators/analyzers/codebaseAnalyzer.d.ts +0 -45
  140. package/dist/generators/analyzers/codebaseAnalyzer.d.ts.map +0 -1
  141. package/dist/generators/analyzers/codebaseAnalyzer.js +0 -293
  142. package/dist/generators/analyzers/codebaseAnalyzer.js.map +0 -1
  143. package/dist/generators/analyzers/index.d.ts +0 -3
  144. package/dist/generators/analyzers/index.d.ts.map +0 -1
  145. package/dist/generators/analyzers/index.js +0 -6
  146. package/dist/generators/analyzers/index.js.map +0 -1
  147. package/dist/generators/documentation/documentationTemplates.d.ts +0 -21
  148. package/dist/generators/documentation/documentationTemplates.d.ts.map +0 -1
  149. package/dist/generators/documentation/documentationTemplates.js +0 -359
  150. package/dist/generators/documentation/documentationTemplates.js.map +0 -1
  151. package/dist/generators/documentation/documentationTypes.d.ts +0 -11
  152. package/dist/generators/documentation/documentationTypes.d.ts.map +0 -1
  153. package/dist/generators/documentation/documentationTypes.js +0 -22
  154. package/dist/generators/documentation/documentationTypes.js.map +0 -1
  155. package/dist/generators/documentation/documentationUtils.d.ts +0 -7
  156. package/dist/generators/documentation/documentationUtils.d.ts.map +0 -1
  157. package/dist/generators/documentation/documentationUtils.js +0 -28
  158. package/dist/generators/documentation/documentationUtils.js.map +0 -1
  159. package/dist/generators/documentation/incrementalDocumentationGenerator.d.ts +0 -33
  160. package/dist/generators/documentation/incrementalDocumentationGenerator.d.ts.map +0 -1
  161. package/dist/generators/documentation/incrementalDocumentationGenerator.js +0 -400
  162. package/dist/generators/documentation/incrementalDocumentationGenerator.js.map +0 -1
  163. package/dist/generators/documentation/templates.d.ts +0 -31
  164. package/dist/generators/documentation/templates.d.ts.map +0 -1
  165. package/dist/generators/documentation/templates.js +0 -566
  166. package/dist/generators/documentation/templates.js.map +0 -1
  167. package/dist/generators/guidelines/agentIntegration.d.ts +0 -43
  168. package/dist/generators/guidelines/agentIntegration.d.ts.map +0 -1
  169. package/dist/generators/guidelines/agentIntegration.js +0 -157
  170. package/dist/generators/guidelines/agentIntegration.js.map +0 -1
  171. package/dist/generators/guidelines/guidelineTypes.d.ts +0 -40
  172. package/dist/generators/guidelines/guidelineTypes.d.ts.map +0 -1
  173. package/dist/generators/guidelines/guidelineTypes.js +0 -144
  174. package/dist/generators/guidelines/guidelineTypes.js.map +0 -1
  175. package/dist/generators/guidelines/guidelinesAnalyzer.d.ts +0 -30
  176. package/dist/generators/guidelines/guidelinesAnalyzer.d.ts.map +0 -1
  177. package/dist/generators/guidelines/guidelinesAnalyzer.js +0 -263
  178. package/dist/generators/guidelines/guidelinesAnalyzer.js.map +0 -1
  179. package/dist/generators/guidelines/guidelinesGenerator.d.ts +0 -30
  180. package/dist/generators/guidelines/guidelinesGenerator.d.ts.map +0 -1
  181. package/dist/generators/guidelines/guidelinesGenerator.js +0 -249
  182. package/dist/generators/guidelines/guidelinesGenerator.js.map +0 -1
  183. package/dist/generators/guidelines/guidelinesTemplates.d.ts +0 -23
  184. package/dist/generators/guidelines/guidelinesTemplates.d.ts.map +0 -1
  185. package/dist/generators/guidelines/guidelinesTemplates.js +0 -304
  186. package/dist/generators/guidelines/guidelinesTemplates.js.map +0 -1
  187. package/dist/generators/guidelines/index.d.ts +0 -6
  188. package/dist/generators/guidelines/index.d.ts.map +0 -1
  189. package/dist/generators/guidelines/index.js +0 -16
  190. package/dist/generators/guidelines/index.js.map +0 -1
  191. package/dist/generators/moduleGrouper.d.ts +0 -14
  192. package/dist/generators/moduleGrouper.d.ts.map +0 -1
  193. package/dist/generators/moduleGrouper.js +0 -82
  194. package/dist/generators/moduleGrouper.js.map +0 -1
  195. package/dist/generators/projectAnalyzer.d.ts +0 -14
  196. package/dist/generators/projectAnalyzer.d.ts.map +0 -1
  197. package/dist/generators/projectAnalyzer.js +0 -217
  198. package/dist/generators/projectAnalyzer.js.map +0 -1
  199. package/dist/services/changeAnalyzer.d.ts +0 -44
  200. package/dist/services/changeAnalyzer.d.ts.map +0 -1
  201. package/dist/services/changeAnalyzer.js +0 -344
  202. package/dist/services/changeAnalyzer.js.map +0 -1
  203. package/dist/utils/interactiveMode.d.ts +0 -21
  204. package/dist/utils/interactiveMode.d.ts.map +0 -1
  205. package/dist/utils/interactiveMode.js +0 -737
  206. package/dist/utils/interactiveMode.js.map +0 -1
  207. package/dist/utils/tokenEstimator.d.ts +0 -28
  208. package/dist/utils/tokenEstimator.d.ts.map +0 -1
  209. package/dist/utils/tokenEstimator.js +0 -134
  210. package/dist/utils/tokenEstimator.js.map +0 -1
package/dist/index.js CHANGED
@@ -37,31 +37,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  return (mod && mod.__esModule) ? mod : { "default": mod };
38
38
  };
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.runInit = runInit;
40
41
  exports.runGenerate = runGenerate;
41
42
  exports.runAnalyze = runAnalyze;
42
43
  exports.runUpdate = runUpdate;
43
44
  exports.runPreview = runPreview;
44
45
  exports.runGuidelines = runGuidelines;
45
- exports.runInit = runInit;
46
+ exports.runLlmFill = runLlmFill;
46
47
  const commander_1 = require("commander");
47
48
  const path = __importStar(require("path"));
48
- const fs = __importStar(require("fs-extra"));
49
- const glob_1 = require("glob");
50
49
  const dotenv = __importStar(require("dotenv"));
51
50
  const chalk_1 = __importDefault(require("chalk"));
52
51
  const inquirer_1 = __importDefault(require("inquirer"));
53
- const fileMapper_1 = require("./utils/fileMapper");
54
- const documentationGenerator_1 = require("./generators/documentation/documentationGenerator");
55
- const agentGenerator_1 = require("./generators/agents/agentGenerator");
56
52
  const planGenerator_1 = require("./generators/plans/planGenerator");
57
- const shared_1 = require("./generators/shared");
58
53
  const cliUI_1 = require("./utils/cliUI");
59
- const promptLoader_1 = require("./utils/promptLoader");
60
54
  const versionChecker_1 = require("./utils/versionChecker");
61
55
  const i18n_1 = require("./utils/i18n");
62
- const llmClientFactory_1 = require("./services/llmClientFactory");
63
- const guideRegistry_1 = require("./generators/documentation/guideRegistry");
64
- const agentTypes_1 = require("./generators/agents/agentTypes");
56
+ const initService_1 = require("./services/init/initService");
57
+ const fillService_1 = require("./services/fill/fillService");
58
+ const planService_1 = require("./services/plan/planService");
65
59
  dotenv.config();
66
60
  const initialLocale = (0, i18n_1.detectLocale)(process.argv.slice(2), process.env.AI_CONTEXT_LANG);
67
61
  let currentLocale = initialLocale;
@@ -73,17 +67,26 @@ const localeLabelKeys = {
73
67
  };
74
68
  const program = new commander_1.Command();
75
69
  const ui = new cliUI_1.CLIInterface(t);
76
- const VERSION = '0.3.1';
70
+ const VERSION = '0.4.0';
77
71
  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
- }));
72
+ const DEFAULT_MODEL = 'x-ai/grok-4-fast';
73
+ const initService = new initService_1.InitService({
74
+ ui,
75
+ t,
76
+ version: VERSION
77
+ });
78
+ const fillService = new fillService_1.FillService({
79
+ ui,
80
+ t,
81
+ version: VERSION,
82
+ defaultModel: DEFAULT_MODEL
83
+ });
84
+ const planService = new planService_1.PlanService({
85
+ ui,
86
+ t,
87
+ version: VERSION,
88
+ defaultModel: DEFAULT_MODEL
89
+ });
87
90
  program
88
91
  .name('ai-context')
89
92
  .description(t('cli.description'))
@@ -111,34 +114,12 @@ program
111
114
  .argument('<repo-path>', t('commands.init.arguments.repoPath'))
112
115
  .argument('[type]', t('commands.init.arguments.type'), 'both')
113
116
  .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'))
119
- .action(async (repoPath, type, options) => {
120
- try {
121
- await runInit(repoPath, type, options);
122
- }
123
- catch (error) {
124
- ui.displayError(t('errors.init.scaffoldFailed'), error);
125
- process.exit(1);
126
- }
127
- });
128
- program
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
117
  .option('--exclude <patterns...>', t('commands.init.options.exclude'))
137
118
  .option('--include <patterns...>', t('commands.init.options.include'))
138
119
  .option('-v, --verbose', t('commands.init.options.verbose'))
139
120
  .action(async (repoPath, type, options) => {
140
121
  try {
141
- await runInit(repoPath, type, options);
122
+ await initService.run(repoPath, type, options);
142
123
  }
143
124
  catch (error) {
144
125
  ui.displayError(t('errors.init.scaffoldFailed'), error);
@@ -155,17 +136,13 @@ program
155
136
  .option('-p, --provider <provider>', t('commands.fill.options.provider'))
156
137
  .option('--base-url <url>', t('commands.fill.options.baseUrl'))
157
138
  .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
139
  .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
140
  .option('--exclude <patterns...>', t('commands.fill.options.exclude'))
164
141
  .option('--include <patterns...>', t('commands.fill.options.include'))
165
142
  .option('-v, --verbose', t('commands.fill.options.verbose'))
166
143
  .action(async (repoPath, options) => {
167
144
  try {
168
- await runLlmFill(repoPath, options);
145
+ await fillService.run(repoPath, options);
169
146
  }
170
147
  catch (error) {
171
148
  ui.displayError(t('errors.fill.failed'), error);
@@ -179,8 +156,6 @@ program
179
156
  .option('-o, --output <dir>', t('commands.plan.options.output'), './.context')
180
157
  .option('--title <title>', t('commands.plan.options.title'))
181
158
  .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
159
  .option('-f, --force', t('commands.plan.options.force'))
185
160
  .option('--fill', t('commands.plan.options.fill'))
186
161
  .option('-r, --repo <path>', t('commands.plan.options.repo'))
@@ -194,26 +169,16 @@ program
194
169
  .option('--exclude <patterns...>', t('commands.plan.options.exclude'))
195
170
  .option('-v, --verbose', t('commands.plan.options.verbose'))
196
171
  .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(', ') }));
200
- }
201
- const docSelection = parseDocSelection(rawOptions.docs);
202
- if (docSelection.invalid.length > 0) {
203
- ui.displayWarning(t('warnings.docs.unknown', { values: docSelection.invalid.join(', ') }));
204
- }
205
172
  const outputDir = path.resolve(rawOptions.output || './.context');
206
173
  if (rawOptions.fill) {
207
174
  try {
208
- await scaffoldPlanIfNeeded(planName, outputDir, {
175
+ await planService.scaffoldPlanIfNeeded(planName, outputDir, {
209
176
  title: rawOptions.title,
210
177
  summary: rawOptions.summary,
211
- agentSelection,
212
- docSelection,
213
178
  force: Boolean(rawOptions.force),
214
179
  verbose: Boolean(rawOptions.verbose)
215
180
  });
216
- await runPlanFill(planName, { ...rawOptions, output: outputDir });
181
+ await planService.fillPlan(planName, { ...rawOptions, output: outputDir });
217
182
  }
218
183
  catch (error) {
219
184
  ui.displayError(t('errors.plan.fillFailed'), error);
@@ -229,8 +194,6 @@ program
229
194
  outputDir,
230
195
  title: rawOptions.title,
231
196
  summary: rawOptions.summary,
232
- selectedAgentTypes: agentSelection.explicitNone ? null : agentSelection.selected,
233
- selectedDocKeys: docSelection.explicitNone ? null : docSelection.selected,
234
197
  force: Boolean(rawOptions.force),
235
198
  verbose: Boolean(rawOptions.verbose)
236
199
  });
@@ -247,90 +210,15 @@ program
247
210
  }
248
211
  });
249
212
  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(', ') }));
255
- }
256
- if (agentSelection.invalid.length > 0) {
257
- ui.displayWarning(t('warnings.agents.unknown', { values: agentSelection.invalid.join(', ') }));
258
- }
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
269
- };
270
- if (!options.scaffoldDocs && !options.scaffoldAgents) {
271
- ui.displayWarning(t('warnings.scaffold.noneSelected'));
272
- return;
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');
285
- let docsGenerated = 0;
286
- let agentsGenerated = 0;
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);
213
+ await initService.run(repoPath, type, rawOptions);
324
214
  }
325
215
  async function runGenerate(repoPath, options) {
326
216
  const type = options?.docsOnly ? 'docs' : options?.agentsOnly ? 'agents' : (options?.type || 'both');
327
- await runInit(repoPath, type, {
217
+ await initService.run(repoPath, type, {
328
218
  output: options?.output ?? options?.outputDir ?? './.context',
329
219
  include: options?.include,
330
220
  exclude: options?.exclude,
331
221
  verbose: options?.verbose,
332
- docs: options?.docs,
333
- agents: options?.agents,
334
222
  docsOnly: options?.docsOnly,
335
223
  agentsOnly: options?.agentsOnly
336
224
  });
@@ -347,621 +235,8 @@ async function runPreview(..._args) {
347
235
  async function runGuidelines(..._args) {
348
236
  throw new Error(t('errors.commands.guidelinesRemoved'));
349
237
  }
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
- }
365
- }
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
- }
377
- }
378
- }
379
- if (!provider) {
380
- if (model) {
381
- provider = llmClientFactory_1.LLMClientFactory.detectProviderFromModel(model);
382
- }
383
- else if (apiKey) {
384
- provider = llmClientFactory_1.LLMClientFactory.getProviderFromApiKey(apiKey);
385
- }
386
- }
387
- if (!model) {
388
- if (provider === 'openrouter' && process.env.OPENROUTER_MODEL) {
389
- model = process.env.OPENROUTER_MODEL;
390
- }
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);
397
- }
398
- }
399
- if (!provider) {
400
- provider = llmClientFactory_1.LLMClientFactory.detectProviderFromModel(model || defaults.fallbackModel);
401
- }
402
- if (!apiKey) {
403
- for (const envVar of providerEnvMap[provider]) {
404
- const value = process.env[envVar];
405
- if (value) {
406
- apiKey = value;
407
- break;
408
- }
409
- }
410
- }
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 {
419
- provider,
420
- model: model || defaults.fallbackModel,
421
- apiKey,
422
- baseUrl: rawOptions.baseUrl
423
- };
424
- }
425
238
  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(', ') }));
439
- }
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
467
- };
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 }));
655
- try {
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' });
681
- }
682
- }
683
- catch (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
- });
690
- }
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;
722
- }
723
- }
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;
734
- }
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);
831
- }
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;
842
- }
843
- const normalized = path.normalize(cleanName).replace(/^\.\//, '');
844
- if (normalized.includes('..')) {
845
- continue;
846
- }
847
- const fullPath = path.join(baseDir, normalized);
848
- if (!(await fs.pathExists(fullPath))) {
849
- continue;
850
- }
851
- const content = await fs.readFile(fullPath, 'utf-8');
852
- results.push({ path: normalized, content });
853
- seen.add(cleanName);
854
- }
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'));
865
- console.log(chalk_1.default.gray('─'.repeat(50)));
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) {
870
- console.log(chalk_1.default.gray('─'.repeat(50)));
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')}`);
881
- });
882
- }
883
- }
884
- function parseDocSelection(input) {
885
- if (input === undefined) {
886
- return { selected: undefined, invalid: [], provided: false, explicitNone: false };
887
- }
888
- if (Array.isArray(input) && input.length === 0) {
889
- return { selected: [], invalid: [], provided: true, explicitNone: true };
890
- }
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 };
899
- }
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(' ');
239
+ await fillService.run(repoPath, rawOptions);
965
240
  }
966
241
  async function selectLocale(showWelcome) {
967
242
  const { locale } = await inquirer_1.default.prompt([
@@ -1041,58 +316,19 @@ async function runInteractiveScaffold() {
1041
316
  default: defaultOutput
1042
317
  }
1043
318
  ]);
1044
- const { includeDocs } = await inquirer_1.default.prompt([
319
+ const { scaffoldType } = await inquirer_1.default.prompt([
1045
320
  {
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;
1064
- }
1065
- else {
1066
- selectedDocs = [];
1067
- }
1068
- const { includeAgents } = await inquirer_1.default.prompt([
1069
- {
1070
- type: 'confirm',
1071
- name: 'includeAgents',
1072
- message: t('prompts.scaffold.includeAgents'),
1073
- default: true
321
+ type: 'list',
322
+ name: 'scaffoldType',
323
+ message: t('prompts.scaffold.type'),
324
+ choices: [
325
+ { name: t('prompts.scaffold.typeBoth'), value: 'both' },
326
+ { name: t('prompts.scaffold.typeDocs'), value: 'docs' },
327
+ { name: t('prompts.scaffold.typeAgents'), value: 'agents' }
328
+ ],
329
+ default: 'both'
1074
330
  }
1075
331
  ]);
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;
1088
- }
1089
- else {
1090
- selectedAgents = [];
1091
- }
1092
- if ((selectedDocs?.length ?? 0) === 0 && (selectedAgents?.length ?? 0) === 0) {
1093
- ui.displayWarning(t('warnings.interactive.nothingSelected'));
1094
- return;
1095
- }
1096
332
  const { verbose } = await inquirer_1.default.prompt([
1097
333
  {
1098
334
  type: 'confirm',
@@ -1101,23 +337,11 @@ async function runInteractiveScaffold() {
1101
337
  default: false
1102
338
  }
1103
339
  ]);
1104
- const scaffoldType = determineScaffoldType(selectedDocs, selectedAgents);
1105
340
  await runInit(resolvedRepo, scaffoldType, {
1106
341
  output: outputDir,
1107
- docs: selectedDocs,
1108
- agents: selectedAgents,
1109
342
  verbose
1110
343
  });
1111
344
  }
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
345
  async function runInteractiveLlmFill() {
1122
346
  const { repoPath } = await inquirer_1.default.prompt([
1123
347
  {
@@ -1144,20 +368,6 @@ async function runInteractiveLlmFill() {
1144
368
  }
1145
369
  ]);
1146
370
  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
371
  const { limit } = await inquirer_1.default.prompt([
1162
372
  {
1163
373
  type: 'input',
@@ -1168,58 +378,6 @@ async function runInteractiveLlmFill() {
1168
378
  ]);
1169
379
  const limitValue = limit ? parseInt(limit, 10) : undefined;
1170
380
  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;
1191
- }
1192
- else {
1193
- selectedDocs = [];
1194
- }
1195
- const { includeAgents } = await inquirer_1.default.prompt([
1196
- {
1197
- type: 'confirm',
1198
- name: 'includeAgents',
1199
- message: t('prompts.fill.includeAgents'),
1200
- default: true
1201
- }
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;
1215
- }
1216
- else {
1217
- selectedAgents = [];
1218
- }
1219
- if ((selectedDocs?.length ?? 0) === 0 && (selectedAgents?.length ?? 0) === 0) {
1220
- ui.displayWarning(t('warnings.interactive.nothingSelected'));
1221
- return;
1222
- }
1223
381
  const { specifyModel } = await inquirer_1.default.prompt([
1224
382
  {
1225
383
  type: 'confirm',
@@ -1231,15 +389,6 @@ async function runInteractiveLlmFill() {
1231
389
  let provider;
1232
390
  let model;
1233
391
  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
392
  const modelAnswer = await inquirer_1.default.prompt([
1244
393
  {
1245
394
  type: 'input',
@@ -1249,6 +398,7 @@ async function runInteractiveLlmFill() {
1249
398
  }
1250
399
  ]);
1251
400
  model = modelAnswer.model.trim();
401
+ provider = 'openrouter';
1252
402
  }
1253
403
  const { provideApiKey } = await inquirer_1.default.prompt([
1254
404
  {
@@ -1278,13 +428,9 @@ async function runInteractiveLlmFill() {
1278
428
  default: false
1279
429
  }
1280
430
  ]);
1281
- await runLlmFill(resolvedRepo, {
431
+ await fillService.run(resolvedRepo, {
1282
432
  output: outputDir,
1283
433
  prompt: promptPath,
1284
- docs: selectedDocs,
1285
- agents: selectedAgents,
1286
- dryRun,
1287
- all: processAll,
1288
434
  limit: parsedLimit,
1289
435
  model,
1290
436
  provider,
@@ -1331,50 +477,6 @@ async function runInteractivePlan() {
1331
477
  filter: (value) => value.trim()
1332
478
  }
1333
479
  ]);
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)
1351
- }
1352
- ]);
1353
- selectedAgents = agents;
1354
- }
1355
- const { includeDocs } = await inquirer_1.default.prompt([
1356
- {
1357
- type: 'confirm',
1358
- name: 'includeDocs',
1359
- message: t('prompts.plan.includeDocs'),
1360
- default: true
1361
- }
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
480
  const { repoPath } = await inquirer_1.default.prompt([
1379
481
  {
1380
482
  type: 'input',
@@ -1393,12 +495,10 @@ async function runInteractivePlan() {
1393
495
  ]);
1394
496
  try {
1395
497
  const resolvedOutput = path.resolve(outputDir.trim() || defaultOutput);
1396
- await scaffoldPlanIfNeeded(planName, resolvedOutput, {
1397
- summary: summary || undefined,
1398
- agentSelection,
1399
- docSelection
498
+ await planService.scaffoldPlanIfNeeded(planName, resolvedOutput, {
499
+ summary: summary || undefined
1400
500
  });
1401
- await runPlanFill(planName, {
501
+ await planService.fillPlan(planName, {
1402
502
  output: resolvedOutput,
1403
503
  repo: repoPath,
1404
504
  dryRun
@@ -1417,48 +517,6 @@ async function runInteractivePlan() {
1417
517
  filter: (value) => value.trim()
1418
518
  }
1419
519
  ]);
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
520
  const generator = new planGenerator_1.PlanGenerator();
1463
521
  ui.startSpinner(t('spinner.plan.creating'));
1464
522
  try {
@@ -1466,8 +524,6 @@ async function runInteractivePlan() {
1466
524
  planName,
1467
525
  outputDir: path.resolve(outputDir.trim() || defaultOutput),
1468
526
  summary: summary || undefined,
1469
- selectedAgentTypes: selectedAgents,
1470
- selectedDocKeys: selectedDocs,
1471
527
  verbose: false
1472
528
  });
1473
529
  ui.updateSpinner(t('spinner.plan.created'), 'success');
@@ -1481,16 +537,6 @@ async function runInteractivePlan() {
1481
537
  ui.stopSpinner();
1482
538
  }
1483
539
  }
1484
- function getAgentFilesByTypes(types) {
1485
- if (!types || types.length === 0) {
1486
- return undefined;
1487
- }
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
540
  function filterOutLocaleArgs(args) {
1495
541
  const filtered = [];
1496
542
  for (let index = 0; index < args.length; index += 1) {