@eldrforge/core 0.1.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 (41) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +59 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +2748 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/src/constants.d.ts +235 -0
  7. package/dist/src/constants.d.ts.map +1 -0
  8. package/dist/src/content/diff.d.ts +25 -0
  9. package/dist/src/content/diff.d.ts.map +1 -0
  10. package/dist/src/content/files.d.ts +10 -0
  11. package/dist/src/content/files.d.ts.map +1 -0
  12. package/dist/src/content/log.d.ts +11 -0
  13. package/dist/src/content/log.d.ts.map +1 -0
  14. package/dist/src/index.d.ts +17 -0
  15. package/dist/src/index.d.ts.map +1 -0
  16. package/dist/src/logging.d.ts +18 -0
  17. package/dist/src/logging.d.ts.map +1 -0
  18. package/dist/src/types.d.ts +1343 -0
  19. package/dist/src/types.d.ts.map +1 -0
  20. package/dist/src/util/aiAdapter.d.ts +7 -0
  21. package/dist/src/util/aiAdapter.d.ts.map +1 -0
  22. package/dist/src/util/errorHandler.d.ts +34 -0
  23. package/dist/src/util/errorHandler.d.ts.map +1 -0
  24. package/dist/src/util/fileLock.d.ts +63 -0
  25. package/dist/src/util/fileLock.d.ts.map +1 -0
  26. package/dist/src/util/general.d.ts +83 -0
  27. package/dist/src/util/general.d.ts.map +1 -0
  28. package/dist/src/util/gitMutex.d.ts +66 -0
  29. package/dist/src/util/gitMutex.d.ts.map +1 -0
  30. package/dist/src/util/interactive.d.ts +36 -0
  31. package/dist/src/util/interactive.d.ts.map +1 -0
  32. package/dist/src/util/loggerAdapter.d.ts +6 -0
  33. package/dist/src/util/loggerAdapter.d.ts.map +1 -0
  34. package/dist/src/util/stopContext.d.ts +27 -0
  35. package/dist/src/util/stopContext.d.ts.map +1 -0
  36. package/dist/src/util/storageAdapter.d.ts +8 -0
  37. package/dist/src/util/storageAdapter.d.ts.map +1 -0
  38. package/dist/src/util/validation.d.ts +21 -0
  39. package/dist/src/util/validation.d.ts.map +1 -0
  40. package/guide/index.md +64 -0
  41. package/package.json +74 -0
