@williamthorsen/release-kit 12.0.0 → 13.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/.meta/changelog.json +229 -0
  2. package/.readyup/kits/default.js +18 -17
  3. package/.readyup/kits/npm-auto-publish.js +24 -23
  4. package/.readyup/manifest.json +16 -11
  5. package/CHANGELOG.md +64 -0
  6. package/README.md +27 -51
  7. package/bin/release-kit.js +1 -1
  8. package/dist/esm/assertCleanWorkingTree.d.ts +1 -5
  9. package/dist/esm/assertTaggedBaseline.d.ts +29 -0
  10. package/dist/esm/assertTaggedBaseline.js +41 -0
  11. package/dist/esm/attachChangelogDiagnostics.d.ts +1 -1
  12. package/dist/esm/attachChangelogDiagnostics.js +3 -0
  13. package/dist/esm/bin/release-kit.js +85 -22
  14. package/dist/esm/buildChangelogEntries.d.ts +23 -13
  15. package/dist/esm/buildChangelogEntries.js +36 -17
  16. package/dist/esm/buildDependencyGraph.d.ts +6 -6
  17. package/dist/esm/buildEmptyReleaseEntry.d.ts +1 -8
  18. package/dist/esm/buildReleaseSummary.d.ts +2 -8
  19. package/dist/esm/buildSyntheticChangelogEntry.d.ts +3 -5
  20. package/dist/esm/buildSyntheticChangelogEntry.js +2 -2
  21. package/dist/esm/bumpVersion.d.ts +1 -8
  22. package/dist/esm/changelogJsonFile.d.ts +5 -7
  23. package/dist/esm/changelogJsonFile.js +2 -17
  24. package/dist/esm/changelogJsonUtils.d.ts +7 -10
  25. package/dist/esm/changelogOverrides.d.ts +59 -98
  26. package/dist/esm/classifyChangelogCommit.d.ts +7 -9
  27. package/dist/esm/classifyChangelogCommit.js +3 -2
  28. package/dist/esm/commitCommand.d.ts +2 -4
  29. package/dist/esm/compareVersions.d.ts +10 -4
  30. package/dist/esm/compareVersions.js +16 -0
  31. package/dist/esm/configFlagSchema.d.ts +1 -1
  32. package/dist/esm/createGithubRelease.d.ts +7 -19
  33. package/dist/esm/createGithubReleaseCommand.d.ts +5 -5
  34. package/dist/esm/createGithubReleaseCommand.js +9 -9
  35. package/dist/esm/createTags.d.ts +2 -3
  36. package/dist/esm/decideRelease.d.ts +2 -6
  37. package/dist/esm/defaults.d.ts +5 -24
  38. package/dist/esm/defaults.js +4 -5
  39. package/dist/esm/defineConfig.d.ts +1 -1
  40. package/dist/esm/deleteFileIfExists.d.ts +1 -1
  41. package/dist/esm/deriveReleaseNotesConfig.d.ts +20 -0
  42. package/dist/esm/deriveReleaseNotesConfig.js +21 -0
  43. package/dist/esm/deriveWorkspaceConfig.d.ts +3 -5
  44. package/dist/esm/detectPackageManager.d.ts +2 -4
  45. package/dist/esm/detectUndeclaredTagPrefixes.d.ts +4 -6
  46. package/dist/esm/detectUndeclaredTagPrefixes.js +1 -1
  47. package/dist/esm/determineBumpType.d.ts +1 -1
  48. package/dist/esm/discoverWorkspaces.d.ts +14 -15
  49. package/dist/esm/discoverWorkspaces.js +12 -12
  50. package/dist/esm/enterRepoRoot.d.ts +12 -0
  51. package/dist/esm/enterRepoRoot.js +16 -0
  52. package/dist/esm/enumerateReleaseWindows.d.ts +8 -8
  53. package/dist/esm/extractMigration.d.ts +6 -8
  54. package/dist/esm/findUnroutedEntryScopes.d.ts +16 -0
  55. package/dist/esm/findUnroutedEntryScopes.js +57 -0
  56. package/dist/esm/formatExcludedSkip.d.ts +3 -0
  57. package/dist/esm/formatExcludedSkip.js +3 -0
  58. package/dist/esm/formatPrivateSkip.d.ts +1 -6
  59. package/dist/esm/generateChangelogs.d.ts +10 -5
  60. package/dist/esm/glyphs.d.ts +1 -1
  61. package/dist/esm/help/prepareHelp.d.ts +3 -4
  62. package/dist/esm/help/prepareHelp.js +4 -3
  63. package/dist/esm/init/checks.d.ts +3 -3
  64. package/dist/esm/init/checks.js +4 -1
  65. package/dist/esm/init/detectRepoType.d.ts +2 -3
  66. package/dist/esm/init/hasProvenance.d.ts +2 -0
  67. package/dist/esm/init/hasProvenance.js +3 -0
  68. package/dist/esm/init/initCommand.d.ts +3 -6
  69. package/dist/esm/init/initCommand.js +10 -10
  70. package/dist/esm/init/parseJsonRecord.d.ts +1 -4
  71. package/dist/esm/init/scaffold.d.ts +1 -1
  72. package/dist/esm/init/templates.d.ts +9 -11
  73. package/dist/esm/init/templates.js +12 -11
  74. package/dist/esm/init/updateTemplatesCommand.d.ts +13 -0
  75. package/dist/esm/init/updateTemplatesCommand.js +64 -0
  76. package/dist/esm/injectReleaseNotesIntoReadme.d.ts +12 -17
  77. package/dist/esm/injectSection.d.ts +3 -3
  78. package/dist/esm/loadConfig.d.ts +16 -14
  79. package/dist/esm/loadConfig.js +2 -2
  80. package/dist/esm/loadValidatedConfig.d.ts +16 -19
  81. package/dist/esm/loadValidatedConfig.js +6 -1
  82. package/dist/esm/packsPath.d.ts +9 -9
  83. package/dist/esm/parseChangeRecordBlock.d.ts +2 -2
  84. package/dist/esm/parseCommitMessage.d.ts +17 -32
  85. package/dist/esm/parseCommitMessage.js +28 -37
  86. package/dist/esm/parseRequestedTags.d.ts +3 -3
  87. package/dist/esm/planReleaseNotesPreviews.d.ts +6 -6
  88. package/dist/esm/planVersionBump.d.ts +5 -3
  89. package/dist/esm/planVersionBump.js +3 -0
  90. package/dist/esm/prepareCommand.d.ts +4 -7
  91. package/dist/esm/prepareCommand.js +13 -6
  92. package/dist/esm/previewTagPrefixes.d.ts +8 -10
  93. package/dist/esm/propagateBumps.d.ts +6 -7
  94. package/dist/esm/publish.d.ts +1 -1
  95. package/dist/esm/publishCommand.d.ts +4 -3
  96. package/dist/esm/publishCommand.js +8 -8
  97. package/dist/esm/pushCommand.d.ts +4 -3
  98. package/dist/esm/pushCommand.js +8 -2
  99. package/dist/esm/pushRelease.d.ts +2 -2
  100. package/dist/esm/readChangelogSections.d.ts +33 -0
  101. package/dist/esm/readChangelogSections.js +80 -0
  102. package/dist/esm/releaseFiles.d.ts +5 -7
  103. package/dist/esm/releasePlan.d.ts +11 -13
  104. package/dist/esm/releasePrepare.d.ts +23 -22
  105. package/dist/esm/releasePrepare.js +31 -11
  106. package/dist/esm/releasePrepareMono.d.ts +9 -7
  107. package/dist/esm/releasePrepareMono.js +85 -33
  108. package/dist/esm/releasePrepareProject.d.ts +19 -29
  109. package/dist/esm/releasePrepareProject.js +31 -12
  110. package/dist/esm/renderChangelogMarkdown.d.ts +10 -4
  111. package/dist/esm/renderChangelogMarkdown.js +27 -2
  112. package/dist/esm/renderReleaseNotes.d.ts +3 -11
  113. package/dist/esm/renderReleaseNotes.js +2 -6
  114. package/dist/esm/reportPrepare.d.ts +4 -5
  115. package/dist/esm/reportPrepare.js +33 -19
  116. package/dist/esm/resolveCommandTags.d.ts +15 -11
  117. package/dist/esm/resolveCommandTags.js +35 -5
  118. package/dist/esm/resolveConfigFlag.d.ts +7 -0
  119. package/dist/esm/resolveConfigFlag.js +4 -0
  120. package/dist/esm/resolveReleaseTags.d.ts +6 -8
  121. package/dist/esm/resolveReleaseTags.js +1 -1
  122. package/dist/esm/resolveStylesOrExit.d.ts +2 -2
  123. package/dist/esm/showTagPrefixesCommand.d.ts +7 -10
  124. package/dist/esm/showTagPrefixesCommand.js +4 -4
  125. package/dist/esm/stripEmojiPrefix.d.ts +1 -0
  126. package/dist/esm/stripScope.d.ts +2 -2
  127. package/dist/esm/stripScope.js +3 -3
  128. package/dist/esm/sync-labels/generateCommand.d.ts +11 -10
  129. package/dist/esm/sync-labels/generateCommand.js +17 -9
  130. package/dist/esm/sync-labels/initCommand.d.ts +6 -7
  131. package/dist/esm/sync-labels/initCommand.js +2 -2
  132. package/dist/esm/sync-labels/presets.d.ts +5 -4
  133. package/dist/esm/sync-labels/presets.js +9 -9
  134. package/dist/esm/sync-labels/resolveLabels.d.ts +7 -7
  135. package/dist/esm/sync-labels/resolveLabels.js +2 -1
  136. package/dist/esm/sync-labels/retiredConfig.d.ts +4 -5
  137. package/dist/esm/sync-labels/syncCommand.d.ts +1 -4
  138. package/dist/esm/sync-labels/templates.d.ts +3 -3
  139. package/dist/esm/sync-labels/templates.js +3 -3
  140. package/dist/esm/sync-labels/typeLabels.d.ts +14 -0
  141. package/dist/esm/sync-labels/typeLabels.js +38 -0
  142. package/dist/esm/sync-labels/types.d.ts +4 -2
  143. package/dist/esm/tagCommand.d.ts +1 -1
  144. package/dist/esm/typeGuards.d.ts +2 -2
  145. package/dist/esm/types.d.ts +122 -93
  146. package/dist/esm/types.js +2 -1
  147. package/dist/esm/validateConfig.d.ts +2 -11
  148. package/dist/esm/validateConfig.js +2 -2
  149. package/dist/esm/validateOnlyExcludesStrandedDependents.d.ts +14 -12
  150. package/dist/esm/validateOverridesCommand.d.ts +17 -20
  151. package/dist/esm/validateOverridesCommand.js +1 -1
  152. package/docs/api.md +6 -6
  153. package/docs/changelogs.md +63 -40
  154. package/docs/configuration.md +29 -27
  155. package/docs/editorial-overrides.md +23 -23
  156. package/docs/labels.md +37 -19
  157. package/docs/project-releases.md +13 -13
  158. package/docs/publishing.md +5 -5
  159. package/docs/readiness-checks.md +3 -3
  160. package/docs/releasing.md +50 -25
  161. package/docs/work-types.md +41 -57
  162. package/package.json +7 -6
  163. package/presets/labels/common.yaml +1 -41
  164. package/dist/esm/resolveReleaseNotesConfig.d.ts +0 -26
  165. package/dist/esm/resolveReleaseNotesConfig.js +0 -29
  166. package/dist/esm/workTypesData.d.ts +0 -50
  167. package/dist/esm/workTypesData.js +0 -145
  168. package/schemas/label-map.json +0 -24
