@eldrforge/commands-tree 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,699 @@
1
+ import { run } from '@eldrforge/git-tools';
2
+ import { getLogger, getGitRepositoryRoot, isInGitRepository } from '@eldrforge/core';
3
+
4
+ /**
5
+ * Check the branch status for a package
6
+ */ async function checkBranchStatus(packagePath, expectedBranch, targetBranch = 'main', checkPR = false, options = {}) {
7
+ const logger = getLogger();
8
+ // Check if path is in a git repository
9
+ if (!isInGitRepository(packagePath)) {
10
+ logger.debug(`Path is not in a git repository: ${packagePath}. Skipping branch status check.`);
11
+ return {
12
+ name: 'non-git',
13
+ isOnExpectedBranch: true,
14
+ ahead: 0,
15
+ behind: 0,
16
+ hasUnpushedCommits: false,
17
+ needsSync: false,
18
+ remoteExists: false
19
+ };
20
+ }
21
+ try {
22
+ // Get current branch
23
+ const { stdout: currentBranch } = await run('git rev-parse --abbrev-ref HEAD', {
24
+ cwd: packagePath,
25
+ suppressErrorLogging: true
26
+ });
27
+ const branch = currentBranch.trim();
28
+ // Check if remote exists
29
+ let remoteExists = false;
30
+ try {
31
+ await run(`git ls-remote --exit-code --heads origin ${branch}`, {
32
+ cwd: packagePath,
33
+ suppressErrorLogging: true
34
+ });
35
+ remoteExists = true;
36
+ } catch {
37
+ remoteExists = false;
38
+ }
39
+ // Get ahead/behind counts if remote exists
40
+ let ahead = 0;
41
+ let behind = 0;
42
+ if (remoteExists) {
43
+ try {
44
+ const { stdout: revList } = await run(`git rev-list --left-right --count origin/${branch}...HEAD`, {
45
+ cwd: packagePath,
46
+ suppressErrorLogging: true
47
+ });
48
+ const [behindStr, aheadStr] = revList.trim().split('\t');
49
+ behind = parseInt(behindStr, 10) || 0;
50
+ ahead = parseInt(aheadStr, 10) || 0;
51
+ } catch (error) {
52
+ logger.verbose(`Could not get ahead/behind counts for ${packagePath}: ${error}`);
53
+ }
54
+ }
55
+ // Check for merge conflicts with target branch
56
+ let hasMergeConflicts = false;
57
+ let conflictsWith;
58
+ if (branch !== targetBranch) {
59
+ try {
60
+ // Fetch latest to ensure we're checking against current target
61
+ if (!options.skipFetch) {
62
+ logger.verbose(` Fetching latest from origin for ${packagePath}...`);
63
+ await run('git fetch origin --quiet', {
64
+ cwd: packagePath,
65
+ suppressErrorLogging: true
66
+ });
67
+ }
68
+ logger.verbose(` Checking for merge conflicts with ${targetBranch}...`);
69
+ // Use git merge-tree to test for conflicts without actually merging
70
+ const { stdout: mergeTree } = await run(`git merge-tree $(git merge-base ${branch} origin/${targetBranch}) ${branch} origin/${targetBranch}`, {
71
+ cwd: packagePath,
72
+ suppressErrorLogging: true
73
+ });
74
+ // If merge-tree output contains conflict markers, there are conflicts
75
+ if (mergeTree.includes('<<<<<<<') || mergeTree.includes('=======') || mergeTree.includes('>>>>>>>')) {
76
+ hasMergeConflicts = true;
77
+ conflictsWith = targetBranch;
78
+ logger.verbose(` ⚠️ Merge conflicts detected with ${targetBranch}`);
79
+ }
80
+ } catch (error) {
81
+ // If merge-tree fails, might be due to git version or other issues
82
+ logger.verbose(`Could not check merge conflicts for ${packagePath}: ${error.message}`);
83
+ }
84
+ }
85
+ // Check for existing PR if requested
86
+ let hasOpenPR = false;
87
+ let prUrl;
88
+ let prNumber;
89
+ if (checkPR) {
90
+ try {
91
+ logger.verbose(` Checking GitHub for existing PRs...`);
92
+ const { findOpenPullRequestByHeadRef } = await import('./index-DqPh_i40.js');
93
+ const pr = await findOpenPullRequestByHeadRef(branch, packagePath);
94
+ if (pr) {
95
+ hasOpenPR = true;
96
+ prUrl = pr.html_url;
97
+ prNumber = pr.number;
98
+ logger.verbose(` Found existing PR #${prNumber}: ${prUrl}`);
99
+ }
100
+ } catch (error) {
101
+ logger.verbose(`Could not check for PR for ${packagePath}: ${error.message}`);
102
+ }
103
+ }
104
+ const isOnExpectedBranch = !expectedBranch || branch === expectedBranch;
105
+ const hasUnpushedCommits = ahead > 0;
106
+ const needsSync = behind > 0;
107
+ return {
108
+ name: branch,
109
+ isOnExpectedBranch,
110
+ expectedBranch,
111
+ ahead,
112
+ behind,
113
+ hasUnpushedCommits,
114
+ needsSync,
115
+ remoteExists,
116
+ hasMergeConflicts,
117
+ conflictsWith,
118
+ hasOpenPR,
119
+ prUrl,
120
+ prNumber
121
+ };
122
+ } catch (error) {
123
+ logger.error(`Error checking branch status for ${packagePath}: ${error.message}`);
124
+ return {
125
+ name: 'unknown',
126
+ isOnExpectedBranch: false,
127
+ ahead: 0,
128
+ behind: 0,
129
+ hasUnpushedCommits: false,
130
+ needsSync: false,
131
+ remoteExists: false
132
+ };
133
+ }
134
+ }
135
+ /**
136
+ * Check if target branch (e.g., main) is exactly in sync with remote
137
+ */ async function checkTargetBranchSync(packagePath, targetBranch = 'main', options = {}) {
138
+ const logger = getLogger();
139
+ // Check if path is in a git repository
140
+ if (!isInGitRepository(packagePath)) {
141
+ return {
142
+ targetBranch,
143
+ localExists: false,
144
+ remoteExists: false,
145
+ exactMatch: true,
146
+ canFastForward: false,
147
+ needsReset: false
148
+ };
149
+ }
150
+ try {
151
+ // Fetch latest from origin to ensure we have current info
152
+ if (!options.skipFetch) {
153
+ try {
154
+ await run('git fetch origin --quiet', {
155
+ cwd: packagePath,
156
+ suppressErrorLogging: true
157
+ });
158
+ } catch (error) {
159
+ logger.verbose(`Could not fetch from origin in ${packagePath}: ${error.message}`);
160
+ }
161
+ }
162
+ // Check if local target branch exists
163
+ let localExists = false;
164
+ let localSha;
165
+ try {
166
+ const { stdout } = await run(`git rev-parse --verify ${targetBranch}`, {
167
+ cwd: packagePath,
168
+ suppressErrorLogging: true
169
+ });
170
+ localSha = stdout.trim();
171
+ localExists = true;
172
+ } catch {
173
+ localExists = false;
174
+ }
175
+ // Check if remote target branch exists
176
+ let remoteExists = false;
177
+ let remoteSha;
178
+ try {
179
+ const { stdout } = await run(`git ls-remote origin ${targetBranch}`, {
180
+ cwd: packagePath,
181
+ suppressErrorLogging: true
182
+ });
183
+ if (stdout.trim()) {
184
+ remoteSha = stdout.split(/\s+/)[0];
185
+ remoteExists = true;
186
+ }
187
+ } catch {
188
+ remoteExists = false;
189
+ }
190
+ // Determine sync status
191
+ const exactMatch = localExists && remoteExists && localSha === remoteSha;
192
+ let canFastForward = false;
193
+ let needsReset = false;
194
+ if (localExists && remoteExists && !exactMatch) {
195
+ // Check if local is ancestor of remote (can fast-forward)
196
+ try {
197
+ await run(`git merge-base --is-ancestor ${targetBranch} origin/${targetBranch}`, {
198
+ cwd: packagePath,
199
+ suppressErrorLogging: true
200
+ });
201
+ canFastForward = true;
202
+ needsReset = false;
203
+ } catch {
204
+ // Local is not ancestor of remote, need reset
205
+ canFastForward = false;
206
+ needsReset = true;
207
+ }
208
+ }
209
+ return {
210
+ targetBranch,
211
+ localExists,
212
+ remoteExists,
213
+ localSha,
214
+ remoteSha,
215
+ exactMatch,
216
+ canFastForward,
217
+ needsReset
218
+ };
219
+ } catch (error) {
220
+ return {
221
+ targetBranch,
222
+ localExists: false,
223
+ remoteExists: false,
224
+ exactMatch: false,
225
+ canFastForward: false,
226
+ needsReset: false,
227
+ error: error.message
228
+ };
229
+ }
230
+ }
231
+ /**
232
+ * Audit branch state across multiple packages
233
+ */ async function auditBranchState(packages, expectedBranch, options = {}) {
234
+ const logger = getLogger();
235
+ const targetBranch = options.targetBranch || 'main';
236
+ const checkPR = options.checkPR !== false; // Default true
237
+ const checkConflicts = options.checkConflicts !== false; // Default true
238
+ const checkVersions = options.checkVersions !== false; // Default true
239
+ const concurrency = options.concurrency || 5;
240
+ logger.info(`BRANCH_STATE_AUDIT: Auditing branch state for packages | Package Count: ${packages.length} | Concurrency: ${concurrency} | Purpose: Verify synchronization`);
241
+ // Helper for concurrency-limited parallel map
242
+ const parallelMap = async (items, fn)=>{
243
+ const results = new Array(items.length);
244
+ const queue = items.map((item, index)=>({
245
+ item,
246
+ index
247
+ }));
248
+ let nextIndex = 0;
249
+ const workers = Array(Math.min(concurrency, items.length)).fill(null).map(async ()=>{
250
+ while(nextIndex < queue.length){
251
+ const task = queue[nextIndex++];
252
+ results[task.index] = await fn(task.item, task.index);
253
+ }
254
+ });
255
+ await Promise.all(workers);
256
+ return results;
257
+ };
258
+ // If no expected branch specified, find the most common branch
259
+ let actualExpectedBranch = expectedBranch;
260
+ if (!expectedBranch) {
261
+ logger.info('📋 Phase 1/3: Detecting most common branch across packages (optimized)...');
262
+ const branchNames = await parallelMap(packages, async (pkg)=>{
263
+ if (!isInGitRepository(pkg.path)) {
264
+ return 'non-git';
265
+ }
266
+ try {
267
+ const { stdout } = await run('git rev-parse --abbrev-ref HEAD', {
268
+ cwd: pkg.path,
269
+ suppressErrorLogging: true
270
+ });
271
+ return stdout.trim();
272
+ } catch {
273
+ return 'unknown';
274
+ }
275
+ });
276
+ const branchCounts = new Map();
277
+ for (const name of branchNames){
278
+ branchCounts.set(name, (branchCounts.get(name) || 0) + 1);
279
+ }
280
+ // Find most common branch
281
+ let maxCount = 0;
282
+ for (const [branch, count] of branchCounts.entries()){
283
+ if (count > maxCount && branch !== 'unknown' && branch !== 'non-git') {
284
+ maxCount = count;
285
+ actualExpectedBranch = branch;
286
+ }
287
+ }
288
+ if (!actualExpectedBranch) actualExpectedBranch = 'main';
289
+ logger.info(`✓ Most common branch: ${actualExpectedBranch} (${maxCount}/${packages.length} packages)`);
290
+ }
291
+ // Phase 2: Identify unique repos and fetch once per repo
292
+ logger.info(`\n📋 Phase 2/3: Fetching latest from remotes (one fetch per repository)...`);
293
+ const repoRoots = new Set();
294
+ for (const pkg of packages){
295
+ const root = getGitRepositoryRoot(pkg.path);
296
+ if (root) repoRoots.add(root);
297
+ }
298
+ const repoList = Array.from(repoRoots);
299
+ await parallelMap(repoList, async (repo, i)=>{
300
+ try {
301
+ logger.verbose(` [${i + 1}/${repoList.length}] Fetching in: ${repo}`);
302
+ await run('git fetch origin --quiet', {
303
+ cwd: repo,
304
+ suppressErrorLogging: true
305
+ });
306
+ } catch (error) {
307
+ logger.debug(`Could not fetch in ${repo}: ${error.message}`);
308
+ }
309
+ });
310
+ logger.info(`✓ Fetched latest information for ${repoList.length} unique repositories`);
311
+ // Phase 3: Full audit in parallel
312
+ logger.info(`\n📋 Phase 3/3: Auditing package state (git status, conflicts, PRs, versions)...`);
313
+ let completedCount = 0;
314
+ const audits = await parallelMap(packages, async (pkg)=>{
315
+ // Check target branch sync (e.g., is local main exactly in sync with remote main?)
316
+ const targetBranchSync = await checkTargetBranchSync(pkg.path, targetBranch, {
317
+ skipFetch: true
318
+ });
319
+ const status = await checkBranchStatus(pkg.path, actualExpectedBranch, targetBranch, checkPR, {
320
+ skipFetch: true
321
+ });
322
+ const issues = [];
323
+ const fixes = [];
324
+ let versionStatus;
325
+ // Skip issues for non-git repositories
326
+ if (status.name === 'non-git') {
327
+ completedCount++;
328
+ if (completedCount % 5 === 0 || completedCount === packages.length) {
329
+ logger.info(` Progress: ${completedCount}/${packages.length} packages audited`);
330
+ }
331
+ return {
332
+ packageName: pkg.name,
333
+ path: pkg.path,
334
+ status,
335
+ versionStatus,
336
+ targetBranchSync,
337
+ issues,
338
+ fixes
339
+ };
340
+ }
341
+ // Check for issues
342
+ if (!status.isOnExpectedBranch && actualExpectedBranch) {
343
+ issues.push(`On branch '${status.name}' (most packages are on '${actualExpectedBranch}')`);
344
+ fixes.push(`cd ${pkg.path} && git checkout ${actualExpectedBranch}`);
345
+ }
346
+ if (checkConflicts && status.hasMergeConflicts && status.conflictsWith) {
347
+ issues.push(`⚠️ MERGE CONFLICTS with '${status.conflictsWith}'`);
348
+ fixes.push(`cd ${pkg.path} && git merge origin/${status.conflictsWith} # Resolve conflicts manually`);
349
+ }
350
+ if (checkPR && status.hasOpenPR) {
351
+ issues.push(`Has existing PR #${status.prNumber}: ${status.prUrl}`);
352
+ fixes.push(`# Review PR: ${status.prUrl}`);
353
+ }
354
+ if (status.hasUnpushedCommits) {
355
+ issues.push(`Ahead of remote by ${status.ahead} commit(s)`);
356
+ fixes.push(`cd ${pkg.path} && git push origin ${status.name}`);
357
+ }
358
+ if (status.needsSync) {
359
+ issues.push(`Behind remote by ${status.behind} commit(s)`);
360
+ fixes.push(`cd ${pkg.path} && git pull origin ${status.name}`);
361
+ }
362
+ if (!status.remoteExists) {
363
+ issues.push(`Remote branch does not exist`);
364
+ fixes.push(`cd ${pkg.path} && git push -u origin ${status.name}`);
365
+ }
366
+ // Check version consistency if enabled
367
+ if (checkVersions) {
368
+ try {
369
+ const { validateVersionForBranch } = await import('@eldrforge/core');
370
+ const fs = await import('fs/promises');
371
+ const pathModule = await import('path');
372
+ const packageJsonPath = pathModule.join(pkg.path, 'package.json');
373
+ const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8');
374
+ const packageJson = JSON.parse(packageJsonContent);
375
+ const version = packageJson.version;
376
+ const validation = validateVersionForBranch(version, status.name);
377
+ versionStatus = {
378
+ version,
379
+ isValid: validation.valid,
380
+ issue: validation.issue,
381
+ fix: validation.fix
382
+ };
383
+ if (!validation.valid) {
384
+ issues.push(`Version: ${version} - ${validation.issue}`);
385
+ fixes.push(`cd ${pkg.path} && kodrdriv development # ${validation.fix}`);
386
+ }
387
+ } catch (error) {
388
+ logger.verbose(`Could not check version for ${pkg.name}: ${error.message}`);
389
+ }
390
+ }
391
+ // Check target branch sync (e.g., is local main exactly in sync with remote main?)
392
+ if (targetBranchSync.localExists && targetBranchSync.remoteExists && !targetBranchSync.exactMatch) {
393
+ if (targetBranchSync.needsReset) {
394
+ issues.push(`Target branch '${targetBranch}' is NOT in sync with remote (local has diverged)`);
395
+ fixes.push(`cd ${pkg.path} && git checkout ${targetBranch} && git reset --hard origin/${targetBranch} && git checkout ${status.name}`);
396
+ } else if (targetBranchSync.canFastForward) {
397
+ issues.push(`Target branch '${targetBranch}' is behind remote (can fast-forward)`);
398
+ fixes.push(`cd ${pkg.path} && git checkout ${targetBranch} && git pull origin ${targetBranch} && git checkout ${status.name}`);
399
+ } else {
400
+ issues.push(`Target branch '${targetBranch}' is NOT in exact sync with remote`);
401
+ fixes.push(`cd ${pkg.path} && git checkout ${targetBranch} && git pull origin ${targetBranch} && git checkout ${status.name}`);
402
+ }
403
+ } else if (!targetBranchSync.localExists && targetBranchSync.remoteExists) {
404
+ // Local target branch doesn't exist but exists on remote
405
+ issues.push(`Target branch '${targetBranch}' does not exist locally (exists on remote)`);
406
+ fixes.push(`cd ${pkg.path} && git branch ${targetBranch} origin/${targetBranch}`);
407
+ } else if (targetBranchSync.error) {
408
+ logger.verbose(`Could not check target branch sync for ${pkg.name}: ${targetBranchSync.error}`);
409
+ }
410
+ completedCount++;
411
+ if (completedCount % 5 === 0 || completedCount === packages.length) {
412
+ logger.info(` Progress: ${completedCount}/${packages.length} packages audited`);
413
+ }
414
+ return {
415
+ packageName: pkg.name,
416
+ path: pkg.path,
417
+ status,
418
+ versionStatus,
419
+ targetBranchSync,
420
+ issues,
421
+ fixes
422
+ };
423
+ });
424
+ const issuesFound = audits.filter((a)=>a.issues.length > 0).length;
425
+ const versionIssues = audits.filter((a)=>a.versionStatus && !a.versionStatus.isValid).length;
426
+ const targetBranchSyncIssues = audits.filter((a)=>a.targetBranchSync && !a.targetBranchSync.exactMatch && a.targetBranchSync.localExists && a.targetBranchSync.remoteExists).length;
427
+ const goodPackages = audits.filter((a)=>a.issues.length === 0).length;
428
+ logger.info(`✓ Audit complete: ${goodPackages}/${packages.length} packages have no issues`);
429
+ if (issuesFound > 0) {
430
+ logger.info(` Issues found in ${issuesFound} package(s)`);
431
+ }
432
+ return {
433
+ totalPackages: packages.length,
434
+ goodPackages,
435
+ issuesFound,
436
+ versionIssues,
437
+ targetBranchSyncIssues,
438
+ audits
439
+ };
440
+ }
441
+ /**
442
+ * Format audit results for display with detailed fix instructions
443
+ */ function formatAuditResults(result) {
444
+ const lines = [];
445
+ // Determine the common branch if any
446
+ const branchCounts = new Map();
447
+ for (const audit of result.audits){
448
+ const branch = audit.status.name;
449
+ branchCounts.set(branch, (branchCounts.get(branch) || 0) + 1);
450
+ }
451
+ let commonBranch;
452
+ let maxCount = 0;
453
+ for (const [branch, count] of branchCounts.entries()){
454
+ if (count > maxCount) {
455
+ maxCount = count;
456
+ commonBranch = branch;
457
+ }
458
+ }
459
+ lines.push('╔══════════════════════════════════════════════════════════════╗');
460
+ lines.push(`║ Branch State Audit (${result.totalPackages} packages)`.padEnd(63) + '║');
461
+ if (commonBranch && maxCount === result.totalPackages) {
462
+ lines.push(`║ All packages on: ${commonBranch}`.padEnd(63) + '║');
463
+ } else if (commonBranch) {
464
+ lines.push(`║ Most packages on: ${commonBranch} (${maxCount}/${result.totalPackages})`.padEnd(63) + '║');
465
+ }
466
+ lines.push('╠══════════════════════════════════════════════════════════════╣');
467
+ lines.push('');
468
+ if (result.goodPackages > 0) {
469
+ lines.push(`✅ Good State (${result.goodPackages} package${result.goodPackages === 1 ? '' : 's'}):`);
470
+ const goodAudits = result.audits.filter((a)=>a.issues.length === 0);
471
+ const displayCount = Math.min(goodAudits.length, 5);
472
+ goodAudits.slice(0, displayCount).forEach((audit)=>{
473
+ const versionInfo = audit.versionStatus ? ` (v${audit.versionStatus.version})` : '';
474
+ lines.push(` ${audit.packageName}${versionInfo}`);
475
+ });
476
+ if (goodAudits.length > displayCount) {
477
+ lines.push(` ... and ${goodAudits.length - displayCount} more`);
478
+ }
479
+ lines.push('');
480
+ }
481
+ // Show version issues prominently if any
482
+ if (result.versionIssues > 0) {
483
+ lines.push(`⚠️ Version Issues (${result.versionIssues} package${result.versionIssues === 1 ? '' : 's'}):`);
484
+ const versionIssueAudits = result.audits.filter((a)=>a.versionStatus && !a.versionStatus.isValid);
485
+ versionIssueAudits.forEach((audit)=>{
486
+ lines.push(` ${audit.packageName}`);
487
+ lines.push(` - Branch: ${audit.status.name}`);
488
+ lines.push(` - Version: ${audit.versionStatus.version}`);
489
+ lines.push(` - Issue: ${audit.versionStatus.issue}`);
490
+ lines.push(` - Fix: ${audit.versionStatus.fix}`);
491
+ lines.push('');
492
+ });
493
+ }
494
+ // Show target branch sync issues prominently if any
495
+ if (result.targetBranchSyncIssues > 0) {
496
+ lines.push(`🚨 Target Branch Sync Issues (${result.targetBranchSyncIssues} package${result.targetBranchSyncIssues === 1 ? '' : 's'}):`);
497
+ lines.push(` ⚠️ ${result.targetBranchSyncIssues} package${result.targetBranchSyncIssues === 1 ? '' : 's'} with target branch NOT in sync with remote`);
498
+ lines.push(` This will cause "branch out of sync" errors during parallel publish!`);
499
+ lines.push('');
500
+ const targetSyncIssueAudits = result.audits.filter((a)=>a.targetBranchSync && !a.targetBranchSync.exactMatch && a.targetBranchSync.localExists && a.targetBranchSync.remoteExists);
501
+ targetSyncIssueAudits.forEach((audit)=>{
502
+ var _sync_localSha, _sync_remoteSha;
503
+ const sync = audit.targetBranchSync;
504
+ lines.push(` ${audit.packageName}`);
505
+ lines.push(` - Target Branch: ${sync.targetBranch}`);
506
+ lines.push(` - Local SHA: ${(_sync_localSha = sync.localSha) === null || _sync_localSha === void 0 ? void 0 : _sync_localSha.substring(0, 8)}...`);
507
+ lines.push(` - Remote SHA: ${(_sync_remoteSha = sync.remoteSha) === null || _sync_remoteSha === void 0 ? void 0 : _sync_remoteSha.substring(0, 8)}...`);
508
+ if (sync.needsReset) {
509
+ lines.push(` - Action: RESET REQUIRED (local has diverged)`);
510
+ } else if (sync.canFastForward) {
511
+ lines.push(` - Action: Pull to fast-forward`);
512
+ }
513
+ lines.push('');
514
+ });
515
+ }
516
+ if (result.issuesFound > 0) {
517
+ // Count critical issues (merge conflicts, existing PRs, target branch sync)
518
+ const conflictCount = result.audits.filter((a)=>a.status.hasMergeConflicts).length;
519
+ const prCount = result.audits.filter((a)=>a.status.hasOpenPR).length;
520
+ const branchInconsistentCount = result.audits.filter((a)=>!a.status.isOnExpectedBranch).length;
521
+ const unpushedCount = result.audits.filter((a)=>a.status.hasUnpushedCommits).length;
522
+ const behindCount = result.audits.filter((a)=>a.status.needsSync).length;
523
+ const noRemoteCount = result.audits.filter((a)=>!a.status.remoteExists).length;
524
+ if (conflictCount > 0 || prCount > 0 || result.targetBranchSyncIssues > 0) {
525
+ lines.push(`🚨 CRITICAL ISSUES:`);
526
+ if (result.targetBranchSyncIssues > 0) {
527
+ lines.push(` 🔄 ${result.targetBranchSyncIssues} package${result.targetBranchSyncIssues === 1 ? '' : 's'} with target branch sync issues`);
528
+ }
529
+ if (conflictCount > 0) {
530
+ lines.push(` ⚠️ ${conflictCount} package${conflictCount === 1 ? '' : 's'} with merge conflicts`);
531
+ }
532
+ if (prCount > 0) {
533
+ lines.push(` 📋 ${prCount} package${prCount === 1 ? '' : 's'} with existing PRs`);
534
+ }
535
+ lines.push('');
536
+ }
537
+ lines.push(`⚠️ Issues Summary:`);
538
+ if (result.targetBranchSyncIssues > 0) lines.push(` • ${result.targetBranchSyncIssues} target branch sync issue${result.targetBranchSyncIssues === 1 ? '' : 's'}`);
539
+ if (conflictCount > 0) lines.push(` • ${conflictCount} merge conflict${conflictCount === 1 ? '' : 's'}`);
540
+ if (prCount > 0) lines.push(` • ${prCount} existing PR${prCount === 1 ? '' : 's'}`);
541
+ if (branchInconsistentCount > 0) lines.push(` • ${branchInconsistentCount} branch inconsistenc${branchInconsistentCount === 1 ? 'y' : 'ies'}`);
542
+ if (unpushedCount > 0) lines.push(` • ${unpushedCount} package${unpushedCount === 1 ? '' : 's'} with unpushed commits`);
543
+ if (behindCount > 0) lines.push(` • ${behindCount} package${behindCount === 1 ? '' : 's'} behind remote`);
544
+ if (noRemoteCount > 0) lines.push(` • ${noRemoteCount} package${noRemoteCount === 1 ? '' : 's'} with no remote branch`);
545
+ lines.push('');
546
+ lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
547
+ lines.push('📋 DETAILED ISSUES AND FIXES:');
548
+ lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
549
+ lines.push('');
550
+ // Sort issues by severity: conflicts first, then PRs, then others
551
+ const auditsWithIssues = result.audits.filter((a)=>a.issues.length > 0);
552
+ const sortedAudits = auditsWithIssues.sort((a, b)=>{
553
+ const aScore = (a.status.hasMergeConflicts ? 1000 : 0) + (a.status.hasOpenPR ? 100 : 0);
554
+ const bScore = (b.status.hasMergeConflicts ? 1000 : 0) + (b.status.hasOpenPR ? 100 : 0);
555
+ return bScore - aScore;
556
+ });
557
+ sortedAudits.forEach((audit, index)=>{
558
+ // Highlight critical issues
559
+ const hasCritical = audit.status.hasMergeConflicts || audit.status.hasOpenPR;
560
+ const prefix = hasCritical ? '🚨 CRITICAL' : '⚠️ WARNING';
561
+ lines.push(`${prefix} [${index + 1}/${sortedAudits.length}] ${audit.packageName}`);
562
+ lines.push(`Location: ${audit.path}`);
563
+ lines.push(`Branch: ${audit.status.name}`);
564
+ if (audit.status.remoteExists) {
565
+ const syncStatus = [];
566
+ if (audit.status.ahead > 0) syncStatus.push(`ahead ${audit.status.ahead}`);
567
+ if (audit.status.behind > 0) syncStatus.push(`behind ${audit.status.behind}`);
568
+ if (syncStatus.length > 0) {
569
+ lines.push(`Sync: ${syncStatus.join(', ')}`);
570
+ }
571
+ } else {
572
+ lines.push(`Remote: Does not exist`);
573
+ }
574
+ lines.push('');
575
+ lines.push('Issues:');
576
+ audit.issues.forEach((issue)=>{
577
+ const icon = issue.includes('MERGE CONFLICTS') ? '⚠️ ' : issue.includes('PR') ? '📋 ' : '❌ ';
578
+ lines.push(` ${icon} ${issue}`);
579
+ });
580
+ lines.push('');
581
+ lines.push('Fix Commands (execute in order):');
582
+ audit.fixes.forEach((fix, fixIndex)=>{
583
+ lines.push(` ${fixIndex + 1}. ${fix}`);
584
+ });
585
+ // Add context-specific guidance
586
+ if (audit.status.hasMergeConflicts) {
587
+ lines.push('');
588
+ lines.push(' ⚠️ Merge Conflict Resolution:');
589
+ lines.push(' After running the merge command above, you will need to:');
590
+ lines.push(' a) Manually edit conflicting files to resolve conflicts');
591
+ lines.push(' b) Stage resolved files: git add <file>');
592
+ lines.push(' c) Complete the merge: git commit');
593
+ lines.push(' d) Push the resolved merge: git push origin ' + audit.status.name);
594
+ lines.push(' e) Re-run audit to verify: kodrdriv tree publish --audit-branches');
595
+ }
596
+ if (audit.status.hasOpenPR) {
597
+ lines.push('');
598
+ lines.push(' 📋 Existing PR Handling:');
599
+ lines.push(' You have options:');
600
+ lines.push(' a) Continue with existing PR (kodrdriv publish will detect and use it)');
601
+ lines.push(' b) Close the PR if no longer needed');
602
+ lines.push(' c) Merge the PR if ready, then create new one');
603
+ }
604
+ lines.push('');
605
+ lines.push('─────────────────────────────────────────────────────────────');
606
+ lines.push('');
607
+ });
608
+ lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
609
+ lines.push('📝 RECOMMENDED WORKFLOW:');
610
+ lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
611
+ lines.push('');
612
+ let stepNumber = 1;
613
+ // Target branch sync is FIRST and most critical
614
+ if (result.targetBranchSyncIssues > 0) {
615
+ lines.push(`${stepNumber}️⃣ SYNC TARGET BRANCHES (CRITICAL - Do this FIRST):`);
616
+ stepNumber++;
617
+ const targetSyncIssueAudits = result.audits.filter((a)=>a.targetBranchSync && !a.targetBranchSync.exactMatch && a.targetBranchSync.localExists && a.targetBranchSync.remoteExists);
618
+ targetSyncIssueAudits.forEach((audit)=>{
619
+ const sync = audit.targetBranchSync;
620
+ if (sync.needsReset) {
621
+ lines.push(` • ${audit.packageName}: cd ${audit.path} && git checkout ${sync.targetBranch} && git reset --hard origin/${sync.targetBranch} && git checkout ${audit.status.name}`);
622
+ } else {
623
+ lines.push(` • ${audit.packageName}: cd ${audit.path} && git checkout ${sync.targetBranch} && git pull origin ${sync.targetBranch} && git checkout ${audit.status.name}`);
624
+ }
625
+ });
626
+ lines.push('');
627
+ }
628
+ if (conflictCount > 0) {
629
+ lines.push(`${stepNumber}️⃣ RESOLVE MERGE CONFLICTS FIRST (blocking):`);
630
+ stepNumber++;
631
+ sortedAudits.filter((a)=>a.status.hasMergeConflicts).forEach((audit)=>{
632
+ lines.push(` • ${audit.packageName}: cd ${audit.path} && git merge origin/${audit.status.conflictsWith}`);
633
+ });
634
+ lines.push(' Then resolve conflicts, commit, and push.');
635
+ lines.push('');
636
+ }
637
+ if (result.versionIssues > 0) {
638
+ lines.push(`${stepNumber}️⃣ FIX VERSION ISSUES (recommended before publish):`);
639
+ stepNumber++;
640
+ sortedAudits.filter((a)=>a.versionStatus && !a.versionStatus.isValid).forEach((audit)=>{
641
+ lines.push(` • ${audit.packageName}: cd ${audit.path} && kodrdriv development`);
642
+ });
643
+ lines.push('');
644
+ }
645
+ if (prCount > 0) {
646
+ lines.push(`${stepNumber}️⃣ HANDLE EXISTING PRS:`);
647
+ stepNumber++;
648
+ sortedAudits.filter((a)=>a.status.hasOpenPR).forEach((audit)=>{
649
+ lines.push(` • ${audit.packageName}: Review ${audit.status.prUrl}`);
650
+ lines.push(` Option: Continue (publish will reuse PR) or close/merge it first`);
651
+ });
652
+ lines.push('');
653
+ }
654
+ if (branchInconsistentCount > 0) {
655
+ lines.push(`${stepNumber}️⃣ ALIGN BRANCHES (if needed):`);
656
+ stepNumber++;
657
+ sortedAudits.filter((a)=>!a.status.isOnExpectedBranch).forEach((audit)=>{
658
+ lines.push(` • ${audit.packageName}: cd ${audit.path} && git checkout ${audit.status.expectedBranch}`);
659
+ });
660
+ lines.push('');
661
+ }
662
+ if (behindCount > 0) {
663
+ lines.push(`${stepNumber}️⃣ SYNC WITH REMOTE:`);
664
+ stepNumber++;
665
+ sortedAudits.filter((a)=>a.status.needsSync && !a.status.hasMergeConflicts).forEach((audit)=>{
666
+ lines.push(` • ${audit.packageName}: cd ${audit.path} && git pull origin ${audit.status.name}`);
667
+ });
668
+ lines.push('');
669
+ }
670
+ if (unpushedCount > 0) {
671
+ lines.push(`${stepNumber}️⃣ PUSH LOCAL COMMITS:`);
672
+ stepNumber++;
673
+ sortedAudits.filter((a)=>a.status.hasUnpushedCommits && !a.status.hasMergeConflicts).forEach((audit)=>{
674
+ lines.push(` • ${audit.packageName}: cd ${audit.path} && git push origin ${audit.status.name}`);
675
+ });
676
+ lines.push('');
677
+ }
678
+ if (noRemoteCount > 0) {
679
+ lines.push(`${stepNumber}️⃣ CREATE REMOTE BRANCHES:`);
680
+ stepNumber++;
681
+ sortedAudits.filter((a)=>!a.status.remoteExists).forEach((audit)=>{
682
+ lines.push(` • ${audit.packageName}: cd ${audit.path} && git push -u origin ${audit.status.name}`);
683
+ });
684
+ lines.push('');
685
+ }
686
+ lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
687
+ lines.push('');
688
+ lines.push('🔄 After fixing issues, re-run audit to verify:');
689
+ lines.push(' kodrdriv tree publish --audit-branches');
690
+ lines.push('');
691
+ lines.push('✅ Once all clear, proceed with publish:');
692
+ lines.push(' kodrdriv tree publish --parallel --model "gpt-5-mini"');
693
+ }
694
+ lines.push('╚══════════════════════════════════════════════════════════════╝');
695
+ return lines.join('\n');
696
+ }
697
+
698
+ export { auditBranchState, checkBranchStatus, checkTargetBranchSync, formatAuditResults };
699
+ //# sourceMappingURL=branchState-CtywDSJf.js.map