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