@@ -1,15 +1,16 @@
1
1
  import { join as joinPath } from 'node:path';
2
+ import { assertTaggedBaseline, findUntaggedBaseline } from "./assertTaggedBaseline.js";
2
3
  import { attachChangelogDiagnostics } from "./attachChangelogDiagnostics.js";
3
- import { readReleaseHistory, toChangelogEntries } from "./buildChangelogEntries.js";
4
- import { buildEmptyReleaseEntry } from "./buildEmptyReleaseEntry.js";
4
+ import { readReleaseHistory, toReleaseEntries } from "./buildChangelogEntries.js";
5
5
  import { mergeChangelogEntriesWithDisk, renderChangelogJson, resolveChangelogJsonPath } from "./changelogJsonFile.js";
6
6
  import { applyChangelogOverrides } from "./changelogOverrides.js";
7
7
  import { decideRelease } from "./decideRelease.js";
8
8
  import { DEFAULT_WORK_TYPES } from "./defaults.js";
9
+ import { deriveSectionOrder } from "./deriveReleaseNotesConfig.js";
9
10
  import { planReleaseNotesPreviews } from "./planReleaseNotesPreviews.js";
10
11
  import { planVersionBump } from "./planVersionBump.js";
12
+ import { planPreservedSections } from "./readChangelogSections.js";
11
13
  import { renderChangelogMarkdown } from "./renderChangelogMarkdown.js";
