@eldrforge/kodrdriv 1.2.29 → 1.2.123

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 (82) hide show
  1. package/dist/application.js +16 -13
  2. package/dist/application.js.map +1 -1
  3. package/dist/arguments.js +5 -5
  4. package/dist/arguments.js.map +1 -1
  5. package/dist/commands/audio-review.js +2 -5
  6. package/dist/commands/audio-review.js.map +1 -1
  7. package/dist/commands/clean.js +2 -4
  8. package/dist/commands/clean.js.map +1 -1
  9. package/dist/commands/commit.js +3 -6
  10. package/dist/commands/commit.js.map +1 -1
  11. package/dist/commands/development.js +7 -7
  12. package/dist/commands/development.js.map +1 -1
  13. package/dist/commands/link.js +3 -7
  14. package/dist/commands/link.js.map +1 -1
  15. package/dist/commands/precommit.js +99 -0
  16. package/dist/commands/precommit.js.map +1 -0
  17. package/dist/commands/publish.js +47 -32
  18. package/dist/commands/publish.js.map +1 -1
  19. package/dist/commands/release.js +3 -7
  20. package/dist/commands/release.js.map +1 -1
  21. package/dist/commands/review.js +4 -6
  22. package/dist/commands/review.js.map +1 -1
  23. package/dist/commands/tree.js +213 -84
  24. package/dist/commands/tree.js.map +1 -1
  25. package/dist/commands/unlink.js +3 -7
  26. package/dist/commands/unlink.js.map +1 -1
  27. package/dist/commands/updates.js +2 -4
  28. package/dist/commands/updates.js.map +1 -1
  29. package/dist/commands/versions.js +3 -7
  30. package/dist/commands/versions.js.map +1 -1
  31. package/dist/constants.js +4 -2
  32. package/dist/constants.js.map +1 -1
  33. package/dist/content/files.js +2 -4
  34. package/dist/content/files.js.map +1 -1
  35. package/dist/execution/CommandValidator.js +33 -1
  36. package/dist/execution/CommandValidator.js.map +1 -1
  37. package/dist/execution/ResourceMonitor.js +26 -1
  38. package/dist/execution/ResourceMonitor.js.map +1 -1
  39. package/dist/execution/TreeExecutionAdapter.js +2 -2
  40. package/dist/execution/TreeExecutionAdapter.js.map +1 -1
  41. package/dist/util/checkpointManager.js +2 -4
  42. package/dist/util/checkpointManager.js.map +1 -1
  43. package/dist/util/dependencyGraph.js +2 -4
  44. package/dist/util/dependencyGraph.js.map +1 -1
  45. package/dist/util/general.js +7 -107
  46. package/dist/util/general.js.map +1 -1
  47. package/dist/util/gitMutex.js +63 -18
  48. package/dist/util/gitMutex.js.map +1 -1
  49. package/dist/util/precommitOptimizations.js +310 -0
  50. package/dist/util/precommitOptimizations.js.map +1 -0
  51. package/dist/util/storageAdapter.js +2 -6
  52. package/dist/util/storageAdapter.js.map +1 -1
  53. package/dist/utils/branchState.js +178 -45
  54. package/dist/utils/branchState.js.map +1 -1
  55. package/package.json +6 -5
  56. package/AI-FRIENDLY-LOGGING-GUIDE.md +0 -237
  57. package/AI-LOGGING-MIGRATION-COMPLETE.md +0 -371
  58. package/ALREADY-PUBLISHED-PACKAGES-FIX.md +0 -264
  59. package/AUDIT-BRANCHES-PROGRESS-FIX.md +0 -90
  60. package/AUDIT-EXAMPLE-OUTPUT.md +0 -113
  61. package/CHECKPOINT-RECOVERY-FIX.md +0 -450
  62. package/LOGGING-MIGRATION-STATUS.md +0 -186
  63. package/MONOREPO-PUBLISH-IMPROVEMENTS.md +0 -281
  64. package/PARALLEL-EXECUTION-FIXES.md +0 -132
  65. package/PARALLEL-PUBLISH-DEBUGGING-GUIDE.md +0 -441
  66. package/PARALLEL-PUBLISH-FIXES-IMPLEMENTED.md +0 -405
  67. package/PARALLEL-PUBLISH-IMPROVEMENTS-IMPLEMENTED.md +0 -439
  68. package/PARALLEL-PUBLISH-LOGGING-FIXES.md +0 -274
  69. package/PARALLEL-PUBLISH-QUICK-REFERENCE.md +0 -375
  70. package/PARALLEL_EXECUTION_FIX.md +0 -146
  71. package/PUBLISH_IMPROVEMENTS_IMPLEMENTED.md +0 -294
  72. package/RECOVERY-FIXES.md +0 -72
  73. package/SUBMODULE-LOCK-FIX.md +0 -132
  74. package/VERSION-AUDIT-FIX.md +0 -333
  75. package/WORKFLOW-PRECHECK-IMPLEMENTATION.md +0 -239
  76. package/WORKFLOW-SKIP-SUMMARY.md +0 -121
  77. package/dist/util/safety.js +0 -166
  78. package/dist/util/safety.js.map +0 -1
  79. package/dist/util/stdin.js +0 -133
  80. package/dist/util/stdin.js.map +0 -1
  81. package/dist/util/storage.js +0 -187
  82. package/dist/util/storage.js.map +0 -1
@@ -5,15 +5,17 @@ import { exec } from 'child_process';
5
5
  import { safeJsonParse, validatePackageJson, getGloballyLinkedPackages, getGitStatusSummary, getLinkedDependencies, getLinkCompatibilityProblems, runSecure, run } from '@eldrforge/git-tools';
6
6
  import util from 'util';
7
7
  import { getLogger } from '../logging.js';
8
- import { create } from '../util/storage.js';
8
+ import { createStorage } from '@eldrforge/shared';
9
9
  import { getOutputPath } from '../util/general.js';
