@grunnverk/kodrdriv 1.3.0 → 1.5.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 (44) hide show
  1. package/README.md +2 -0
  2. package/dist/application.js +3 -3
  3. package/dist/application.js.map +1 -1
  4. package/dist/arguments.js +3 -4
  5. package/dist/arguments.js.map +1 -1
  6. package/dist/constants.js +3 -5
  7. package/dist/constants.js.map +1 -1
  8. package/dist/mcp/prompts/check_development.md +123 -147
  9. package/dist/mcp/prompts/dependency_update.md +53 -40
  10. package/dist/mcp/prompts/fix_and_commit.md +17 -6
  11. package/dist/mcp/prompts/publish.md +22 -6
  12. package/dist/mcp/prompts/tree_fix_and_commit.md +16 -7
  13. package/dist/mcp/prompts/tree_publish.md +23 -6
  14. package/dist/mcp-server.js +4855 -227
  15. package/dist/mcp-server.js.map +4 -4
  16. package/dist/types.js +3 -0
  17. package/dist/types.js.map +1 -1
  18. package/guide/index.md +1 -0
  19. package/guide/mcp-configuration.md +649 -0
  20. package/package.json +15 -15
  21. package/.claude/settings.local.json +0 -12
  22. package/.gitignore~ +0 -23
  23. package/BUG_TREE_PUBLISH_CONFIG_DIR.md +0 -79
  24. package/input/250509-kodrdriv-library-rules.m4a +0 -0
  25. package/processed/250705-kodrdriv-confirm-editor-for-commit-and-release.m4a +0 -0
  26. package/processed/250705-kodrdriv-confirm-flag-release.m4a +0 -0
  27. package/processed/250705-kodrdriv-context-for-review.m4a +0 -0
  28. package/processed/250705-kodrdriv-feedback-on-publish-pipeline.m4a +0 -0
  29. package/processed/250705-kodrdriv-intelligent-eslint-style.m4a +0 -0
  30. package/processed/250705-kodrdriv-make-review-less-strict.m4a +0 -0
  31. package/processed/250705-kodrdriv-multilevel-transcription.m4a +0 -0
  32. package/processed/250705-kodrdriv-opinionated-review.m4a +0 -0
  33. package/processed/250705-kodrdriv-publish-next-version.m4a +0 -0
  34. package/processed/250705-kodrdriv-release-branches-and-milestones.m4a +0 -0
  35. package/processed/250705-kodrdriv-scope-check-fix-or-ignore.m4a +0 -0
  36. package/processed/250705-kodrdriv-scope-checker.m4a +0 -0
  37. package/processed/250705-kodrdriv-specify-a-release-note-for-publish.m4a +0 -0
  38. package/temp-dist/arguments.js +0 -817
  39. package/temp-dist/constants.js +0 -202
  40. package/temp-dist/logging.js +0 -130
  41. package/temp-dist/types.js +0 -112
  42. package/temp-dist/util/stdin.js +0 -132
  43. package/temp-dist/util/storage.js +0 -149
  44. package/temp-dist/util/validation.js +0 -110