12
- import { deriveSectionOrder } from "./resolveReleaseNotesConfig.js";
13
14
  const ROOT_PACKAGE_FILE = './package.json';
14
15
  const ROOT_CHANGELOG_PATH = '.';
15
16
  export function releasePrepareProject(args) {
@@ -22,6 +23,15 @@ export function releasePrepareProject(args) {
22
23
  }
23
24
  const workTypes = config.workTypes ?? { ...DEFAULT_WORK_TYPES };
24
25
  const history = readReleaseHistory(config, { tagPrefixes: [project.tagPrefix], paths: project.paths });
26
+ assertTaggedBaseline([
27
+ findUntaggedBaseline({
28
+ label: 'project',
29
+ packageFiles: [ROOT_PACKAGE_FILE],
30
+ changelogPaths: [ROOT_CHANGELOG_PATH],
31
+ tagPrefixes: [project.tagPrefix],
32
+ previousTag: history.previousTag,
33
+ }, config),
34
+ ]);
25
35
  const { commits, diagnostics, parsedCommitCount, unparseableCommits } = history.unreleased;
26
36
  const tag = history.previousTag;
27
37
  const since = tag === undefined ? '(no previous release found)' : `since ${tag}`;
@@ -59,7 +69,6 @@ export function releasePrepareProject(args) {
59
69
  config,
60
70
  history,
61
71
  newTag,
62
- newVersion: bump.newVersion,
63
72
  rootOverrides,
64
73
  overrideWarnings,
65
74
  globalMatchedRootKeys,
@@ -95,6 +104,9 @@ export function releasePrepareProject(args) {
95
104
  if (previewFiles.length > 0) {
96
105
  result.previewFiles = previewFiles;
97
106
  }
107
+ if (changelogs.changelogPreservation.length > 0) {
108
+ result.changelogPreservation = changelogs.changelogPreservation;
109
+ }
98
110
  if (tag !== undefined) {
99
111
  result.previousTag = tag;
100
112
  }
@@ -116,10 +128,9 @@ function resolveOptionalOverrideArgs(args) {
116
128
  };
117
129
  }
118
130
  function planProjectChangelogs(args) {
119
- const { config, history, newTag, newVersion, rootOverrides, overrideWarnings, globalMatchedRootKeys } = args;
120
- const isEmptyRange = history.unreleased.commits.length === 0;
131
+ const { config, history, newTag, rootOverrides, overrideWarnings, globalMatchedRootKeys } = args;
121
132
  const today = new Date().toISOString().slice(0, 10);
122
- const builtEntries = isEmptyRange ? [buildEmptyReleaseEntry(newVersion, today)] : toChangelogEntries(history, newTag);
133
+ const builtEntries = toReleaseEntries(history, newTag, today);
123
134
  const applied = applyChangelogOverrides(builtEntries, rootOverrides);
124
135
  if (applied.errors.length > 0) {
125
136
  throw new Error(`Changelog override application failed:\n - ${applied.errors.join('\n - ')}`);
@@ -130,9 +141,7 @@ function planProjectChangelogs(args) {
130
141
  }
131
142
  const changelogJsonPath = resolveChangelogJsonPath(config, ROOT_CHANGELOG_PATH);
132
143
  const sectionOrder = deriveSectionOrder(config.workTypes ?? { ...DEFAULT_WORK_TYPES });
133
- const renderEntries = isEmptyRange
134
- ? mergeChangelogEntriesWithDisk(changelogJsonPath, applied.entries)
135
- : applied.entries;
144
+ const renderEntries = mergeChangelogEntriesWithDisk(changelogJsonPath, applied.entries);
136
145
  const writes = [];
137
146
  const changelogJsonFiles = [];
138
147
  if (config.changelogJson.enabled) {
@@ -140,6 +149,16 @@ function planProjectChangelogs(args) {
140
149
  changelogJsonFiles.push(changelogJsonPath);
141
150
  }
142
151
  const changelogFile = joinPath(ROOT_CHANGELOG_PATH, 'CHANGELOG.md');
143
- writes.push({ path: changelogFile, content: renderChangelogMarkdown(renderEntries, { sectionOrder }) });
144
- return { changelogFiles: [changelogFile], changelogJsonFiles, entries: renderEntries, writes };
152
+ const preserved = planPreservedSections(changelogFile, renderEntries);
153
+ writes.push({
154
+ path: changelogFile,
155
+ content: renderChangelogMarkdown(renderEntries, { sectionOrder, preservedSections: preserved.sections }),
156
+ });
157
+ return {
158
+ changelogFiles: [changelogFile],
159
+ changelogJsonFiles,
160
+ changelogPreservation: preserved.preservation === undefined ? [] : [preserved.preservation],
161
+ entries: renderEntries,
162
+ writes,
163
+ };
145
164
  }
@@ -1,22 +1,28 @@
1
+ import type { VersionedMarkdownSection } from './readChangelogSections.js';
1
2
  import type { ChangelogEntry } from './types.js';
2
3
  /** Options for `renderChangelogMarkdown`. */
3
4
  export interface RenderChangelogMarkdownOptions {
4
- /** Section titles in canonical order; passed through to `renderReleaseNotesMulti`. */
5
+ /** Section titles in canonical order; passed through to `renderReleaseNotesSingle`. */
5
6
  sectionOrder?: string[];
7
+ /**
8
+ * Sections of the existing `CHANGELOG.md` to keep verbatim, interleaved by version among the rendered entries. A
9
+ * section's version must not also be an entry's version.
10
+ */
11
+ preservedSections?: readonly VersionedMarkdownSection[];
6
12
  }
7
13
  /**
8
- * Render `ChangelogEntry[]` into a complete `CHANGELOG.md`-shaped string.
14
+ * Renders `ChangelogEntry[]` into a complete `CHANGELOG.md`-shaped string.
9
15
  *
10
16
  * Format:
11
17
  * `# Changelog`
12
18
  * blank line
13
19
  * `All notable changes …`
14
20
  * blank line
15
- * per-entry sections (delegated to `renderReleaseNotesMulti`)
21
+ * per-entry sections (delegated to `renderReleaseNotesSingle`), with preserved sections interleaved by version
16
22
  * blank line
17
23
  * footer comment
18
24
  *
19
25
  * Entries are rendered in the order provided; the caller sorts (typically newest-first via
20
- * `changelogJsonFile`'s SemVer-aware comparator).
26
+ * `compareVersionsDescending`).
21
27
  */
22
28
  export declare function renderChangelogMarkdown(entries: ChangelogEntry[], options?: RenderChangelogMarkdownOptions): string;
@@ -1,10 +1,15 @@
1
- import { renderReleaseNotesMulti } from "./renderReleaseNotes.js";
1
+ import { compareVersionsDescending } from "./compareVersions.js";
2
+ import { renderReleaseNotesSingle } from "./renderReleaseNotes.js";
2
3
  const CHANGELOG_HEADER = `# Changelog
3
4
 
4
5
  All notable changes to this project will be documented in this file.`;
5
6
  const CHANGELOG_FOOTER = `<!-- Generated by release-kit. Do not edit this file. Use .meta/changelog-overrides.json to override entries. -->`;
6
7
  export function renderChangelogMarkdown(entries, options) {
7
- const body = renderReleaseNotesMulti(entries, options);
8
+ const sections = interleavePreservedSections(entries.map((entry) => ({ version: entry.version, text: renderReleaseNotesSingle(entry, options) })), options?.preservedSections ?? []);
9
+ const body = sections
10
+ .map((part) => part.text)
11
+ .filter((text) => text.length > 0)
12
+ .join('\n');
8
13
  const trimmedBody = body.trimEnd();
9
14
  const parts = [CHANGELOG_HEADER];
10
15
  if (trimmedBody.length > 0) {
@@ -13,3 +18,23 @@ export function renderChangelogMarkdown(entries, options) {
13
18
  parts.push(CHANGELOG_FOOTER);
14
19
  return parts.join('\n\n') + '\n';
15
20
  }
21
+ function interleavePreservedSections(rendered, preserved) {
22
+ const sorted = preserved.toSorted((a, b) => compareVersionsDescending(a.version, b.version));
23
+ const pending = sorted.map(withTrailingNewline);
24
+ const result = [];
25
+ let index = 0;
26
+ for (const part of rendered) {
27
+ let section = pending[index];
28
+ while (section !== undefined && compareVersionsDescending(section.version, part.version) < 0) {
29
+ result.push(section);
30
+ index++;
31
+ section = pending[index];
32
+ }
33
+ result.push(part);
34
+ }
35
+ result.push(...pending.slice(index));
36
+ return result;
37
+ }
38
+ function withTrailingNewline(section) {
39
+ return { version: section.version, text: `${section.text}\n` };
40
+ }
@@ -13,22 +13,14 @@ export interface RenderOptions {
13
13
  sectionOrder?: string[];
14
14
  }
15
15
  /**
16
- * Create a predicate that matches sections visible to the given audience.
16
+ * Creates a predicate that matches sections visible to the given audience.
17
17
  *
18
18
  * `"all"` matches only sections with `audience: "all"` (public-facing).
19
19
  * `"dev"` matches all sections (developers see everything).
20
20
  */
21
21
  export declare function matchesAudience(audience: ChangelogAudience): (section: ChangelogSection) => boolean;
22
22
  /**
23
- * Render a single `ChangelogEntry` to markdown.
24
- *
25
- * Output format mirrors the existing CHANGELOG.md style: an H2 version heading followed by
26
- * H3 section headings with bulleted items.
23
+ * Renders a single `ChangelogEntry` to markdown: an H2 version heading followed by H3 section
24
+ * headings with bulleted items.
27
25
  */
28
26
  export declare function renderReleaseNotesSingle(entry: ChangelogEntry, options?: RenderOptions): string;
29
- /**
30
- * Render multiple `ChangelogEntry` values to a single markdown document.
31
- *
32
- * Entries are rendered in the order provided; the caller is responsible for sorting.
33
- */
34
- export declare function renderReleaseNotesMulti(entries: ChangelogEntry[], options?: RenderOptions): string;
@@ -1,6 +1,6 @@
1
- import { WORK_TYPES_DATA } from "./workTypesData.js";
1
+ import { CANONICAL_TAXONOMY } from '@williamthorsen/change-grammar';
2
2
  function getBreakingPrefix() {
3
- const { emoji, label } = WORK_TYPES_DATA.markers.breaking;
3
+ const { emoji, label } = CANONICAL_TAXONOMY.markers.breaking;
4
4
  return `${emoji} **${label}:** `;
5
5
  }
6
6
  function allSections() {
@@ -62,7 +62,3 @@ function sortSectionsByOrder(sections, order) {
62
62
  function indentBodyLines(body) {
63
63
  return body.split('\n').map((line) => (line.length === 0 ? '' : ` ${line}`));
64
64
  }
65
- export function renderReleaseNotesMulti(entries, options) {
66
- const parts = entries.map((entry) => renderReleaseNotesSingle(entry, options)).filter((part) => part.length > 0);
67
- return parts.join('\n');
68
- }
@@ -1,6 +1,6 @@
1
1
  import { type OutputStyle } from '@williamthorsen/nmr-core';
2
2
  import type { PrepareResult } from './types.js';
3
- /** How the plan was carried out, which decides the tense the report is rendered in. */
3
+ /** How the plan was carried out, which decides the report's tense. */
4
4
  export interface ReportPrepareOptions {
5
5
  /** True when the plan was written to disk; false for a dry run. */
6
6
  applied: boolean;
@@ -9,14 +9,13 @@ export interface ReportPrepareOptions {
9
9
  style: OutputStyle;
10
10
  }
11
11
  /**
12
- * Format a release plan into styled terminal output.
12
+ * Formats a release plan into styled terminal output, without writing it.
13
13
  *
14
- * Pure function: accepts structured data, returns a string. Never writes to stdout.
15
- * Single-workspace mode (no `name` field) renders flat output; multi-workspace mode
14
+ * Single-workspace mode (without a `name` field) renders flat output; multi-workspace mode
16
15
  * renders section headers per workspace, an optional project section, and a tag summary
17
16
  * at the end.
18
17
  *
19
- * Whether the plan was applied is a property of the render, not of the plan: the same plan
18
+ * Whether the plan was applied is a property of the render, not of the plan: The same plan
20
19
  * produces the dry-run report and the applied report.
21
20
  */
22
21
  export declare function reportPrepare(plan: PrepareResult, options: ReportPrepareOptions): string;
@@ -51,7 +51,7 @@ function formatSingleWorkspace(result, options) {
51
51
  formatChangeRecordWarnings(lines, workspace, style);
52
52
  formatBumpFiles(lines, workspace, dryRun);
53
53
  lines.push(dim('Generating changelogs...'));
54
- formatChangelogFiles(lines, workspace, dryRun);
54
+ formatChangelogFiles(lines, workspace, dryRun, style);
55
55
  formatPreviewFiles(lines, workspace, dryRun);
56
56
  formatFormatCommand(lines, result, options);
57
57
  formatWarnings(lines, result, style);
@@ -77,7 +77,7 @@ function formatMultiWorkspace(result, options) {
77
77
  }
78
78
  }
79
79
  else {
80
- lines.push(`\n${formatStatusLine(style, 'skipped', 'No workspaces had release-worthy changes.')}`);
80
+ lines.push(`\n${formatStatusLine(style, 'skipped', 'The run did not find release-worthy changes in any workspace.')}`);
81
81
  }
82
82
  return lines.join('\n');
83
83
  }
@@ -111,14 +111,7 @@ function formatProjectSection(lines, project, dryRun, style) {
111
111
  }
112
112
  }
113
113
  lines.push(dim(' Generating changelogs...'));
114
- for (const file of project.changelogFiles) {
115
- if (dryRun) {
116
- lines.push(dim(` [dry-run] Would generate changelog: ${file}`));
117
- }
118
- else {
119
- lines.push(dim(` Generating changelog: ${file}`));
120
- }
121
- }
114
+ formatChangelogFiles(lines, project, dryRun, style, ' ');
122
115
  formatPreviewFiles(lines, project, dryRun, ' ');
123
116
  lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(tag))}`);
124
117
  }
@@ -136,7 +129,7 @@ function formatWorkspaceSection(lines, workspace, dryRun, style) {
136
129
  return;
137
130
  }
138
131
  const { propagatedFrom } = workspace;
139
- const isPropagatedOnly = propagatedFrom !== undefined && workspace.commitCount === 0;
132
+ const isPropagatedOnly = workspace.propagatedOnly === true;
140
133
  formatCommitSummary(lines, workspace, propagatedFrom, isPropagatedOnly);
141
134
  formatUnparseableWarning(lines, workspace, style, ' ');
142
135
  formatPolicyViolations(lines, workspace.policyViolations, style, ' ');
@@ -145,14 +138,14 @@ function formatWorkspaceSection(lines, workspace, dryRun, style) {
145
138
  formatVersionLine(lines, workspace, propagatedFrom, isPropagatedOnly, style);
146
139
  formatBumpFiles(lines, workspace, dryRun, ' ');
147
140
  lines.push(dim(' Generating changelogs...'));
148
- formatChangelogFiles(lines, workspace, dryRun, ' ');
141
+ formatChangelogFiles(lines, workspace, dryRun, style, ' ');
149
142
  formatPreviewFiles(lines, workspace, dryRun, ' ');
150
143
  lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(workspace.tag))}`);
151
144
  }
152
145
  function formatCommitSummary(lines, workspace, propagatedFrom, isPropagatedOnly) {
153
146
  if (isPropagatedOnly && propagatedFrom !== undefined) {
154
147
  const depNames = propagatedFrom.map((p) => p.packageName).join(', ');
155
- lines.push(dim(` 0 commits (bumped via dependency: ${depNames})`));
148
+ lines.push(dim(` Bumped via dependency: ${depNames}`));
156
149
  }
157
150
  else if (workspace.parsedCommitCount !== undefined && workspace.parsedCommitCount > 0) {
158
151
  lines.push(dim(` Parsed ${workspace.parsedCommitCount} typed commits`));
@@ -192,14 +185,26 @@ function formatBumpFiles(lines, workspace, dryRun, indent = '') {
192
185
  }
193
186
  }
194
187
  }
195
- function formatChangelogFiles(lines, workspace, dryRun, indent = '') {
196
- for (const file of workspace.changelogFiles) {
188
+ function formatChangelogFiles(lines, release, dryRun, style, indent = '') {
189
+ for (const file of release.changelogFiles) {
197
190
  if (dryRun) {
198
191
  lines.push(dim(`${indent} [dry-run] Would generate changelog: ${file}`));
199
192
  }
200
193
  else {
201
194
  lines.push(dim(`${indent} Generating changelog: ${file}`));
202
195
  }
196
+ const preservation = release.changelogPreservation?.find((candidate) => candidate.file === file);
197
+ if (preservation === undefined) {
198
+ continue;
199
+ }
200
+ if (preservation.preservedVersions.length > 0) {
201
+ lines.push(dim(`${indent} Kept from the existing file: ${preservation.preservedVersions.join(', ')}`));
202
+ }
203
+ const dropped = preservation.droppedUnversionedHeadings;
204
+ if (dropped.length > 0) {
205
+ const message = `Dropped ${dropped.length} section${dropped.length === 1 ? '' : 's'} without a version: ${dropped.join(', ')}`;
206
+ lines.push(`${indent} ${formatStatusLine(style, 'warning', message)}`);
207
+ }
203
208
  }
204
209
  }
205
210
  function formatPreviewFiles(lines, release, dryRun, indent = '') {
@@ -237,17 +242,17 @@ function formatPolicyViolations(lines, violations, style, indent = '') {
237
242
  const subject = violation.commitSubject;
238
243
  const truncatedSubject = truncateSubject(subject);
239
244
  const location = violation.surface === 'entry' ? `entry ${violation.entryPosition ?? '?'}` : `${violation.surface} surface`;
240
- lines.push(`${indent} · ${shortHash} '${truncatedSubject}' — type '${violation.type}' at ${location}`);
245
+ lines.push(`${indent} · ${shortHash} '${truncatedSubject}': type '${violation.type}' at ${location}`);
241
246
  }
242
247
  }
243
248
  function formatChangeRecordWarnings(lines, release, style, indent = '') {
244
- const { malformedBlocks = [], undeclaredEntryTypes = [] } = release;
249
+ const { malformedBlocks = [], undeclaredEntryTypes = [], unroutedEntryScopes = [] } = release;
245
250
  if (malformedBlocks.length > 0) {
246
251
  const count = malformedBlocks.length;
247
252
  const header = `${count} change-record block${count === 1 ? '' : 's'} could not be read (item taken from the title)`;
248
253
  lines.push(`${indent} ${formatStatusLine(style, 'warning', `${header}:`)}`);
249
254
  for (const block of malformedBlocks) {
250
- lines.push(`${indent} · ${block.commitHash.slice(0, 7)} '${truncateSubject(block.commitSubject)}' — ${block.reason}`);
255
+ lines.push(`${indent} · ${block.commitHash.slice(0, 7)} '${truncateSubject(block.commitSubject)}': ${block.reason}`);
251
256
  }
252
257
  }
253
258
  if (undeclaredEntryTypes.length > 0) {
@@ -256,7 +261,16 @@ function formatChangeRecordWarnings(lines, release, style, indent = '') {
256
261
  lines.push(`${indent} ${formatStatusLine(style, 'warning', `${header}:`)}`);
257
262
  for (const entry of undeclaredEntryTypes) {
258
263
  const shortHash = entry.commitHash.slice(0, 7);
259
- lines.push(`${indent} · ${shortHash} '${truncateSubject(entry.commitSubject)}' — type '${entry.type}' at entry ${entry.entryPosition}`);
264
+ lines.push(`${indent} · ${shortHash} '${truncateSubject(entry.commitSubject)}': type '${entry.type}' at entry ${entry.entryPosition}`);
265
+ }
266
+ }
267
+ if (unroutedEntryScopes.length > 0) {
268
+ const count = unroutedEntryScopes.length;
269
+ const header = `${count} change-record entry scope${count === 1 ? ' does not match' : 's do not match'} any workspace in the commit's window (no item there)`;
270
+ lines.push(`${indent} ${formatStatusLine(style, 'warning', `${header}:`)}`);
271
+ for (const finding of unroutedEntryScopes) {
272
+ const shortHash = finding.commitHash.slice(0, 7);
273
+ lines.push(`${indent} · ${shortHash} '${truncateSubject(finding.commitSubject)}': scope '${finding.scope}' at entry ${finding.entryPosition}`);
260
274
  }
261
275
  }
262
276
  }
@@ -1,15 +1,19 @@
1
1
  import { type ResolvedTag } from './resolveReleaseTags.js';
2
+ import type { ReleaseKitConfig } from './types.js';
2
3
  /**
3
- * Discover workspaces, resolve release tags from HEAD, validate `--tags` names against the
4
- * full resolved tag names (e.g., `nmr-core-v1.3.0`), and return the filtered tag list.
5
- * Works in both single-package and monorepo modes. Exits with an error message on any validation
6
- * failure — including `deriveWorkspaceConfig()` throws for workspaces missing a `package.json` `name` field,
7
- * and a workspace declaring patterns that resolve to no package, which is not single-package mode.
4
+ * Discovers workspaces, resolves release tags from HEAD, validates `--tags` names against the full resolved tag
5
+ * names (e.g., `nmr-core-v1.3.0`), and returns the filtered tag list. Exits with an error message on any validation
6
+ * failure, including a workspace whose `package.json` does not have a `name` field, a config that
7
+ * `mergeMonorepoConfig` rejects, and a workspace that does not resolve to any package, which is not single-package
8
+ * mode.
8
9
  *
9
- * In both modes `deriveWorkspaceConfig` is called so `WorkspaceConfig.isPublishable` (read
10
- * from `package.json#private`) propagates onto each `ResolvedTag`. Single-package mode now
11
- * derives `./package.json`'s workspace config too — consumers that previously relied on
12
- * single-package mode skipping the derive step need a `name` field on `./package.json`,
13
- * but in practice every npm/pnpm package already has one.
10
+ * In monorepo mode the workspace list is `userConfig` merged over the discovered workspaces, as `prepare` builds it.
11
+ * A tag on HEAD whose workspace the config excludes still counts as a tag on HEAD, so `--tags` may name it and a
12
+ * HEAD whose tags are all excluded returns an empty list rather than failing. Each excluded tag that the run
13
+ * selects, every one without `--tags` or the named ones with it, is dropped with a warning.
14
+ * Single-package mode ignores `userConfig`.
15
+ *
16
+ * In both modes the workspace configs come from `deriveWorkspaceConfig`, so that `WorkspaceConfig.isPublishable`
17
+ * (read from `package.json#private`) propagates onto each `ResolvedTag`.
14
18
  */
15
- export declare function resolveCommandTags(tags: string[] | undefined): ResolvedTag[];
19
+ export declare function resolveCommandTags(tags: string[] | undefined, userConfig: ReleaseKitConfig | undefined): ResolvedTag[];
@@ -1,9 +1,12 @@
1
+ import { basename } from 'node:path';
1
2
  import { reportError } from '@williamthorsen/nmr-core';
2
3
  import { describeError } from '@williamthorsen/toolbelt.errors';
3
4
  import { deriveWorkspaceConfig } from "./deriveWorkspaceConfig.js";
4
5
  import { describeEmptyWorkspace, discoverWorkspaces } from "./discoverWorkspaces.js";
6
+ import { formatExcludedSkip } from "./formatExcludedSkip.js";
7
+ import { mergeMonorepoConfig, readRootPackageVersion } from "./loadConfig.js";
5
8
  import { resolveReleaseTags } from "./resolveReleaseTags.js";
6
- export function resolveCommandTags(tags) {
9
+ export function resolveCommandTags(tags, userConfig) {
7
10
  let workspace;
8
11
  try {
9
12
  workspace = discoverWorkspaces();
@@ -18,12 +21,13 @@ export function resolveCommandTags(tags) {
18
21
  }
19
22
  let workspaces;
20
23
  let singleWorkspace;
24
+ const excludedDirs = workspace.kind === 'packages' ? collectExcludedDirs(userConfig) : new Set();
21
25
  try {
22
26
  if (workspace.kind === 'single-package') {
23
27
  singleWorkspace = deriveWorkspaceConfig('.');
24
28
  }
25
29
  else {
26
- workspaces = workspace.packageDirs.map((workspacePath) => deriveWorkspaceConfig(workspacePath));
30
+ workspaces = buildMonorepoWorkspaces(workspace.packageDirs, userConfig, excludedDirs);
27
31
  }
28
32
  }
29
33
  catch (error) {
@@ -38,10 +42,10 @@ export function resolveCommandTags(tags) {
38
42
  resolvedTags = resolveReleaseTags({ singleWorkspace });
39
43
  }
40
44
  else {
41
- throw new Error('resolveCommandTags: invariant violated — neither workspaces nor singleWorkspace was derived');
45
+ throw new Error('resolveCommandTags: invariant violated; neither workspaces nor singleWorkspace was derived');
42
46
  }
43
47
  if (resolvedTags.length === 0) {
44
- reportError('No release tags found on HEAD. Create tags with `release-kit tag` first.');
48
+ reportError('HEAD does not have any release tags. Create tags with `release-kit tag` first.');
45
49
  process.exit(1);
46
50
  }
47
51
  if (tags !== undefined) {
@@ -55,5 +59,31 @@ export function resolveCommandTags(tags) {
55
59
  }
56
60
  resolvedTags = resolvedTags.filter((t) => tags.includes(t.tag));
57
61
  }
58
- return resolvedTags;
62
+ return dropExcludedTags(resolvedTags, excludedDirs);
63
+ }
64
+ function buildMonorepoWorkspaces(packageDirs, userConfig, excludedDirs) {
65
+ const { workspaces } = mergeMonorepoConfig(packageDirs, userConfig, readRootPackageVersion());
66
+ if (excludedDirs.size === 0) {
67
+ return workspaces;
68
+ }
69
+ const excluded = packageDirs
70
+ .filter((packageDir) => excludedDirs.has(basename(packageDir)))
71
+ .map((packageDir) => deriveWorkspaceConfig(packageDir));
72
+ return [...workspaces, ...excluded];
73
+ }
74
+ function collectExcludedDirs(userConfig) {
75
+ const overrides = userConfig?.workspaces ?? [];
76
+ return new Set(overrides.filter((override) => override.shouldExclude === true).map((override) => override.dir));
77
+ }
78
+ function dropExcludedTags(resolvedTags, excludedDirs) {
79
+ const retained = [];
80
+ for (const resolvedTag of resolvedTags) {
81
+ if (excludedDirs.has(resolvedTag.dir)) {
82
+ console.warn(formatExcludedSkip(resolvedTag));
83
+ }
84
+ else {
85
+ retained.push(resolvedTag);
86
+ }
87
+ }
88
+ return retained;
59
89
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Resolves a `--config` value against the directory from which `release-kit` was invoked, which differs from the
3
+ * working directory once the process has moved to the repo root.
4
+ *
5
+ * Returns `undefined` when the flag is absent, so that `loadConfig` falls back to the default path under the root.
6
+ */
7
+ export declare function resolveConfigFlag(config: string | undefined, invocationDir: string): string | undefined;
@@ -0,0 +1,4 @@
1
+ import path from 'node:path';
2
+ export function resolveConfigFlag(config, invocationDir) {
3
+ return config === undefined ? undefined : path.resolve(invocationDir, config);
4
+ }
@@ -4,10 +4,8 @@ export interface ResolvedTag {
4
4
  dir: string;
5
5
  workspacePath: string;
6
6
  /**
7
- * Whether the workspace this tag belongs to can be published to a registry. Propagated
8
- * from the matched `WorkspaceConfig.isPublishable` (monorepo) or from the single-package
9
- * workspace config (single-package mode). Consumed by `release-kit publish` to filter the
10
- * tag set; other commands ignore it.
7
+ * Whether the workspace to which this tag belongs can be published to a registry, copied from the matched
8
+ * workspace's `WorkspaceConfig.isPublishable`.
11
9
  */
12
10
  isPublishable: boolean;
13
11
  }
@@ -18,15 +16,15 @@ type ResolveReleaseTagsArgs = {
18
16
  singleWorkspace: WorkspaceConfig;
19
17
  };
20
18
  /**
21
- * Resolve release tags pointing at HEAD into publishable package descriptors.
19
+ * Resolves release tags pointing at HEAD into publishable package descriptors.
22
20
  *
23
- * Pass `{ workspaces }` for monorepo mode: each tag is matched against the workspace
21
+ * Pass `{ workspaces }` for monorepo mode: Each tag is matched against the workspace
24
22
  * whose `tagPrefix` it starts with, and that workspace's `isPublishable` propagates onto
25
23
  * the `ResolvedTag`. Because `tagPrefix` is derived from `deriveWorkspaceConfig()` (the
26
24
  * same source that produced the tag), encoding and decoding stay colocated.
27
25
  *
28
- * Pass `{ singleWorkspace }` for single-package mode: tags like `v1.2.3` are matched and
29
- * each carries the workspace's `isPublishable` bit derived from `./package.json#private`.
26
+ * Pass `{ singleWorkspace }` for single-package mode: Tags like `v1.2.3` are matched and
27
+ * each has the workspace's `isPublishable` bit derived from `./package.json#private`.
30
28
  *
31
29
  * The no-arg form is a test-isolation convenience for unit tests that exercise tag
32
30
  * matching without needing `isPublishable` propagation; resolved tags default to
@@ -30,7 +30,7 @@ function resolveSinglePackageTags(tags, singleWorkspace) {
30
30
  return matched.map((tag) => ({ tag, dir: '.', workspacePath: '.', isPublishable }));
31
31
  }
32
32
  function resolveMonorepoTags(tags, workspaces) {
33
- const sortedWorkspaces = [...workspaces].sort((a, b) => b.tagPrefix.length - a.tagPrefix.length);
33
+ const sortedWorkspaces = workspaces.toSorted((a, b) => b.tagPrefix.length - a.tagPrefix.length);
34
34
  const resolved = [];
35
35
  for (const tag of tags) {
36
36
  const match = findMatchingWorkspace(tag, sortedWorkspaces);
@@ -1,10 +1,10 @@
1
1
  import { type StreamStyles } from '@williamthorsen/nmr-core';
2
- /** The environment variable holding the caller's standing style preference: `auto`, `plain`, or `rich`. */
2
+ /** The environment variable that sets the caller's standing style preference: `auto`, `plain`, or `rich`. */
3
3
  export declare const OUTPUT_STYLE_ENV_VAR = "RELEASE_KIT_OUTPUT_STYLE";
4
4
  export interface ResolveStylesOptions {
5
5
  readonly env: Readonly<Record<string, string | undefined>>;
6
6
  readonly stderrIsTty: boolean;
7
7
  readonly stdoutIsTty: boolean;
8
8
  }
9
- /** Resolves the style of each output stream, exiting with a usage error when the variable names no setting. */
9
+ /** Resolves the style of each output stream, exiting with a usage error when the variable does not name a setting. */
10
10
  export declare function resolveStylesOrExit(options: ResolveStylesOptions): StreamStyles;
@@ -1,15 +1,12 @@
1
1
  import { type StreamStyles } from '@williamthorsen/nmr-core';
2
2
  /**
3
- * Orchestrate the CLI `show-tag-prefixes` command.
3
+ * Runs the CLI `show-tag-prefixes` command and returns its exit code.
4
4
  *
5
- * Prints a per-workspace table of derived prefixes, tag counts, and declared legacy
6
- * entries, followed by an "Undeclared tag prefixes" section when candidate-shaped tags
7
- * exist outside the known set. Exits `0` on full derivation success and no collisions;
8
- * `1` on any derivation failure or collision. Undeclared candidates do not affect the
9
- * exit code. A config that exists and either fails to load or fails validation reports to stderr and
10
- * exits `1` in both repo modes, rather than falling back to a default-config preview that would
11
- * understate the declared legacy prefixes. An absent default config previews against derived defaults.
12
- *
13
- * @returns The exit code the caller should use.
5
+ * Prints a per-workspace table of derived prefixes, tag counts, and declared legacy entries, followed by an
6
+ * "Undeclared tag prefixes" section when candidate-shaped tags exist outside the known set. Returns `1` on any
7
+ * derivation failure or collision and `0` otherwise; undeclared candidates do not affect the exit code. When a config
8
+ * exists and either fails to load or fails validation, the command reports it to stderr and returns `1` in both repo
9
+ * modes, because a default-config preview would understate the declared legacy prefixes. When the default config is
10
+ * absent, the command previews against derived defaults.
14
11
  */
15
12
  export declare function showTagPrefixesCommand(styles: StreamStyles, configPath?: string): Promise<number>;
@@ -41,7 +41,7 @@ function renderMonorepo(preview, style) {
41
41
  lines.push('', ...preview.collisions.map((collision) => formatStatusLine(style, 'failed', `tag prefix collision: '${collision.tagPrefix}' used by ${collision.workspacePaths.join(', ')}`)));
42
42
  }
43
43
  if (preview.undeclaredCandidates.length > 0) {
44
- lines.push('', 'Undeclared tag prefixes:', '', ...preview.undeclaredCandidates.map((candidate) => ` '${candidate.prefix}' — ${candidate.tagCount} tags (e.g., ${candidate.exampleTags.join(', ')})`), '', 'Suggested config snippet (adjust `dir` to match your workspace if the guess is wrong, and replace the `name` placeholder with the legacy npm name):', '', renderSuggestedSnippet(preview.undeclaredCandidates), '', "If the suggested `dir` does not match your workspace, adjust before pasting. Each legacy identity requires a `name` — replace the `TODO-fill-in-legacy-npm-name` placeholder with the package's prior npm name.");
44
+ lines.push('', 'Undeclared tag prefixes:', '', ...preview.undeclaredCandidates.map((candidate) => ` '${candidate.prefix}': ${candidate.tagCount} tags (e.g., ${candidate.exampleTags.join(', ')})`), '', 'Suggested config snippet (adjust `dir` to match the workspace if the guess is wrong, and replace the `name` placeholder with the legacy npm name):', '', renderSuggestedSnippet(preview.undeclaredCandidates), '', "If the suggested `dir` does not match the workspace, adjust it before pasting. Each legacy identity requires a `name`: Replace the `TODO-fill-in-legacy-npm-name` placeholder with the package's prior npm name.");
45
45
  }
46
46
  lines.push('');
47
47
  return lines.join('\n');
@@ -50,20 +50,20 @@ function renderWorkspaceRow(row, style) {
50
50
  const lines = [];
51
51
  if (row.derivedPrefix === null) {
52
52
  const failure = formatStatusLine(style, 'failed', `derivation failed: ${row.derivationError ?? 'unknown error'}`);
53
- lines.push(` ${row.workspacePath} — ${failure}`);
53
+ lines.push(` ${row.workspacePath}: ${failure}`);
54
54
  return lines;
55
55
  }
56
56
  const statusMarker = row.derivedTagCount > 0
57
57
  ? formatStatusLine(style, 'passed', `${row.derivedTagCount} tags`)
58
58
  : formatStatusLine(style, 'warning', 'no existing tags');
59
- lines.push(` ${row.workspacePath} — derived prefix '${row.derivedPrefix}', ${statusMarker}`);
59
+ lines.push(` ${row.workspacePath}: derived prefix '${row.derivedPrefix}', ${statusMarker}`);
60
60
  for (const entry of row.legacyEntries) {
61
61
  if (entry.tagCount > 0) {
62
62
  const recognized = `${entry.tagCount} legacy tags with '${entry.prefix}' prefix (recognized)`;
63
63
  lines.push(` ${formatStatusLine(style, 'passed', recognized)}`);
64
64
  }
65
65
  else {
66
- const tagless = `recorded legacy prefix '${entry.prefix}' has no tags`;
66
+ const tagless = `recorded legacy prefix '${entry.prefix}' does not have any tags`;
67
67
  lines.push(` ${formatStatusLine(style, 'warning', tagless)}`);
68
68
  }
69
69
  }
@@ -1 +1,2 @@
1
+ /** Removes a leading emoji and one space from a section title, returning the title unchanged when it has none. */
1
2
  export declare function stripEmojiPrefix(value: string): string;
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Strip scope indicators from a raw commit message.
2
+ * Strips scope indicators from a raw commit message.
3
3
  *
4
4
  * Handles `scope|type: desc`, `type(scope): desc`, and messages with
5
5
  * ticket prefixes (e.g., `#72 release-kit|fix: ...`). Returns the
6
- * message unchanged when no scope is detected.
6
+ * message unchanged when it does not detect a scope.
7
7
  */
8
8
  export declare function stripScope(message: string): string;