10
10
  import { DEFAULT_OUTPUT_DIRECTORY } from '../constants.js';
11
11
  import { execute as execute$3 } from './commit.js';
12
12
  import { execute as execute$1 } from './link.js';
13
13
  import { execute as execute$2 } from './unlink.js';
14
14
  import { execute as execute$4 } from './updates.js';
15
- import { runGitWithLock } from '../util/gitMutex.js';
15
+ import { isInGitRepository, runGitWithLock } from '../util/gitMutex.js';
16
16
  import { scanForPackageJsonFiles, buildDependencyGraph, topologicalSort, parsePackageJson, shouldExclude } from '../util/dependencyGraph.js';
17
+ import { optimizePrecommitCommand, recordTestRun } from '../util/precommitOptimizations.js';
18
+ import { PerformanceTimer } from '../util/performance.js';
17
19
  import { SimpleMutex } from '../util/mutex.js';
18
20
 
19
21
  // Global state to track published versions during tree execution - protected by mutex
@@ -22,9 +24,7 @@ let executionContext = null;
22
24
  const globalStateMutex = new SimpleMutex();
23
25
  // Update inter-project dependencies in package.json based on published versions
24
26
  const updateInterProjectDependencies = async (packageDir, publishedVersions, allPackageNames, packageLogger, isDryRun)=>{
25
- const storage = create({
26
- log: packageLogger.info
27
- });
27
+ const storage = createStorage();
28
28
  const packageJsonPath = path__default.join(packageDir, 'package.json');
29
29
  if (!await storage.exists(packageJsonPath)) {
30
30
  packageLogger.verbose('No package.json found, skipping dependency updates');
@@ -83,9 +83,7 @@ const updateInterProjectDependencies = async (packageDir, publishedVersions, all
83
83
  };
84
84
  // Detect scoped dependencies from package.json and run updates for them
85
85
  const updateScopedDependencies = async (packageDir, packageLogger, isDryRun, runConfig)=>{
86
- const storage = create({
87
- log: packageLogger.info
88
- });
86
+ const storage = createStorage();
89
87
  const packageJsonPath = path__default.join(packageDir, 'package.json');
90
88
  if (!await storage.exists(packageJsonPath)) {
91
89
  packageLogger.verbose('No package.json found, skipping scoped dependency updates');
@@ -167,9 +165,7 @@ const getContextFilePath = (outputDirectory)=>{
167
165
  };
168
166
  // Save execution context to file
169
167
  const saveExecutionContext = async (context, outputDirectory)=>{
170
- const storage = create({
171
- log: ()=>{}
172
- }); // Silent storage for context operations
168
+ const storage = createStorage(); // Silent storage for context operations
173
169
  const contextFilePath = getContextFilePath(outputDirectory);
174
170
  try {
175
171
  // Ensure output directory exists
@@ -193,9 +189,7 @@ const saveExecutionContext = async (context, outputDirectory)=>{
193
189
  };
194
190
  // Load execution context from file
195
191
  const loadExecutionContext = async (outputDirectory)=>{
196
- const storage = create({
197
- log: ()=>{}
198
- }); // Silent storage for context operations
192
+ const storage = createStorage(); // Silent storage for context operations
199
193
  const contextFilePath = getContextFilePath(outputDirectory);
200
194
  try {
201
195
  if (!await storage.exists(contextFilePath)) {
@@ -221,9 +215,7 @@ const loadExecutionContext = async (outputDirectory)=>{
221
215
  };
222
216
  // Clean up context file
223
217
  const cleanupContext = async (outputDirectory)=>{
224
- const storage = create({
225
- log: ()=>{}
226
- }); // Silent storage for context operations
218
+ const storage = createStorage(); // Silent storage for context operations
227
219
  const contextFilePath = getContextFilePath(outputDirectory);
228
220
  try {
229
221
  if (await storage.exists(contextFilePath)) {
@@ -237,9 +229,7 @@ const cleanupContext = async (outputDirectory)=>{
237
229
  };
238
230
  // Helper function to promote a package to completed status in the context
239
231
  const promotePackageToCompleted = async (packageName, outputDirectory)=>{
240
- const storage = create({
241
- log: ()=>{}
242
- });
232
+ const storage = createStorage();
243
233
  const contextFilePath = getContextFilePath(outputDirectory);
244
234
  try {
245
235
  if (!await storage.exists(contextFilePath)) {
@@ -272,9 +262,7 @@ const promotePackageToCompleted = async (packageName, outputDirectory)=>{
272
262
  const validateScripts = async (packages, scripts)=>{
273
263
  const logger = getLogger();
274
264
  const missingScripts = new Map();
275
- const storage = create({
276
- log: ()=>{}
277
- });
265
+ const storage = createStorage();
278
266
  logger.debug(`Validating scripts: ${scripts.join(', ')}`);
279
267
  for (const [packageName, packageInfo] of packages){
280
268
  const packageJsonPath = path__default.join(packageInfo.path, 'package.json');
@@ -317,9 +305,7 @@ const validateScripts = async (packages, scripts)=>{
317
305
  // After kodrdriv publish, the release version is captured in the git tag,
318
306
  // while package.json contains the next dev version
319
307
  const extractPublishedVersion = async (packageDir, packageLogger)=>{
320
- const storage = create({
321
- log: packageLogger.info
322
- });
308
+ const storage = createStorage();
323
309
  const packageJsonPath = path__default.join(packageDir, 'package.json');
324
310
  try {
325
311
  // Get package name from package.json
@@ -437,23 +423,31 @@ const runWithLogging = async (command, packageLogger, options = {}, showOutput =
437
423
  stderr: String(result.stderr)
438
424
  };
439
425
  } catch (error) {
426
+ // Always show error message
427
+ packageLogger.error(`Command failed: ${command}`);
428
+ // Always show stderr on failure (contains important error details like coverage failures)
429
+ if (error.stderr && error.stderr.trim()) {
430
+ packageLogger.error(`❌ STDERR:`);
431
+ error.stderr.split('\n').forEach((line)=>{
432
+ if (line.trim()) packageLogger.error(`${line}`);
433
+ });
434
+ }
435
+ // Show stdout on failure if available (may contain error context)
436
+ if (error.stdout && error.stdout.trim() && (showOutput === 'full' || showOutput === 'minimal')) {
437
+ packageLogger.info(`📤 STDOUT:`);
438
+ error.stdout.split('\n').forEach((line)=>{
439
+ if (line.trim()) packageLogger.info(`${line}`);
440
+ });
441
+ }
442
+ // Show full output in debug/verbose mode
440
443
  if (showOutput === 'full' || showOutput === 'minimal') {
441
- packageLogger.error(`Command failed: ${command}`);
442
- if (error.stdout && showOutput === 'full') {
444
+ if (error.stdout && error.stdout.trim() && showOutput === 'full') {
443
445
  packageLogger.debug('STDOUT:');
444
446
  packageLogger.debug(error.stdout);
445
- packageLogger.info(`📤 STDOUT:`);
446
- error.stdout.split('\n').forEach((line)=>{
447
- if (line.trim()) packageLogger.info(`${line}`);
448
- });
449
447
  }
450
- if (error.stderr && showOutput === 'full') {
448
+ if (error.stderr && error.stderr.trim() && showOutput === 'full') {
451
449
  packageLogger.debug('STDERR:');
452
450
  packageLogger.debug(error.stderr);
453
- packageLogger.info(`❌ STDERR:`);
454
- error.stderr.split('\n').forEach((line)=>{
455
- if (line.trim()) packageLogger.info(`${line}`);
456
- });
457
451
  }
458
452
  }
459
453
  // Write error output to log file
@@ -547,6 +541,9 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
547
541
  }
548
542
  // Track if publish was skipped due to no changes
549
543
  let publishWasSkipped = false;
544
+ // Track execution timing
545
+ const executionTimer = PerformanceTimer.start(packageLogger, `Package ${packageName} execution`);
546
+ let executionDuration;
550
547
  try {
551
548
  if (isDryRun && !isBuiltInCommand) {
552
549
  // Handle inter-project dependency updates for publish commands in dry run mode
@@ -649,18 +646,53 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
649
646
  }
650
647
  }, `${packageName}: dependency updates`);
651
648
  }
649
+ // Optimize precommit commands for custom commands (not built-in)
650
+ let effectiveCommandToRun = commandToRun;
651
+ let optimizationInfo = null;
652
+ if (!isBuiltInCommand && !isDryRun) {
653
+ const isPrecommitCommand = commandToRun.includes('precommit') || commandToRun.includes('pre-commit');
654
+ if (isPrecommitCommand) {
655
+ try {
656
+ const optimization = await optimizePrecommitCommand(packageDir, commandToRun);
657
+ effectiveCommandToRun = optimization.optimizedCommand;
658
+ optimizationInfo = {
659
+ skipped: optimization.skipped,
660
+ reasons: optimization.reasons
661
+ };
662
+ if (optimization.skipped.clean || optimization.skipped.test) {
663
+ const skippedParts = [];
664
+ if (optimization.skipped.clean) {
665
+ skippedParts.push(`clean (${optimization.reasons.clean})`);
666
+ }
667
+ if (optimization.skipped.test) {
668
+ skippedParts.push(`test (${optimization.reasons.test})`);
669
+ }
670
+ packageLogger.info(`⚡ Optimized: Skipped ${skippedParts.join(', ')}`);
671
+ if (runConfig.verbose || runConfig.debug) {
672
+ packageLogger.info(` Original: ${commandToRun}`);
673
+ packageLogger.info(` Optimized: ${effectiveCommandToRun}`);
674
+ }
675
+ }
676
+ } catch (error) {
677
+ // If optimization fails, fall back to original command
678
+ logger.debug(`Precommit optimization failed for ${packageName}: ${error.message}`);
679
+ }
680
+ }
681
+ }
652
682
  if (runConfig.debug || runConfig.verbose) {
653
683
  if (isBuiltInCommand) {
654
684
  packageLogger.info(`Executing built-in command: ${commandToRun}`);
655
685
  } else {
656
- packageLogger.info(`Executing command: ${commandToRun}`);
686
+ packageLogger.info(`Executing command: ${effectiveCommandToRun}`);
657
687
  }
658
688
  }
659
689
  // For built-in commands, shell out to a separate kodrdriv process
660
690
  // This preserves individual project configurations
661
691
  if (isBuiltInCommand) {
662
- // Extract the command name from "kodrdriv <command>"
663
- const builtInCommandName = commandToRun.replace('kodrdriv ', '');
692
+ // Extract the command name from "kodrdriv <command> [args...]"
693
+ // Split by space and take the second element (after "kodrdriv")
694
+ const commandParts = commandToRun.replace(/^kodrdriv\s+/, '').split(/\s+/);
695
+ const builtInCommandName = commandParts[0];
664
696
  if (runConfig.debug) {
665
697
  packageLogger.debug(`Shelling out to separate kodrdriv process for ${builtInCommandName} command`);
666
698
  }
@@ -670,21 +702,35 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
670
702
  packageLogger.info('⏱️ This may take several minutes (AI processing, PR creation, etc.)');
671
703
  }
672
704
  // Ensure dry-run propagates to subprocess even during overall dry-run mode
673
- const effectiveCommand = runConfig.dryRun && !commandToRun.includes('--dry-run') ? `${commandToRun} --dry-run` : commandToRun;
674
- // Add timeout wrapper for publish commands
675
- const commandTimeoutMs = 1800000; // 30 minutes for publish commands
705
+ let effectiveCommand = runConfig.dryRun && !commandToRun.includes('--dry-run') ? `${commandToRun} --dry-run` : commandToRun;
706
+ // For commit commands, ensure --sendit is used to avoid interactive prompts
707
+ // This prevents hanging when running via tree command
708
+ if (builtInCommandName === 'commit' && !effectiveCommand.includes('--sendit') && !runConfig.dryRun) {
709
+ effectiveCommand = `${effectiveCommand} --sendit`;
710
+ packageLogger.info('💡 Auto-adding --sendit flag to avoid interactive prompts in tree mode');
711
+ }
712
+ // Set timeout based on command type
713
+ let commandTimeoutMs;
676
714
  if (builtInCommandName === 'publish') {
715
+ commandTimeoutMs = 1800000; // 30 minutes for publish commands
677
716
  packageLogger.info(`⏰ Setting timeout of ${commandTimeoutMs / 60000} minutes for publish command`);
717
+ } else if (builtInCommandName === 'commit') {
718
+ commandTimeoutMs = 600000; // 10 minutes for commit commands (AI processing can take time)
719
+ packageLogger.info(`⏰ Setting timeout of ${commandTimeoutMs / 60000} minutes for commit command`);
720
+ } else {
721
+ commandTimeoutMs = 300000; // 5 minutes default for other commands
678
722
  }
679
723
  const commandPromise = runWithLogging(effectiveCommand, packageLogger, {}, showOutput, logFilePath);
680
724
  const commandTimeoutPromise = new Promise((_, reject)=>{
681
725
  setTimeout(()=>reject(new Error(`Command timed out after ${commandTimeoutMs / 60000} minutes`)), commandTimeoutMs);
682
726
  });
683
727
  try {
728
+ const startTime = Date.now();
684
729
  const { stdout, stderr } = await Promise.race([
685
730
  commandPromise,
686
731
  commandTimeoutPromise
687
732
  ]);
733
+ executionDuration = Date.now() - startTime;
688
734
  // Detect explicit skip marker from publish to avoid propagating versions
689
735
  // Check both stdout (where we now write it) and stderr (winston logger output, for backward compat)
690
736
  if (builtInCommandName === 'publish' && (stdout && stdout.includes('KODRDRIV_PUBLISH_SKIPPED') || stderr && stderr.includes('KODRDRIV_PUBLISH_SKIPPED'))) {
@@ -701,7 +747,9 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
701
747
  }
702
748
  } else {
703
749
  // For custom commands, use the existing logic
704
- await runWithLogging(commandToRun, packageLogger, {}, showOutput, logFilePath);
750
+ const startTime = Date.now();
751
+ await runWithLogging(effectiveCommandToRun, packageLogger, {}, showOutput, logFilePath);
752
+ executionDuration = Date.now() - startTime;
705
753
  }
706
754
  // Track published version after successful publish (skip during dry run)
707
755
  if (!isDryRun && isBuiltInCommand && commandToRun.includes('publish')) {
@@ -729,11 +777,32 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
729
777
  }
730
778
  }
731
779
  }
732
- if (runConfig.debug || runConfig.verbose) {
733
- packageLogger.info(`Command completed successfully`);
780
+ // Record test run if tests were executed (not skipped)
781
+ if (!isDryRun && !isBuiltInCommand && effectiveCommandToRun.includes('test') && (!optimizationInfo || !optimizationInfo.skipped.test)) {
782
+ try {
783
+ await recordTestRun(packageDir);
784
+ } catch (error) {
785
+ logger.debug(`Failed to record test run for ${packageName}: ${error.message}`);
786
+ }
787
+ }
788
+ // End timing and show duration
789
+ if (executionDuration !== undefined) {
790
+ executionTimer.end(`Package ${packageName} execution`);
791
+ const seconds = (executionDuration / 1000).toFixed(1);
792
+ if (runConfig.debug || runConfig.verbose) {
793
+ packageLogger.info(`⏱️ Execution time: ${seconds}s`);
794
+ } else if (!isPublishCommand) {
795
+ // Show timing in completion message (publish commands have their own completion message)
796
+ logger.info(`[${index + 1}/${total}] ${packageName}: ✅ Completed (${seconds}s)`);
797
+ }
734
798
  } else {
735
- // Basic completion info
736
- logger.info(`[${index + 1}/${total}] ${packageName}: ✅ Completed`);
799
+ executionTimer.end(`Package ${packageName} execution`);
800
+ if (runConfig.debug || runConfig.verbose) {
801
+ packageLogger.info(`Command completed successfully`);
802
+ } else if (!isPublishCommand) {
803
+ // Basic completion info (publish commands have their own completion message)
804
+ logger.info(`[${index + 1}/${total}] ${packageName}: ✅ Completed`);
805
+ }
737
806
  }
738
807
  } finally{
739
808
  // Safely restore working directory
@@ -753,7 +822,7 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
753
822
  }
754
823
  }
755
824
  }
756
- // Show completion status
825
+ // Show completion status (for publish commands, this supplements the timing message above)
757
826
  if (runConfig.debug || runConfig.verbose) {
758
827
  if (publishWasSkipped) {
759
828
  packageLogger.info(`⊘ Skipped (no code changes)`);
@@ -762,12 +831,18 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
762
831
  }
763
832
  } else if (isPublishCommand) {
764
833
  // For publish commands, always show completion even without verbose
834
+ // Include timing if available
835
+ const timeStr = executionDuration !== undefined ? ` (${(executionDuration / 1000).toFixed(1)}s)` : '';
765
836
  if (publishWasSkipped) {
766
837
  logger.info(`[${index + 1}/${total}] ${packageName}: ⊘ Skipped (no code changes)`);
767
838
  } else {
768
- logger.info(`[${index + 1}/${total}] ${packageName}: ✅ Completed`);
839
+ logger.info(`[${index + 1}/${total}] ${packageName}: ✅ Completed${timeStr}`);
769
840
  }
770
841
  }
842
+ // Ensure timing is recorded even if there was an early return
843
+ if (executionDuration === undefined) {
844
+ executionDuration = executionTimer.end(`Package ${packageName} execution`);
845
+ }
771
846
  return {
772
847
  success: true,
773
848
  skippedNoChanges: publishWasSkipped,
@@ -775,11 +850,38 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
775
850
  };
776
851
  } catch (error) {
777
852
  var _error_message;
853
+ // Record timing even on error
854
+ if (executionDuration === undefined) {
855
+ executionDuration = executionTimer.end(`Package ${packageName} execution`);
856
+ const seconds = (executionDuration / 1000).toFixed(1);
857
+ if (runConfig.debug || runConfig.verbose) {
858
+ packageLogger.error(`⏱️ Execution time before failure: ${seconds}s`);
859
+ }
860
+ }
778
861
  if (runConfig.debug || runConfig.verbose) {
779
862
  packageLogger.error(`❌ Execution failed: ${error.message}`);
780
863
  } else {
781
864
  logger.error(`[${index + 1}/${total}] ${packageName}: ❌ Failed - ${error.message}`);
782
865
  }
866
+ // Always show stderr if available (contains important error details)
867
+ // Note: runWithLogging already logs stderr, but we show it here too for visibility
868
+ // when error is caught at this level (e.g., from timeout wrapper)
869
+ if (error.stderr && error.stderr.trim() && !runConfig.debug && !runConfig.verbose) {
870
+ // Extract key error lines from stderr (coverage failures, test failures, etc.)
871
+ const stderrLines = error.stderr.split('\n').filter((line)=>{
872
+ const trimmed = line.trim();
873
+ return trimmed && (trimmed.includes('ERROR:') || trimmed.includes('FAIL') || trimmed.includes('coverage') || trimmed.includes('threshold') || trimmed.includes('fatal:') || trimmed.startsWith('❌'));
874
+ });
875
+ if (stderrLines.length > 0) {
876
+ logger.error(` Error details:`);
877
+ stderrLines.slice(0, 10).forEach((line)=>{
878
+ logger.error(` ${line.trim()}`);
879
+ });
880
+ if (stderrLines.length > 10) {
881
+ logger.error(` ... and ${stderrLines.length - 10} more error lines (use --verbose to see full output)`);
882
+ }
883
+ }
884
+ }
783
885
  // Check if this is a timeout error
784
886
  const errorMessage = ((_error_message = error.message) === null || _error_message === void 0 ? void 0 : _error_message.toLowerCase()) || '';
785
887
  const isTimeoutError = errorMessage && (errorMessage.includes('timeout waiting for pr') || errorMessage.includes('timeout waiting for release workflows') || errorMessage.includes('timeout reached') || errorMessage.includes('timeout') || errorMessage.includes('timed out') || errorMessage.includes('timed_out'));
@@ -922,7 +1024,7 @@ const execute = async (runConfig)=>{
922
1024
  }
923
1025
  // Handle audit-branches command
924
1026
  if ((_runConfig_tree4 = runConfig.tree) === null || _runConfig_tree4 === void 0 ? void 0 : _runConfig_tree4.auditBranches) {
925
- var _runConfig_tree16, _runConfig_tree17, _runConfig_publish;
1027
+ var _runConfig_tree16, _runConfig_tree17, _runConfig_publish, _runConfig_tree18;
926
1028
  logger.info('🔍 Auditing branch state across all packages...');
927
1029
  const directories = ((_runConfig_tree16 = runConfig.tree) === null || _runConfig_tree16 === void 0 ? void 0 : _runConfig_tree16.directories) || [
928
1030
  process.cwd()
@@ -942,14 +1044,30 @@ const execute = async (runConfig)=>{
942
1044
  path: pkg.path
943
1045
  }));
944
1046
  const { auditBranchState, formatAuditResults } = await import('../utils/branchState.js');
1047
+ const { getRemoteDefaultBranch } = await import('@eldrforge/git-tools');
945
1048
  // For publish workflows, check branch consistency, merge conflicts, and existing PRs
946
1049
  // Don't pass an expected branch - let the audit find the most common branch
947
- const targetBranch = ((_runConfig_publish = runConfig.publish) === null || _runConfig_publish === void 0 ? void 0 : _runConfig_publish.targetBranch) || 'main';
1050
+ let targetBranch = (_runConfig_publish = runConfig.publish) === null || _runConfig_publish === void 0 ? void 0 : _runConfig_publish.targetBranch;
1051
+ if (!targetBranch) {
1052
+ // Try to detect default branch from the first package that is a git repo
1053
+ const firstGitPkg = packages.find((pkg)=>isInGitRepository(pkg.path));
1054
+ if (firstGitPkg) {
1055
+ try {
1056
+ // Cast to any to avoid type mismatch with node_modules version
1057
+ targetBranch = await getRemoteDefaultBranch(firstGitPkg.path) || 'main';
1058
+ } catch {
1059
+ targetBranch = 'main';
1060
+ }
1061
+ } else {
1062
+ targetBranch = 'main';
1063
+ }
1064
+ }
948
1065
  logger.info(`Checking for merge conflicts with '${targetBranch}' and existing pull requests...`);
949
1066
  const auditResult = await auditBranchState(packages, undefined, {
950
1067
  targetBranch,
951
1068
  checkPR: true,
952
- checkConflicts: true
1069
+ checkConflicts: true,
1070
+ concurrency: ((_runConfig_tree18 = runConfig.tree) === null || _runConfig_tree18 === void 0 ? void 0 : _runConfig_tree18.maxConcurrency) || 10
953
1071
  });
954
1072
  const formatted = formatAuditResults(auditResult);
955
1073
  logger.info('\n' + formatted);
@@ -964,13 +1082,13 @@ const execute = async (runConfig)=>{
964
1082
  const { loadRecoveryManager } = await import('../execution/RecoveryManager.js');
965
1083
  // Handle status-parallel command
966
1084
  if ((_runConfig_tree5 = runConfig.tree) === null || _runConfig_tree5 === void 0 ? void 0 : _runConfig_tree5.statusParallel) {
967
- var _runConfig_tree18, _runConfig_tree19;
1085
+ var _runConfig_tree19, _runConfig_tree20;
968
1086
  logger.info('📊 Checking parallel execution status...');
969
1087
  // Need to build dependency graph first
970
- const directories = ((_runConfig_tree18 = runConfig.tree) === null || _runConfig_tree18 === void 0 ? void 0 : _runConfig_tree18.directories) || [
1088
+ const directories = ((_runConfig_tree19 = runConfig.tree) === null || _runConfig_tree19 === void 0 ? void 0 : _runConfig_tree19.directories) || [
971
1089
  process.cwd()
972
1090
  ];
973
- const excludedPatterns = ((_runConfig_tree19 = runConfig.tree) === null || _runConfig_tree19 === void 0 ? void 0 : _runConfig_tree19.exclude) || [];
1091
+ const excludedPatterns = ((_runConfig_tree20 = runConfig.tree) === null || _runConfig_tree20 === void 0 ? void 0 : _runConfig_tree20.exclude) || [];
974
1092
  let allPackageJsonPaths = [];
975
1093
  for (const targetDirectory of directories){
976
1094
  const packageJsonPaths = await scanForPackageJsonFiles(targetDirectory, excludedPatterns);
@@ -991,12 +1109,12 @@ const execute = async (runConfig)=>{
991
1109
  }
992
1110
  // Handle validate-state command
993
1111
  if ((_runConfig_tree6 = runConfig.tree) === null || _runConfig_tree6 === void 0 ? void 0 : _runConfig_tree6.validateState) {
994
- var _runConfig_tree20, _runConfig_tree21;
1112
+ var _runConfig_tree21, _runConfig_tree22;
995
1113
  logger.info('🔍 Validating checkpoint state...');
996
- const directories = ((_runConfig_tree20 = runConfig.tree) === null || _runConfig_tree20 === void 0 ? void 0 : _runConfig_tree20.directories) || [
1114
+ const directories = ((_runConfig_tree21 = runConfig.tree) === null || _runConfig_tree21 === void 0 ? void 0 : _runConfig_tree21.directories) || [
997
1115
  process.cwd()
998
1116
  ];
999
- const excludedPatterns = ((_runConfig_tree21 = runConfig.tree) === null || _runConfig_tree21 === void 0 ? void 0 : _runConfig_tree21.exclude) || [];
1117
+ const excludedPatterns = ((_runConfig_tree22 = runConfig.tree) === null || _runConfig_tree22 === void 0 ? void 0 : _runConfig_tree22.exclude) || [];
1000
1118
  let allPackageJsonPaths = [];
1001
1119
  for (const targetDirectory of directories){
1002
1120
  const packageJsonPaths = await scanForPackageJsonFiles(targetDirectory, excludedPatterns);
@@ -1065,10 +1183,10 @@ const execute = async (runConfig)=>{
1065
1183
  }
1066
1184
  // Handle continue mode
1067
1185
  if (isContinue) {
1068
- var _runConfig_tree22;
1186
+ var _runConfig_tree23;
1069
1187
  // For parallel execution, the checkpoint is managed by DynamicTaskPool/CheckpointManager
1070
1188
  // For sequential execution, we use the legacy context file
1071
- const isParallelMode = (_runConfig_tree22 = runConfig.tree) === null || _runConfig_tree22 === void 0 ? void 0 : _runConfig_tree22.parallel;
1189
+ const isParallelMode = (_runConfig_tree23 = runConfig.tree) === null || _runConfig_tree23 === void 0 ? void 0 : _runConfig_tree23.parallel;
1072
1190
  if (!isParallelMode) {
1073
1191
  // Sequential execution: load legacy context
1074
1192
  const savedContext = await loadExecutionContext(runConfig.outputDirectory);
@@ -1122,15 +1240,16 @@ const execute = async (runConfig)=>{
1122
1240
  'branches',
1123
1241
  'run',
1124
1242
  'checkout',
1125
- 'updates'
1243
+ 'updates',
1244
+ 'precommit'
1126
1245
  ];
1127
1246
  if (builtInCommand && !supportedBuiltInCommands.includes(builtInCommand)) {
1128
1247
  throw new Error(`Unsupported built-in command: ${builtInCommand}. Supported commands: ${supportedBuiltInCommands.join(', ')}`);
1129
1248
  }
1130
1249
  // Handle run subcommand - convert space-separated scripts to npm run commands
1131
1250
  if (builtInCommand === 'run') {
1132
- var _runConfig_tree23;
1133
- const packageArgument = (_runConfig_tree23 = runConfig.tree) === null || _runConfig_tree23 === void 0 ? void 0 : _runConfig_tree23.packageArgument;
1251
+ var _runConfig_tree24;
1252
+ const packageArgument = (_runConfig_tree24 = runConfig.tree) === null || _runConfig_tree24 === void 0 ? void 0 : _runConfig_tree24.packageArgument;
1134
1253
  if (!packageArgument) {
1135
1254
  throw new Error('run subcommand requires script names. Usage: kodrdriv tree run "clean build test"');
1136
1255
  }
@@ -1202,9 +1321,9 @@ const execute = async (runConfig)=>{
1202
1321
  logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Analyzing workspaces at: ${directories.join(', ')}`);
1203
1322
  }
1204
1323
  try {
1205
- var _runConfig_tree24, _runConfig_tree25, _runConfig_tree26, _runConfig_tree27;
1324
+ var _runConfig_tree25, _runConfig_tree26, _runConfig_tree27, _runConfig_tree28;
1206
1325
  // Get exclusion patterns from config, fallback to empty array
1207
- const excludedPatterns = ((_runConfig_tree24 = runConfig.tree) === null || _runConfig_tree24 === void 0 ? void 0 : _runConfig_tree24.exclude) || [];
1326
+ const excludedPatterns = ((_runConfig_tree25 = runConfig.tree) === null || _runConfig_tree25 === void 0 ? void 0 : _runConfig_tree25.exclude) || [];
1208
1327
  if (excludedPatterns.length > 0) {
1209
1328
  logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Using exclusion patterns: ${excludedPatterns.join(', ')}`);
1210
1329
  }
@@ -1231,7 +1350,7 @@ const execute = async (runConfig)=>{
1231
1350
  logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Determining build order...`);
1232
1351
  let buildOrder = topologicalSort(dependencyGraph);
1233
1352
  // Handle start-from functionality if specified
1234
- const startFrom = (_runConfig_tree25 = runConfig.tree) === null || _runConfig_tree25 === void 0 ? void 0 : _runConfig_tree25.startFrom;
1353
+ const startFrom = (_runConfig_tree26 = runConfig.tree) === null || _runConfig_tree26 === void 0 ? void 0 : _runConfig_tree26.startFrom;
1235
1354
  if (startFrom) {
1236
1355
  logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Looking for start package: ${startFrom}`);
1237
1356
  // Resolve the actual package name (can be package name or directory name)
@@ -1288,7 +1407,7 @@ const execute = async (runConfig)=>{
1288
1407
  logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Starting execution from package '${startFrom}' (${buildOrder.length} of ${originalLength} packages remaining).`);
1289
1408
  }
1290
1409
  // Handle stop-at functionality if specified
1291
- const stopAt = (_runConfig_tree26 = runConfig.tree) === null || _runConfig_tree26 === void 0 ? void 0 : _runConfig_tree26.stopAt;
1410
+ const stopAt = (_runConfig_tree27 = runConfig.tree) === null || _runConfig_tree27 === void 0 ? void 0 : _runConfig_tree27.stopAt;
1292
1411
  if (stopAt) {
1293
1412
  logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Looking for stop package: ${stopAt}`);
1294
1413
  // Find the package that matches the stopAt directory name
@@ -1420,9 +1539,7 @@ const execute = async (runConfig)=>{
1420
1539
  let maxConsumersLength = 'Consumers'.length;
1421
1540
  const branchInfos = [];
1422
1541
  // Create storage instance for consumer lookup
1423
- const storage = create({
1424
- log: ()=>{}
1425
- });
1542
+ const storage = createStorage();
1426
1543
  // Get globally linked packages once at the beginning
1427
1544
  const globallyLinkedPackages = await getGloballyLinkedPackages();
1428
1545
  // ANSI escape codes for progress display
@@ -1607,8 +1724,8 @@ const execute = async (runConfig)=>{
1607
1724
  }
1608
1725
  // Handle special "checkout" command that switches all packages to specified branch
1609
1726
  if (builtInCommand === 'checkout') {
1610
- var _runConfig_tree28;
1611
- const targetBranch = (_runConfig_tree28 = runConfig.tree) === null || _runConfig_tree28 === void 0 ? void 0 : _runConfig_tree28.packageArgument;
1727
+ var _runConfig_tree29;
1728
+ const targetBranch = (_runConfig_tree29 = runConfig.tree) === null || _runConfig_tree29 === void 0 ? void 0 : _runConfig_tree29.packageArgument;
1612
1729
  if (!targetBranch) {
1613
1730
  throw new Error('checkout subcommand requires a branch name. Usage: kodrdriv tree checkout <branch-name>');
1614
1731
  }
@@ -1787,12 +1904,12 @@ const execute = async (runConfig)=>{
1787
1904
  returnOutput = `\nBuild order: ${buildOrder.join(' → ')}\n`;
1788
1905
  }
1789
1906
  // Execute command if provided (custom command or built-in command)
1790
- const cmd = (_runConfig_tree27 = runConfig.tree) === null || _runConfig_tree27 === void 0 ? void 0 : _runConfig_tree27.cmd;
1907
+ const cmd = (_runConfig_tree28 = runConfig.tree) === null || _runConfig_tree28 === void 0 ? void 0 : _runConfig_tree28.cmd;
1791
1908
  // Determine command to execute
1792
1909
  let commandToRun;
1793
1910
  let isBuiltInCommand = false;
1794
1911
  if (builtInCommand) {
1795
- var _runConfig_tree29, _runConfig_tree30, _runConfig_tree31;
1912
+ var _runConfig_tree30, _runConfig_tree31, _runConfig_tree32;
1796
1913
  // Built-in command mode: shell out to kodrdriv subprocess
1797
1914
  // Build command with propagated global options
1798
1915
  const globalOptions = [];
@@ -1809,14 +1926,14 @@ const execute = async (runConfig)=>{
1809
1926
  // Build the command with global options
1810
1927
  const optionsString = globalOptions.length > 0 ? ` ${globalOptions.join(' ')}` : '';
1811
1928
  // Add package argument for link/unlink/updates commands
1812
- const packageArg = (_runConfig_tree29 = runConfig.tree) === null || _runConfig_tree29 === void 0 ? void 0 : _runConfig_tree29.packageArgument;
1929
+ const packageArg = (_runConfig_tree30 = runConfig.tree) === null || _runConfig_tree30 === void 0 ? void 0 : _runConfig_tree30.packageArgument;
1813
1930
  const packageArgString = packageArg && (builtInCommand === 'link' || builtInCommand === 'unlink' || builtInCommand === 'updates') ? ` "${packageArg}"` : '';
1814
1931
  // Add command-specific options
1815
1932
  let commandSpecificOptions = '';
1816
- if (builtInCommand === 'unlink' && ((_runConfig_tree30 = runConfig.tree) === null || _runConfig_tree30 === void 0 ? void 0 : _runConfig_tree30.cleanNodeModules)) {
1933
+ if (builtInCommand === 'unlink' && ((_runConfig_tree31 = runConfig.tree) === null || _runConfig_tree31 === void 0 ? void 0 : _runConfig_tree31.cleanNodeModules)) {
1817
1934
  commandSpecificOptions += ' --clean-node-modules';
1818
1935
  }
1819
- if ((builtInCommand === 'link' || builtInCommand === 'unlink') && ((_runConfig_tree31 = runConfig.tree) === null || _runConfig_tree31 === void 0 ? void 0 : _runConfig_tree31.externals) && runConfig.tree.externals.length > 0) {
1936
+ if ((builtInCommand === 'link' || builtInCommand === 'unlink') && ((_runConfig_tree32 = runConfig.tree) === null || _runConfig_tree32 === void 0 ? void 0 : _runConfig_tree32.externals) && runConfig.tree.externals.length > 0) {
1820
1937
  commandSpecificOptions += ` --externals ${runConfig.tree.externals.join(' ')}`;
1821
1938
  }
1822
1939
  commandToRun = `kodrdriv ${builtInCommand}${optionsString}${packageArgString}${commandSpecificOptions}`;
@@ -1826,7 +1943,7 @@ const execute = async (runConfig)=>{
1826
1943
  commandToRun = cmd;
1827
1944
  }
1828
1945
  if (commandToRun) {
1829
- var _runConfig_tree32, _runConfig_tree33;
1946
+ var _runConfig_tree33, _runConfig_tree34;
1830
1947
  // Validate scripts for run command before execution
1831
1948
  const scriptsToValidate = runConfig.__scriptsToValidate;
1832
1949
  if (scriptsToValidate && scriptsToValidate.length > 0) {
@@ -1845,7 +1962,7 @@ const execute = async (runConfig)=>{
1845
1962
  }
1846
1963
  }
1847
1964
  // Validate command for parallel execution if parallel mode is enabled
1848
- if ((_runConfig_tree32 = runConfig.tree) === null || _runConfig_tree32 === void 0 ? void 0 : _runConfig_tree32.parallel) {
1965
+ if ((_runConfig_tree33 = runConfig.tree) === null || _runConfig_tree33 === void 0 ? void 0 : _runConfig_tree33.parallel) {
1849
1966
  const { CommandValidator } = await import('../execution/CommandValidator.js');
1850
1967
  const validation = CommandValidator.validateForParallel(commandToRun, builtInCommand);
1851
1968
  CommandValidator.logValidation(validation);
@@ -1856,11 +1973,12 @@ const execute = async (runConfig)=>{
1856
1973
  throw new Error('Command validation failed for parallel execution');
1857
1974
  }
1858
1975
  // Apply recommended concurrency if not explicitly set
1859
- if (!runConfig.tree.maxConcurrency && builtInCommand) {
1976
+ if (!runConfig.tree.maxConcurrency) {
1860
1977
  const os = await import('os');
1861
- const recommended = CommandValidator.getRecommendedConcurrency(builtInCommand, os.cpus().length);
1978
+ const recommended = CommandValidator.getRecommendedConcurrency(builtInCommand, os.cpus().length, commandToRun);
1862
1979
  if (recommended !== os.cpus().length) {
1863
- logger.info(`💡 Using recommended concurrency for ${builtInCommand}: ${recommended}`);
1980
+ const reason = builtInCommand ? builtInCommand : `custom command "${commandToRun}"`;
1981
+ logger.info(`💡 Using recommended concurrency for ${reason}: ${recommended}`);
1864
1982
  runConfig.tree.maxConcurrency = recommended;
1865
1983
  }
1866
1984
  }
@@ -1896,7 +2014,7 @@ const execute = async (runConfig)=>{
1896
2014
  // If continuing, start from where we left off
1897
2015
  const startIndex = isContinue && executionContext ? executionContext.completedPackages.length : 0;
1898
2016
  // Check if parallel execution is enabled
1899
- if ((_runConfig_tree33 = runConfig.tree) === null || _runConfig_tree33 === void 0 ? void 0 : _runConfig_tree33.parallel) {
2017
+ if ((_runConfig_tree34 = runConfig.tree) === null || _runConfig_tree34 === void 0 ? void 0 : _runConfig_tree34.parallel) {
1900
2018
  var _runConfig_tree_retry1, _runConfig_tree_retry2, _runConfig_tree_retry3, _runConfig_tree_retry4;
1901
2019
  logger.info('🚀 Using parallel execution mode');
1902
2020
  // If dry run, show preview instead of executing
@@ -1929,6 +2047,7 @@ const execute = async (runConfig)=>{
1929
2047
  return formattedResult;
1930
2048
  }
1931
2049
  // Sequential execution
2050
+ const executionStartTime = Date.now();
1932
2051
  for(let i = startIndex; i < buildOrder.length; i++){
1933
2052
  const packageName = buildOrder[i];
1934
2053
  // Skip if already completed (in continue mode)
@@ -2031,8 +2150,18 @@ const execute = async (runConfig)=>{
2031
2150
  }
2032
2151
  }
2033
2152
  if (!failedPackage) {
2153
+ const totalExecutionTime = Date.now() - executionStartTime;
2154
+ const totalSeconds = (totalExecutionTime / 1000).toFixed(1);
2155
+ const totalMinutes = (totalExecutionTime / 60000).toFixed(1);
2156
+ const timeDisplay = totalExecutionTime < 60000 ? `${totalSeconds}s` : `${totalMinutes}min (${totalSeconds}s)`;
2157
+ logger.info('');
2158
+ logger.info('═══════════════════════════════════════════════════════════');
2034
2159
  const summary = `${isDryRun ? 'DRY RUN: ' : ''}All ${buildOrder.length} packages completed successfully! 🎉`;
2035
2160
  logger.info(summary);
2161
+ logger.info(`⏱️ Total execution time: ${timeDisplay}`);
2162
+ logger.info(`📦 Packages processed: ${successCount}/${buildOrder.length}`);
2163
+ logger.info('═══════════════════════════════════════════════════════════');
2164
+ logger.info('');
2036
2165
  // Clean up context on successful completion
2037
2166
  if (isBuiltInCommand && (builtInCommand === 'publish' || builtInCommand === 'run') && !isDryRun) {
2038
2167
  await cleanupContext(runConfig.outputDirectory);