package/dist/index.js ADDED
@@ -0,0 +1,2748 @@
1
+ import { ExitError, createStorage, incrementPatchVersion, validateVersionString, convertToReleaseVersion, incrementPrereleaseVersion, UserCancellationError, CommandError } from '@eldrforge/shared';
2
+ export { calculateTargetVersion, convertToReleaseVersion, deepMerge, incrementMajorVersion, incrementMinorVersion, incrementPatchVersion, incrementPrereleaseVersion, stringifyJSON, validateVersionString } from '@eldrforge/shared';
3
+ import winston from 'winston';
4
+ import * as fs from 'fs';
5
+ import { statSync } from 'fs';
6
+ import * as path from 'path';
7
+ import path__default from 'path';
8
+ import * as os from 'os';
9
+ import os__default from 'os';
10
+ import { run } from '@eldrforge/git-tools';
11
+ import { glob } from 'glob';
12
+ import { STANDARD_CHOICES, SecureTempFile, cleanupTempFile, createSecureTempFile, editContentInEditor, getLLMFeedbackInEditor, getUserChoice, getUserTextInput, requireTTY } from '@eldrforge/ai-service';
13
+ export { STANDARD_CHOICES, SecureTempFile, cleanupTempFile, createSecureTempFile, editContentInEditor, getLLMFeedbackInEditor, getUserChoice, getUserTextInput, requireTTY } from '@eldrforge/ai-service';
14
+ import { execSync } from 'child_process';
15
+ import { z } from 'zod';
16
+
17
+ const VERSION = '__VERSION__ (__GIT_BRANCH__/__GIT_COMMIT__ __GIT_TAGS__ __GIT_COMMIT_DATE__) __SYSTEM_INFO__';
18
+ const PROGRAM_NAME = 'kodrdriv';
19
+ const DEFAULT_CHARACTER_ENCODING = 'utf-8';
20
+ const DEFAULT_BINARY_TO_TEXT_ENCODING = 'base64';
21
+ const DEFAULT_DIFF = true;
22
+ const DEFAULT_LOG = false;
23
+ const DEFAULT_OVERRIDES = false;
24
+ const DATE_FORMAT_MONTH_DAY = 'MM-DD';
25
+ const DATE_FORMAT_YEAR = 'YYYY';
26
+ const DATE_FORMAT_YEAR_MONTH = 'YYYY-MM';
27
+ const DATE_FORMAT_YEAR_MONTH_DAY = 'YYYY-MM-DD';
28
+ const DATE_FORMAT_YEAR_MONTH_DAY_SLASH = 'YYYY/MM/DD';
29
+ const DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES = 'YYYY-MM-DD-HHmm';
30
+ const DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS = 'YYYY-MM-DD-HHmmss';
31
+ const DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS_MILLISECONDS = 'YYYY-MM-DD-HHmmss.SSS';
32
+ const DATE_FORMAT_SHORT_TIMESTAMP = 'YYMMdd-HHmm';
33
+ const DATE_FORMAT_MONTH = 'MM';
34
+ const DATE_FORMAT_DAY = 'DD';
35
+ const DATE_FORMAT_HOURS = 'HHmm';
36
+ const DATE_FORMAT_MINUTES = 'mm';
37
+ const DATE_FORMAT_SECONDS = 'ss';
38
+ const DATE_FORMAT_MILLISECONDS = 'SSS';
39
+ const DEFAULT_VERBOSE = false;
40
+ const DEFAULT_DRY_RUN = false;
41
+ const DEFAULT_DEBUG = false;
42
+ const DEFAULT_MODEL = 'gpt-4o-mini';
43
+ const DEFAULT_MODEL_STRONG = 'gpt-4o';
44
+ const DEFAULT_OPENAI_REASONING = 'low';
45
+ const DEFAULT_OPENAI_MAX_OUTPUT_TOKENS = 10000;
46
+ const DEFAULT_OUTPUT_DIRECTORY = 'output/kodrdriv';
47
+ // Buffer size for git commands that may produce large output (like git log)
48
+ const DEFAULT_GIT_COMMAND_MAX_BUFFER = 50 * 1024 * 1024; // 50MB
49
+ const DEFAULT_CONTEXT_DIRECTORIES = [];
50
+ const DEFAULT_CONFIG_DIR = '.kodrdriv';
51
+ const DEFAULT_PREFERENCES_DIRECTORY = path__default.join(os__default.homedir(), '.kodrdriv');
52
+ const DEFAULT_FROM_COMMIT_ALIAS = 'main';
53
+ const DEFAULT_TO_COMMIT_ALIAS = 'HEAD';
54
+ const DEFAULT_ADD = false;
55
+ const DEFAULT_CACHED = false;
56
+ const DEFAULT_SENDIT_MODE = false;
57
+ const DEFAULT_INTERACTIVE_MODE = false;
58
+ const DEFAULT_AMEND_MODE = false;
59
+ // CRITICAL: Keep this low (3-5) to prevent log context contamination.
60
+ // The LLM tends to pattern-match against recent commits instead of describing
61
+ // the actual diff when it sees too much commit history. Set to 0 to disable.
62
+ const DEFAULT_MESSAGE_LIMIT = 3;
63
+ const DEFAULT_MAX_DIFF_BYTES = 20480; // 20KB limit per file
64
+ const DEFAULT_MERGE_METHOD = 'squash';
65
+ const DEFAULT_EXCLUDED_PATTERNS = [
66
+ 'node_modules',
67
+ 'package-lock.json',
68
+ 'yarn.lock',
69
+ 'bun.lockb',
70
+ 'composer.lock',
71
+ 'Cargo.lock',
72
+ 'Gemfile.lock',
73
+ 'dist',
74
+ 'build',
75
+ 'out',
76
+ '.next',
77
+ '.nuxt',
78
+ 'coverage',
79
+ '.vscode',
80
+ '.idea',
81
+ '.DS_Store',
82
+ '.git',
83
+ '.gitignore',
84
+ 'logs',
85
+ 'tmp',
86
+ '.cache',
87
+ '*.log',
88
+ '.env',
89
+ '.env.*',
90
+ '*.pem',
91
+ '*.crt',
92
+ '*.key',
93
+ '*.sqlite',
94
+ '*.db',
95
+ '*.zip',
96
+ '*.tar',
97
+ '*.gz',
98
+ '*.exe',
99
+ '*.bin'
100
+ ];
101
+ const COMMAND_COMMIT = 'commit';
102
+ const COMMAND_AUDIO_COMMIT = 'audio-commit';
103
+ const COMMAND_SELECT_AUDIO = 'select-audio';
104
+ const COMMAND_RELEASE = 'release';
105
+ const COMMAND_REVIEW = 'review';
106
+ const COMMAND_AUDIO_REVIEW = 'audio-review';
107
+ const COMMAND_PUBLISH = 'publish';
108
+ const COMMAND_TREE = 'tree';
109
+ const COMMAND_LINK = 'link';
110
+ const COMMAND_UNLINK = 'unlink';
111
+ const COMMAND_CLEAN = 'clean';
112
+ const COMMAND_PRECOMMIT = 'precommit';
113
+ const COMMAND_DEVELOPMENT = 'development';
114
+ const COMMAND_VERSIONS = 'versions';
115
+ const COMMAND_UPDATES = 'updates';
116
+ const COMMAND_CHECK_CONFIG = 'check-config';
117
+ const COMMAND_INIT_CONFIG = 'init-config';
118
+ const ALLOWED_COMMANDS = [
119
+ COMMAND_COMMIT,
120
+ COMMAND_AUDIO_COMMIT,
121
+ COMMAND_SELECT_AUDIO,
122
+ COMMAND_RELEASE,
123
+ COMMAND_REVIEW,
124
+ COMMAND_AUDIO_REVIEW,
125
+ COMMAND_PUBLISH,
126
+ COMMAND_TREE,
127
+ COMMAND_LINK,
128
+ COMMAND_UNLINK,
129
+ COMMAND_CLEAN,
130
+ COMMAND_PRECOMMIT,
131
+ COMMAND_DEVELOPMENT,
132
+ COMMAND_VERSIONS,
133
+ COMMAND_UPDATES
134
+ ];
135
+ const DEFAULT_COMMAND = COMMAND_COMMIT;
136
+ const DEFAULT_INSTRUCTIONS_DIR = `instructions`;
137
+ const DEFAULT_PERSONA_DIR = `personas`;
138
+ const DEFAULT_INSTRUCTIONS_COMMIT_FILE = `${DEFAULT_INSTRUCTIONS_DIR}/commit.md`;
139
+ const DEFAULT_INSTRUCTIONS_RELEASE_FILE = `${DEFAULT_INSTRUCTIONS_DIR}/release.md`;
140
+ const DEFAULT_INSTRUCTIONS_REVIEW_FILE = `${DEFAULT_INSTRUCTIONS_DIR}/review.md`;
141
+ const DEFAULT_PERSONA_RELEASER_FILE = `${DEFAULT_PERSONA_DIR}/releaser.md`;
142
+ const DEFAULT_PERSONA_YOU_FILE = `${DEFAULT_PERSONA_DIR}/you.md`;
143
+ // Default instructions for each persona
144
+ const DEFAULT_INSTRUCTIONS_MAP = {
145
+ [COMMAND_COMMIT]: DEFAULT_INSTRUCTIONS_COMMIT_FILE,
146
+ [COMMAND_AUDIO_COMMIT]: DEFAULT_INSTRUCTIONS_COMMIT_FILE,
147
+ [COMMAND_RELEASE]: DEFAULT_INSTRUCTIONS_RELEASE_FILE,
148
+ [COMMAND_REVIEW]: DEFAULT_INSTRUCTIONS_REVIEW_FILE,
149
+ [COMMAND_AUDIO_REVIEW]: DEFAULT_INSTRUCTIONS_REVIEW_FILE
150
+ };
151
+ // Default personas for each command
152
+ const DEFAULT_PERSONA_MAP = {
153
+ [COMMAND_COMMIT]: DEFAULT_PERSONA_YOU_FILE,
154
+ [COMMAND_AUDIO_COMMIT]: DEFAULT_PERSONA_YOU_FILE,
155
+ [COMMAND_RELEASE]: DEFAULT_PERSONA_RELEASER_FILE,
156
+ [COMMAND_REVIEW]: DEFAULT_PERSONA_YOU_FILE,
157
+ [COMMAND_AUDIO_REVIEW]: DEFAULT_PERSONA_YOU_FILE
158
+ };
159
+ // Used by child process to create paths
160
+ const DEFAULT_PATH_SEPARATOR = '/';
161
+ // Used by util/general for file filtering
162
+ const DEFAULT_IGNORE_PATTERNS = [
163
+ 'node_modules/**',
164
+ '**/*.log',
165
+ '.git/**',
166
+ 'dist/**',
167
+ 'build/**',
168
+ 'coverage/**',
169
+ 'output/**',
170
+ '.DS_Store',
171
+ '*.tmp',
172
+ '*.cache',
173
+ '**/.kodrdriv-*.json'
174
+ ];
175
+ // Used by util/storage for directory names
176
+ const DEFAULT_DIRECTORY_PREFIX = '.kodrdriv';
177
+ // Used by other commands but not exposed in CLI
178
+ const INTERNAL_DEFAULT_OUTPUT_FILE = 'output.txt';
179
+ const INTERNAL_DATETIME_FORMAT = 'YYYY-MM-DD_HH-mm-ss';
180
+ // Define defaults in one place
181
+ const KODRDRIV_DEFAULTS = {
182
+ dryRun: DEFAULT_DRY_RUN,
183
+ verbose: DEFAULT_VERBOSE,
184
+ debug: DEFAULT_DEBUG,
185
+ overrides: DEFAULT_OVERRIDES,
186
+ model: DEFAULT_MODEL,
187
+ openaiReasoning: DEFAULT_OPENAI_REASONING,
188
+ openaiMaxOutputTokens: DEFAULT_OPENAI_MAX_OUTPUT_TOKENS,
189
+ contextDirectories: DEFAULT_CONTEXT_DIRECTORIES,
190
+ commandName: DEFAULT_COMMAND,
191
+ configDirectory: DEFAULT_CONFIG_DIR,
192
+ outputDirectory: DEFAULT_OUTPUT_DIRECTORY,
193
+ preferencesDirectory: DEFAULT_PREFERENCES_DIRECTORY,
194
+ commit: {
195
+ add: DEFAULT_ADD,
196
+ cached: DEFAULT_CACHED,
197
+ sendit: DEFAULT_SENDIT_MODE,
198
+ interactive: DEFAULT_INTERACTIVE_MODE,
199
+ amend: DEFAULT_AMEND_MODE,
200
+ messageLimit: DEFAULT_MESSAGE_LIMIT,
201
+ skipFileCheck: false,
202
+ maxDiffBytes: DEFAULT_MAX_DIFF_BYTES,
203
+ contextFiles: undefined,
204
+ openaiReasoning: DEFAULT_OPENAI_REASONING,
205
+ openaiMaxOutputTokens: DEFAULT_OPENAI_MAX_OUTPUT_TOKENS
206
+ },
207
+ release: {
208
+ from: DEFAULT_FROM_COMMIT_ALIAS,
209
+ to: DEFAULT_TO_COMMIT_ALIAS,
210
+ messageLimit: DEFAULT_MESSAGE_LIMIT,
211
+ interactive: DEFAULT_INTERACTIVE_MODE,
212
+ maxDiffBytes: DEFAULT_MAX_DIFF_BYTES,
213
+ contextFiles: undefined,
214
+ noMilestones: false,
215
+ openaiReasoning: DEFAULT_OPENAI_REASONING,
216
+ openaiMaxOutputTokens: DEFAULT_OPENAI_MAX_OUTPUT_TOKENS
217
+ },
218
+ audioCommit: {
219
+ maxRecordingTime: 300,
220
+ audioDevice: undefined,
221
+ openaiReasoning: DEFAULT_OPENAI_REASONING,
222
+ openaiMaxOutputTokens: DEFAULT_OPENAI_MAX_OUTPUT_TOKENS
223
+ },
224
+ review: {
225
+ includeCommitHistory: true,
226
+ includeRecentDiffs: true,
227
+ includeReleaseNotes: false,
228
+ includeGithubIssues: true,
229
+ commitHistoryLimit: 10,
230
+ diffHistoryLimit: 5,
231
+ releaseNotesLimit: 3,
232
+ githubIssuesLimit: 20,
233
+ sendit: DEFAULT_SENDIT_MODE,
234
+ openaiReasoning: DEFAULT_OPENAI_REASONING,
235
+ openaiMaxOutputTokens: DEFAULT_OPENAI_MAX_OUTPUT_TOKENS
236
+ },
237
+ audioReview: {
238
+ includeCommitHistory: true,
239
+ includeRecentDiffs: true,
240
+ includeReleaseNotes: false,
241
+ includeGithubIssues: true,
242
+ commitHistoryLimit: 10,
243
+ diffHistoryLimit: 5,
244
+ releaseNotesLimit: 3,
245
+ githubIssuesLimit: 20,
246
+ sendit: DEFAULT_SENDIT_MODE,
247
+ maxRecordingTime: 300,
248
+ audioDevice: undefined,
249
+ directory: undefined,
250
+ openaiReasoning: DEFAULT_OPENAI_REASONING,
251
+ openaiMaxOutputTokens: DEFAULT_OPENAI_MAX_OUTPUT_TOKENS
252
+ },
253
+ publish: {
254
+ mergeMethod: DEFAULT_MERGE_METHOD,
255
+ from: DEFAULT_FROM_COMMIT_ALIAS,
256
+ targetVersion: 'patch',
257
+ interactive: DEFAULT_INTERACTIVE_MODE,
258
+ requiredEnvVars: [
259
+ 'GITHUB_TOKEN',
260
+ 'OPENAI_API_KEY'
261
+ ],
262
+ linkWorkspacePackages: true,
263
+ unlinkWorkspacePackages: true,
264
+ sendit: DEFAULT_SENDIT_MODE,
265
+ targetBranch: 'main',
266
+ noMilestones: false,
267
+ checksTimeout: 3600000,
268
+ releaseWorkflowsTimeout: 1800000
269
+ },
270
+ link: {
271
+ scopeRoots: {},
272
+ dryRun: false,
273
+ packageArgument: undefined,
274
+ externals: []
275
+ },
276
+ unlink: {
277
+ scopeRoots: {},
278
+ workspaceFile: undefined,
279
+ dryRun: false,
280
+ cleanNodeModules: false,
281
+ packageArgument: undefined,
282
+ externals: []
283
+ },
284
+ tree: {
285
+ directories: undefined,
286
+ exclude: undefined,
287
+ startFrom: undefined,
288
+ stopAt: undefined,
289
+ cmd: undefined,
290
+ builtInCommand: undefined,
291
+ continue: false,
292
+ packageArgument: undefined,
293
+ cleanNodeModules: false,
294
+ externals: []
295
+ },
296
+ development: {
297
+ targetVersion: 'patch',
298
+ noMilestones: false
299
+ },
300
+ versions: {
301
+ subcommand: undefined,
302
+ directories: undefined
303
+ },
304
+ updates: {
305
+ scope: undefined,
306
+ directories: undefined
307
+ },
308
+ excludedPatterns: DEFAULT_EXCLUDED_PATTERNS,
309
+ branches: {
310
+ working: {
311
+ targetBranch: 'main',
312
+ developmentBranch: true,
313
+ version: {
314
+ type: 'prerelease',
315
+ increment: true,
316
+ incrementLevel: 'patch',
317
+ tag: 'dev'
318
+ }
319
+ },
320
+ main: {
321
+ version: {
322
+ type: 'release'
323
+ }
324
+ }
325
+ }
326
+ };
327
+
328
+ const constants = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
329
+ __proto__: null,
330
+ ALLOWED_COMMANDS,
331
+ COMMAND_AUDIO_COMMIT,
332
+ COMMAND_AUDIO_REVIEW,
333
+ COMMAND_CHECK_CONFIG,
334
+ COMMAND_CLEAN,
335
+ COMMAND_COMMIT,
336
+ COMMAND_DEVELOPMENT,
337
+ COMMAND_INIT_CONFIG,
338
+ COMMAND_LINK,
339
+ COMMAND_PRECOMMIT,
340
+ COMMAND_PUBLISH,
341
+ COMMAND_RELEASE,
342
+ COMMAND_REVIEW,
343
+ COMMAND_SELECT_AUDIO,
344
+ COMMAND_TREE,
345
+ COMMAND_UNLINK,
346
+ COMMAND_UPDATES,
347
+ COMMAND_VERSIONS,
348
+ DATE_FORMAT_DAY,
349
+ DATE_FORMAT_HOURS,
350
+ DATE_FORMAT_MILLISECONDS,
351
+ DATE_FORMAT_MINUTES,
352
+ DATE_FORMAT_MONTH,
353
+ DATE_FORMAT_MONTH_DAY,
354
+ DATE_FORMAT_SECONDS,
355
+ DATE_FORMAT_SHORT_TIMESTAMP,
356
+ DATE_FORMAT_YEAR,
357
+ DATE_FORMAT_YEAR_MONTH,
358
+ DATE_FORMAT_YEAR_MONTH_DAY,
359
+ DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES,
360
+ DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS,
361
+ DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS_MILLISECONDS,
362
+ DATE_FORMAT_YEAR_MONTH_DAY_SLASH,
363
+ DEFAULT_ADD,
364
+ DEFAULT_AMEND_MODE,
365
+ DEFAULT_BINARY_TO_TEXT_ENCODING,
366
+ DEFAULT_CACHED,
367
+ DEFAULT_CHARACTER_ENCODING,
368
+ DEFAULT_COMMAND,
369
+ DEFAULT_CONFIG_DIR,
370
+ DEFAULT_CONTEXT_DIRECTORIES,
371
+ DEFAULT_DEBUG,
372
+ DEFAULT_DIFF,
373
+ DEFAULT_DIRECTORY_PREFIX,
374
+ DEFAULT_DRY_RUN,
375
+ DEFAULT_EXCLUDED_PATTERNS,
376
+ DEFAULT_FROM_COMMIT_ALIAS,
377
+ DEFAULT_GIT_COMMAND_MAX_BUFFER,
378
+ DEFAULT_IGNORE_PATTERNS,
379
+ DEFAULT_INSTRUCTIONS_COMMIT_FILE,
380
+ DEFAULT_INSTRUCTIONS_DIR,
381
+ DEFAULT_INSTRUCTIONS_MAP,
382
+ DEFAULT_INSTRUCTIONS_RELEASE_FILE,
383
+ DEFAULT_INSTRUCTIONS_REVIEW_FILE,
384
+ DEFAULT_INTERACTIVE_MODE,
385
+ DEFAULT_LOG,
386
+ DEFAULT_MAX_DIFF_BYTES,
387
+ DEFAULT_MERGE_METHOD,
388
+ DEFAULT_MESSAGE_LIMIT,
389
+ DEFAULT_MODEL,
390
+ DEFAULT_MODEL_STRONG,
391
+ DEFAULT_OPENAI_MAX_OUTPUT_TOKENS,
392
+ DEFAULT_OPENAI_REASONING,
393
+ DEFAULT_OUTPUT_DIRECTORY,
394
+ DEFAULT_OVERRIDES,
395
+ DEFAULT_PATH_SEPARATOR,
396
+ DEFAULT_PERSONA_DIR,
397
+ DEFAULT_PERSONA_MAP,
398
+ DEFAULT_PERSONA_RELEASER_FILE,
399
+ DEFAULT_PERSONA_YOU_FILE,
400
+ DEFAULT_PREFERENCES_DIRECTORY,
401
+ DEFAULT_SENDIT_MODE,
402
+ DEFAULT_TO_COMMIT_ALIAS,
403
+ DEFAULT_VERBOSE,
404
+ INTERNAL_DATETIME_FORMAT,
405
+ INTERNAL_DEFAULT_OUTPUT_FILE,
406
+ KODRDRIV_DEFAULTS,
407
+ PROGRAM_NAME,
408
+ VERSION
409
+ }, Symbol.toStringTag, { value: 'Module' }));
410
+
411
+ // Track if debug directory has been ensured for this session
412
+ let debugDirectoryEnsured = false;
413
+ const ensureDebugDirectory = ()=>{
414
+ if (debugDirectoryEnsured) return;
415
+ const debugDir = path__default.join(DEFAULT_OUTPUT_DIRECTORY, 'debug');
416
+ try {
417
+ fs.mkdirSync(debugDir, {
418
+ recursive: true
419
+ });
420
+ debugDirectoryEnsured = true;
421
+ } catch (error) {
422
+ // eslint-disable-next-line no-console
423
+ console.error(`Failed to create debug directory ${debugDir}:`, error);
424
+ }
425
+ };
426
+ const generateDebugLogFilename = ()=>{
427
+ const now = new Date();
428
+ const timestamp = now.toISOString().replace(/[-:]/g, '').replace(/\./g, '').replace('T', '-').replace('Z', '');
429
+ return `${timestamp}-debug.log`;
430
+ };
431
+ const createTransports = (level)=>{
432
+ const transports = [];
433
+ // Always add console transport for info level and above
434
+ if (level === 'info') {
435
+ transports.push(new winston.transports.Console({
436
+ format: winston.format.combine(winston.format.colorize(), winston.format.printf(({ level, message, dryRun })=>{
437
+ const dryRunPrefix = dryRun ? '🔍 DRY RUN: ' : '';
438
+ // For info level messages, don't show the level prefix
439
+ if (level.includes('info')) {
440
+ return `${dryRunPrefix}${String(message)}`;
441
+ }
442
+ // For warn, error, etc., show the level prefix
443
+ return `${level}: ${dryRunPrefix}${String(message)}`;
444
+ }))
445
+ }));
446
+ } else {
447
+ // For debug/verbose levels, add console transport that shows info and above
448
+ transports.push(new winston.transports.Console({
449
+ level: 'info',
450
+ format: winston.format.combine(winston.format.colorize(), winston.format.printf(({ timestamp, level, message, dryRun, ...meta })=>{
451
+ // For info level messages, use simpler format without timestamp
452
+ if (level.includes('info')) {
453
+ const dryRunPrefix = dryRun ? '🔍 DRY RUN: ' : '';
454
+ return `${dryRunPrefix}${String(message)}`;
455
+ }
456
+ // Filter out winston internal metadata
457
+ const filteredMeta = Object.keys(meta).reduce((acc, key)=>{
458
+ if (![
459
+ 'level',
460
+ 'message',
461
+ 'timestamp',
462
+ 'dryRun',
463
+ 'service',
464
+ 'splat',
465
+ 'Symbol(level)',
466
+ 'Symbol(message)'
467
+ ].includes(key)) {
468
+ acc[key] = meta[key];
469
+ }
470
+ return acc;
471
+ }, {});
472
+ const metaStr = Object.keys(filteredMeta).length ? ` ${JSON.stringify(filteredMeta, null, 2)}` : '';
473
+ const dryRunPrefix = dryRun ? '🔍 DRY RUN: ' : '';
474
+ return `${timestamp} ${level}: ${dryRunPrefix}${String(message)}${metaStr}`;
475
+ }))
476
+ }));
477
+ // Add file transport for debug levels (debug and silly)
478
+ if (level === 'debug' || level === 'silly') {
479
+ ensureDebugDirectory();
480
+ const debugLogPath = path__default.join(DEFAULT_OUTPUT_DIRECTORY, 'debug', generateDebugLogFilename());
481
+ transports.push(new winston.transports.File({
482
+ filename: debugLogPath,
483
+ level: 'debug',
484
+ format: winston.format.combine(winston.format.timestamp({
485
+ format: DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS_MILLISECONDS
486
+ }), winston.format.errors({
487
+ stack: true
488
+ }), winston.format.splat(), winston.format.printf(({ timestamp, level, message, service, ...meta })=>{
489
+ // Filter out winston internal metadata and properly format remaining meta
490
+ const filteredMeta = Object.keys(meta).reduce((acc, key)=>{
491
+ // Skip internal winston fields
492
+ if (![
493
+ 'level',
494
+ 'message',
495
+ 'timestamp',
496
+ 'splat',
497
+ 'Symbol(level)',
498
+ 'Symbol(message)'
499
+ ].includes(key)) {
500
+ acc[key] = meta[key];
501
+ }
502
+ return acc;
503
+ }, {});
504
+ const metaStr = Object.keys(filteredMeta).length ? ` ${JSON.stringify(filteredMeta, null, 2)}` : '';
505
+ const serviceStr = service ? ` [${service}]` : '';
506
+ return `${timestamp}${serviceStr} ${level}: ${message}${metaStr}`;
507
+ }))
508
+ }));
509
+ }
510
+ }
511
+ return transports;
512
+ };
513
+ const createFormat = (level)=>{
514
+ const baseFormats = [
515
+ winston.format.errors({
516
+ stack: true
517
+ }),
518
+ winston.format.splat()
519
+ ];
520
+ if (level === 'info') {
521
+ return winston.format.combine(...baseFormats, winston.format.printf(({ message, dryRun, ..._meta })=>{
522
+ // Auto-format dry-run messages
523
+ if (dryRun) {
524
+ return `🔍 DRY RUN: ${message}`;
525
+ }
526
+ return String(message);
527
+ }));
528
+ }
529
+ return winston.format.combine(winston.format.timestamp({
530
+ format: DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS_MILLISECONDS
531
+ }), ...baseFormats, winston.format.printf(({ timestamp, level, message, dryRun, ...meta })=>{
532
+ // Filter out winston internal metadata
533
+ const filteredMeta = Object.keys(meta).reduce((acc, key)=>{
534
+ if (![
535
+ 'level',
536
+ 'message',
537
+ 'timestamp',
538
+ 'dryRun',
539
+ 'service',
540
+ 'splat',
541
+ 'Symbol(level)',
542
+ 'Symbol(message)'
543
+ ].includes(key)) {
544
+ acc[key] = meta[key];
545
+ }
546
+ return acc;
547
+ }, {});
548
+ const metaStr = Object.keys(filteredMeta).length ? ` ${JSON.stringify(filteredMeta, null, 2)}` : '';
549
+ const dryRunPrefix = dryRun ? '🔍 DRY RUN: ' : '';
550
+ return `${timestamp} ${level}: ${dryRunPrefix}${String(message)}${metaStr}`;
551
+ }));
552
+ };
553
+ // Create the logger instance once
554
+ const logger = winston.createLogger({
555
+ level: 'info',
556
+ format: createFormat('info'),
557
+ defaultMeta: {
558
+ service: PROGRAM_NAME
559
+ },
560
+ transports: createTransports('info')
561
+ });
562
+ const setLogLevel = (level)=>{
563
+ // Reconfigure the existing logger instead of creating a new one
564
+ logger.configure({
565
+ level,
566
+ format: createFormat(level),
567
+ defaultMeta: {
568
+ service: PROGRAM_NAME
569
+ },
570
+ transports: createTransports(level)
571
+ });
572
+ };
573
+ const getLogger = ()=>logger;
574
+ /**
575
+ * Get a logger that automatically formats messages for dry-run mode
576
+ */ const getDryRunLogger = (isDryRun)=>{
577
+ if (!isDryRun) {
578
+ return logger;
579
+ }
580
+ // Return a wrapper that adds dry-run context to all log calls
581
+ return {
582
+ info: (message, ...args)=>logger.info(message, {
583
+ dryRun: true
584
+ }, ...args),
585
+ warn: (message, ...args)=>logger.warn(message, {
586
+ dryRun: true
587
+ }, ...args),
588
+ error: (message, ...args)=>logger.error(message, {
589
+ dryRun: true
590
+ }, ...args),
591
+ debug: (message, ...args)=>logger.debug(message, {
592
+ dryRun: true
593
+ }, ...args),
594
+ verbose: (message, ...args)=>logger.verbose(message, {
595
+ dryRun: true
596
+ }, ...args),
597
+ silly: (message, ...args)=>logger.silly(message, {
598
+ dryRun: true
599
+ }, ...args)
600
+ };
601
+ };
602
+
603
+ const logging = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
604
+ __proto__: null,
605
+ getDryRunLogger,
606
+ getLogger,
607
+ setLogLevel
608
+ }, Symbol.toStringTag, { value: 'Module' }));
609
+
610
+ // Enhanced exclusion patterns specifically for review context
611
+ // These focus on excluding large files, binaries, and content that doesn't help with issue analysis
612
+ const getReviewExcludedPatterns = (basePatterns)=>{
613
+ const reviewSpecificExclusions = [
614
+ // Lock files and dependency files (often massive)
615
+ "*lock*",
616
+ "*.lock",
617
+ "package-lock.json",
618
+ "yarn.lock",
619
+ "bun.lockb",
620
+ "composer.lock",
621
+ "Cargo.lock",
622
+ "Gemfile.lock",
623
+ "Pipfile.lock",
624
+ "poetry.lock",
625
+ // Image files (binary and large)
626
+ "*.png",
627
+ "*.jpg",
628
+ "*.jpeg",
629
+ "*.gif",
630
+ "*.bmp",
631
+ "*.tiff",
632
+ "*.webp",
633
+ "*.svg",
634
+ "*.ico",
635
+ "*.icns",
636
+ // Video and audio files
637
+ "*.mp4",
638
+ "*.avi",
639
+ "*.mov",
640
+ "*.wmv",
641
+ "*.flv",
642
+ "*.mp3",
643
+ "*.wav",
644
+ "*.flac",
645
+ // Archives and compressed files
646
+ "*.zip",
647
+ "*.tar",
648
+ "*.tar.gz",
649
+ "*.tgz",
650
+ "*.rar",
651
+ "*.7z",
652
+ "*.bz2",
653
+ "*.xz",
654
+ // Binary executables and libraries
655
+ "*.exe",
656
+ "*.dll",
657
+ "*.so",
658
+ "*.dylib",
659
+ "*.bin",
660
+ "*.app",
661
+ // Database files
662
+ "*.db",
663
+ "*.sqlite",
664
+ "*.sqlite3",
665
+ "*.mdb",
666
+ // Large generated files
667
+ "*.map",
668
+ "*.min.js",
669
+ "*.min.css",
670
+ "bundle.*",
671
+ "vendor.*",
672
+ // Documentation that's often large
673
+ "*.pdf",
674
+ "*.doc",
675
+ "*.docx",
676
+ "*.ppt",
677
+ "*.pptx",
678
+ // IDE and OS generated files
679
+ ".DS_Store",
680
+ "Thumbs.db",
681
+ "*.swp",
682
+ "*.tmp",
683
+ // Certificate and key files
684
+ "*.pem",
685
+ "*.crt",
686
+ "*.key",
687
+ "*.p12",
688
+ "*.pfx",
689
+ // Large config/data files that are often auto-generated
690
+ "tsconfig.tsbuildinfo",
691
+ "*.cache",
692
+ ".eslintcache"
693
+ ];
694
+ // Combine base patterns with review specific exclusions, removing duplicates
695
+ const combinedPatterns = [
696
+ ...new Set([
697
+ ...basePatterns,
698
+ ...reviewSpecificExclusions
699
+ ])
700
+ ];
701
+ return combinedPatterns;
702
+ };
703
+ // Check if there are changes to critical files that are normally excluded
704
+ const hasCriticalExcludedChanges = async ()=>{
705
+ const logger = getLogger();
706
+ const criticalPatterns = [
707
+ 'package-lock.json',
708
+ 'yarn.lock',
709
+ 'bun.lockb',
710
+ '.gitignore',
711
+ '.env.example'
712
+ ];
713
+ try {
714
+ // Check for unstaged changes to critical files
715
+ const { stdout } = await run('git status --porcelain');
716
+ const changedFiles = stdout.split('\n').filter((line)=>line.trim()).map((line)=>line.substring(3).trim()); // Remove status prefix
717
+ const criticalFiles = changedFiles.filter((file)=>criticalPatterns.some((pattern)=>file === pattern || file.endsWith(`/${pattern}`)));
718
+ logger.debug('Found %d critical excluded files with changes: %s', criticalFiles.length, criticalFiles.join(', '));
719
+ return {
720
+ hasChanges: criticalFiles.length > 0,
721
+ files: criticalFiles
722
+ };
723
+ } catch (error) {
724
+ logger.debug('Error checking for critical excluded changes: %s', error.message);
725
+ return {
726
+ hasChanges: false,
727
+ files: []
728
+ };
729
+ }
730
+ };
731
+ // Get minimal excluded patterns that still includes critical files
732
+ const getMinimalExcludedPatterns = (basePatterns)=>{
733
+ const criticalPatterns = [
734
+ 'package-lock.json',
735
+ 'yarn.lock',
736
+ 'bun.lockb',
737
+ '.gitignore',
738
+ '.env.example'
739
+ ];
740
+ // Filter out critical patterns from base patterns
741
+ return basePatterns.filter((pattern)=>!criticalPatterns.some((critical)=>pattern === critical || pattern.includes(critical)));
742
+ };
743
+ // Function to truncate overly large diff content while preserving structure
744
+ const truncateLargeDiff = (diffContent, maxLength = 5000)=>{
745
+ if (diffContent.length <= maxLength) {
746
+ return diffContent;
747
+ }
748
+ const lines = diffContent.split('\n');
749
+ const truncatedLines = [];
750
+ let currentLength = 0;
751
+ let truncated = false;
752
+ for (const line of lines){
753
+ if (currentLength + line.length + 1 > maxLength) {
754
+ truncated = true;
755
+ break;
756
+ }
757
+ truncatedLines.push(line);
758
+ currentLength += line.length + 1; // +1 for newline
759
+ }
760
+ if (truncated) {
761
+ truncatedLines.push('');
762
+ truncatedLines.push(`... [TRUNCATED: Original diff was ${diffContent.length} characters, showing first ${currentLength}] ...`);
763
+ }
764
+ return truncatedLines.join('\n');
765
+ };
766
+ // Smart diff truncation that identifies and handles large files individually
767
+ const truncateDiffByFiles = (diffContent, maxDiffBytes)=>{
768
+ if (diffContent.length <= maxDiffBytes) {
769
+ return diffContent;
770
+ }
771
+ const lines = diffContent.split('\n');
772
+ const result = [];
773
+ let currentFile = [];
774
+ let currentFileHeader = '';
775
+ let totalSize = 0;
776
+ let filesOmitted = 0;
777
+ for (const line of lines){
778
+ // Check if this is a file header (starts with diff --git)
779
+ if (line.startsWith('diff --git ')) {
780
+ // Process the previous file if it exists
781
+ if (currentFile.length > 0) {
782
+ const fileContent = currentFile.join('\n');
783
+ const fileSizeBytes = Buffer.byteLength(fileContent, 'utf8');
784
+ if (fileSizeBytes > maxDiffBytes) {
785
+ // This single file is too large, replace with a summary
786
+ result.push(currentFileHeader);
787
+ result.push(`... [CHANGE OMITTED: File too large (${fileSizeBytes} bytes > ${maxDiffBytes} limit)] ...`);
788
+ result.push('');
789
+ filesOmitted++;
790
+ } else if (totalSize + fileSizeBytes > maxDiffBytes * 10) {
791
+ // Adding this file would make total too large
792
+ result.push(currentFileHeader);
793
+ result.push(`... [CHANGE OMITTED: Would exceed total size limit] ...`);
794
+ result.push('');
795
+ filesOmitted++;
796
+ } else {
797
+ // File is acceptable size
798
+ result.push(...currentFile);
799
+ totalSize += fileSizeBytes;
800
+ }
801
+ }
802
+ // Start new file
803
+ currentFileHeader = line;
804
+ currentFile = [
805
+ line
806
+ ];
807
+ } else {
808
+ // Add line to current file
809
+ currentFile.push(line);
810
+ }
811
+ }
812
+ // Handle the last file
813
+ if (currentFile.length > 0) {
814
+ const fileContent = currentFile.join('\n');
815
+ const fileSizeBytes = Buffer.byteLength(fileContent, 'utf8');
816
+ if (fileSizeBytes > maxDiffBytes) {
817
+ result.push(currentFileHeader);
818
+ result.push(`... [CHANGE OMITTED: File too large (${fileSizeBytes} bytes > ${maxDiffBytes} limit)] ...`);
819
+ result.push('');
820
+ filesOmitted++;
821
+ } else if (totalSize + fileSizeBytes > maxDiffBytes * 10) {
822
+ result.push(currentFileHeader);
823
+ result.push(`... [CHANGE OMITTED: Would exceed total size limit] ...`);
824
+ result.push('');
825
+ filesOmitted++;
826
+ } else {
827
+ result.push(...currentFile);
828
+ totalSize += fileSizeBytes;
829
+ }
830
+ }
831
+ const finalResult = result.join('\n');
832
+ if (filesOmitted > 0) {
833
+ return finalResult + `\n\n[SUMMARY: ${filesOmitted} files omitted due to size limits. Original diff: ${diffContent.length} bytes, processed diff: ${finalResult.length} bytes]`;
834
+ }
835
+ return finalResult;
836
+ };
837
+ const create$2 = async (options)=>{
838
+ const logger = getLogger();
839
+ async function get() {
840
+ try {
841
+ logger.verbose('Gathering change information from Git');
842
+ try {
843
+ logger.debug('Executing git diff');
844
+ const excludeString = options.excludedPatterns.map((p)=>`':(exclude)${p}'`).join(' ');
845
+ let range = '';
846
+ if (options.from && options.to) {
847
+ range = `${options.from}..${options.to}`;
848
+ } else if (options.from) {
849
+ range = `${options.from}`;
850
+ } else if (options.to) {
851
+ range = `${options.to}`;
852
+ }
853
+ let command = '';
854
+ if (options.cached) {
855
+ command = `git diff --cached${range ? ' ' + range : ''} -- . ${excludeString}`;
856
+ } else {
857
+ command = `git diff${range ? ' ' + range : ''} -- . ${excludeString}`;
858
+ }
859
+ const { stdout, stderr } = await run(command, {
860
+ maxBuffer: DEFAULT_GIT_COMMAND_MAX_BUFFER
861
+ });
862
+ if (stderr) {
863
+ logger.warn('GIT_DIFF_STDERR: Git diff produced stderr output | Stderr: %s | Impact: May indicate warnings', stderr);
864
+ }
865
+ logger.debug('Git diff output: %s', stdout);
866
+ // Apply intelligent diff truncation if maxDiffBytes is specified
867
+ if (options.maxDiffBytes && stdout.length > 0) {
868
+ const originalSize = Buffer.byteLength(stdout, 'utf8');
869
+ const truncatedDiff = truncateDiffByFiles(stdout, options.maxDiffBytes);
870
+ const newSize = Buffer.byteLength(truncatedDiff, 'utf8');
871
+ if (originalSize !== newSize) {
872
+ logger.info('DIFF_TRUNCATED: Applied diff size truncation | Original: %d bytes | Truncated: %d bytes | Limit: %d bytes | Reason: Size exceeds limit', originalSize, newSize, options.maxDiffBytes);
873
+ }
874
+ return truncatedDiff;
875
+ }
876
+ return stdout;
877
+ } catch (error) {
878
+ logger.error('GIT_DIFF_FAILED: Failed to execute git diff command | Error: %s | Impact: Cannot gather change information', error.message);
879
+ throw error;
880
+ }
881
+ } catch (error) {
882
+ logger.error('DIFF_GATHER_ERROR: Error during change gathering phase | Error: %s | Stack: %s | Impact: Cannot collect diff', error.message, error.stack);
883
+ throw new ExitError('Error occurred during gather change phase');
884
+ }
885
+ }
886
+ return {
887
+ get
888
+ };
889
+ };
890
+ const hasStagedChanges = async ()=>{
891
+ const logger = getLogger();
892
+ try {
893
+ logger.debug('Checking for staged changes');
894
+ // Suppress error logging since exit code 1 is expected when there are staged changes
895
+ const { stderr } = await run('git diff --cached --quiet', {
896
+ suppressErrorLogging: true
897
+ });
898
+ if (stderr) {
899
+ logger.warn('GIT_DIFF_STDERR: Git diff produced stderr output | Stderr: %s | Impact: May indicate warnings', stderr);
900
+ }
901
+ // If there are staged changes, git diff --cached --quiet will return non-zero
902
+ // So if we get here without an error, there are no staged changes
903
+ return false;
904
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
905
+ } catch (error) {
906
+ // If we get an error, it means there are staged changes (exit code 1 is expected behavior)
907
+ return true;
908
+ }
909
+ };
910
+ // High-level function to get recent diffs formatted for review context
911
+ const getRecentDiffsForReview = async (options)=>{
912
+ const logger = getLogger();
913
+ const diffLimit = options.limit || 5;
914
+ // Get enhanced exclusion patterns for review context
915
+ const reviewExcluded = getReviewExcludedPatterns(options.baseExcludedPatterns);
916
+ logger.debug('Using %d exclusion patterns for diff context (including %d review specific)', reviewExcluded.length, reviewExcluded.length - options.baseExcludedPatterns.length);
917
+ logger.debug('Sample exclusions: %s', reviewExcluded.slice(0, 10).join(', ') + (reviewExcluded.length > 10 ? '...' : ''));
918
+ const diffSections = [];
919
+ // Get recent commits and their diffs
920
+ for(let i = 0; i < diffLimit; i++){
921
+ try {
922
+ const diffRange = i === 0 ? 'HEAD~1' : `HEAD~${i + 1}..HEAD~${i}`;
923
+ const diff = await create$2({
924
+ from: `HEAD~${i + 1}`,
925
+ to: `HEAD~${i}`,
926
+ excludedPatterns: reviewExcluded
927
+ });
928
+ const diffContent = await diff.get();
929
+ if (diffContent.trim()) {
930
+ const truncatedDiff = truncateLargeDiff(diffContent);
931
+ diffSections.push(`[Recent Diff ${i + 1} (${diffRange})]\n${truncatedDiff}`);
932
+ if (truncatedDiff.length < diffContent.length) {
933
+ logger.debug('Added diff %d to context (%d characters, truncated from %d)', i + 1, truncatedDiff.length, diffContent.length);
934
+ } else {
935
+ logger.debug('Added diff %d to context (%d characters)', i + 1, diffContent.length);
936
+ }
937
+ } else {
938
+ logger.debug('Diff %d was empty after exclusions', i + 1);
939
+ }
940
+ } catch (error) {
941
+ logger.debug('Could not fetch diff %d: %s', i + 1, error.message);
942
+ break; // Stop if we can't fetch more diffs
943
+ }
944
+ }
945
+ return diffSections.length > 0 ? '\n\n' + diffSections.join('\n\n') : '';
946
+ };
947
+
948
+ const diff = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
949
+ __proto__: null,
950
+ create: create$2,
951
+ getMinimalExcludedPatterns,
952
+ getRecentDiffsForReview,
953
+ getReviewExcludedPatterns,
954
+ hasCriticalExcludedChanges,
955
+ hasStagedChanges,
956
+ truncateDiffByFiles,
957
+ truncateLargeDiff
958
+ }, Symbol.toStringTag, { value: 'Module' }));
959
+
960
+ const create$1 = async (options)=>{
961
+ const logger = getLogger();
962
+ async function get() {
963
+ try {
964
+ logger.verbose('Gathering change information from Git');
965
+ try {
966
+ logger.debug('Executing git log');
967
+ // Build git log range
968
+ let range = '';
969
+ let extraArgs = '';
970
+ // If currentBranchOnly, show only commits unique to HEAD vs. to-branch (or main/master if not provided)
971
+ if (options.currentBranchOnly) {
972
+ const toBranch = options.to || 'main'; // Default to 'main' if not provided
973
+ range = `${toBranch}..HEAD`;
974
+ } else if (options.from && options.to) {
975
+ range = `${options.from}..${options.to}`;
976
+ } else if (options.from) {
977
+ range = `${options.from}`;
978
+ } else if (options.to) {
979
+ range = `${options.to}`;
980
+ } // else, no range: show all
981
+ if (options.limit && options.limit > 0) {
982
+ extraArgs += ` -n ${options.limit}`;
983
+ }
984
+ const gitLogCmd = `git log${range ? ' ' + range : ''}${extraArgs}`;
985
+ logger.debug('Git log command: %s', gitLogCmd);
986
+ const { stdout, stderr } = await run(gitLogCmd, {
987
+ maxBuffer: DEFAULT_GIT_COMMAND_MAX_BUFFER
988
+ });
989
+ if (stderr) {
990
+ logger.warn('GIT_LOG_STDERR: Git log produced stderr output | Stderr: %s | Impact: May indicate warnings', stderr);
991
+ }
992
+ logger.debug('Git log output: %s', stdout);
993
+ return stdout;
994
+ } catch (error) {
995
+ // Check if this is an empty repository (no commits) scenario
996
+ const errorMessage = error.message || '';
997
+ const isEmptyRepo = errorMessage.includes('does not have any commits yet') || errorMessage.includes('bad default revision') || errorMessage.includes('unknown revision or path not in the working tree') || errorMessage.includes('ambiguous argument \'HEAD\'');
998
+ if (isEmptyRepo) {
999
+ logger.debug('Empty repository detected (no commits): %s', errorMessage);
1000
+ logger.verbose('No git history available, returning empty log context');
1001
+ return ''; // Return empty string for empty repositories
1002
+ }
1003
+ logger.error('GIT_LOG_FAILED: Failed to execute git log command | Error: %s | Impact: Cannot gather commit history', error.message);
1004
+ throw error;
1005
+ }
1006
+ } catch (error) {
1007
+ // Check again at the outer level in case the error wasn't caught by the inner try-catch
1008
+ const errorMessage = error.message || '';
1009
+ const isEmptyRepo = errorMessage.includes('does not have any commits yet') || errorMessage.includes('bad default revision') || errorMessage.includes('unknown revision or path not in the working tree') || errorMessage.includes('ambiguous argument \'HEAD\'');
1010
+ if (isEmptyRepo) {
1011
+ logger.debug('Empty repository detected at outer level: %s', errorMessage);
1012
+ logger.verbose('No git history available, returning empty log context');
1013
+ return ''; // Return empty string for empty repositories
1014
+ }
1015
+ logger.error('LOG_GATHER_ERROR: Error during change gathering phase | Error: %s | Stack: %s | Impact: Cannot collect log', error.message, error.stack);
1016
+ throw new ExitError('Error occurred during gather change phase');
1017
+ }
1018
+ }
1019
+ return {
1020
+ get
1021
+ };
1022
+ };
1023
+
1024
+ const log = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
1025
+ __proto__: null,
1026
+ create: create$1
1027
+ }, Symbol.toStringTag, { value: 'Module' }));
1028
+
1029
+ // Convert excluded patterns to glob patterns for file filtering
1030
+ const convertToGlobPatterns = (excludedPatterns)=>{
1031
+ return excludedPatterns.map((pattern)=>{
1032
+ // Convert simple patterns to glob patterns
1033
+ if (!pattern.includes('*') && !pattern.includes('/')) {
1034
+ // Simple name like 'node_modules' -> '**/node_modules/**'
1035
+ return `**/${pattern}/**`;
1036
+ }
1037
+ if (pattern.includes('*')) {
1038
+ // Already a glob pattern, ensure it starts with **/ for recursive matching
1039
+ return pattern.startsWith('**/') ? pattern : `**/${pattern}`;
1040
+ }
1041
+ if (pattern.endsWith('/')) {
1042
+ // Directory pattern like 'dist/' -> '**/dist/**'
1043
+ return `**/${pattern}**`;
1044
+ }
1045
+ // File pattern like '.env' -> '**/.env'
1046
+ return `**/${pattern}`;
1047
+ });
1048
+ };
1049
+ // Check if a file path matches any excluded pattern
1050
+ const isFileExcluded = (filePath, excludedPatterns)=>{
1051
+ const normalizedPath = filePath.replace(/\\/g, '/');
1052
+ for (const pattern of excludedPatterns){
1053
+ if (pattern.includes('*')) {
1054
+ // Use minimatch-style matching for glob patterns
1055
+ const regex = new RegExp(pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*').replace(/\?/g, '[^/]'));
1056
+ if (regex.test(normalizedPath)) {
1057
+ return true;
1058
+ }
1059
+ } else {
1060
+ // Simple string matching
1061
+ if (normalizedPath.includes(pattern) || normalizedPath.endsWith(pattern) || normalizedPath.split('/').includes(pattern)) {
1062
+ return true;
1063
+ }
1064
+ }
1065
+ }
1066
+ return false;
1067
+ };
1068
+ // Get file content with size limit
1069
+ const getFileContent = async (filePath, storage, maxSize = 10240)=>{
1070
+ const logger = getLogger();
1071
+ try {
1072
+ if (!await storage.isFileReadable(filePath)) {
1073
+ return null;
1074
+ }
1075
+ // Read file content
1076
+ const content = await storage.readFile(filePath, 'utf-8');
1077
+ const sizeBytes = Buffer.byteLength(content, 'utf-8');
1078
+ if (sizeBytes > maxSize) {
1079
+ // Truncate large files
1080
+ const truncatedContent = content.substring(0, Math.floor(maxSize * 0.8));
1081
+ return `${truncatedContent}\n\n... [TRUNCATED: File was ${sizeBytes} bytes, showing first ${truncatedContent.length} characters] ...`;
1082
+ }
1083
+ return content;
1084
+ } catch (error) {
1085
+ logger.debug('Failed to read file %s: %s', filePath, error.message);
1086
+ return null;
1087
+ }
1088
+ };
1089
+ const create = async (options)=>{
1090
+ const logger = getLogger();
1091
+ const storage = createStorage();
1092
+ const maxTotalBytes = options.maxTotalBytes || 100 * 1024; // 100KB default
1093
+ const workingDir = options.workingDirectory || process.cwd();
1094
+ async function get() {
1095
+ try {
1096
+ logger.verbose('Collecting file content from working directory for commit analysis');
1097
+ // Find all files in the working directory, excluding common patterns
1098
+ const globPatterns = [
1099
+ '**/*',
1100
+ '!**/node_modules/**',
1101
+ '!**/.git/**',
1102
+ '!**/dist/**',
1103
+ '!**/build/**',
1104
+ '!**/coverage/**',
1105
+ '!**/*.log',
1106
+ '!**/tmp/**',
1107
+ '!**/.cache/**'
1108
+ ];
1109
+ // Add user-specified exclusions
1110
+ const additionalExclusions = convertToGlobPatterns(options.excludedPatterns);
1111
+ for (const exclusion of additionalExclusions){
1112
+ if (!exclusion.startsWith('!')) {
1113
+ globPatterns.push(`!${exclusion}`);
1114
+ }
1115
+ }
1116
+ logger.debug('Using glob patterns: %s', globPatterns.join(', '));
1117
+ const files = await glob(globPatterns, {
1118
+ cwd: workingDir,
1119
+ nodir: true,
1120
+ dot: false // Exclude hidden files by default
1121
+ });
1122
+ logger.debug('Found %d files to analyze', files.length);
1123
+ const fileContents = [];
1124
+ let totalBytes = 0;
1125
+ let filesProcessed = 0;
1126
+ let filesSkipped = 0;
1127
+ // Sort files by likely importance (source files first)
1128
+ const sortedFiles = files.sort((a, b)=>{
1129
+ const getFileImportance = (file)=>{
1130
+ const ext = path__default.extname(file).toLowerCase();
1131
+ const name = path__default.basename(file).toLowerCase();
1132
+ // High importance: main source files
1133
+ if ([
1134
+ 'package.json',
1135
+ 'tsconfig.json',
1136
+ 'webpack.config.js',
1137
+ 'vite.config.ts'
1138
+ ].includes(name)) return 1;
1139
+ if ([
1140
+ '.ts',
1141
+ '.tsx',
1142
+ '.js',
1143
+ '.jsx',
1144
+ '.py',
1145
+ '.rs',
1146
+ '.go',
1147
+ '.java',
1148
+ '.cpp',
1149
+ '.c',
1150
+ '.h'
1151
+ ].includes(ext)) return 2;
1152
+ if ([
1153
+ '.md',
1154
+ '.txt',
1155
+ '.yml',
1156
+ '.yaml',
1157
+ '.json'
1158
+ ].includes(ext)) return 3;
1159
+ if ([
1160
+ '.css',
1161
+ '.scss',
1162
+ '.sass',
1163
+ '.less',
1164
+ '.html'
1165
+ ].includes(ext)) return 4;
1166
+ return 5; // Lower importance
1167
+ };
1168
+ return getFileImportance(a) - getFileImportance(b);
1169
+ });
1170
+ for (const file of sortedFiles){
1171
+ const fullPath = path__default.join(workingDir, file);
1172
+ // Double-check exclusions
1173
+ if (isFileExcluded(file, options.excludedPatterns)) {
1174
+ filesSkipped++;
1175
+ continue;
1176
+ }
1177
+ const content = await getFileContent(fullPath, storage);
1178
+ if (content === null) {
1179
+ filesSkipped++;
1180
+ continue;
1181
+ }
1182
+ const contentSize = Buffer.byteLength(content, 'utf-8');
1183
+ // Check if adding this file would exceed our total limit
1184
+ if (totalBytes + contentSize > maxTotalBytes && filesProcessed > 0) {
1185
+ logger.debug('Reached size limit (%d bytes), stopping at %d files', maxTotalBytes, filesProcessed);
1186
+ break;
1187
+ }
1188
+ fileContents.push(`=== ${file} ===\n${content}\n`);
1189
+ totalBytes += contentSize;
1190
+ filesProcessed++;
1191
+ logger.debug('Added file %s (%d bytes, total: %d bytes)', file, contentSize, totalBytes);
1192
+ }
1193
+ logger.info('FILES_COLLECTED: Collected file content successfully | Files Collected: %d | Total Bytes: %d | Files Skipped: %d | Status: completed', filesProcessed, totalBytes, filesSkipped);
1194
+ if (fileContents.length === 0) {
1195
+ return 'No readable files found in working directory.';
1196
+ }
1197
+ const result = fileContents.join('\n');
1198
+ // Add summary header
1199
+ const summary = `File Content Analysis (${filesProcessed} files, ${totalBytes} bytes)\n` + `Working Directory: ${workingDir}\n` + `Files Processed: ${filesProcessed}, Files Skipped: ${filesSkipped}\n\n` + result;
1200
+ return summary;
1201
+ } catch (error) {
1202
+ logger.error('FILES_COLLECTION_ERROR: Error during file content collection | Error: %s | Stack: %s | Impact: Cannot collect file content', error.message, error.stack);
1203
+ throw new Error('Error occurred during file content collection');
1204
+ }
1205
+ }
1206
+ return {
1207
+ get
1208
+ };
1209
+ };
1210
+
1211
+ const files = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
1212
+ __proto__: null,
1213
+ create
1214
+ }, Symbol.toStringTag, { value: 'Module' }));
1215
+
1216
+ /**
1217
+ * Get version from a specific branch's package.json
1218
+ */ const getVersionFromBranch = async (branchName)=>{
1219
+ const { runSecure, validateGitRef, safeJsonParse, validatePackageJson } = await import('@eldrforge/git-tools');
1220
+ try {
1221
+ // Validate branch name to prevent injection
1222
+ if (!validateGitRef(branchName)) {
1223
+ throw new Error(`Invalid branch name: ${branchName}`);
1224
+ }
1225
+ // Cast to any to avoid type mismatch with node_modules version
1226
+ const { stdout } = await runSecure('git', [
1227
+ 'show',
1228
+ `${branchName}:package.json`
1229
+ ], {
1230
+ suppressErrorLogging: true
1231
+ });
1232
+ const packageJson = safeJsonParse(stdout, 'package.json');
1233
+ const validated = validatePackageJson(packageJson, 'package.json');
1234
+ return validated.version;
1235
+ } catch {
1236
+ // Return null if we can't get the version (branch may not exist or no package.json)
1237
+ return null;
1238
+ }
1239
+ };
1240
+ /**
1241
+ * Calculate target version based on branch configuration
1242
+ * SEMANTICS: The version config specifies what version should be ON the target branch
1243
+ */ const calculateBranchDependentVersion = async (currentVersion, currentBranch, branchesConfig, targetBranch)=>{
1244
+ const { getLogger } = await Promise.resolve().then(() => logging);
1245
+ const logger = getLogger();
1246
+ // Look up the source branch to find the target branch
1247
+ if (!branchesConfig || !branchesConfig[currentBranch]) {
1248
+ // Use default configuration from constants
1249
+ const { KODRDRIV_DEFAULTS } = await Promise.resolve().then(() => constants);
1250
+ const defaultConfig = KODRDRIV_DEFAULTS.branches;
1251
+ if (defaultConfig && defaultConfig[currentBranch]) {
1252
+ const sourceConfig = defaultConfig[currentBranch];
1253
+ const finalTargetBranch = sourceConfig.targetBranch || targetBranch || 'main';
1254
+ // Look at target branch's version config to determine what version it should have
1255
+ const targetConfig = defaultConfig[finalTargetBranch];
1256
+ logger.info(`VERSION_BRANCH_DEFAULT: Using default branch configuration | Source Branch: ${currentBranch} | Target Branch: ${finalTargetBranch} | Source: default config`);
1257
+ if (!(targetConfig === null || targetConfig === void 0 ? void 0 : targetConfig.version)) {
1258
+ const defaultVersion = incrementPatchVersion(currentVersion);
1259
+ logger.debug(`No version config for target branch '${finalTargetBranch}', using default increment`);
1260
+ return {
1261
+ version: defaultVersion,
1262
+ targetBranch: finalTargetBranch
1263
+ };
1264
+ }
1265
+ return calculateVersionFromTargetConfig(currentVersion, finalTargetBranch, targetConfig.version, logger);
1266
+ }
1267
+ // No config at all, use traditional defaults
1268
+ const defaultTargetBranch = targetBranch || 'main';
1269
+ const defaultVersion = incrementPatchVersion(currentVersion);
1270
+ logger.debug(`No branch-specific config found for '${currentBranch}', using defaults`);
1271
+ return {
1272
+ version: defaultVersion,
1273
+ targetBranch: defaultTargetBranch
1274
+ };
1275
+ }
1276
+ const sourceConfig = branchesConfig[currentBranch];
1277
+ const finalTargetBranch = sourceConfig.targetBranch || targetBranch || 'main';
1278
+ // Look at target branch's version config to determine what version it should have
1279
+ const targetConfig = branchesConfig[finalTargetBranch];
1280
+ logger.info(`VERSION_BRANCH_DEPENDENT: Using branch-dependent targeting | Source Branch: ${currentBranch} | Target Branch: ${finalTargetBranch} | Source: branch config`);
1281
+ if (!(targetConfig === null || targetConfig === void 0 ? void 0 : targetConfig.version)) {
1282
+ // No version config for target, use default increment
1283
+ const defaultVersion = incrementPatchVersion(currentVersion);
1284
+ logger.debug(`No version config for target branch '${finalTargetBranch}', using default increment`);
1285
+ return {
1286
+ version: defaultVersion,
1287
+ targetBranch: finalTargetBranch
1288
+ };
1289
+ }
1290
+ return calculateVersionFromTargetConfig(currentVersion, finalTargetBranch, targetConfig.version, logger);
1291
+ };
1292
+ /**
1293
+ * Calculate version based on target branch configuration
1294
+ */ const calculateVersionFromTargetConfig = async (currentVersion, targetBranch, versionConfig, logger)=>{
1295
+ if (versionConfig.type === 'release') {
1296
+ // Convert to release version (remove prerelease tags)
1297
+ const releaseVersion = convertToReleaseVersion(currentVersion);
1298
+ logger.info(`VERSION_RELEASE_CONVERSION: Converting prerelease to release version | Current: ${currentVersion} | Release: ${releaseVersion} | Action: Remove prerelease tag`);
1299
+ return {
1300
+ version: releaseVersion,
1301
+ targetBranch
1302
+ };
1303
+ } else if (versionConfig.type === 'prerelease') {
1304
+ if (!versionConfig.tag) {
1305
+ throw new Error(`Prerelease version type requires a tag in branch configuration`);
1306
+ }
1307
+ const tag = versionConfig.tag;
1308
+ if (versionConfig.increment) {
1309
+ // Check if there's already a version with this tag in the target branch
1310
+ const targetBranchVersion = await getVersionFromBranch(targetBranch);
1311
+ if (targetBranchVersion) {
1312
+ // Use the target branch version as the base and increment
1313
+ const newVersion = incrementPrereleaseVersion(targetBranchVersion, tag);
1314
+ logger.info(`VERSION_PRERELEASE_INCREMENT: Incrementing prerelease version | Current: ${targetBranchVersion} | New: ${newVersion} | Action: Increment prerelease number`);
1315
+ return {
1316
+ version: newVersion,
1317
+ targetBranch
1318
+ };
1319
+ } else {
1320
+ // No version in target branch, use current version as base
1321
+ const newVersion = incrementPrereleaseVersion(currentVersion, tag);
1322
+ logger.info(`VERSION_PRERELEASE_CREATE: Creating new prerelease version | Current: ${currentVersion} | New: ${newVersion} | Action: Add prerelease tag`);
1323
+ return {
1324
+ version: newVersion,
1325
+ targetBranch
1326
+ };
1327
+ }
1328
+ } else {
1329
+ // Just add/change the prerelease tag without incrementing
1330
+ const baseVersion = convertToReleaseVersion(currentVersion);
1331
+ const newVersion = `${baseVersion}-${tag}.0`;
1332
+ logger.info(`VERSION_PRERELEASE_TAG: Setting prerelease tag | Current: ${currentVersion} | New: ${newVersion} | Tag: ${versionConfig.tag}`);
1333
+ return {
1334
+ version: newVersion,
1335
+ targetBranch
1336
+ };
1337
+ }
1338
+ }
1339
+ throw new Error(`Invalid version type: ${versionConfig.type}`);
1340
+ };
1341
+ /**
1342
+ * Find the development branch from branches configuration
1343
+ * Returns the branch marked with developmentBranch: true
1344
+ */ const findDevelopmentBranch = (branchesConfig)=>{
1345
+ if (!branchesConfig || typeof branchesConfig !== 'object') {
1346
+ return null;
1347
+ }
1348
+ for (const [branchName, branchConfig] of Object.entries(branchesConfig)){
1349
+ if (branchConfig && typeof branchConfig === 'object' && branchConfig.developmentBranch === true) {
1350
+ return branchName;
1351
+ }
1352
+ }
1353
+ return null;
1354
+ };
1355
+ /**
1356
+ * Check if two prerelease versions have the same tag
1357
+ * Examples:
1358
+ * - haveSamePrereleaseTag("1.2.3-dev.0", "1.2.3-dev.5") => true
1359
+ * - haveSamePrereleaseTag("1.2.3-dev.0", "1.2.3-test.0") => false
1360
+ * - haveSamePrereleaseTag("1.2.3", "1.2.3-dev.0") => false
1361
+ */ const haveSamePrereleaseTag = (version1, version2)=>{
1362
+ const extractTag = (version)=>{
1363
+ const cleanVersion = version.startsWith('v') ? version.slice(1) : version;
1364
+ const parts = cleanVersion.split('.');
1365
+ if (parts.length < 3) return null;
1366
+ const patchAndPrerelease = parts.slice(2).join('.');
1367
+ const patchComponents = patchAndPrerelease.split('-');
1368
+ if (patchComponents.length > 1) {
1369
+ const prereleaseString = patchComponents.slice(1).join('-');
1370
+ const prereleaseComponents = prereleaseString.split('.');
1371
+ return prereleaseComponents[0] || null;
1372
+ }
1373
+ return null;
1374
+ };
1375
+ const tag1 = extractTag(version1);
1376
+ const tag2 = extractTag(version2);
1377
+ return tag1 !== null && tag2 !== null && tag1 === tag2;
1378
+ };
1379
+ const checkIfTagExists = async (tagName)=>{
1380
+ const { runSecure, validateGitRef } = await import('@eldrforge/git-tools');
1381
+ try {
1382
+ // Validate tag name to prevent injection
1383
+ if (!validateGitRef(tagName)) {
1384
+ throw new Error(`Invalid tag name: ${tagName}`);
1385
+ }
1386
+ const { stdout } = await runSecure('git', [
1387
+ 'tag',
1388
+ '-l',
1389
+ tagName
1390
+ ]);
1391
+ return stdout.trim() === tagName;
1392
+ } catch {
1393
+ // If git command fails, assume tag doesn't exist
1394
+ return false;
1395
+ }
1396
+ };
1397
+ const confirmVersionInteractively = async (currentVersion, proposedVersion, targetVersionInput)=>{
1398
+ const { getUserChoice, getUserTextInput, requireTTY } = await Promise.resolve().then(() => interactive);
1399
+ const { getLogger } = await Promise.resolve().then(() => logging);
1400
+ requireTTY('Interactive version confirmation requires a terminal.');
1401
+ const logger = getLogger();
1402
+ logger.info(`\nVERSION_CONFIRMATION: Version confirmation required | Current: ${currentVersion} | Proposed: ${proposedVersion}`);
1403
+ logger.info(`VERSION_CURRENT: Current package version | Version: ${currentVersion}`);
1404
+ logger.info(`VERSION_PROPOSED: Proposed new version | Version: ${proposedVersion}`);
1405
+ if (targetVersionInput) {
1406
+ logger.info(`VERSION_TARGET_INPUT: Target version provided | Input: ${targetVersionInput}`);
1407
+ }
1408
+ const choices = [
1409
+ {
1410
+ key: 'c',
1411
+ label: `Confirm ${proposedVersion}`
1412
+ },
1413
+ {
1414
+ key: 'e',
1415
+ label: 'Enter custom version'
1416
+ },
1417
+ {
1418
+ key: 'a',
1419
+ label: 'Abort publish'
1420
+ }
1421
+ ];
1422
+ const choice = await getUserChoice('\n🤔 Confirm the version for this release:', choices);
1423
+ switch(choice){
1424
+ case 'c':
1425
+ return proposedVersion;
1426
+ case 'e':
1427
+ {
1428
+ const customVersion = await getUserTextInput('\n📝 Enter the version number (e.g., "4.30.0"):');
1429
+ if (!validateVersionString(customVersion)) {
1430
+ throw new Error(`Invalid version format: ${customVersion}. Expected format: "x.y.z"`);
1431
+ }
1432
+ const cleanCustomVersion = customVersion.startsWith('v') ? customVersion.slice(1) : customVersion;
1433
+ logger.info(`VERSION_CUSTOM_SELECTED: Using custom version from user input | Version: ${cleanCustomVersion} | Source: interactive input`);
1434
+ return cleanCustomVersion;
1435
+ }
1436
+ case 'a':
1437
+ throw new Error('Release aborted by user');
1438
+ default:
1439
+ throw new Error(`Unexpected choice: ${choice}`);
1440
+ }
1441
+ };
1442
+ const getOutputPath = (outputDirectory, filename)=>{
1443
+ return path__default.join(outputDirectory, filename);
1444
+ };
1445
+ const getTimestampedFilename = (baseName, extension = '.json')=>{
1446
+ const now = new Date();
1447
+ // Format as YYMMdd-HHmm (e.g., 250701-1030)
1448
+ const yy = now.getFullYear().toString().slice(-2);
1449
+ const mm = (now.getMonth() + 1).toString().padStart(2, '0');
1450
+ const dd = now.getDate().toString().padStart(2, '0');
1451
+ const hh = now.getHours().toString().padStart(2, '0');
1452
+ const min = now.getMinutes().toString().padStart(2, '0');
1453
+ const timestamp = `${yy}${mm}${dd}-${hh}${min}`;
1454
+ return `${timestamp}-${baseName}${extension}`;
1455
+ };
1456
+ const getTimestampedRequestFilename = (baseName)=>{
1457
+ return getTimestampedFilename(baseName, '.request.json');
1458
+ };
1459
+ const getTimestampedResponseFilename = (baseName)=>{
1460
+ return getTimestampedFilename(baseName, '.response.json');
1461
+ };
1462
+ const getTimestampedCommitFilename = ()=>{
1463
+ return getTimestampedFilename('commit-message', '.md');
1464
+ };
1465
+ const getTimestampedReleaseNotesFilename = ()=>{
1466
+ return getTimestampedFilename('release-notes', '.md');
1467
+ };
1468
+ const getTimestampedAudioFilename = ()=>{
1469
+ return getTimestampedFilename('audio-recording', '.wav');
1470
+ };
1471
+ const getTimestampedTranscriptFilename = ()=>{
1472
+ return getTimestampedFilename('audio-transcript', '.md');
1473
+ };
1474
+ const getTimestampedReviewFilename = ()=>{
1475
+ return getTimestampedFilename('review-analysis', '.md');
1476
+ };
1477
+ const getTimestampedReviewNotesFilename = ()=>{
1478
+ return getTimestampedFilename('review-notes', '.md');
1479
+ };
1480
+ const getTimestampedArchivedAudioFilename = (originalExtension = '.wav')=>{
1481
+ return getTimestampedFilename('review-audio', originalExtension);
1482
+ };
1483
+ const getTimestampedArchivedTranscriptFilename = ()=>{
1484
+ return getTimestampedFilename('review-transcript', '.md');
1485
+ };
1486
+ // archiveAudio function moved to @eldrforge/audio-tools
1487
+ /**
1488
+ * Query npm registry for published version of a package
1489
+ * Returns null if package is not published or on error
1490
+ */ const getNpmPublishedVersion = async (packageName)=>{
1491
+ const logger = getLogger();
1492
+ try {
1493
+ const { runSecure } = await import('@eldrforge/git-tools');
1494
+ // Use npm view to get the latest published version
1495
+ // --json flag ensures parseable output
1496
+ const { stdout } = await runSecure('npm', [
1497
+ 'view',
1498
+ packageName,
1499
+ 'version',
1500
+ '--json'
1501
+ ]);
1502
+ if (!stdout || stdout.trim() === '') {
1503
+ logger.verbose(`Package ${packageName} not found on npm registry`);
1504
+ return null;
1505
+ }
1506
+ // npm view returns just the version string for a single version
1507
+ const version = stdout.trim().replace(/^["']|["']$/g, ''); // Remove quotes if present
1508
+ logger.verbose(`Found ${packageName}@${version} on npm registry`);
1509
+ return version;
1510
+ } catch (error) {
1511
+ // Package not found or network error
1512
+ logger.verbose(`Could not query npm for ${packageName}: ${error.message}`);
1513
+ return null;
1514
+ }
1515
+ };
1516
+ /**
1517
+ * Check if a package version already exists on npm registry
1518
+ */ const isVersionPublishedOnNpm = async (packageName, version)=>{
1519
+ const logger = getLogger();
1520
+ try {
1521
+ const { runSecure } = await import('@eldrforge/git-tools');
1522
+ // Use npm view to check for specific version
1523
+ const { stdout } = await runSecure('npm', [
1524
+ 'view',
1525
+ `${packageName}@${version}`,
1526
+ 'version',
1527
+ '--json'
1528
+ ]);
1529
+ if (!stdout || stdout.trim() === '') {
1530
+ logger.verbose(`Version ${packageName}@${version} not found on npm registry`);
1531
+ return false;
1532
+ }
1533
+ logger.verbose(`Version ${packageName}@${version} exists on npm registry`);
1534
+ return true;
1535
+ } catch (error) {
1536
+ // Version not found
1537
+ logger.verbose(`Version ${packageName}@${version} not published: ${error.message}`);
1538
+ return false;
1539
+ }
1540
+ };
1541
+ /**
1542
+ * Get detailed info about a tag including the version it points to
1543
+ */ const getTagInfo = async (tagName)=>{
1544
+ try {
1545
+ const { runSecure, validateGitRef } = await import('@eldrforge/git-tools');
1546
+ if (!validateGitRef(tagName)) {
1547
+ throw new Error(`Invalid tag name: ${tagName}`);
1548
+ }
1549
+ // Check if tag exists
1550
+ const { stdout: tagList } = await runSecure('git', [
1551
+ 'tag',
1552
+ '-l',
1553
+ tagName
1554
+ ]);
1555
+ if (tagList.trim() !== tagName) {
1556
+ return {
1557
+ exists: false
1558
+ };
1559
+ }
1560
+ // Get the commit the tag points to
1561
+ const { stdout: commit } = await runSecure('git', [
1562
+ 'rev-list',
1563
+ '-n',
1564
+ '1',
1565
+ tagName
1566
+ ]);
1567
+ // Extract version from tag name (assumes format like v1.2.3 or working/v1.2.3)
1568
+ const versionMatch = tagName.match(/v?(\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?)/);
1569
+ const version = versionMatch ? versionMatch[1] : undefined;
1570
+ return {
1571
+ exists: true,
1572
+ commit: commit.trim(),
1573
+ version
1574
+ };
1575
+ } catch {
1576
+ return null;
1577
+ }
1578
+ };
1579
+ /**
1580
+ * Check if a version is a development/prerelease version (has prerelease tag)
1581
+ */ const isDevelopmentVersion = (version)=>{
1582
+ // Development versions have prerelease tags: 1.2.3-dev.0, 1.2.3-alpha.1, etc.
1583
+ return version.includes('-');
1584
+ };
1585
+ /**
1586
+ * Check if a version is a release version (no prerelease tag)
1587
+ */ const isReleaseVersion = (version)=>{
1588
+ // Release versions are X.Y.Z without any suffix
1589
+ return /^\d+\.\d+\.\d+$/.test(version);
1590
+ };
1591
+ /**
1592
+ * Get expected version pattern for a branch
1593
+ */ const getExpectedVersionPattern = (branchName)=>{
1594
+ // Development/working branches should have prerelease versions
1595
+ const devBranchPatterns = /^(working|development|dev|feature\/|wip\/)/i;
1596
+ if (devBranchPatterns.test(branchName)) {
1597
+ return {
1598
+ pattern: /^\d+\.\d+\.\d+-[a-zA-Z0-9.-]+$/,
1599
+ description: 'X.Y.Z-<tag> (e.g., 1.2.3-dev.0)',
1600
+ isDevelopment: true
1601
+ };
1602
+ }
1603
+ // Main/master/production branches should have release versions
1604
+ const releaseBranchPatterns = /^(main|master|production|release\/)/i;
1605
+ if (releaseBranchPatterns.test(branchName)) {
1606
+ return {
1607
+ pattern: /^\d+\.\d+\.\d+$/,
1608
+ description: 'X.Y.Z (e.g., 1.2.3)',
1609
+ isDevelopment: false
1610
+ };
1611
+ }
1612
+ // For other branches, allow both but prefer release versions
1613
+ return {
1614
+ pattern: /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/,
1615
+ description: 'X.Y.Z or X.Y.Z-<tag>',
1616
+ isDevelopment: false
1617
+ };
1618
+ };
1619
+ /**
1620
+ * Validate version against branch expectations
1621
+ */ const validateVersionForBranch = (version, branchName)=>{
1622
+ const expected = getExpectedVersionPattern(branchName);
1623
+ if (!expected.pattern.test(version)) {
1624
+ return {
1625
+ valid: false,
1626
+ issue: `Invalid version format for branch '${branchName}'`,
1627
+ fix: `Version should match ${expected.description}`
1628
+ };
1629
+ }
1630
+ const isDevVersion = isDevelopmentVersion(version);
1631
+ // Development branches should have development versions
1632
+ if (expected.isDevelopment && !isDevVersion) {
1633
+ return {
1634
+ valid: false,
1635
+ issue: `Release version on development branch '${branchName}'`,
1636
+ fix: 'Run kodrdriv development to update to development version'
1637
+ };
1638
+ }
1639
+ // Release branches should NOT have development versions
1640
+ if (!expected.isDevelopment && branchName.match(/^(main|master|production|release\/)/) && isDevVersion) {
1641
+ return {
1642
+ valid: false,
1643
+ issue: `Development version on release branch '${branchName}'`,
1644
+ fix: 'Do not commit development versions to release branches'
1645
+ };
1646
+ }
1647
+ return {
1648
+ valid: true
1649
+ };
1650
+ };
1651
+
1652
+ /**
1653
+ * Generic LLM improvement function that can be configured for different content types
1654
+ *
1655
+ * This is kodrdriv-specific orchestration logic that combines multiple ai-service
1656
+ * primitives into a higher-level workflow.
1657
+ *
1658
+ * @param currentContent The current content to improve
1659
+ * @param runConfig Runtime configuration
1660
+ * @param promptConfig Prompt configuration
1661
+ * @param promptContext Prompt context
1662
+ * @param outputDirectory Output directory for debug files
1663
+ * @param improvementConfig Configuration for this specific improvement type
1664
+ * @returns Promise resolving to the improved content
1665
+ */ async function improveContentWithLLM(currentContent, runConfig, promptConfig, promptContext, outputDirectory, improvementConfig) {
1666
+ const logger = getDryRunLogger(false);
1667
+ logger.info(`INTERACTIVE_LLM_IMPROVING: Requesting LLM to improve content | Content Type: ${improvementConfig.contentType} | Service: AI | Purpose: Enhance quality`);
1668
+ // Create the improved prompt using the provided function
1669
+ const improvedPromptResult = await improvementConfig.createImprovedPrompt(promptConfig, currentContent, promptContext);
1670
+ // Call the LLM with the improved prompt
1671
+ const improvedResponse = await improvementConfig.callLLM(improvedPromptResult, runConfig, outputDirectory);
1672
+ // Process the response if a processor is provided
1673
+ const finalResult = improvementConfig.processResponse ? improvementConfig.processResponse(improvedResponse) : improvedResponse;
1674
+ logger.info(`INTERACTIVE_LLM_IMPROVED: LLM provided improved content | Content Type: ${improvementConfig.contentType} | Status: enhanced`);
1675
+ return finalResult;
1676
+ }
1677
+
1678
+ const interactive = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
1679
+ __proto__: null,
1680
+ STANDARD_CHOICES,
1681
+ SecureTempFile,
1682
+ cleanupTempFile,
1683
+ createSecureTempFile,
1684
+ editContentInEditor,
1685
+ getLLMFeedbackInEditor,
1686
+ getUserChoice,
1687
+ getUserTextInput,
1688
+ improveContentWithLLM,
1689
+ requireTTY
1690
+ }, Symbol.toStringTag, { value: 'Module' }));
1691
+
1692
+ /**
1693
+ * Compile stop-context configuration into efficient filter rules
1694
+ */ function compileFilters(config) {
1695
+ var _config_caseSensitive;
1696
+ const filters = [];
1697
+ const replacement = config.replacement || '[REDACTED]';
1698
+ const caseSensitive = (_config_caseSensitive = config.caseSensitive) !== null && _config_caseSensitive !== void 0 ? _config_caseSensitive : false;
1699
+ // Compile literal string filters
1700
+ if (config.strings && config.strings.length > 0) {
1701
+ for (const str of config.strings){
1702
+ filters.push({
1703
+ type: 'string',
1704
+ value: str,
1705
+ replacement,
1706
+ caseSensitive
1707
+ });
1708
+ }
1709
+ }
1710
+ // Compile regex pattern filters
1711
+ if (config.patterns && config.patterns.length > 0) {
1712
+ for (const pattern of config.patterns){
1713
+ try {
1714
+ const flags = pattern.flags || (caseSensitive ? 'g' : 'gi');
1715
+ const regex = new RegExp(pattern.regex, flags);
1716
+ filters.push({
1717
+ type: 'pattern',
1718
+ value: regex,
1719
+ replacement,
1720
+ caseSensitive
1721
+ });
1722
+ } catch (error) {
1723
+ const logger = getLogger();
1724
+ logger.warn(`STOP_CONTEXT_INVALID_PATTERN: Failed to compile regex pattern | Pattern: ${pattern.regex} | Error: ${error instanceof Error ? error.message : String(error)} | Action: Skipping pattern`);
1725
+ }
1726
+ }
1727
+ }
1728
+ return filters;
1729
+ }
1730
+ /**
1731
+ * Apply a single filter to text and track matches
1732
+ */ function applyFilter(text, filter, matches) {
1733
+ if (filter.type === 'string') {
1734
+ const searchStr = filter.value;
1735
+ const flags = filter.caseSensitive ? 'g' : 'gi';
1736
+ const regex = new RegExp(escapeRegExp(searchStr), flags);
1737
+ let match;
1738
+ while((match = regex.exec(text)) !== null){
1739
+ matches.push({
1740
+ type: 'string',
1741
+ matched: match[0],
1742
+ position: match.index,
1743
+ replacement: filter.replacement
1744
+ });
1745
+ }
1746
+ return text.replace(regex, filter.replacement);
1747
+ } else {
1748
+ // Pattern filter
1749
+ const regex = filter.value;
1750
+ let match;
1751
+ // Reset regex lastIndex to ensure we start from beginning
1752
+ regex.lastIndex = 0;
1753
+ while((match = regex.exec(text)) !== null){
1754
+ matches.push({
1755
+ type: 'pattern',
1756
+ matched: match[0],
1757
+ position: match.index,
1758
+ replacement: filter.replacement
1759
+ });
1760
+ }
1761
+ // Reset regex lastIndex again before replace
1762
+ regex.lastIndex = 0;
1763
+ return text.replace(regex, filter.replacement);
1764
+ }
1765
+ }
1766
+ /**
1767
+ * Escape special regex characters in a string
1768
+ */ function escapeRegExp(str) {
1769
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1770
+ }
1771
+ /**
1772
+ * Filter content based on stop-context configuration
1773
+ *
1774
+ * @param text - The text to filter
1775
+ * @param config - The stop-context configuration
1776
+ * @returns FilterResult with filtered text and metadata
1777
+ */ function filterContent(text, config) {
1778
+ const logger = getLogger();
1779
+ // If no config or disabled, return original text
1780
+ if (!config || config.enabled === false) {
1781
+ return {
1782
+ filtered: text,
1783
+ originalLength: text.length,
1784
+ filteredLength: text.length,
1785
+ matchCount: 0,
1786
+ matches: []
1787
+ };
1788
+ }
1789
+ const filters = compileFilters(config);
1790
+ // If no filters configured, return original text
1791
+ if (filters.length === 0) {
1792
+ return {
1793
+ filtered: text,
1794
+ originalLength: text.length,
1795
+ filteredLength: text.length,
1796
+ matchCount: 0,
1797
+ matches: []
1798
+ };
1799
+ }
1800
+ let filtered = text;
1801
+ const allMatches = [];
1802
+ // Apply each filter in sequence
1803
+ for (const filter of filters){
1804
+ filtered = applyFilter(filtered, filter, allMatches);
1805
+ }
1806
+ const result = {
1807
+ filtered,
1808
+ originalLength: text.length,
1809
+ filteredLength: filtered.length,
1810
+ matchCount: allMatches.length,
1811
+ matches: allMatches
1812
+ };
1813
+ // Log warning if filters were applied and warnOnFilter is enabled
1814
+ if (config.warnOnFilter !== false && allMatches.length > 0) {
1815
+ logger.warn(`STOP_CONTEXT_FILTERED: Sensitive content filtered from generated text | Matches: ${allMatches.length} | Original Length: ${text.length} | Filtered Length: ${filtered.length} | Action: Review filtered content`);
1816
+ // Log verbose details if logger level is verbose or debug
1817
+ if (logger.level === 'verbose' || logger.level === 'debug') {
1818
+ logger.verbose('STOP_CONTEXT_DETAILS: Filter details:');
1819
+ const stringMatches = allMatches.filter((m)=>m.type === 'string').length;
1820
+ const patternMatches = allMatches.filter((m)=>m.type === 'pattern').length;
1821
+ logger.verbose(` - String matches: ${stringMatches}`);
1822
+ logger.verbose(` - Pattern matches: ${patternMatches}`);
1823
+ logger.verbose(` - Character change: ${text.length - filtered.length} characters removed`);
1824
+ }
1825
+ }
1826
+ // Warn if too much content was filtered
1827
+ const percentFiltered = (text.length - filtered.length) / text.length * 100;
1828
+ if (percentFiltered > 50) {
1829
+ logger.warn(`STOP_CONTEXT_HIGH_FILTER: High percentage of content filtered | Percentage: ${percentFiltered.toFixed(1)}% | Impact: Generated content may be incomplete | Action: Review stop-context configuration`);
1830
+ }
1831
+ return result;
1832
+ }
1833
+ /**
1834
+ * Check if stop-context filtering is enabled in config
1835
+ */ function isStopContextEnabled(config) {
1836
+ if (!config) {
1837
+ return false;
1838
+ }
1839
+ const hasFilters = config.strings && config.strings.length > 0 || config.patterns && config.patterns.length > 0;
1840
+ return Boolean(config.enabled !== false && hasFilters);
1841
+ }
1842
+
1843
+ function _define_property$1(obj, key, value) {
1844
+ if (key in obj) {
1845
+ Object.defineProperty(obj, key, {
1846
+ value: value,
1847
+ enumerable: true,
1848
+ configurable: true,
1849
+ writable: true
1850
+ });
1851
+ } else {
1852
+ obj[key] = value;
1853
+ }
1854
+ return obj;
1855
+ }
1856
+ /**
1857
+ * File-based lock for cross-process synchronization
1858
+ * Uses atomic file operations to coordinate across multiple Node processes
1859
+ */ class FileLock {
1860
+ /**
1861
+ * Acquire the file lock with exponential backoff retry
1862
+ */ async lock() {
1863
+ let attempts = 0;
1864
+ let currentDelay = this.retryDelay;
1865
+ while(attempts < this.maxRetries){
1866
+ try {
1867
+ // Try to create lock file atomically with 'wx' flag (fails if exists)
1868
+ const lockData = {
1869
+ pid: process.pid,
1870
+ timestamp: Date.now(),
1871
+ hostname: os.hostname()
1872
+ };
1873
+ // Check if lock file exists and is stale
1874
+ if (fs.existsSync(this.lockPath)) {
1875
+ const lockContent = fs.readFileSync(this.lockPath, 'utf-8');
1876
+ try {
1877
+ const existingLock = JSON.parse(lockContent);
1878
+ const lockAge = Date.now() - existingLock.timestamp;
1879
+ // If lock is stale, try to remove it
1880
+ if (lockAge > this.lockTimeout) {
1881
+ this.logger.debug(`Removing stale lock file (age: ${lockAge}ms, pid: ${existingLock.pid})`);
1882
+ try {
1883
+ fs.unlinkSync(this.lockPath);
1884
+ } catch {
1885
+ // Lock might have been removed by another process, continue
1886
+ }
1887
+ }
1888
+ } catch {
1889
+ // Invalid lock file, try to remove it
1890
+ try {
1891
+ fs.unlinkSync(this.lockPath);
1892
+ } catch {
1893
+ // Ignore errors
1894
+ }
1895
+ }
1896
+ }
1897
+ // Try to acquire lock
1898
+ fs.writeFileSync(this.lockPath, JSON.stringify(lockData, null, 2), {
1899
+ flag: 'wx'
1900
+ });
1901
+ this.lockAcquired = true;
1902
+ if (attempts > 0) {
1903
+ this.logger.debug(`Acquired file lock after ${attempts} attempts: ${this.lockPath}`);
1904
+ }
1905
+ return;
1906
+ } catch (error) {
1907
+ if (error.code === 'EEXIST') {
1908
+ // Lock file exists, retry with backoff
1909
+ attempts++;
1910
+ if (attempts === 1 || attempts % 10 === 0) {
1911
+ this.logger.verbose(`Waiting for file lock (attempt ${attempts}/${this.maxRetries}): ${this.lockPath}`);
1912
+ }
1913
+ await new Promise((resolve)=>setTimeout(resolve, currentDelay));
1914
+ // Exponential backoff
1915
+ currentDelay = Math.min(currentDelay * 1.5, this.maxRetryDelay);
1916
+ } else {
1917
+ // Unexpected error
1918
+ throw new Error(`Failed to acquire file lock ${this.lockPath}: ${error.message}`);
1919
+ }
1920
+ }
1921
+ }
1922
+ throw new Error(`Failed to acquire file lock after ${this.maxRetries} attempts: ${this.lockPath}`);
1923
+ }
1924
+ /**
1925
+ * Release the file lock
1926
+ */ unlock() {
1927
+ if (!this.lockAcquired) {
1928
+ return;
1929
+ }
1930
+ try {
1931
+ if (fs.existsSync(this.lockPath)) {
1932
+ fs.unlinkSync(this.lockPath);
1933
+ }
1934
+ this.lockAcquired = false;
1935
+ this.logger.silly(`Released file lock: ${this.lockPath}`);
1936
+ } catch (error) {
1937
+ // Lock file might have been removed by another process or stale lock cleanup
1938
+ this.logger.debug(`Error releasing file lock ${this.lockPath}: ${error.message}`);
1939
+ this.lockAcquired = false;
1940
+ }
1941
+ }
1942
+ /**
1943
+ * Check if this instance currently holds the lock
1944
+ */ isLocked() {
1945
+ return this.lockAcquired;
1946
+ }
1947
+ constructor(lockPath){
1948
+ _define_property$1(this, "lockPath", void 0);
1949
+ _define_property$1(this, "lockAcquired", false);
1950
+ _define_property$1(this, "maxRetries", 100); // Maximum number of lock attempts
1951
+ _define_property$1(this, "retryDelay", 100); // Initial retry delay in ms
1952
+ _define_property$1(this, "maxRetryDelay", 2000); // Maximum retry delay in ms
1953
+ _define_property$1(this, "lockTimeout", 30000); // Consider lock stale after 30 seconds
1954
+ _define_property$1(this, "logger", getLogger());
1955
+ this.lockPath = lockPath;
1956
+ }
1957
+ }
1958
+ /**
1959
+ * Manages file-based locks for git repositories (cross-process safe)
1960
+ */ class RepositoryFileLockManager {
1961
+ /**
1962
+ * Get or create a file lock for a specific git repository
1963
+ * @param repoPath Path to the git repository root
1964
+ * @returns FileLock for this repository
1965
+ */ getRepositoryLock(repoPath) {
1966
+ const normalizedPath = path.resolve(repoPath);
1967
+ if (!this.locks.has(normalizedPath)) {
1968
+ // Resolve the actual .git directory (handles both regular repos and submodules)
1969
+ const gitDirPath = this.resolveGitDirectory(normalizedPath);
1970
+ const lockPath = path.join(gitDirPath, 'kodrdriv.lock');
1971
+ this.logger.debug(`Creating file lock for repository: ${normalizedPath} at ${lockPath}`);
1972
+ this.locks.set(normalizedPath, new FileLock(lockPath));
1973
+ // Register cleanup handler on first lock creation
1974
+ if (!this.cleanupRegistered) {
1975
+ this.registerCleanupHandlers();
1976
+ this.cleanupRegistered = true;
1977
+ }
1978
+ }
1979
+ return this.locks.get(normalizedPath);
1980
+ }
1981
+ /**
1982
+ * Resolve the actual .git directory path, handling both regular repos and submodules
1983
+ * @param repoPath Path to the repository root
1984
+ * @returns Path to the actual .git directory
1985
+ */ resolveGitDirectory(repoPath) {
1986
+ const gitPath = path.join(repoPath, '.git');
1987
+ try {
1988
+ const stat = fs.statSync(gitPath);
1989
+ if (stat.isDirectory()) {
1990
+ // Regular git repository
1991
+ return gitPath;
1992
+ } else if (stat.isFile()) {
1993
+ // Git submodule - .git is a file with format: gitdir: <path>
1994
+ const gitFileContent = fs.readFileSync(gitPath, 'utf-8').trim();
1995
+ const match = gitFileContent.match(/^gitdir:\s*(.+)$/);
1996
+ if (match && match[1]) {
1997
+ // Resolve the gitdir path (it's relative to the repo path)
1998
+ const gitDirPath = path.resolve(repoPath, match[1]);
1999
+ this.logger.debug(`Resolved submodule gitdir: ${gitDirPath}`);
2000
+ // Ensure the git directory exists
2001
+ if (!fs.existsSync(gitDirPath)) {
2002
+ throw new Error(`Submodule git directory does not exist: ${gitDirPath}`);
2003
+ }
2004
+ return gitDirPath;
2005
+ }
2006
+ throw new Error(`Invalid .git file format in ${gitPath}: ${gitFileContent}`);
2007
+ }
2008
+ } catch (error) {
2009
+ // Check if error is from statSync (file doesn't exist)
2010
+ if (error.code === 'ENOENT') {
2011
+ throw new Error(`No .git directory or file found in ${repoPath}`);
2012
+ }
2013
+ throw new Error(`Failed to resolve git directory for ${repoPath}: ${error.message}`);
2014
+ }
2015
+ throw new Error(`No .git directory or file found in ${repoPath}`);
2016
+ }
2017
+ /**
2018
+ * Register cleanup handlers to release locks on process exit
2019
+ */ registerCleanupHandlers() {
2020
+ const cleanup = ()=>{
2021
+ this.destroy();
2022
+ };
2023
+ // Handle various exit scenarios
2024
+ process.on('exit', cleanup);
2025
+ process.on('SIGINT', ()=>{
2026
+ cleanup();
2027
+ process.exit(130); // Standard exit code for SIGINT
2028
+ });
2029
+ process.on('SIGTERM', ()=>{
2030
+ cleanup();
2031
+ process.exit(143); // Standard exit code for SIGTERM
2032
+ });
2033
+ process.on('uncaughtException', (error)=>{
2034
+ this.logger.error('FILELOCK_UNCAUGHT_EXCEPTION: Uncaught exception detected, cleaning up locks | Error: ' + error + ' | Action: Release all locks');
2035
+ cleanup();
2036
+ process.exit(1);
2037
+ });
2038
+ }
2039
+ /**
2040
+ * Execute a git operation with repository-level file locking
2041
+ * @param repoPath Path to the git repository root
2042
+ * @param operation The async operation to execute under lock
2043
+ * @param operationName Optional name for logging
2044
+ * @returns Result of the operation
2045
+ */ async withGitLock(repoPath, operation, operationName) {
2046
+ const lock = this.getRepositoryLock(repoPath);
2047
+ const startWait = Date.now();
2048
+ this.logger.silly(`Acquiring file lock for ${repoPath}${operationName ? ` for: ${operationName}` : ''}`);
2049
+ await lock.lock();
2050
+ const waitTime = Date.now() - startWait;
2051
+ if (waitTime > 100) {
2052
+ this.logger.debug(`Acquired file lock for ${repoPath} after ${waitTime}ms${operationName ? ` for: ${operationName}` : ''}`);
2053
+ }
2054
+ try {
2055
+ return await operation();
2056
+ } finally{
2057
+ lock.unlock();
2058
+ }
2059
+ }
2060
+ /**
2061
+ * Clean up all locks
2062
+ */ destroy() {
2063
+ this.logger.debug(`Cleaning up ${this.locks.size} file lock(s)`);
2064
+ for (const lock of this.locks.values()){
2065
+ lock.unlock();
2066
+ }
2067
+ this.locks.clear();
2068
+ }
2069
+ constructor(){
2070
+ _define_property$1(this, "locks", new Map());
2071
+ _define_property$1(this, "logger", getLogger());
2072
+ _define_property$1(this, "cleanupRegistered", false);
2073
+ }
2074
+ }
2075
+
2076
+ function _define_property(obj, key, value) {
2077
+ if (key in obj) {
2078
+ Object.defineProperty(obj, key, {
2079
+ value: value,
2080
+ enumerable: true,
2081
+ configurable: true,
2082
+ writable: true
2083
+ });
2084
+ } else {
2085
+ obj[key] = value;
2086
+ }
2087
+ return obj;
2088
+ }
2089
+ /**
2090
+ * Manages per-repository locks for git operations (cross-process safe)
2091
+ * Prevents concurrent git operations in the same repository (which cause .git/index.lock conflicts)
2092
+ * while still allowing parallel operations across different repositories
2093
+ *
2094
+ * Uses file-based locks to coordinate across multiple processes (e.g., parallel tree execution)
2095
+ */ class RepositoryMutexManager {
2096
+ /**
2097
+ * Execute a git operation with repository-level locking
2098
+ * @param packagePath Path to the package (will find its git repo root)
2099
+ * @param operation The async operation to execute under lock
2100
+ * @param operationName Optional name for logging
2101
+ * @returns Result of the operation
2102
+ */ async withGitLock(packagePath, operation, operationName) {
2103
+ const repoPath = getGitRepositoryRoot(packagePath);
2104
+ if (!repoPath) {
2105
+ // Not in a git repository, execute without lock
2106
+ this.logger.debug(`No git repository found for ${packagePath}, executing without lock`);
2107
+ return await operation();
2108
+ }
2109
+ return await this.lockManager.withGitLock(repoPath, operation, operationName);
2110
+ }
2111
+ /**
2112
+ * Destroy all locks and clean up resources
2113
+ */ destroy() {
2114
+ this.lockManager.destroy();
2115
+ }
2116
+ constructor(){
2117
+ _define_property(this, "lockManager", void 0);
2118
+ _define_property(this, "logger", getLogger());
2119
+ this.lockManager = new RepositoryFileLockManager();
2120
+ }
2121
+ }
2122
+ /**
2123
+ * Find the git repository root for a given path
2124
+ * Walks up the directory tree until it finds a .git directory
2125
+ * @param startPath Starting path (can be a file or directory)
2126
+ * @returns Absolute path to git repository root, or null if not in a git repo
2127
+ */ function getGitRepositoryRoot(startPath) {
2128
+ let currentPath = path.resolve(startPath);
2129
+ // If startPath is a file, start from its directory
2130
+ try {
2131
+ const stats = statSync(currentPath);
2132
+ if (stats.isFile()) {
2133
+ currentPath = path.dirname(currentPath);
2134
+ }
2135
+ } catch {
2136
+ // If stat fails, assume it's a directory and continue
2137
+ }
2138
+ // First try using git command as it's the most reliable
2139
+ try {
2140
+ const root = execSync('git rev-parse --show-toplevel', {
2141
+ cwd: currentPath,
2142
+ stdio: [
2143
+ 'ignore',
2144
+ 'pipe',
2145
+ 'ignore'
2146
+ ],
2147
+ encoding: 'utf-8'
2148
+ }).trim();
2149
+ return root;
2150
+ } catch {
2151
+ // Fallback to manual walk-up if git command fails (e.g. git not in path or other issues)
2152
+ const root = path.parse(currentPath).root;
2153
+ while(currentPath !== root){
2154
+ const gitPath = path.join(currentPath, '.git');
2155
+ try {
2156
+ const stats = statSync(gitPath);
2157
+ if (stats.isDirectory() || stats.isFile()) {
2158
+ // Found .git (can be directory or file for submodules)
2159
+ return currentPath;
2160
+ }
2161
+ } catch {
2162
+ // .git doesn't exist at this level, continue up
2163
+ }
2164
+ // Move up one directory
2165
+ const parentPath = path.dirname(currentPath);
2166
+ if (parentPath === currentPath) {
2167
+ break;
2168
+ }
2169
+ currentPath = parentPath;
2170
+ }
2171
+ }
2172
+ return null;
2173
+ }
2174
+ /**
2175
+ * Check if a path is within a git repository
2176
+ * @param checkPath Path to check
2177
+ * @returns true if path is in a git repository
2178
+ */ function isInGitRepository(checkPath) {
2179
+ // If it's not a directory that exists, it's not in a git repository
2180
+ try {
2181
+ const stats = statSync(checkPath);
2182
+ if (!stats.isDirectory()) {
2183
+ return false;
2184
+ }
2185
+ } catch {
2186
+ return false;
2187
+ }
2188
+ // Try using git command first
2189
+ try {
2190
+ execSync('git rev-parse --is-inside-work-tree', {
2191
+ cwd: checkPath,
2192
+ stdio: [
2193
+ 'ignore',
2194
+ 'ignore',
2195
+ 'ignore'
2196
+ ]
2197
+ });
2198
+ return true;
2199
+ } catch {
2200
+ // If git command fails, it's definitely not a git repo according to git
2201
+ return false;
2202
+ }
2203
+ }
2204
+ /**
2205
+ * Check if two paths are in the same git repository
2206
+ * @param path1 First path
2207
+ * @param path2 Second path
2208
+ * @returns true if both paths are in the same git repository
2209
+ */ function areInSameRepository(path1, path2) {
2210
+ const repo1 = getGitRepositoryRoot(path1);
2211
+ const repo2 = getGitRepositoryRoot(path2);
2212
+ if (!repo1 || !repo2) {
2213
+ return false;
2214
+ }
2215
+ return repo1 === repo2;
2216
+ }
2217
+ // Global singleton instance
2218
+ let globalGitMutexManager = null;
2219
+ /**
2220
+ * Get the global git mutex manager instance
2221
+ * Creates one if it doesn't exist
2222
+ */ function getGitMutexManager() {
2223
+ if (!globalGitMutexManager) {
2224
+ globalGitMutexManager = new RepositoryMutexManager();
2225
+ }
2226
+ return globalGitMutexManager;
2227
+ }
2228
+ /**
2229
+ * Destroy the global git mutex manager
2230
+ * Should be called when shutting down or during cleanup
2231
+ */ function destroyGitMutexManager() {
2232
+ if (globalGitMutexManager) {
2233
+ globalGitMutexManager.destroy();
2234
+ globalGitMutexManager = null;
2235
+ }
2236
+ }
2237
+ /**
2238
+ * Helper function to wrap git operations with automatic locking
2239
+ * Uses the global git mutex manager
2240
+ *
2241
+ * @example
2242
+ * await runGitWithLock(packagePath, async () => {
2243
+ * await run('git add package.json');
2244
+ * await run('git commit -m "Update version"');
2245
+ * }, 'version bump commit');
2246
+ */ async function runGitWithLock(packagePath, operation, operationName) {
2247
+ const manager = getGitMutexManager();
2248
+ return await manager.withGitLock(packagePath, operation, operationName);
2249
+ }
2250
+
2251
+ /**
2252
+ * Adapter for converting kodrdriv Config to ai-service AIConfig
2253
+ */ /**
2254
+ * Convert kodrdriv Config to AIConfig
2255
+ */ function toAIConfig(config) {
2256
+ return {
2257
+ apiKey: config.openaiApiKey || process.env.OPENAI_API_KEY,
2258
+ model: config.model,
2259
+ reasoning: config.openaiReasoning,
2260
+ commands: {
2261
+ commit: config.commit ? {
2262
+ model: config.commit.model,
2263
+ reasoning: config.commit.openaiReasoning
2264
+ } : undefined,
2265
+ release: config.release ? {
2266
+ model: config.release.model,
2267
+ reasoning: config.release.openaiReasoning
2268
+ } : undefined,
2269
+ review: config.review ? {
2270
+ model: config.review.model,
2271
+ reasoning: config.review.openaiReasoning
2272
+ } : undefined
2273
+ }
2274
+ };
2275
+ }
2276
+
2277
+ /**
2278
+ * Create a StorageAdapter implementation using kodrdriv Storage
2279
+ *
2280
+ * @param outputDirectory - Directory where output files should be written (default: 'output')
2281
+ */ function createStorageAdapter(outputDirectory = 'output') {
2282
+ const storage = createStorage();
2283
+ return {
2284
+ async writeOutput (fileName, content) {
2285
+ // Ensure output directory exists
2286
+ await storage.ensureDirectory(outputDirectory);
2287
+ // Write file to output directory
2288
+ const filePath = path__default.join(outputDirectory, fileName);
2289
+ await storage.writeFile(filePath, content, 'utf8');
2290
+ },
2291
+ async readTemp (fileName) {
2292
+ return await storage.readFile(fileName, 'utf8');
2293
+ },
2294
+ async writeTemp (fileName, content) {
2295
+ await storage.writeFile(fileName, content, 'utf8');
2296
+ },
2297
+ async readFile (fileName, encoding = 'utf8') {
2298
+ return await storage.readFile(fileName, encoding);
2299
+ }
2300
+ };
2301
+ }
2302
+
2303
+ /**
2304
+ * Create a Logger implementation using kodrdriv logging
2305
+ */ function createLoggerAdapter(dryRun) {
2306
+ const logger = getDryRunLogger(dryRun);
2307
+ return {
2308
+ info (message, ...meta) {
2309
+ logger.info(message, ...meta);
2310
+ },
2311
+ error (message, ...meta) {
2312
+ logger.error(message, ...meta);
2313
+ },
2314
+ warn (message, ...meta) {
2315
+ logger.warn(message, ...meta);
2316
+ },
2317
+ debug (message, ...meta) {
2318
+ logger.debug(message, ...meta);
2319
+ },
2320
+ // Additional methods required by riotprompt
2321
+ verbose (message, ...meta) {
2322
+ // Use debug for verbose if available, otherwise info
2323
+ if ('verbose' in logger && typeof logger.verbose === 'function') {
2324
+ logger.verbose(message, ...meta);
2325
+ } else {
2326
+ logger.debug(message, ...meta);
2327
+ }
2328
+ },
2329
+ silly (message, ...meta) {
2330
+ // Use debug for silly if available, otherwise skip
2331
+ if ('silly' in logger && typeof logger.silly === 'function') {
2332
+ logger.silly(message, ...meta);
2333
+ } else {
2334
+ logger.debug(message, ...meta);
2335
+ }
2336
+ }
2337
+ };
2338
+ }
2339
+
2340
+ /**
2341
+ * Kodrdriv-specific validation utilities
2342
+ *
2343
+ * Note: Generic validation functions (validateString, validateNumber, etc.)
2344
+ * are in @eldrforge/shared
2345
+ */ /**
2346
+ * Validates and safely casts data to ReleaseSummary type
2347
+ */ const validateReleaseSummary = (data)=>{
2348
+ if (!data || typeof data !== 'object') {
2349
+ throw new Error('Invalid release summary: not an object');
2350
+ }
2351
+ if (typeof data.title !== 'string') {
2352
+ throw new Error('Invalid release summary: title must be a string');
2353
+ }
2354
+ if (typeof data.body !== 'string') {
2355
+ throw new Error('Invalid release summary: body must be a string');
2356
+ }
2357
+ return data;
2358
+ };
2359
+ /**
2360
+ * Validates transcription result has required text property
2361
+ */ const validateTranscriptionResult = (data)=>{
2362
+ if (!data || typeof data !== 'object') {
2363
+ throw new Error('Invalid transcription result: not an object');
2364
+ }
2365
+ if (typeof data.text !== 'string') {
2366
+ throw new Error('Invalid transcription result: text property must be a string');
2367
+ }
2368
+ return data;
2369
+ };
2370
+ /**
2371
+ * Sanitizes and truncates direction parameter for safe use in prompts
2372
+ * @param direction The direction string to sanitize
2373
+ * @param maxLength Maximum length before truncation (default: 2000)
2374
+ * @returns Sanitized and truncated direction string
2375
+ */ const sanitizeDirection = (direction, maxLength = 2000)=>{
2376
+ if (!direction) {
2377
+ return undefined;
2378
+ }
2379
+ // Remove newlines and excessive whitespace to prevent template breakage
2380
+ const sanitized = direction.replace(/\r?\n/g, ' ') // Replace newlines with spaces
2381
+ .replace(/\s+/g, ' ') // Replace multiple whitespace with single space
2382
+ .trim();
2383
+ // Truncate if too long
2384
+ if (sanitized.length > maxLength) {
2385
+ const truncated = sanitized.substring(0, maxLength - 3) + '...';
2386
+ // Log truncation for debugging
2387
+ // eslint-disable-next-line no-console
2388
+ console.warn(`Direction truncated from ${sanitized.length} to ${truncated.length} characters`);
2389
+ return truncated;
2390
+ }
2391
+ return sanitized;
2392
+ };
2393
+
2394
+ /**
2395
+ * Standardized error handler for all commands
2396
+ */ const handleCommandError = async (error, options)=>{
2397
+ const { logger, command, exitOnError = false } = options;
2398
+ // Handle user cancellation gracefully
2399
+ if (error instanceof UserCancellationError) {
2400
+ logger.info('USER_CANCELLATION: Operation cancelled by user | Reason: ' + error.message + ' | Status: aborted');
2401
+ if (exitOnError) process.exit(0);
2402
+ return;
2403
+ }
2404
+ // Handle known command errors
2405
+ if (error instanceof CommandError) {
2406
+ // Import PullRequestCheckError dynamically to avoid circular imports
2407
+ const { PullRequestCheckError } = await import('@eldrforge/shared');
2408
+ // Special handling for PR check errors since they have detailed recovery instructions
2409
+ if (error instanceof PullRequestCheckError) {
2410
+ // The error has already displayed its detailed recovery instructions
2411
+ // Just show a brief summary here
2412
+ logger.error(`COMMAND_FAILED: Command execution failed | Command: ${command} | Error: ${error.message} | Recovery: See above`);
2413
+ logger.info('ERROR_RECOVERY_INFO: Detailed recovery instructions provided above | Action: Review and follow steps');
2414
+ } else {
2415
+ logger.error(`COMMAND_FAILED: Command execution failed | Command: ${command} | Error: ${error.message}`);
2416
+ if (error.cause && typeof error.cause === 'object' && 'message' in error.cause) {
2417
+ logger.debug(`Caused by: ${error.cause.message}`);
2418
+ if (logger.isDebugEnabled() && 'stack' in error.cause) {
2419
+ logger.debug(`Stack trace:`, error.cause.stack);
2420
+ }
2421
+ }
2422
+ // Provide recovery suggestions for recoverable errors
2423
+ if (error.recoverable) {
2424
+ logger.info('ERROR_RECOVERABLE: This error is recoverable | Action: Retry operation or adjust configuration | Status: can-retry');
2425
+ }
2426
+ }
2427
+ if (exitOnError) process.exit(1);
2428
+ throw error;
2429
+ }
2430
+ // Handle unexpected errors
2431
+ logger.error(`ERROR_UNEXPECTED: Command encountered unexpected error | Command: ${command} | Error: ${error.message} | Type: unexpected`);
2432
+ if (logger.isDebugEnabled()) {
2433
+ logger.debug(`Stack trace:`, error.stack);
2434
+ }
2435
+ if (exitOnError) process.exit(1);
2436
+ throw error;
2437
+ };
2438
+ /**
2439
+ * Wrapper for command execution with standardized error handling
2440
+ */ const executeWithErrorHandling = async (command, logger, execution, exitOnError = true)=>{
2441
+ try {
2442
+ return await execution();
2443
+ } catch (error) {
2444
+ await handleCommandError(error, {
2445
+ logger,
2446
+ command,
2447
+ exitOnError
2448
+ });
2449
+ // This line only reached if exitOnError is false
2450
+ throw error;
2451
+ }
2452
+ };
2453
+ /**
2454
+ * Creates a command result for successful operations
2455
+ */ const createSuccessResult = (data, warnings)=>({
2456
+ success: true,
2457
+ data,
2458
+ warnings
2459
+ });
2460
+ /**
2461
+ * Creates a command result for failed operations
2462
+ */ const createErrorResult = (error, warnings)=>({
2463
+ success: false,
2464
+ error,
2465
+ warnings
2466
+ });
2467
+
2468
+ const ConfigSchema = z.object({
2469
+ dryRun: z.boolean().optional(),
2470
+ verbose: z.boolean().optional(),
2471
+ debug: z.boolean().optional(),
2472
+ overrides: z.boolean().optional(),
2473
+ model: z.string().optional(),
2474
+ openaiReasoning: z.enum([
2475
+ 'low',
2476
+ 'medium',
2477
+ 'high'
2478
+ ]).optional(),
2479
+ openaiMaxOutputTokens: z.number().optional(),
2480
+ contextDirectories: z.array(z.string()).optional(),
2481
+ outputDirectory: z.string().optional(),
2482
+ preferencesDirectory: z.string().optional(),
2483
+ commit: z.object({
2484
+ add: z.boolean().optional(),
2485
+ cached: z.boolean().optional(),
2486
+ sendit: z.boolean().optional(),
2487
+ interactive: z.boolean().optional(),
2488
+ amend: z.boolean().optional(),
2489
+ push: z.union([
2490
+ z.boolean(),
2491
+ z.string()
2492
+ ]).optional(),
2493
+ messageLimit: z.number().optional(),
2494
+ context: z.string().optional(),
2495
+ contextFiles: z.array(z.string()).optional(),
2496
+ direction: z.string().optional(),
2497
+ skipFileCheck: z.boolean().optional(),
2498
+ maxDiffBytes: z.number().optional(),
2499
+ model: z.string().optional(),
2500
+ openaiReasoning: z.enum([
2501
+ 'low',
2502
+ 'medium',
2503
+ 'high'
2504
+ ]).optional(),
2505
+ openaiMaxOutputTokens: z.number().optional(),
2506
+ // Agentic options (always enabled)
2507
+ maxAgenticIterations: z.number().optional(),
2508
+ allowCommitSplitting: z.boolean().optional(),
2509
+ autoSplit: z.boolean().optional(),
2510
+ toolTimeout: z.number().optional(),
2511
+ selfReflection: z.boolean().optional()
2512
+ }).optional(),
2513
+ audioCommit: z.object({
2514
+ maxRecordingTime: z.number().optional(),
2515
+ audioDevice: z.string().optional(),
2516
+ file: z.string().optional(),
2517
+ keepTemp: z.boolean().optional(),
2518
+ model: z.string().optional(),
2519
+ openaiReasoning: z.enum([
2520
+ 'low',
2521
+ 'medium',
2522
+ 'high'
2523
+ ]).optional(),
2524
+ openaiMaxOutputTokens: z.number().optional()
2525
+ }).optional(),
2526
+ release: z.object({
2527
+ from: z.string().optional(),
2528
+ to: z.string().optional(),
2529
+ messageLimit: z.number().optional(),
2530
+ context: z.string().optional(),
2531
+ contextFiles: z.array(z.string()).optional(),
2532
+ interactive: z.boolean().optional(),
2533
+ focus: z.string().optional(),
2534
+ maxDiffBytes: z.number().optional(),
2535
+ model: z.string().optional(),
2536
+ openaiReasoning: z.enum([
2537
+ 'low',
2538
+ 'medium',
2539
+ 'high'
2540
+ ]).optional(),
2541
+ openaiMaxOutputTokens: z.number().optional(),
2542
+ noMilestones: z.boolean().optional(),
2543
+ fromMain: z.boolean().optional(),
2544
+ currentBranch: z.string().optional(),
2545
+ // Agentic options (always enabled)
2546
+ maxAgenticIterations: z.number().optional(),
2547
+ selfReflection: z.boolean().optional()
2548
+ }).optional(),
2549
+ review: z.object({
2550
+ includeCommitHistory: z.boolean().optional(),
2551
+ includeRecentDiffs: z.boolean().optional(),
2552
+ includeReleaseNotes: z.boolean().optional(),
2553
+ includeGithubIssues: z.boolean().optional(),
2554
+ commitHistoryLimit: z.number().optional(),
2555
+ diffHistoryLimit: z.number().optional(),
2556
+ releaseNotesLimit: z.number().optional(),
2557
+ githubIssuesLimit: z.number().optional(),
2558
+ context: z.string().optional(),
2559
+ sendit: z.boolean().optional(),
2560
+ note: z.string().optional(),
2561
+ editorTimeout: z.number().optional(),
2562
+ maxContextErrors: z.number().optional(),
2563
+ model: z.string().optional(),
2564
+ openaiReasoning: z.enum([
2565
+ 'low',
2566
+ 'medium',
2567
+ 'high'
2568
+ ]).optional(),
2569
+ openaiMaxOutputTokens: z.number().optional(),
2570
+ file: z.string().optional(),
2571
+ directory: z.string().optional()
2572
+ }).optional(),
2573
+ audioReview: z.object({
2574
+ includeCommitHistory: z.boolean().optional(),
2575
+ includeRecentDiffs: z.boolean().optional(),
2576
+ includeReleaseNotes: z.boolean().optional(),
2577
+ includeGithubIssues: z.boolean().optional(),
2578
+ commitHistoryLimit: z.number().optional(),
2579
+ diffHistoryLimit: z.number().optional(),
2580
+ releaseNotesLimit: z.number().optional(),
2581
+ githubIssuesLimit: z.number().optional(),
2582
+ context: z.string().optional(),
2583
+ sendit: z.boolean().optional(),
2584
+ maxRecordingTime: z.number().optional(),
2585
+ audioDevice: z.string().optional(),
2586
+ file: z.string().optional(),
2587
+ directory: z.string().optional(),
2588
+ keepTemp: z.boolean().optional(),
2589
+ model: z.string().optional(),
2590
+ openaiReasoning: z.enum([
2591
+ 'low',
2592
+ 'medium',
2593
+ 'high'
2594
+ ]).optional(),
2595
+ openaiMaxOutputTokens: z.number().optional()
2596
+ }).optional(),
2597
+ publish: z.object({
2598
+ mergeMethod: z.enum([
2599
+ 'merge',
2600
+ 'squash',
2601
+ 'rebase'
2602
+ ]).optional(),
2603
+ from: z.string().optional(),
2604
+ targetVersion: z.string().optional(),
2605
+ interactive: z.boolean().optional(),
2606
+ skipAlreadyPublished: z.boolean().optional(),
2607
+ forceRepublish: z.boolean().optional(),
2608
+ dependencyUpdatePatterns: z.array(z.string()).optional(),
2609
+ scopedDependencyUpdates: z.array(z.string()).optional(),
2610
+ requiredEnvVars: z.array(z.string()).optional(),
2611
+ linkWorkspacePackages: z.boolean().optional(),
2612
+ unlinkWorkspacePackages: z.boolean().optional(),
2613
+ checksTimeout: z.number().optional(),
2614
+ skipUserConfirmation: z.boolean().optional(),
2615
+ syncTarget: z.boolean().optional(),
2616
+ sendit: z.boolean().optional(),
2617
+ waitForReleaseWorkflows: z.boolean().optional(),
2618
+ releaseWorkflowsTimeout: z.number().optional(),
2619
+ releaseWorkflowNames: z.array(z.string()).optional(),
2620
+ targetBranch: z.string().optional(),
2621
+ noMilestones: z.boolean().optional(),
2622
+ fromMain: z.boolean().optional(),
2623
+ skipPrePublishMerge: z.boolean().optional(),
2624
+ updateDeps: z.string().optional(),
2625
+ agenticPublish: z.boolean().optional(),
2626
+ agenticPublishMaxIterations: z.number().optional()
2627
+ }).optional(),
2628
+ branches: z.record(z.string(), z.object({
2629
+ targetBranch: z.string().optional(),
2630
+ developmentBranch: z.boolean().optional(),
2631
+ version: z.object({
2632
+ type: z.enum([
2633
+ 'release',
2634
+ 'prerelease'
2635
+ ]),
2636
+ increment: z.boolean().optional(),
2637
+ incrementLevel: z.enum([
2638
+ 'patch',
2639
+ 'minor',
2640
+ 'major'
2641
+ ]).optional(),
2642
+ tag: z.string().optional()
2643
+ }).optional()
2644
+ })).optional(),
2645
+ link: z.object({
2646
+ scopeRoots: z.record(z.string(), z.string()).optional(),
2647
+ dryRun: z.boolean().optional(),
2648
+ packageArgument: z.string().optional(),
2649
+ externals: z.array(z.string()).optional()
2650
+ }).optional(),
2651
+ unlink: z.object({
2652
+ scopeRoots: z.record(z.string(), z.string()).optional(),
2653
+ workspaceFile: z.string().optional(),
2654
+ dryRun: z.boolean().optional(),
2655
+ cleanNodeModules: z.boolean().optional(),
2656
+ packageArgument: z.string().optional(),
2657
+ externals: z.array(z.string()).optional()
2658
+ }).optional(),
2659
+ tree: z.object({
2660
+ directories: z.array(z.string()).optional(),
2661
+ exclude: z.array(z.string()).optional(),
2662
+ startFrom: z.string().optional(),
2663
+ stopAt: z.string().optional(),
2664
+ cmd: z.string().optional(),
2665
+ builtInCommand: z.string().optional(),
2666
+ continue: z.boolean().optional(),
2667
+ status: z.boolean().optional(),
2668
+ promote: z.string().optional(),
2669
+ packageArgument: z.string().optional(),
2670
+ cleanNodeModules: z.boolean().optional(),
2671
+ externals: z.array(z.string()).optional(),
2672
+ // Parallel execution options
2673
+ parallel: z.boolean().optional(),
2674
+ maxConcurrency: z.number().optional(),
2675
+ retry: z.object({
2676
+ maxAttempts: z.number().optional(),
2677
+ initialDelayMs: z.number().optional(),
2678
+ maxDelayMs: z.number().optional(),
2679
+ backoffMultiplier: z.number().optional(),
2680
+ retriableErrors: z.array(z.string()).optional()
2681
+ }).optional(),
2682
+ recovery: z.object({
2683
+ checkpointInterval: z.enum([
2684
+ 'package',
2685
+ 'batch'
2686
+ ]).optional(),
2687
+ autoRetry: z.boolean().optional(),
2688
+ continueOnError: z.boolean().optional()
2689
+ }).optional(),
2690
+ monitoring: z.object({
2691
+ showProgress: z.boolean().optional(),
2692
+ showMetrics: z.boolean().optional(),
2693
+ logLevel: z.enum([
2694
+ 'minimal',
2695
+ 'normal',
2696
+ 'verbose'
2697
+ ]).optional()
2698
+ }).optional(),
2699
+ // Recovery options
2700
+ markCompleted: z.array(z.string()).optional(),
2701
+ skipPackages: z.array(z.string()).optional(),
2702
+ retryFailed: z.boolean().optional(),
2703
+ skipFailed: z.boolean().optional(),
2704
+ resetPackage: z.string().optional(),
2705
+ statusParallel: z.boolean().optional(),
2706
+ auditBranches: z.boolean().optional(),
2707
+ validateState: z.boolean().optional()
2708
+ }).optional(),
2709
+ development: z.object({
2710
+ targetVersion: z.string().optional(),
2711
+ noMilestones: z.boolean().optional(),
2712
+ tagWorkingBranch: z.boolean().optional(),
2713
+ createRetroactiveTags: z.boolean().optional(),
2714
+ workingTagPrefix: z.string().optional()
2715
+ }).optional(),
2716
+ versions: z.object({
2717
+ subcommand: z.string().optional(),
2718
+ directories: z.array(z.string()).optional()
2719
+ }).optional(),
2720
+ updates: z.object({
2721
+ scope: z.string().optional(),
2722
+ directories: z.array(z.string()).optional(),
2723
+ interProject: z.boolean().optional()
2724
+ }).optional(),
2725
+ excludedPatterns: z.array(z.string()).optional(),
2726
+ traits: z.any().optional(),
2727
+ stopContext: z.object({
2728
+ enabled: z.boolean().optional(),
2729
+ strings: z.array(z.string()).optional(),
2730
+ patterns: z.array(z.object({
2731
+ regex: z.string(),
2732
+ flags: z.string().optional(),
2733
+ description: z.string().optional()
2734
+ })).optional(),
2735
+ caseSensitive: z.boolean().optional(),
2736
+ replacement: z.string().optional(),
2737
+ warnOnFilter: z.boolean().optional()
2738
+ }).optional()
2739
+ });
2740
+ const SecureConfigSchema = z.object({
2741
+ openaiApiKey: z.string().optional()
2742
+ });
2743
+ const CommandConfigSchema = z.object({
2744
+ commandName: z.string().optional()
2745
+ });
2746
+
2747
+ export { ALLOWED_COMMANDS, COMMAND_AUDIO_COMMIT, COMMAND_AUDIO_REVIEW, COMMAND_CHECK_CONFIG, COMMAND_CLEAN, COMMAND_COMMIT, COMMAND_DEVELOPMENT, COMMAND_INIT_CONFIG, COMMAND_LINK, COMMAND_PRECOMMIT, COMMAND_PUBLISH, COMMAND_RELEASE, COMMAND_REVIEW, COMMAND_SELECT_AUDIO, COMMAND_TREE, COMMAND_UNLINK, COMMAND_UPDATES, COMMAND_VERSIONS, CommandConfigSchema, ConfigSchema, DATE_FORMAT_DAY, DATE_FORMAT_HOURS, DATE_FORMAT_MILLISECONDS, DATE_FORMAT_MINUTES, DATE_FORMAT_MONTH, DATE_FORMAT_MONTH_DAY, DATE_FORMAT_SECONDS, DATE_FORMAT_SHORT_TIMESTAMP, DATE_FORMAT_YEAR, DATE_FORMAT_YEAR_MONTH, DATE_FORMAT_YEAR_MONTH_DAY, DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES, DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS, DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS_MILLISECONDS, DATE_FORMAT_YEAR_MONTH_DAY_SLASH, DEFAULT_ADD, DEFAULT_AMEND_MODE, DEFAULT_BINARY_TO_TEXT_ENCODING, DEFAULT_CACHED, DEFAULT_CHARACTER_ENCODING, DEFAULT_COMMAND, DEFAULT_CONFIG_DIR, DEFAULT_CONTEXT_DIRECTORIES, DEFAULT_DEBUG, DEFAULT_DIFF, DEFAULT_DIRECTORY_PREFIX, DEFAULT_DRY_RUN, DEFAULT_EXCLUDED_PATTERNS, DEFAULT_FROM_COMMIT_ALIAS, DEFAULT_GIT_COMMAND_MAX_BUFFER, DEFAULT_IGNORE_PATTERNS, DEFAULT_INSTRUCTIONS_COMMIT_FILE, DEFAULT_INSTRUCTIONS_DIR, DEFAULT_INSTRUCTIONS_MAP, DEFAULT_INSTRUCTIONS_RELEASE_FILE, DEFAULT_INSTRUCTIONS_REVIEW_FILE, DEFAULT_INTERACTIVE_MODE, DEFAULT_LOG, DEFAULT_MAX_DIFF_BYTES, DEFAULT_MERGE_METHOD, DEFAULT_MESSAGE_LIMIT, DEFAULT_MODEL, DEFAULT_MODEL_STRONG, DEFAULT_OPENAI_MAX_OUTPUT_TOKENS, DEFAULT_OPENAI_REASONING, DEFAULT_OUTPUT_DIRECTORY, DEFAULT_OVERRIDES, DEFAULT_PATH_SEPARATOR, DEFAULT_PERSONA_DIR, DEFAULT_PERSONA_MAP, DEFAULT_PERSONA_RELEASER_FILE, DEFAULT_PERSONA_YOU_FILE, DEFAULT_PREFERENCES_DIRECTORY, DEFAULT_SENDIT_MODE, DEFAULT_TO_COMMIT_ALIAS, DEFAULT_VERBOSE, diff as Diff, FileLock, files as Files, INTERNAL_DATETIME_FORMAT, INTERNAL_DEFAULT_OUTPUT_FILE, KODRDRIV_DEFAULTS, log as Log, PROGRAM_NAME, RepositoryFileLockManager, RepositoryMutexManager, SecureConfigSchema, VERSION, areInSameRepository, calculateBranchDependentVersion, checkIfTagExists, confirmVersionInteractively, createErrorResult, createLoggerAdapter, createStorageAdapter, createSuccessResult, destroyGitMutexManager, executeWithErrorHandling, filterContent, findDevelopmentBranch, getDryRunLogger, getExpectedVersionPattern, getGitMutexManager, getGitRepositoryRoot, getLogger, getNpmPublishedVersion, getOutputPath, getTagInfo, getTimestampedArchivedAudioFilename, getTimestampedArchivedTranscriptFilename, getTimestampedAudioFilename, getTimestampedCommitFilename, getTimestampedFilename, getTimestampedReleaseNotesFilename, getTimestampedRequestFilename, getTimestampedResponseFilename, getTimestampedReviewFilename, getTimestampedReviewNotesFilename, getTimestampedTranscriptFilename, getVersionFromBranch, handleCommandError, haveSamePrereleaseTag, improveContentWithLLM, isDevelopmentVersion, isInGitRepository, isReleaseVersion, isStopContextEnabled, isVersionPublishedOnNpm, runGitWithLock, sanitizeDirection, setLogLevel, toAIConfig, validateReleaseSummary, validateTranscriptionResult, validateVersionForBranch };
2748
+ //# sourceMappingURL=index.js.map