@@ -1,817 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
- import { Command } from "commander";
3
- import path from "path";
4
- import { z } from "zod";
5
- import { ALLOWED_COMMANDS, DEFAULT_COMMAND, KODRDRIV_DEFAULTS, PROGRAM_NAME, VERSION } from "./constants";
6
- import { getLogger } from "./logging";
7
- import * as Storage from "./util/storage";
8
- import { safeJsonParse } from './util/validation';
9
- import { readStdin } from "./util/stdin";
10
- export const InputSchema = z.object({
11
- dryRun: z.boolean().optional(),
12
- verbose: z.boolean().optional(),
13
- debug: z.boolean().optional(),
14
- overrides: z.boolean().optional(),
15
- checkConfig: z.boolean().optional(),
16
- initConfig: z.boolean().optional(),
17
- model: z.string().optional(),
18
- contextDirectories: z.array(z.string()).optional(),
19
- configDir: z.string().optional(),
20
- outputDir: z.string().optional(),
21
- preferencesDir: z.string().optional(),
22
- cached: z.boolean().optional(),
23
- add: z.boolean().optional(),
24
- sendit: z.boolean().optional(),
25
- interactive: z.boolean().optional(),
26
- from: z.string().optional(),
27
- to: z.string().optional(),
28
- excludedPatterns: z.array(z.string()).optional(),
29
- excludedPaths: z.array(z.string()).optional(),
30
- context: z.string().optional(),
31
- note: z.string().optional(), // For review command positional argument/STDIN
32
- direction: z.string().optional(),
33
- messageLimit: z.number().optional(),
34
- skipFileCheck: z.boolean().optional(),
35
- maxDiffBytes: z.number().optional(),
36
- mergeMethod: z.enum(['merge', 'squash', 'rebase']).optional(),
37
- scopeRoots: z.string().optional(),
38
- startFrom: z.string().optional(),
39
- script: z.string().optional(),
40
- cmd: z.string().optional(),
41
- publish: z.boolean().optional(),
42
- parallel: z.boolean().optional(),
43
- continue: z.boolean().optional(),
44
- includeCommitHistory: z.boolean().optional(),
45
- includeRecentDiffs: z.boolean().optional(),
46
- editorTimeout: z.number().optional(),
47
- includeReleaseNotes: z.boolean().optional(),
48
- includeGithubIssues: z.boolean().optional(),
49
- commitHistoryLimit: z.number().optional(),
50
- diffHistoryLimit: z.number().optional(),
51
- releaseNotesLimit: z.number().optional(),
52
- githubIssuesLimit: z.number().optional(),
53
- file: z.string().optional(), // Audio file path for audio-commit and audio-review
54
- directory: z.string().optional(), // Add directory option at top level (for audio commands)
55
- directories: z.array(z.string()).optional(), // Add directories option at top level (for tree commands)
56
- keepTemp: z.boolean().optional(), // Keep temporary recording files
57
- });
58
- // Function to transform flat CLI args into nested Config structure
59
- export const transformCliArgs = (finalCliArgs, commandName) => {
60
- const transformedCliArgs = {};
61
- // Direct mappings from Input to Config
62
- if (finalCliArgs.dryRun !== undefined)
63
- transformedCliArgs.dryRun = finalCliArgs.dryRun;
64
- if (finalCliArgs.verbose !== undefined)
65
- transformedCliArgs.verbose = finalCliArgs.verbose;
66
- if (finalCliArgs.debug !== undefined)
67
- transformedCliArgs.debug = finalCliArgs.debug;
68
- if (finalCliArgs.overrides !== undefined)
69
- transformedCliArgs.overrides = finalCliArgs.overrides;
70
- if (finalCliArgs.model !== undefined)
71
- transformedCliArgs.model = finalCliArgs.model;
72
- if (finalCliArgs.contextDirectories !== undefined)
73
- transformedCliArgs.contextDirectories = finalCliArgs.contextDirectories;
74
- // Map configDir (CLI) to configDirectory (Cardigantime standard)
75
- if (finalCliArgs.configDir !== undefined)
76
- transformedCliArgs.configDirectory = finalCliArgs.configDir;
77
- // Map outputDir (CLI) to outputDirectory (Config standard)
78
- if (finalCliArgs.outputDir !== undefined)
79
- transformedCliArgs.outputDirectory = finalCliArgs.outputDir;
80
- // Map preferencesDir (CLI) to preferencesDirectory (Config standard)
81
- if (finalCliArgs.preferencesDir !== undefined)
82
- transformedCliArgs.preferencesDirectory = finalCliArgs.preferencesDir;
83
- // Nested mappings for 'commit' options
84
- if (finalCliArgs.cached !== undefined || finalCliArgs.sendit !== undefined || finalCliArgs.add !== undefined || finalCliArgs.skipFileCheck !== undefined || finalCliArgs.maxDiffBytes !== undefined || finalCliArgs.interactive !== undefined) {
85
- transformedCliArgs.commit = {};
86
- if (finalCliArgs.add !== undefined)
87
- transformedCliArgs.commit.add = finalCliArgs.add;
88
- if (finalCliArgs.cached !== undefined)
89
- transformedCliArgs.commit.cached = finalCliArgs.cached;
90
- if (finalCliArgs.sendit !== undefined)
91
- transformedCliArgs.commit.sendit = finalCliArgs.sendit;
92
- if (finalCliArgs.interactive !== undefined)
93
- transformedCliArgs.commit.interactive = finalCliArgs.interactive;
94
- if (finalCliArgs.messageLimit !== undefined)
95
- transformedCliArgs.commit.messageLimit = finalCliArgs.messageLimit;
96
- if (finalCliArgs.context !== undefined)
97
- transformedCliArgs.commit.context = finalCliArgs.context;
98
- if (finalCliArgs.direction !== undefined)
99
- transformedCliArgs.commit.direction = finalCliArgs.direction;
100
- if (finalCliArgs.skipFileCheck !== undefined)
101
- transformedCliArgs.commit.skipFileCheck = finalCliArgs.skipFileCheck;
102
- if (finalCliArgs.maxDiffBytes !== undefined)
103
- transformedCliArgs.commit.maxDiffBytes = finalCliArgs.maxDiffBytes;
104
- }
105
- // Nested mappings for 'audioCommit' options
106
- if (finalCliArgs.file !== undefined || finalCliArgs.keepTemp !== undefined) {
107
- transformedCliArgs.audioCommit = {};
108
- if (finalCliArgs.file !== undefined)
109
- transformedCliArgs.audioCommit.file = finalCliArgs.file;
110
- if (finalCliArgs.keepTemp !== undefined)
111
- transformedCliArgs.audioCommit.keepTemp = finalCliArgs.keepTemp;
112
- }
113
- // Nested mappings for 'release' options
114
- if (finalCliArgs.from !== undefined || finalCliArgs.to !== undefined || finalCliArgs.maxDiffBytes !== undefined || finalCliArgs.interactive !== undefined) {
115
- transformedCliArgs.release = {};
116
- if (finalCliArgs.from !== undefined)
117
- transformedCliArgs.release.from = finalCliArgs.from;
118
- if (finalCliArgs.to !== undefined)
119
- transformedCliArgs.release.to = finalCliArgs.to;
120
- if (finalCliArgs.context !== undefined)
121
- transformedCliArgs.release.context = finalCliArgs.context;
122
- if (finalCliArgs.interactive !== undefined)
123
- transformedCliArgs.release.interactive = finalCliArgs.interactive;
124
- if (finalCliArgs.messageLimit !== undefined)
125
- transformedCliArgs.release.messageLimit = finalCliArgs.messageLimit;
126
- if (finalCliArgs.maxDiffBytes !== undefined)
127
- transformedCliArgs.release.maxDiffBytes = finalCliArgs.maxDiffBytes;
128
- }
129
- // Nested mappings for 'publish' options
130
- if (finalCliArgs.mergeMethod !== undefined) {
131
- transformedCliArgs.publish = {};
132
- if (finalCliArgs.mergeMethod !== undefined)
133
- transformedCliArgs.publish.mergeMethod = finalCliArgs.mergeMethod;
134
- }
135
- // Nested mappings for 'link' and 'unlink' options (both use the same configuration)
136
- if (finalCliArgs.scopeRoots !== undefined) {
137
- transformedCliArgs.link = {};
138
- if (finalCliArgs.scopeRoots !== undefined) {
139
- try {
140
- transformedCliArgs.link.scopeRoots = safeJsonParse(finalCliArgs.scopeRoots, 'scopeRoots CLI argument');
141
- }
142
- catch (error) {
143
- throw new Error(`Invalid JSON for scope-roots: ${finalCliArgs.scopeRoots}`);
144
- }
145
- }
146
- }
147
- // Nested mappings for 'audio-review' options (only when it's not a tree command)
148
- if (commandName !== 'tree' && (finalCliArgs.includeCommitHistory !== undefined ||
149
- finalCliArgs.includeRecentDiffs !== undefined ||
150
- finalCliArgs.includeReleaseNotes !== undefined ||
151
- finalCliArgs.includeGithubIssues !== undefined ||
152
- finalCliArgs.commitHistoryLimit !== undefined ||
153
- finalCliArgs.diffHistoryLimit !== undefined ||
154
- finalCliArgs.releaseNotesLimit !== undefined ||
155
- finalCliArgs.githubIssuesLimit !== undefined ||
156
- finalCliArgs.file !== undefined ||
157
- finalCliArgs.directories !== undefined ||
158
- finalCliArgs.keepTemp !== undefined)) {
159
- transformedCliArgs.audioReview = {};
160
- if (finalCliArgs.includeCommitHistory !== undefined)
161
- transformedCliArgs.audioReview.includeCommitHistory = finalCliArgs.includeCommitHistory;
162
- if (finalCliArgs.includeRecentDiffs !== undefined)
163
- transformedCliArgs.audioReview.includeRecentDiffs = finalCliArgs.includeRecentDiffs;
164
- if (finalCliArgs.includeReleaseNotes !== undefined)
165
- transformedCliArgs.audioReview.includeReleaseNotes = finalCliArgs.includeReleaseNotes;
166
- if (finalCliArgs.includeGithubIssues !== undefined)
167
- transformedCliArgs.audioReview.includeGithubIssues = finalCliArgs.includeGithubIssues;
168
- if (finalCliArgs.commitHistoryLimit !== undefined)
169
- transformedCliArgs.audioReview.commitHistoryLimit = finalCliArgs.commitHistoryLimit;
170
- if (finalCliArgs.diffHistoryLimit !== undefined)
171
- transformedCliArgs.audioReview.diffHistoryLimit = finalCliArgs.diffHistoryLimit;
172
- if (finalCliArgs.releaseNotesLimit !== undefined)
173
- transformedCliArgs.audioReview.releaseNotesLimit = finalCliArgs.releaseNotesLimit;
174
- if (finalCliArgs.githubIssuesLimit !== undefined)
175
- transformedCliArgs.audioReview.githubIssuesLimit = finalCliArgs.githubIssuesLimit;
176
- if (finalCliArgs.context !== undefined)
177
- transformedCliArgs.audioReview.context = finalCliArgs.context;
178
- if (finalCliArgs.sendit !== undefined)
179
- transformedCliArgs.audioReview.sendit = finalCliArgs.sendit;
180
- if (finalCliArgs.file !== undefined)
181
- transformedCliArgs.audioReview.file = finalCliArgs.file;
182
- if (finalCliArgs.directory !== undefined)
183
- transformedCliArgs.audioReview.directory = finalCliArgs.directory;
184
- if (finalCliArgs.keepTemp !== undefined)
185
- transformedCliArgs.audioReview.keepTemp = finalCliArgs.keepTemp;
186
- }
187
- // Nested mappings for 'review' options
188
- if (finalCliArgs.includeCommitHistory !== undefined ||
189
- finalCliArgs.includeRecentDiffs !== undefined ||
190
- finalCliArgs.includeReleaseNotes !== undefined ||
191
- finalCliArgs.includeGithubIssues !== undefined ||
192
- finalCliArgs.commitHistoryLimit !== undefined ||
193
- finalCliArgs.diffHistoryLimit !== undefined ||
194
- finalCliArgs.releaseNotesLimit !== undefined ||
195
- finalCliArgs.githubIssuesLimit !== undefined ||
196
- finalCliArgs.context !== undefined ||
197
- finalCliArgs.sendit !== undefined ||
198
- finalCliArgs.note !== undefined) {
199
- transformedCliArgs.review = {};
200
- if (finalCliArgs.note !== undefined)
201
- transformedCliArgs.review.note = finalCliArgs.note;
202
- // Include optional review configuration options if specified
203
- if (finalCliArgs.includeCommitHistory !== undefined)
204
- transformedCliArgs.review.includeCommitHistory = finalCliArgs.includeCommitHistory;
205
- if (finalCliArgs.includeRecentDiffs !== undefined)
206
- transformedCliArgs.review.includeRecentDiffs = finalCliArgs.includeRecentDiffs;
207
- if (finalCliArgs.includeReleaseNotes !== undefined)
208
- transformedCliArgs.review.includeReleaseNotes = finalCliArgs.includeReleaseNotes;
209
- if (finalCliArgs.includeGithubIssues !== undefined)
210
- transformedCliArgs.review.includeGithubIssues = finalCliArgs.includeGithubIssues;
211
- if (finalCliArgs.commitHistoryLimit !== undefined)
212
- transformedCliArgs.review.commitHistoryLimit = finalCliArgs.commitHistoryLimit;
213
- if (finalCliArgs.diffHistoryLimit !== undefined)
214
- transformedCliArgs.review.diffHistoryLimit = finalCliArgs.diffHistoryLimit;
215
- if (finalCliArgs.releaseNotesLimit !== undefined)
216
- transformedCliArgs.review.releaseNotesLimit = finalCliArgs.releaseNotesLimit;
217
- if (finalCliArgs.githubIssuesLimit !== undefined)
218
- transformedCliArgs.review.githubIssuesLimit = finalCliArgs.githubIssuesLimit;
219
- if (finalCliArgs.context !== undefined)
220
- transformedCliArgs.review.context = finalCliArgs.context;
221
- if (finalCliArgs.sendit !== undefined)
222
- transformedCliArgs.review.sendit = finalCliArgs.sendit;
223
- if (finalCliArgs.editorTimeout !== undefined)
224
- transformedCliArgs.review.editorTimeout = finalCliArgs.editorTimeout;
225
- }
226
- // Nested mappings for 'tree' options (add when relevant args present)
227
- if (commandName === 'tree') {
228
- const treeExcludedPatterns = finalCliArgs.excludedPatterns || finalCliArgs.excludedPaths;
229
- const builtInCommand = finalCliArgs.builtInCommand;
230
- if (finalCliArgs.directory !== undefined || finalCliArgs.directories !== undefined || treeExcludedPatterns !== undefined || finalCliArgs.startFrom !== undefined || finalCliArgs.cmd !== undefined || finalCliArgs.parallel !== undefined || builtInCommand !== undefined || finalCliArgs.continue !== undefined) {
231
- transformedCliArgs.tree = {};
232
- if (finalCliArgs.directories !== undefined)
233
- transformedCliArgs.tree.directories = finalCliArgs.directories;
234
- else if (finalCliArgs.directory !== undefined)
235
- transformedCliArgs.tree.directories = [finalCliArgs.directory];
236
- if (treeExcludedPatterns !== undefined)
237
- transformedCliArgs.tree.excludedPatterns = treeExcludedPatterns;
238
- if (finalCliArgs.startFrom !== undefined)
239
- transformedCliArgs.tree.startFrom = finalCliArgs.startFrom;
240
- if (finalCliArgs.cmd !== undefined)
241
- transformedCliArgs.tree.cmd = finalCliArgs.cmd;
242
- if (finalCliArgs.parallel !== undefined)
243
- transformedCliArgs.tree.parallel = finalCliArgs.parallel;
244
- if (builtInCommand !== undefined)
245
- transformedCliArgs.tree.builtInCommand = builtInCommand;
246
- if (finalCliArgs.continue !== undefined)
247
- transformedCliArgs.tree.continue = finalCliArgs.continue;
248
- }
249
- }
250
- // Handle excluded patterns (Commander.js converts --excluded-paths to excludedPaths)
251
- const excludedPatterns = finalCliArgs.excludedPatterns || finalCliArgs.excludedPaths;
252
- if (excludedPatterns !== undefined)
253
- transformedCliArgs.excludedPatterns = excludedPatterns;
254
- // Note: openaiApiKey is handled separately via environment variable only
255
- return transformedCliArgs;
256
- };
257
- // Update configure signature to accept cardigantime
258
- export const configure = async (cardigantime) => {
259
- const logger = getLogger();
260
- let program = new Command();
261
- // Configure program basics
262
- program
263
- .name(PROGRAM_NAME)
264
- .summary('Create Intelligent Release Notes or Change Logs from Git')
265
- .description('Create Intelligent Release Notes or Change Logs from Git')
266
- .version(VERSION);
267
- // Let cardigantime add its arguments first
268
- program = await cardigantime.configure(program);
269
- // Check if --check-config is in process.argv early
270
- if (process.argv.includes('--check-config')) {
271
- // For check-config, use CardiganTime's built-in checkConfig method
272
- program.parse();
273
- const cliArgs = program.opts();
274
- // Transform the flat CLI args
275
- const transformedCliArgs = transformCliArgs(cliArgs);
276
- // Use CardiganTime's built-in checkConfig method which displays
277
- // hierarchical configuration information in a well-formatted way
278
- await cardigantime.checkConfig(transformedCliArgs);
279
- // Return minimal config for consistency, but main processing is done
280
- const config = await validateAndProcessOptions({});
281
- const secureConfig = await validateAndProcessSecureOptions();
282
- const commandConfig = { commandName: 'check-config' };
283
- return [config, secureConfig, commandConfig];
284
- }
285
- // Check if --init-config is in process.argv early
286
- if (process.argv.includes('--init-config')) {
287
- // For init-config, use CardiganTime's built-in generateConfig method
288
- program.parse();
289
- const cliArgs = program.opts();
290
- // Transform the flat CLI args
291
- const transformedCliArgs = transformCliArgs(cliArgs);
292
- // Use CardiganTime's built-in generateConfig method
293
- await cardigantime.generateConfig(transformedCliArgs.configDirectory || KODRDRIV_DEFAULTS.configDirectory);
294
- // Return minimal config for consistency, but main processing is done
295
- const config = await validateAndProcessOptions({});
296
- const secureConfig = await validateAndProcessSecureOptions();
297
- const commandConfig = { commandName: 'init-config' };
298
- return [config, secureConfig, commandConfig];
299
- }
300
- // Get CLI arguments using the new function
301
- const [finalCliArgs, commandConfig] = await getCliConfig(program);
302
- logger.silly('Loaded Command Line Options: %s', JSON.stringify(finalCliArgs, null, 2));
303
- // Transform the flat CLI args using the new function
304
- const transformedCliArgs = transformCliArgs(finalCliArgs, commandConfig.commandName);
305
- logger.silly('Transformed CLI Args for merging: %s', JSON.stringify(transformedCliArgs, null, 2));
306
- // Get values from config file using Cardigantime's hierarchical configuration
307
- const fileValues = await cardigantime.read(transformedCliArgs);
308
- // Merge configurations: Defaults -> File -> CLI
309
- // Properly merge the link section to preserve scope roots from config file
310
- const mergedLink = {
311
- ...KODRDRIV_DEFAULTS.link,
312
- ...fileValues.link,
313
- ...transformedCliArgs.link,
314
- };
315
- const partialConfig = {
316
- ...KODRDRIV_DEFAULTS, // Start with Kodrdriv defaults
317
- ...fileValues, // Apply file values (overwrites defaults)
318
- ...transformedCliArgs, // Apply CLI args last (highest precedence)
319
- link: mergedLink, // Override with properly merged link section
320
- }; // Cast to Partial<Config> initially
321
- // Specific validation and processing after merge
322
- const config = await validateAndProcessOptions(partialConfig);
323
- // Log effective configuration summary at verbose level
324
- logger.verbose('Configuration complete. Effective settings:');
325
- logger.verbose(` Command: ${commandConfig.commandName}`);
326
- logger.verbose(` Model: ${config.model}`);
327
- logger.verbose(` Dry run: ${config.dryRun}`);
328
- logger.verbose(` Debug: ${config.debug}`);
329
- logger.verbose(` Verbose: ${config.verbose}`);
330
- logger.verbose(` Config directory: ${config.configDirectory}`);
331
- logger.verbose(` Output directory: ${config.outputDirectory}`);
332
- logger.verbose(` Context directories: ${config.contextDirectories?.join(', ') || 'none'}`);
333
- if (config.excludedPatterns && config.excludedPatterns.length > 0) {
334
- logger.verbose(` Excluded patterns: ${config.excludedPatterns.join(', ')}`);
335
- }
336
- if (Object.keys(config.link?.scopeRoots || {}).length > 0) {
337
- logger.verbose(` Link scope roots: ${Object.keys(config.link.scopeRoots).join(', ')}`);
338
- }
339
- logger.silly('Final configuration: %s', JSON.stringify(config, null, 2));
340
- const secureConfig = await validateAndProcessSecureOptions();
341
- return [config, secureConfig, commandConfig];
342
- };
343
- // Function to handle CLI argument parsing and processing
344
- export async function getCliConfig(program) {
345
- const addSharedOptions = (command) => {
346
- command
347
- .option('--dry-run', 'perform a dry run without saving files') // Removed default, will be handled by merging
348
- .option('--verbose', 'enable verbose logging')
349
- .option('--debug', 'enable debug logging')
350
- .option('--overrides', 'enable overrides')
351
- .option('--model <model>', 'OpenAI model to use')
352
- .option('-d, --context-directories [contextDirectories...]', 'directories to scan for context')
353
- .option('--config-dir <configDir>', 'configuration directory') // Keep config-dir for specifying location
354
- .option('--output-dir <outputDir>', 'output directory for generated files')
355
- .option('--preferences-dir <preferencesDir>', 'preferences directory for personal settings')
356
- .option('--excluded-paths [excludedPatterns...]', 'paths to exclude from the diff')
357
- .option('--keep-temp', 'keep temporary recording files');
358
- };
359
- // Add global options to the main program
360
- // (cardigantime already adds most global options like --verbose, --debug, --config-dir)
361
- // Add subcommands
362
- const commitCommand = program
363
- .command('commit')
364
- .argument('[direction]', 'direction or guidance for the commit message')
365
- .description('Generate commit notes')
366
- .option('--context <context>', 'context for the commit message')
367
- .option('--cached', 'use cached diff')
368
- .option('--add', 'add all changes before committing')
369
- .option('--sendit', 'Commit with the message generated. No review.')
370
- .option('--interactive', 'Present commit message for interactive review and editing')
371
- .option('--message-limit <messageLimit>', 'limit the number of messages to generate')
372
- .option('--skip-file-check', 'skip check for file: dependencies before committing')
373
- .option('--max-diff-bytes <maxDiffBytes>', 'maximum bytes per file in diff (default: 2048)');
374
- // Add shared options to commit command
375
- addSharedOptions(commitCommand);
376
- // Customize help output for commit command
377
- commitCommand.configureHelp({
378
- formatHelp: (cmd, helper) => {
379
- const nameAndVersion = `${helper.commandUsage(cmd)}\n\n${helper.commandDescription(cmd)}\n`;
380
- const commitOptions = [
381
- ['--context <context>', 'context for the commit message']
382
- ];
383
- const behavioralOptions = [
384
- ['--cached', 'use cached diff'],
385
- ['--add', 'add all changes before committing'],
386
- ['--sendit', 'Commit with the message generated. No review.'],
387
- ['--interactive', 'Present commit message for interactive review and editing'],
388
- ['--message-limit <messageLimit>', 'limit the number of messages to generate']
389
- ];
390
- const globalOptions = [
391
- ['--dry-run', 'perform a dry run without saving files'],
392
- ['--verbose', 'enable verbose logging'],
393
- ['--debug', 'enable debug logging'],
394
- ['--overrides', 'enable overrides'],
395
- ['--model <model>', 'OpenAI model to use'],
396
- ['-d, --context-directories [contextDirectories...]', 'directories to scan for context'],
397
- ['--config-dir <configDir>', 'configuration directory'],
398
- ['--excluded-paths [excludedPatterns...]', 'paths to exclude from the diff'],
399
- ['-h, --help', 'display help for command']
400
- ];
401
- const formatOptionsSection = (title, options) => {
402
- const maxWidth = Math.max(...options.map(([flag]) => flag.length));
403
- return `${title}:\n` + options.map(([flag, desc]) => ` ${flag.padEnd(maxWidth + 2)} ${desc}`).join('\n') + '\n';
404
- };
405
- return nameAndVersion + '\n' +
406
- formatOptionsSection('Commit Message Options', commitOptions) + '\n' +
407
- formatOptionsSection('Behavioral Options', behavioralOptions) + '\n' +
408
- formatOptionsSection('Global Options', globalOptions) + '\n' +
409
- 'Environment Variables:\n' +
410
- ' OPENAI_API_KEY OpenAI API key (required)\n';
411
- }
412
- });
413
- const audioCommitCommand = program
414
- .command('audio-commit')
415
- .option('--cached', 'use cached diff')
416
- .option('--add', 'add all changes before committing')
417
- .option('--sendit', 'Commit with the message generated. No review.')
418
- .option('--direction <direction>', 'direction or guidance for the commit message')
419
- .option('--message-limit <messageLimit>', 'limit the number of messages to generate')
420
- .option('--file <file>', 'audio file path')
421
- .description('Record audio to provide context, then generate and optionally commit with AI-generated message');
422
- addSharedOptions(audioCommitCommand);
423
- const releaseCommand = program
424
- .command('release')
425
- .option('--from <from>', 'branch to generate release notes from')
426
- .option('--to <to>', 'branch to generate release notes to')
427
- .option('--context <context>', 'context for the commit message')
428
- .option('--interactive', 'Present release notes for interactive review and editing')
429
- .option('--max-diff-bytes <maxDiffBytes>', 'maximum bytes per file in diff (default: 2048)')
430
- .description('Generate release notes');
431
- addSharedOptions(releaseCommand);
432
- const publishCommand = program
433
- .command('publish')
434
- .option('--merge-method <method>', 'method to merge PR (merge, squash, rebase)', 'squash')
435
- .option('--sendit', 'skip all confirmation prompts and proceed automatically')
436
- .description('Publish a release');
437
- addSharedOptions(publishCommand);
438
- const treeCommand = program
439
- .command('tree [command]')
440
- .option('--directory <directory>', 'target directory containing multiple packages (defaults to current directory)')
441
- .option('--directories [directories...]', 'target directories containing multiple packages (defaults to current directory)')
442
- .option('--start-from <startFrom>', 'resume execution from this package directory name (useful for restarting failed builds)')
443
- .option('--cmd <cmd>', 'shell command to execute in each package directory (e.g., "npm install", "git status")')
444
- .option('--parallel', 'execute packages in parallel when dependencies allow (packages with no interdependencies run simultaneously)')
445
- .option('--excluded-patterns [excludedPatterns...]', 'patterns to exclude packages from processing (e.g., "**/node_modules/**", "dist/*")')
446
- .option('--continue', 'continue from previous tree publish execution')
447
- .description('Analyze package dependencies in workspace and execute commands in dependency order. Supports built-in commands: commit, publish, link, unlink');
448
- addSharedOptions(treeCommand);
449
- const linkCommand = program
450
- .command('link')
451
- .option('--scope-roots <scopeRoots>', 'JSON mapping of scopes to root directories (e.g., \'{"@company": "../"}\')')
452
- .description('Create npm file: dependencies for local development');
453
- addSharedOptions(linkCommand);
454
- const unlinkCommand = program
455
- .command('unlink')
456
- .option('--scope-roots <scopeRoots>', 'JSON mapping of scopes to root directories (e.g., \'{"@company": "../"}\')')
457
- .description('Restore original dependencies and rebuild node_modules');
458
- addSharedOptions(unlinkCommand);
459
- const audioReviewCommand = program
460
- .command('audio-review')
461
- .option('--include-commit-history', 'include recent commit log messages in context (default: true)')
462
- .option('--no-include-commit-history', 'exclude commit log messages from context')
463
- .option('--include-recent-diffs', 'include recent commit diffs in context (default: true)')
464
- .option('--no-include-recent-diffs', 'exclude recent diffs from context')
465
- .option('--include-release-notes', 'include recent release notes in context (default: false)')
466
- .option('--no-include-release-notes', 'exclude release notes from context')
467
- .option('--include-github-issues', 'include open GitHub issues in context (default: true)')
468
- .option('--no-include-github-issues', 'exclude GitHub issues from context')
469
- .option('--commit-history-limit <limit>', 'number of recent commits to include', parseInt)
470
- .option('--diff-history-limit <limit>', 'number of recent commit diffs to include', parseInt)
471
- .option('--release-notes-limit <limit>', 'number of recent release notes to include', parseInt)
472
- .option('--github-issues-limit <limit>', 'number of open GitHub issues to include (max 20)', parseInt)
473
- .option('--context <context>', 'additional context for the audio review')
474
- .option('--file <file>', 'audio file path')
475
- .option('--directory <directory>', 'directory containing audio files to process')
476
- .option('--max-recording-time <time>', 'maximum recording time in seconds', parseInt)
477
- .option('--sendit', 'Create GitHub issues automatically without confirmation')
478
- .description('Record audio, transcribe with Whisper, and analyze for project issues using AI');
479
- addSharedOptions(audioReviewCommand);
480
- const reviewCommand = program
481
- .command('review')
482
- .argument('[note]', 'review note to analyze for project issues')
483
- .option('--include-commit-history', 'include recent commit log messages in context (default: true)')
484
- .option('--no-include-commit-history', 'exclude commit log messages from context')
485
- .option('--include-recent-diffs', 'include recent commit diffs in context (default: true)')
486
- .option('--no-include-recent-diffs', 'exclude recent diffs from context')
487
- .option('--include-release-notes', 'include recent release notes in context (default: false)')
488
- .option('--no-include-release-notes', 'exclude release notes from context')
489
- .option('--include-github-issues', 'include open GitHub issues in context (default: true)')
490
- .option('--no-include-github-issues', 'exclude GitHub issues from context')
491
- .option('--commit-history-limit <limit>', 'number of recent commits to include', parseInt)
492
- .option('--diff-history-limit <limit>', 'number of recent commit diffs to include', parseInt)
493
- .option('--release-notes-limit <limit>', 'number of recent release notes to include', parseInt)
494
- .option('--github-issues-limit <limit>', 'number of open GitHub issues to include (max 20)', parseInt)
495
- .option('--context <context>', 'additional context for the review')
496
- .option('--sendit', 'Create GitHub issues automatically without confirmation')
497
- .option('--editor-timeout <timeout>', 'timeout for editor in milliseconds (default: no timeout)', parseInt)
498
- .description('Analyze review note for project issues using AI');
499
- addSharedOptions(reviewCommand);
500
- // Customize help output for review command
501
- reviewCommand.configureHelp({
502
- formatHelp: (cmd, helper) => {
503
- const nameAndVersion = `kodrdriv review [note] [options]\n\nAnalyze review note for project issues using AI\n`;
504
- const argumentsSection = [
505
- ['note', 'review note to analyze for project issues (can also be piped via STDIN)']
506
- ];
507
- const reviewOptions = [
508
- ['--context <context>', 'additional context for the review']
509
- ];
510
- const gitContextOptions = [
511
- ['--include-commit-history', 'include recent commit log messages in context (default: true)'],
512
- ['--no-include-commit-history', 'exclude commit log messages from context'],
513
- ['--include-recent-diffs', 'include recent commit diffs in context (default: true)'],
514
- ['--no-include-recent-diffs', 'exclude recent diffs from context'],
515
- ['--include-release-notes', 'include recent release notes in context (default: false)'],
516
- ['--no-include-release-notes', 'exclude release notes from context'],
517
- ['--include-github-issues', 'include open GitHub issues in context (default: true)'],
518
- ['--no-include-github-issues', 'exclude GitHub issues from context'],
519
- ['--commit-history-limit <limit>', 'number of recent commits to include'],
520
- ['--diff-history-limit <limit>', 'number of recent commit diffs to include'],
521
- ['--release-notes-limit <limit>', 'number of recent release notes to include'],
522
- ['--github-issues-limit <limit>', 'number of open GitHub issues to include (max 20)']
523
- ];
524
- const behavioralOptions = [
525
- ['--sendit', 'Create GitHub issues automatically without confirmation']
526
- ];
527
- const globalOptions = [
528
- ['--dry-run', 'perform a dry run without saving files'],
529
- ['--verbose', 'enable verbose logging'],
530
- ['--debug', 'enable debug logging'],
531
- ['--overrides', 'enable overrides'],
532
- ['--model <model>', 'OpenAI model to use'],
533
- ['-d, --context-directories [contextDirectories...]', 'directories to scan for context'],
534
- ['--config-dir <configDir>', 'configuration directory'],
535
- ['--output-dir <outputDir>', 'output directory for generated files'],
536
- ['--excluded-paths [excludedPatterns...]', 'paths to exclude from the diff'],
537
- ['-h, --help', 'display help for command']
538
- ];
539
- const formatOptionsSection = (title, options) => {
540
- const maxWidth = Math.max(...options.map(([flag]) => flag.length));
541
- return `${title}:\n` + options.map(([flag, desc]) => ` ${flag.padEnd(maxWidth + 2)} ${desc}`).join('\n') + '\n';
542
- };
543
- return nameAndVersion + '\n' +
544
- formatOptionsSection('Arguments', argumentsSection) + '\n' +
545
- formatOptionsSection('Options', reviewOptions) + '\n' +
546
- formatOptionsSection('Git Context Parameters', gitContextOptions) + '\n' +
547
- formatOptionsSection('Behavioral Options', behavioralOptions) + '\n' +
548
- formatOptionsSection('Global Options', globalOptions) + '\n' +
549
- 'Environment Variables:\n' +
550
- ' OPENAI_API_KEY OpenAI API key (required)\n';
551
- }
552
- });
553
- const cleanCommand = program
554
- .command('clean')
555
- .description('Remove the output directory and all generated files');
556
- addSharedOptions(cleanCommand);
557
- const selectAudioCommand = program
558
- .command('select-audio')
559
- .description('Interactively select and save audio device for recording');
560
- addSharedOptions(selectAudioCommand);
561
- program.parse();
562
- const cliArgs = program.opts(); // Get all opts initially
563
- // Determine which command is being run
564
- let commandName = DEFAULT_COMMAND;
565
- let commandOptions = {}; // Store specific command options
566
- if (program.args.length > 0) {
567
- commandName = program.args[0];
568
- validateCommand(commandName);
569
- }
570
- // Only proceed with command-specific options if validation passed
571
- if (ALLOWED_COMMANDS.includes(commandName)) {
572
- if (commandName === 'commit' && commitCommand.opts) {
573
- commandOptions = commitCommand.opts();
574
- // Handle positional argument for direction
575
- const args = commitCommand.args;
576
- if (args && args.length > 0 && args[0]) {
577
- commandOptions.direction = args[0];
578
- }
579
- // Check for STDIN input for direction (takes precedence over positional argument)
580
- const stdinInput = await readStdin();
581
- if (stdinInput) {
582
- commandOptions.direction = stdinInput;
583
- }
584
- }
585
- else if (commandName === 'audio-commit' && audioCommitCommand.opts) {
586
- commandOptions = audioCommitCommand.opts();
587
- }
588
- else if (commandName === 'release' && releaseCommand.opts) {
589
- commandOptions = releaseCommand.opts();
590
- }
591
- else if (commandName === 'publish' && publishCommand.opts) {
592
- commandOptions = publishCommand.opts();
593
- }
594
- else if (commandName === 'tree' && treeCommand.opts) {
595
- commandOptions = treeCommand.opts();
596
- // Handle positional argument for built-in command
597
- const args = treeCommand.args;
598
- if (args && args.length > 0 && args[0]) {
599
- // Store the built-in command for later processing
600
- commandOptions.builtInCommand = args[0];
601
- }
602
- }
603
- else if (commandName === 'link' && linkCommand.opts) {
604
- commandOptions = linkCommand.opts();
605
- }
606
- else if (commandName === 'unlink' && unlinkCommand.opts) {
607
- commandOptions = unlinkCommand.opts();
608
- }
609
- else if (commandName === 'audio-review' && audioReviewCommand.opts) {
610
- commandOptions = audioReviewCommand.opts();
611
- }
612
- else if (commandName === 'review' && reviewCommand.opts) {
613
- commandOptions = reviewCommand.opts();
614
- // Handle positional argument for note
615
- const args = reviewCommand.args;
616
- if (args && args.length > 0 && args[0]) {
617
- commandOptions.note = args[0];
618
- }
619
- // Check for STDIN input for note (takes precedence over positional argument)
620
- const stdinInput = await readStdin();
621
- if (stdinInput) {
622
- commandOptions.note = stdinInput;
623
- }
624
- }
625
- else if (commandName === 'clean' && cleanCommand.opts) {
626
- commandOptions = cleanCommand.opts();
627
- }
628
- else if (commandName === 'select-audio' && selectAudioCommand.opts) {
629
- commandOptions = selectAudioCommand.opts();
630
- }
631
- }
632
- // Include command name in CLI args for merging
633
- const finalCliArgs = { ...cliArgs, ...commandOptions };
634
- const commandConfig = { commandName };
635
- // Debug logging to help troubleshoot configuration issues
636
- const logger = getLogger();
637
- logger.debug('CLI parsing debug:');
638
- logger.debug(' cliArgs:', JSON.stringify(cliArgs, null, 2));
639
- logger.debug(' commandOptions:', JSON.stringify(commandOptions, null, 2));
640
- logger.debug(' finalCliArgs:', JSON.stringify(finalCliArgs, null, 2));
641
- logger.debug(' commandName:', commandName);
642
- return [finalCliArgs, commandConfig];
643
- }
644
- export async function validateAndProcessSecureOptions() {
645
- // For check-config and init-config commands, we don't want to throw an error for missing API key
646
- const isCheckConfig = process.argv.includes('--check-config');
647
- const isInitConfig = process.argv.includes('--init-config');
648
- if (!process.env.OPENAI_API_KEY && !isCheckConfig && !isInitConfig) {
649
- throw new Error('OpenAI API key is required. Please set the OPENAI_API_KEY environment variable.');
650
- }
651
- // Prefer CLI key if provided, otherwise use env var (might be undefined for check-config/init-config)
652
- const openaiApiKey = process.env.OPENAI_API_KEY;
653
- const secureConfig = {
654
- openaiApiKey: openaiApiKey,
655
- };
656
- return secureConfig;
657
- }
658
- // Renamed validation function to reflect its broader role
659
- export async function validateAndProcessOptions(options) {
660
- const contextDirectories = await validateContextDirectories(options.contextDirectories || KODRDRIV_DEFAULTS.contextDirectories);
661
- const configDir = options.configDirectory || KODRDRIV_DEFAULTS.configDirectory;
662
- // Skip config directory validation since Cardigantime handles hierarchical lookup
663
- // Ensure all required fields are present and have correct types after merging
664
- const finalConfig = {
665
- dryRun: options.dryRun ?? KODRDRIV_DEFAULTS.dryRun,
666
- verbose: options.verbose ?? KODRDRIV_DEFAULTS.verbose,
667
- debug: options.debug ?? KODRDRIV_DEFAULTS.debug,
668
- overrides: options.overrides ?? KODRDRIV_DEFAULTS.overrides,
669
- model: options.model ?? KODRDRIV_DEFAULTS.model,
670
- contextDirectories: contextDirectories,
671
- configDirectory: configDir,
672
- outputDirectory: options.outputDirectory ?? KODRDRIV_DEFAULTS.outputDirectory,
673
- preferencesDirectory: options.preferencesDirectory ?? KODRDRIV_DEFAULTS.preferencesDirectory,
674
- // Cardigantime-specific properties (from fileValues or defaults)
675
- discoveredConfigDirs: options.discoveredConfigDirs ?? [],
676
- resolvedConfigDirs: options.resolvedConfigDirs ?? [],
677
- // Command-specific options with defaults
678
- commit: {
679
- add: options.commit?.add ?? KODRDRIV_DEFAULTS.commit.add,
680
- cached: options.commit?.cached ?? KODRDRIV_DEFAULTS.commit.cached, // Might be undefined if not commit command
681
- sendit: options.commit?.sendit ?? KODRDRIV_DEFAULTS.commit.sendit,
682
- interactive: options.commit?.interactive ?? KODRDRIV_DEFAULTS.commit.interactive,
683
- messageLimit: options.commit?.messageLimit ?? KODRDRIV_DEFAULTS.commit.messageLimit,
684
- context: options.commit?.context,
685
- direction: options.commit?.direction,
686
- skipFileCheck: options.commit?.skipFileCheck ?? KODRDRIV_DEFAULTS.commit.skipFileCheck,
687
- maxDiffBytes: options.commit?.maxDiffBytes ?? KODRDRIV_DEFAULTS.commit.maxDiffBytes,
688
- },
689
- audioCommit: {
690
- maxRecordingTime: options.audioCommit?.maxRecordingTime ?? KODRDRIV_DEFAULTS.audioCommit.maxRecordingTime,
691
- audioDevice: options.audioCommit?.audioDevice ?? KODRDRIV_DEFAULTS.audioCommit.audioDevice,
692
- file: options.audioCommit?.file,
693
- keepTemp: options.audioCommit?.keepTemp,
694
- },
695
- release: {
696
- from: options.release?.from ?? KODRDRIV_DEFAULTS.release.from,
697
- to: options.release?.to ?? KODRDRIV_DEFAULTS.release.to,
698
- messageLimit: options.release?.messageLimit ?? KODRDRIV_DEFAULTS.release.messageLimit,
699
- context: options.release?.context,
700
- interactive: options.release?.interactive ?? KODRDRIV_DEFAULTS.release.interactive,
701
- maxDiffBytes: options.release?.maxDiffBytes ?? KODRDRIV_DEFAULTS.release.maxDiffBytes,
702
- },
703
- audioReview: {
704
- includeCommitHistory: options.audioReview?.includeCommitHistory ?? KODRDRIV_DEFAULTS.audioReview.includeCommitHistory,
705
- includeRecentDiffs: options.audioReview?.includeRecentDiffs ?? KODRDRIV_DEFAULTS.audioReview.includeRecentDiffs,
706
- includeReleaseNotes: options.audioReview?.includeReleaseNotes ?? KODRDRIV_DEFAULTS.audioReview.includeReleaseNotes,
707
- includeGithubIssues: options.audioReview?.includeGithubIssues ?? KODRDRIV_DEFAULTS.audioReview.includeGithubIssues,
708
- commitHistoryLimit: options.audioReview?.commitHistoryLimit ?? KODRDRIV_DEFAULTS.audioReview.commitHistoryLimit,
709
- diffHistoryLimit: options.audioReview?.diffHistoryLimit ?? KODRDRIV_DEFAULTS.audioReview.diffHistoryLimit,
710
- releaseNotesLimit: options.audioReview?.releaseNotesLimit ?? KODRDRIV_DEFAULTS.audioReview.releaseNotesLimit,
711
- githubIssuesLimit: options.audioReview?.githubIssuesLimit ?? KODRDRIV_DEFAULTS.audioReview.githubIssuesLimit,
712
- context: options.audioReview?.context,
713
- sendit: options.audioReview?.sendit ?? KODRDRIV_DEFAULTS.audioReview.sendit,
714
- maxRecordingTime: options.audioReview?.maxRecordingTime ?? KODRDRIV_DEFAULTS.audioReview.maxRecordingTime,
715
- audioDevice: options.audioReview?.audioDevice ?? KODRDRIV_DEFAULTS.audioReview.audioDevice,
716
- file: options.audioReview?.file,
717
- directory: options.audioReview?.directory,
718
- keepTemp: options.audioReview?.keepTemp,
719
- },
720
- review: {
721
- includeCommitHistory: options.review?.includeCommitHistory ?? KODRDRIV_DEFAULTS.review.includeCommitHistory,
722
- includeRecentDiffs: options.review?.includeRecentDiffs ?? KODRDRIV_DEFAULTS.review.includeRecentDiffs,
723
- includeReleaseNotes: options.review?.includeReleaseNotes ?? KODRDRIV_DEFAULTS.review.includeReleaseNotes,
724
- includeGithubIssues: options.review?.includeGithubIssues ?? KODRDRIV_DEFAULTS.review.includeGithubIssues,
725
- commitHistoryLimit: options.review?.commitHistoryLimit ?? KODRDRIV_DEFAULTS.review.commitHistoryLimit,
726
- diffHistoryLimit: options.review?.diffHistoryLimit ?? KODRDRIV_DEFAULTS.review.diffHistoryLimit,
727
- releaseNotesLimit: options.review?.releaseNotesLimit ?? KODRDRIV_DEFAULTS.review.releaseNotesLimit,
728
- githubIssuesLimit: options.review?.githubIssuesLimit ?? KODRDRIV_DEFAULTS.review.githubIssuesLimit,
729
- context: options.review?.context,
730
- sendit: options.review?.sendit ?? KODRDRIV_DEFAULTS.review.sendit,
731
- note: options.review?.note,
732
- editorTimeout: options.review?.editorTimeout,
733
- },
734
- publish: {
735
- mergeMethod: options.publish?.mergeMethod ?? KODRDRIV_DEFAULTS.publish.mergeMethod,
736
- dependencyUpdatePatterns: options.publish?.dependencyUpdatePatterns,
737
- requiredEnvVars: options.publish?.requiredEnvVars ?? KODRDRIV_DEFAULTS.publish.requiredEnvVars,
738
- linkWorkspacePackages: options.publish?.linkWorkspacePackages ?? KODRDRIV_DEFAULTS.publish.linkWorkspacePackages,
739
- unlinkWorkspacePackages: options.publish?.unlinkWorkspacePackages ?? KODRDRIV_DEFAULTS.publish.unlinkWorkspacePackages,
740
- sendit: options.publish?.sendit ?? KODRDRIV_DEFAULTS.publish.sendit,
741
- },
742
- link: {
743
- scopeRoots: options.link?.scopeRoots ?? KODRDRIV_DEFAULTS.link.scopeRoots,
744
- dryRun: options.link?.dryRun ?? KODRDRIV_DEFAULTS.link.dryRun,
745
- },
746
- tree: {
747
- directories: options.tree?.directories ?? KODRDRIV_DEFAULTS.tree.directories,
748
- excludedPatterns: options.tree?.excludedPatterns ?? KODRDRIV_DEFAULTS.tree.excludedPatterns,
749
- startFrom: options.tree?.startFrom ?? KODRDRIV_DEFAULTS.tree.startFrom,
750
- cmd: options.tree?.cmd ?? KODRDRIV_DEFAULTS.tree.cmd,
751
- parallel: options.tree?.parallel ?? KODRDRIV_DEFAULTS.tree.parallel,
752
- builtInCommand: options.tree?.builtInCommand ?? KODRDRIV_DEFAULTS.tree.builtInCommand,
753
- continue: options.tree?.continue ?? KODRDRIV_DEFAULTS.tree.continue,
754
- },
755
- excludedPatterns: options.excludedPatterns ?? KODRDRIV_DEFAULTS.excludedPatterns,
756
- };
757
- // Final validation against the MainConfig shape (optional, cardigantime might handle it)
758
- // You could potentially use ConfigShape.parse(finalConfig) here if needed
759
- return finalConfig;
760
- }
761
- // Export for testing
762
- export function validateCommand(commandName) {
763
- if (!ALLOWED_COMMANDS.includes(commandName)) {
764
- throw new Error(`Invalid command: ${commandName}, allowed commands: ${ALLOWED_COMMANDS.join(', ')}`);
765
- }
766
- return commandName;
767
- }
768
- export async function validateConfigDir(configDir) {
769
- const logger = getLogger();
770
- const storage = Storage.create({ log: logger.info });
771
- // Make sure the config directory is absolute
772
- const absoluteConfigDir = path.isAbsolute(configDir) ?
773
- configDir :
774
- path.resolve(process.cwd(), configDir);
775
- try {
776
- // Check if the path exists
777
- if (!(await storage.exists(absoluteConfigDir))) {
778
- // Directory doesn't exist, warn and fall back to defaults
779
- logger.warn(`Config directory does not exist: ${absoluteConfigDir}. Using default configuration.`);
780
- return absoluteConfigDir; // Return the path anyway, app will use defaults
781
- }
782
- // Path exists, check if it's a directory
783
- if (!(await storage.isDirectory(absoluteConfigDir))) {
784
- throw new Error(`Config directory is not a directory: ${absoluteConfigDir}`);
785
- }
786
- // Check if it's writable
787
- if (!(await storage.isDirectoryWritable(absoluteConfigDir))) {
788
- throw new Error(`Config directory is not writable: ${absoluteConfigDir}`);
789
- }
790
- }
791
- catch (error) {
792
- logger.error(`Failed to validate config directory: ${absoluteConfigDir}`, error);
793
- throw new Error(`Failed to validate config directory: ${absoluteConfigDir}: ${error.message}`);
794
- }
795
- return absoluteConfigDir;
796
- }
797
- // Export for testing
798
- export async function validateContextDirectories(contextDirectories) {
799
- const logger = getLogger();
800
- const storage = Storage.create({ log: logger.info });
801
- // Filter out directories that don't exist
802
- const validDirectories = [];
803
- for (const dir of contextDirectories) {
804
- try {
805
- if (await storage.isDirectoryReadable(dir)) {
806
- validDirectories.push(dir);
807
- }
808
- else {
809
- logger.warn(`Directory not readable: ${dir}`);
810
- }
811
- }
812
- catch (error) {
813
- logger.warn(`Error validating directory ${dir}: ${error.message}`);
814
- }
815
- }
816
- return validDirectories;
817
- }