gitxplain 0.1.3 → 0.1.6

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.
@@ -1,4 +1,4 @@
1
- import { execFileSync } from "node:child_process";
1
+ import { execFileSync, spawnSync } from "node:child_process";
2
2
  import os from "node:os";
3
3
  import { mkdtempSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
4
4
  import path from "node:path";
@@ -87,6 +87,33 @@ export function runGitCommandUnchecked(args, cwd) {
87
87
  }
88
88
  }
89
89
 
90
+ export function listGitSubcommands() {
91
+ const output = execFileSync("git", ["help", "-a"], {
92
+ encoding: "utf8",
93
+ stdio: ["ignore", "pipe", "pipe"]
94
+ });
95
+
96
+ return new Set(
97
+ output
98
+ .split("\n")
99
+ .map((line) => line.match(/^\s{3}([a-z0-9][a-z0-9-]*)\s{2,}/i)?.[1] ?? null)
100
+ .filter(Boolean)
101
+ );
102
+ }
103
+
104
+ export function runNativeGitPassthrough(args, cwd) {
105
+ const result = spawnSync("git", args, {
106
+ cwd,
107
+ stdio: "inherit"
108
+ });
109
+
110
+ if (result.error) {
111
+ throw result.error;
112
+ }
113
+
114
+ return result.status ?? 0;
115
+ }
116
+
90
117
  export function isGitRepository(cwd) {
91
118
  try {
92
119
  return runGitCommand(["rev-parse", "--is-inside-work-tree"], cwd) === "true";
@@ -231,6 +258,21 @@ export function gitPush(cwd, remote = null, branch = null, runner = runGitComman
231
258
  return runner(args, cwd);
232
259
  }
233
260
 
261
+ export function gitPull(cwd, remote = null, branch = null, runner = runGitCommand) {
262
+ const args = ["pull"];
263
+
264
+ if (remote) {
265
+ args.push(remote);
266
+ }
267
+
268
+ if (branch) {
269
+ args.push(branch);
270
+ }
271
+
272
+ return runner(args, cwd);
273
+ }
274
+
275
+
234
276
  export function gitCreateAnnotatedTag(tagName, ref, message, cwd) {
235
277
  return runGitCommand(["tag", "-a", tagName, ref, "-m", message], cwd);
236
278
  }
@@ -438,8 +480,8 @@ export function isAncestorCommit(ancestorRef, descendantRef, cwd) {
438
480
  throw new Error(result.stderr || "Unable to determine commit ancestry.");
439
481
  }
440
482
 
441
- export function gitResetHard(ref, cwd) {
442
- return runGitCommand(["reset", "--hard", ref], cwd);
483
+ export function gitResetHard(ref, cwd, runner = runGitCommand) {
484
+ return runner(["reset", "--hard", ref], cwd);
443
485
  }
444
486
 
445
487
  export function gitCherryPickNoCommit(ref, cwd) {
@@ -1,26 +1,22 @@
1
1
  import process from "node:process";
2
2
  import {
3
- deletePaths,
3
+ createCommitFromTree,
4
+ getCommitMetadata,
4
5
  getCurrentBranchName,
5
- getCurrentHeadSha,
6
6
  getDefaultBaseRef,
7
7
  getMergeBase,
8
8
  gitCheckout,
9
- gitCheckoutOrphan,
10
- gitCherryPickAbort,
11
- gitCherryPickNoCommit,
12
- gitCommit,
9
+ gitCreateBranch,
13
10
  gitCreateAnnotatedTag,
14
11
  gitDeleteBranch,
12
+ gitForceBranch,
15
13
  gitDeleteTag,
16
- gitRemoveCachedAll,
17
- gitResetHard,
18
14
  isWorkingTreeClean,
19
15
  listBranchCommits,
20
16
  listCommitsAfter,
21
- listFilesInRef,
22
17
  listTags,
23
18
  localBranchExists,
19
+ resolveTreeSha,
24
20
  resolveCommitSha,
25
21
  runGitCommand
26
22
  } from "./gitService.js";
@@ -315,15 +311,8 @@ export function selectReleaseWindows(sourceCommits, releaseCommits = []) {
315
311
  const releasedVersions = getReleasedVersions(releaseCommits);
316
312
  const unreleasedWindows = windows.filter((window) => !releasedVersions.has(window.version));
317
313
 
318
- const selectedWindows =
319
- releasedVersions.size === 0
320
- ? unreleasedWindows
321
- : unreleasedWindows.length > 0
322
- ? [unreleasedWindows.at(-1)]
323
- : [];
324
-
325
314
  return {
326
- windows: selectedWindows,
315
+ windows: unreleasedWindows,
327
316
  releasedVersions: [...releasedVersions],
328
317
  latestDetectedVersion: windows.at(-1)?.version ?? null
329
318
  };
@@ -353,37 +342,63 @@ export function selectReleaseTags(sourceCommits, existingTagNames = []) {
353
342
  };
354
343
  }
355
344
 
356
- function getReleaseTrackSourceCommitShas(releaseExists, baseRef, cwd) {
345
+ export function selectReleaseTagsFromReleaseCommits(releaseCommits, existingTagNames = []) {
346
+ const taggedVersions = extractTaggedVersions(existingTagNames);
347
+ const tags = releaseCommits
348
+ .map((commit) => ({
349
+ commit,
350
+ version: commit.subject.match(RELEASE_SUBJECT_PATTERN)?.[1]?.trim() ?? null
351
+ }))
352
+ .filter((entry) => entry.version)
353
+ .filter((entry) => !taggedVersions.has(entry.version))
354
+ .map(({ commit, version }) => ({
355
+ version,
356
+ tagName: version,
357
+ startRef: commit.shortSha,
358
+ endRef: commit.shortSha,
359
+ targetSha: commit.sha,
360
+ targetShortSha: commit.shortSha,
361
+ targetSubject: commit.subject,
362
+ commits: [commit]
363
+ }));
364
+
365
+ return {
366
+ tags,
367
+ taggedVersions: [...taggedVersions],
368
+ latestDetectedVersion:
369
+ releaseCommits
370
+ .map((commit) => commit.subject.match(RELEASE_SUBJECT_PATTERN)?.[1]?.trim() ?? null)
371
+ .filter(Boolean)
372
+ .at(-1) ?? null
373
+ };
374
+ }
375
+
376
+ function getReleaseTrackSourceCommitShas(releaseExists, baseRef, sourceRef, cwd) {
357
377
  if (!releaseExists) {
358
378
  return {
359
379
  mergeBase: null,
360
- sourceCommitShas: listBranchCommits("HEAD", cwd)
380
+ sourceCommitShas: listBranchCommits(sourceRef, cwd)
361
381
  };
362
382
  }
363
383
 
364
384
  try {
365
- const mergeBase = getMergeBase(baseRef, "HEAD", cwd);
385
+ const mergeBase = getMergeBase(baseRef, sourceRef, cwd);
366
386
  return {
367
387
  mergeBase,
368
- sourceCommitShas: listCommitsAfter(mergeBase, "HEAD", cwd)
388
+ sourceCommitShas: listCommitsAfter(mergeBase, sourceRef, cwd)
369
389
  };
370
390
  } catch {
371
391
  return {
372
392
  mergeBase: null,
373
- sourceCommitShas: listBranchCommits("HEAD", cwd)
393
+ sourceCommitShas: listBranchCommits(sourceRef, cwd)
374
394
  };
375
395
  }
376
396
  }
377
397
 
378
- export function buildReleaseMergePlan(cwd) {
379
- const sourceBranch = getCurrentBranchName(cwd);
380
- if (sourceBranch === RELEASE_BRANCH) {
381
- throw new Error(`Already on "${RELEASE_BRANCH}". Switch to a source branch before running --merge.`);
382
- }
383
-
398
+ function buildReleaseMergePlanForSource(sourceBranch, sourceRef, cwd) {
384
399
  const releaseExists = localBranchExists(RELEASE_BRANCH, cwd);
385
400
  const baseRef = releaseExists ? RELEASE_BRANCH : getDefaultBaseRef(cwd);
386
- const { mergeBase, sourceCommitShas } = getReleaseTrackSourceCommitShas(releaseExists, baseRef, cwd);
401
+ const { mergeBase, sourceCommitShas } = getReleaseTrackSourceCommitShas(releaseExists, baseRef, sourceRef, cwd);
387
402
  const sourceCommits = sourceCommitShas.map((sha) => inspectCommit(sha, cwd));
388
403
  const releaseCommits = releaseExists ? listBranchCommits(RELEASE_BRANCH, cwd).map((sha) => inspectCommit(sha, cwd)) : [];
389
404
  const selection = selectReleaseWindows(sourceCommits, releaseCommits);
@@ -401,6 +416,15 @@ export function buildReleaseMergePlan(cwd) {
401
416
  };
402
417
  }
403
418
 
419
+ export function buildReleaseMergePlan(cwd) {
420
+ const sourceBranch = getCurrentBranchName(cwd);
421
+ if (sourceBranch === RELEASE_BRANCH) {
422
+ throw new Error(`Already on "${RELEASE_BRANCH}". Switch to a source branch before running --merge.`);
423
+ }
424
+
425
+ return buildReleaseMergePlanForSource(sourceBranch, "HEAD", cwd);
426
+ }
427
+
404
428
  export function buildReleaseTagPlan(cwd) {
405
429
  const sourceBranch = getCurrentBranchName(cwd);
406
430
  if (sourceBranch === RELEASE_BRANCH) {
@@ -409,9 +433,14 @@ export function buildReleaseTagPlan(cwd) {
409
433
 
410
434
  const releaseExists = localBranchExists(RELEASE_BRANCH, cwd);
411
435
  const baseRef = releaseExists ? RELEASE_BRANCH : getDefaultBaseRef(cwd);
412
- const { mergeBase, sourceCommitShas } = getReleaseTrackSourceCommitShas(releaseExists, baseRef, cwd);
413
- const sourceCommits = sourceCommitShas.map((sha) => inspectCommit(sha, cwd));
414
- const selection = selectReleaseTags(sourceCommits, listTags(cwd));
436
+ const { mergeBase, sourceCommitShas } = getReleaseTrackSourceCommitShas(releaseExists, baseRef, "HEAD", cwd);
437
+ const existingTagNames = listTags(cwd);
438
+ const selection = releaseExists
439
+ ? selectReleaseTagsFromReleaseCommits(
440
+ listBranchCommits(RELEASE_BRANCH, cwd).map((sha) => inspectCommit(sha, cwd)),
441
+ existingTagNames
442
+ )
443
+ : selectReleaseTags(sourceCommits.map((sha) => inspectCommit(sha, cwd)), existingTagNames);
415
444
 
416
445
  return {
417
446
  sourceBranch,
@@ -438,6 +467,132 @@ export function finalizeReleaseTagPlan(plan) {
438
467
  };
439
468
  }
440
469
 
470
+ function findLatestReleaseVersion(releaseCommits) {
471
+ return releaseCommits
472
+ .map((commit) => commit.subject.match(RELEASE_SUBJECT_PATTERN)?.[1]?.trim() ?? null)
473
+ .filter(Boolean)
474
+ .at(-1) ?? null;
475
+ }
476
+
477
+ function findLatestTaggedReleaseVersion(releaseCommits, taggedVersions) {
478
+ const tagged = new Set(taggedVersions);
479
+ return releaseCommits
480
+ .map((commit) => commit.subject.match(RELEASE_SUBJECT_PATTERN)?.[1]?.trim() ?? null)
481
+ .filter((version) => version && tagged.has(version))
482
+ .at(-1) ?? null;
483
+ }
484
+
485
+ function buildDriftStatus(sourceRef, sourceLabel, releaseExists, cwd) {
486
+ if (!releaseExists) {
487
+ return {
488
+ hasReleaseBranch: false,
489
+ disconnectedHistory: false,
490
+ sourceOnlyCount: listBranchCommits(sourceRef, cwd).length,
491
+ releaseOnlyCount: 0,
492
+ summary: `Release branch "${RELEASE_BRANCH}" does not exist yet.`
493
+ };
494
+ }
495
+
496
+ try {
497
+ const mergeBase = getMergeBase(sourceRef, RELEASE_BRANCH, cwd);
498
+ const sourceOnlyCount = listCommitsAfter(mergeBase, sourceRef, cwd).length;
499
+ const releaseOnlyCount = listCommitsAfter(mergeBase, RELEASE_BRANCH, cwd).length;
500
+
501
+ return {
502
+ hasReleaseBranch: true,
503
+ disconnectedHistory: false,
504
+ mergeBase,
505
+ sourceOnlyCount,
506
+ releaseOnlyCount,
507
+ summary:
508
+ sourceOnlyCount === 0 && releaseOnlyCount === 0
509
+ ? `${sourceLabel} and ${RELEASE_BRANCH} point at the same history.`
510
+ : `${sourceLabel} has ${sourceOnlyCount} unique commit(s); ${RELEASE_BRANCH} has ${releaseOnlyCount} unique commit(s).`
511
+ };
512
+ } catch {
513
+ return {
514
+ hasReleaseBranch: true,
515
+ disconnectedHistory: true,
516
+ mergeBase: null,
517
+ sourceOnlyCount: listBranchCommits(sourceRef, cwd).length,
518
+ releaseOnlyCount: listBranchCommits(RELEASE_BRANCH, cwd).length,
519
+ summary: `${sourceLabel} and ${RELEASE_BRANCH} do not share a merge base. This is expected when the release branch is orphaned.`
520
+ };
521
+ }
522
+ }
523
+
524
+ function getNextRecommendedAction({ releaseExists, mergePlan, missingTagCount }) {
525
+ if (!releaseExists && mergePlan.windows.length > 0) {
526
+ return `Run \`gitxplain merge --execute\` to create ${RELEASE_BRANCH} and promote ${mergePlan.windows.length} unreleased version(s).`;
527
+ }
528
+
529
+ if (!releaseExists) {
530
+ return `No ${RELEASE_BRANCH} branch exists yet, and no releasable version bumps were detected.`;
531
+ }
532
+
533
+ if (mergePlan.windows.length > 0 && missingTagCount > 0) {
534
+ return `Run \`gitxplain merge --execute\` first, then \`gitxplain tag --execute\` to finish tagging release commits.`;
535
+ }
536
+
537
+ if (mergePlan.windows.length > 0) {
538
+ return `Run \`gitxplain merge --execute\` to promote ${mergePlan.windows.length} unreleased version(s) to ${RELEASE_BRANCH}.`;
539
+ }
540
+
541
+ if (missingTagCount > 0) {
542
+ return `Run \`gitxplain tag --execute\` to create ${missingTagCount} missing release tag(s).`;
543
+ }
544
+
545
+ return "No action required. Release branch and tags are up to date.";
546
+ }
547
+
548
+ export function buildReleaseStatus(cwd) {
549
+ const currentBranch = getCurrentBranchName(cwd);
550
+ const releaseExists = localBranchExists(RELEASE_BRANCH, cwd);
551
+ const sourceBranch = currentBranch === RELEASE_BRANCH ? getDefaultBaseRef(cwd) : currentBranch;
552
+ const sourceRef = currentBranch === RELEASE_BRANCH ? sourceBranch : "HEAD";
553
+ const mergePlan = finalizeReleaseMergePlan(buildReleaseMergePlanForSource(sourceBranch, sourceRef, cwd));
554
+ const releaseCommits = releaseExists ? listBranchCommits(RELEASE_BRANCH, cwd).map((sha) => inspectCommit(sha, cwd)) : [];
555
+ const tagSelection = releaseExists
556
+ ? selectReleaseTagsFromReleaseCommits(releaseCommits, listTags(cwd))
557
+ : { tags: [], taggedVersions: [], latestDetectedVersion: null };
558
+ const drift = buildDriftStatus(sourceRef, sourceBranch, releaseExists, cwd);
559
+ const missingTagVersions = tagSelection.tags.map((tag) => tag.tagName);
560
+ const unmergedVersions = mergePlan.windows.map((window) => window.version);
561
+
562
+ return {
563
+ sourceBranch,
564
+ sourceRef,
565
+ releaseBranch: RELEASE_BRANCH,
566
+ releaseExists,
567
+ currentBranch,
568
+ health:
569
+ !releaseExists || unmergedVersions.length > 0 || missingTagVersions.length > 0
570
+ ? "needs attention"
571
+ : "healthy",
572
+ latestSourceVersion: mergePlan.latestDetectedVersion,
573
+ latestReleaseVersion: findLatestReleaseVersion(releaseCommits),
574
+ latestTaggedVersion: findLatestTaggedReleaseVersion(releaseCommits, tagSelection.taggedVersions),
575
+ unmergedVersions,
576
+ missingTagVersions,
577
+ drift,
578
+ mergePlan,
579
+ tagPlan: finalizeReleaseTagPlan({
580
+ sourceBranch,
581
+ baseRef: mergePlan.baseRef,
582
+ mergeBase: mergePlan.mergeBase,
583
+ releaseExists,
584
+ taggedVersions: tagSelection.taggedVersions,
585
+ latestDetectedVersion: tagSelection.latestDetectedVersion,
586
+ tags: tagSelection.tags
587
+ }),
588
+ nextRecommendedAction: getNextRecommendedAction({
589
+ releaseExists,
590
+ mergePlan,
591
+ missingTagCount: missingTagVersions.length
592
+ })
593
+ };
594
+ }
595
+
441
596
  export function formatReleaseMergePlan(plan) {
442
597
  const lines = [
443
598
  colorize("Release Merge Plan", ANSI.bold + ANSI.cyan),
@@ -504,6 +659,50 @@ export function formatReleaseTagPlan(plan) {
504
659
  return lines.join("\n");
505
660
  }
506
661
 
662
+ export function formatReleaseStatus(status) {
663
+ const lines = [
664
+ colorize("Release Status", ANSI.bold + ANSI.cyan),
665
+ `${colorize("Source Branch:", ANSI.bold + ANSI.cyan)} ${status.sourceBranch}`,
666
+ `${colorize("Release Branch:", ANSI.bold + ANSI.cyan)} ${status.releaseBranch}`,
667
+ `${colorize("Current Branch:", ANSI.bold + ANSI.cyan)} ${status.currentBranch}`,
668
+ `${colorize("Overall:", ANSI.bold + ANSI.cyan)} ${status.health}`,
669
+ `${colorize("Latest Source Version:", ANSI.bold + ANSI.cyan)} ${status.latestSourceVersion ?? "none"}`,
670
+ `${colorize("Latest Release Version:", ANSI.bold + ANSI.cyan)} ${status.latestReleaseVersion ?? "none"}`,
671
+ `${colorize("Latest Tagged Version:", ANSI.bold + ANSI.cyan)} ${status.latestTaggedVersion ?? "none"}`
672
+ ];
673
+
674
+ lines.push("");
675
+ lines.push(colorize("Unmerged Version Bumps", ANSI.bold + ANSI.yellow));
676
+ if (status.unmergedVersions.length === 0) {
677
+ lines.push("none");
678
+ } else {
679
+ for (const window of status.mergePlan.windows) {
680
+ lines.push(`- ${window.version} (${window.startRef}..${window.endRef})`);
681
+ }
682
+ }
683
+
684
+ lines.push("");
685
+ lines.push(colorize("Missing Release Tags", ANSI.bold + ANSI.yellow));
686
+ if (status.missingTagVersions.length === 0) {
687
+ lines.push("none");
688
+ } else {
689
+ for (const tag of status.tagPlan.tags) {
690
+ lines.push(`- ${tag.tagName} -> ${tag.targetShortSha} ${tag.targetSubject}`);
691
+ }
692
+ }
693
+
694
+ lines.push("");
695
+ lines.push(colorize("Branch Drift", ANSI.bold + ANSI.yellow));
696
+ lines.push(status.drift.summary);
697
+ lines.push(`- Commits only on ${status.sourceBranch}: ${status.drift.sourceOnlyCount}`);
698
+ lines.push(`- Commits only on ${status.releaseBranch}: ${status.drift.releaseOnlyCount}`);
699
+
700
+ lines.push("");
701
+ lines.push(`${colorize("Next Recommended Action:", ANSI.bold + ANSI.cyan)} ${status.nextRecommendedAction}`);
702
+
703
+ return lines.join("\n");
704
+ }
705
+
507
706
  function buildRecoveryMessage({ originalBranch, originalReleaseSha, createdReleaseBranch }) {
508
707
  const lines = ["Release promotion failed. Recovery steps:"];
509
708
 
@@ -518,6 +717,15 @@ function buildRecoveryMessage({ originalBranch, originalReleaseSha, createdRelea
518
717
  return lines.join("\n");
519
718
  }
520
719
 
720
+ function buildReleaseCommitMetadata(ref, version, cwd) {
721
+ const metadata = getCommitMetadata(ref, cwd);
722
+
723
+ return {
724
+ ...metadata,
725
+ message: `release ${version}`
726
+ };
727
+ }
728
+
521
729
  export function executeReleaseMerge(plan, cwd) {
522
730
  if (plan.windows.length === 0) {
523
731
  throw new Error("No unreleased release commits detected. Nothing to merge.");
@@ -530,36 +738,50 @@ export function executeReleaseMerge(plan, cwd) {
530
738
  const originalBranch = getCurrentBranchName(cwd);
531
739
  const releaseExists = localBranchExists(RELEASE_BRANCH, cwd);
532
740
  const originalReleaseSha = releaseExists ? resolveCommitSha(RELEASE_BRANCH, cwd) : null;
533
- const originalHeadSha = getCurrentHeadSha(cwd);
534
- const originalHeadFiles = releaseExists ? [] : listFilesInRef("HEAD", cwd);
741
+ let updatedReleaseSha = originalReleaseSha;
535
742
 
536
743
  try {
537
- if (releaseExists) {
538
- gitCheckout(RELEASE_BRANCH, cwd);
539
- } else {
540
- gitCheckoutOrphan(RELEASE_BRANCH, cwd);
541
- gitRemoveCachedAll(cwd);
542
- deletePaths(originalHeadFiles, cwd);
543
- }
544
-
545
744
  for (const window of plan.windows) {
546
- for (const commit of window.commits) {
547
- gitCherryPickNoCommit(commit.sha, cwd);
745
+ const targetCommit = window.commits.at(-1);
746
+ if (targetCommit?.sha == null) {
747
+ throw new Error(`Unable to determine the source commit for release ${window.version}.`);
548
748
  }
549
749
 
550
- gitCommit(`release ${window.version}`, cwd);
750
+ const treeSha = resolveTreeSha(targetCommit.sha, cwd);
751
+ const metadata = buildReleaseCommitMetadata(targetCommit.sha, window.version, cwd);
752
+ updatedReleaseSha = createCommitFromTree(
753
+ treeSha,
754
+ updatedReleaseSha == null ? [] : [updatedReleaseSha],
755
+ metadata,
756
+ cwd
757
+ );
551
758
  }
552
- } catch (error) {
553
- gitCherryPickAbort(cwd);
554
759
 
760
+ if (updatedReleaseSha == null || updatedReleaseSha === originalReleaseSha) {
761
+ throw new Error("Release merge did not create any new commits.");
762
+ }
763
+
764
+ if (releaseExists) {
765
+ gitForceBranch(RELEASE_BRANCH, updatedReleaseSha, cwd);
766
+ } else {
767
+ gitCreateBranch(RELEASE_BRANCH, updatedReleaseSha, cwd);
768
+ }
769
+
770
+ gitCheckout(RELEASE_BRANCH, cwd);
771
+ } catch (error) {
555
772
  try {
556
773
  if (releaseExists) {
557
- gitResetHard(originalReleaseSha, cwd);
558
- gitCheckout(originalBranch, cwd);
559
- } else {
560
- gitCheckout(originalBranch, cwd);
774
+ gitForceBranch(RELEASE_BRANCH, originalReleaseSha, cwd);
775
+ } else if (localBranchExists(RELEASE_BRANCH, cwd)) {
776
+ if (getCurrentBranchName(cwd) === RELEASE_BRANCH) {
777
+ gitCheckout(originalBranch, cwd);
778
+ }
561
779
  gitDeleteBranch(RELEASE_BRANCH, cwd);
562
780
  }
781
+
782
+ if (getCurrentBranchName(cwd) !== originalBranch) {
783
+ gitCheckout(originalBranch, cwd);
784
+ }
563
785
  } catch {
564
786
  // Preserve original failure and print recovery guidance below.
565
787
  }
@@ -574,11 +796,6 @@ export function executeReleaseMerge(plan, cwd) {
574
796
  );
575
797
  throw new Error("Release merge aborted.");
576
798
  }
577
-
578
- const updatedReleaseSha = getCurrentHeadSha(cwd);
579
- if (updatedReleaseSha === originalHeadSha) {
580
- throw new Error("Release merge did not create any new commits.");
581
- }
582
799
  }
583
800
 
584
801
  export function executeReleaseTagPlan(plan, cwd) {
@@ -138,18 +138,6 @@ function classifyTone(line) {
138
138
  }
139
139
 
140
140
  function colorizeByTone(line, tone) {
141
- if (tone === "good") {
142
- return colorize(line, ANSI.green);
143
- }
144
-
145
- if (tone === "bad") {
146
- return colorize(line, ANSI.red);
147
- }
148
-
149
- if (tone === "neutral") {
150
- return colorize(line, ANSI.yellow);
151
- }
152
-
153
141
  return line;
154
142
  }
155
143
 
@@ -161,17 +149,7 @@ function formatBulletLine(line) {
161
149
  }
162
150
 
163
151
  const [, indent, marker, content] = match;
164
- const tone = classifyTone(content);
165
- const coloredMarker =
166
- tone === "good"
167
- ? colorize(marker, ANSI.green)
168
- : tone === "bad"
169
- ? colorize(marker, ANSI.red)
170
- : tone === "neutral"
171
- ? colorize(marker, ANSI.yellow)
172
- : colorize(marker, ANSI.cyan);
173
-
174
- return `${indent}${coloredMarker} ${colorizeByTone(content, tone)}`;
152
+ return `${indent}${colorize(marker, ANSI.cyan)} ${content}`;
175
153
  }
176
154
 
177
155
  function formatSeverityLine(line) {
@@ -179,19 +157,7 @@ function formatSeverityLine(line) {
179
157
  return null;
180
158
  }
181
159
 
182
- if (/\blow\b/i.test(line)) {
183
- return colorize(line, ANSI.green);
184
- }
185
-
186
- if (/\bmedium\b/i.test(line)) {
187
- return colorize(line, ANSI.yellow);
188
- }
189
-
190
- if (/\bhigh\b/i.test(line)) {
191
- return colorize(line, ANSI.red);
192
- }
193
-
194
- return colorize(line, ANSI.bold + ANSI.yellow);
160
+ return line;
195
161
  }
196
162
 
197
163
  function formatLine(line) {