@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,17 +1,18 @@
1
1
  import { type OutputStyle, type StreamStyles } from '@williamthorsen/nmr-core';
2
+ import { type ReleaseHistoryConfig } from './buildChangelogEntries.js';
2
3
  import { type ValidateAllChangelogOverridesInputs, type ValidateAllChangelogOverridesResult } from './changelogOverrides.js';
3
4
  import { type WorkspaceDiscovery } from './discoverWorkspaces.js';
4
5
  import { type GenerateChangelogOptions } from './generateChangelogs.js';
5
6
  import { type LoadValidatedConfigResult } from './loadValidatedConfig.js';
6
- import type { ChangelogEntry, ReleaseConfig } from './types.js';
7
+ import type { ChangelogEntry } from './types.js';
7
8
  /**
8
- * Result of {@link validateOverridesCommand}: tiered exit code paired with a human-readable
9
- * message, which keeps the CLI dispatch layer thin.
9
+ * Result of {@link validateOverridesCommand}: a tiered exit code paired with a human-readable message.
10
10
  *
11
11
  * Exit codes:
12
- * - `0` — clean: no errors, no warnings.
13
- * - `1` — only stale-key warnings.
14
- * - `2` — schema/parse errors or keys that fail to match (ambiguous prefix, overlapping keys, a bare key setting fields on several items) (errors dominate when both classes exist).
12
+ * - `0`: clean, with no errors and no warnings.
13
+ * - `1`: only stale-key warnings.
14
+ * - `2`: schema or parse errors, or keys that fail to match (an ambiguous prefix, overlapping keys, or a bare key
15
+ * that sets fields on several items). Errors take precedence when both classes exist.
15
16
  */
16
17
  export interface ValidateOverridesCommandResult {
17
18
  exitCode: 0 | 1 | 2;
@@ -22,30 +23,26 @@ export interface ValidateOverridesCommandDependencies {
22
23
  discoverWorkspaces?: () => WorkspaceDiscovery;
23
24
  loadValidatedConfig?: () => Promise<LoadValidatedConfigResult>;
24
25
  /**
25
- * Build changelog entries for a scope. Defaults to `buildChangelogEntries`, the same path
26
- * `release-kit prepare` uses — anchoring `validate`'s item universe to `prepare`'s by
27
- * construction.
26
+ * Builds changelog entries for a scope. Defaults to `buildChangelogEntries`, which `release-kit prepare` also
27
+ * uses, so that `validate` sees the same items as `prepare`.
28
28
  */
29
- buildEntries?: (config: Pick<ReleaseConfig, 'breakingPolicies' | 'changelogJson' | 'workTypes'>, options: GenerateChangelogOptions) => ChangelogEntry[];
30
- /** Pluggable validator (default: the production library function). Tests use this to drive specific result shapes through the formatter. */
29
+ buildEntries?: (config: ReleaseHistoryConfig, options: GenerateChangelogOptions) => ChangelogEntry[];
30
+ /** Validator; defaults to `validateAllChangelogOverrides`. Tests use it to feed specific results to the formatter. */
31
31
  validate?: (inputs: ValidateAllChangelogOverridesInputs) => ValidateAllChangelogOverridesResult;
32
32
  }
33
33
  /**
34
- * Validate every changelog override file across the project and per-workspace scopes, and
35
- * return a tiered exit-code-plus-message result. Performs workspace discovery, config load,
34
+ * Validates every changelog override file across the project and per-workspace scopes, and
35
+ * returns a tiered exit-code-plus-message result. Performs workspace discovery, config load,
36
36
  * and per-scope item collection, then delegates the actual validation to
37
37
  * {@link validateAllChangelogOverrides}.
38
38
  *
39
- * Single-package and monorepo modes are handled uniformly: single-package collapses to one
40
- * project scope; monorepo expands to a project scope plus one scope per workspace. A workspace declaring
41
- * patterns that resolve to no package is neither, and exits `2`.
39
+ * Single-package and monorepo modes are handled uniformly: Single-package collapses to one
40
+ * project scope; monorepo expands to a project scope plus one scope per workspace. A workspace that doesn't resolve
41
+ * to any package is neither, and the command returns exit code `2` for it.
42
42
  *
43
43
  * `configPath` names the config file to read, relative to the working directory; it defaults to
44
44
  * `CONFIG_FILE_PATH`.
45
45
  */
46
46
  export declare function validateOverridesCommand(styles: StreamStyles, configPath?: string, dependencies?: ValidateOverridesCommandDependencies): Promise<ValidateOverridesCommandResult>;
47
- /**
48
- * Pure formatter — take an aggregated validation result, return the tiered exit code and a
49
- * rendered message. Exported for unit testing without going through the full discovery path.
50
- */
47
+ /** Returns the tiered exit code and rendered message for an aggregated validation result. */
51
48
  export declare function formatValidateOverridesResult(result: ValidateAllChangelogOverridesResult, style: OutputStyle): ValidateOverridesCommandResult;
@@ -100,7 +100,7 @@ function buildMonorepoInputs(discoveredPaths, userConfig, buildEntries) {
100
100
  const rootPackage = readRootPackageVersion();
101
101
  const config = mergeMonorepoConfig(discoveredPaths, userConfig, rootPackage);
102
102
  const workspaces = config.workspaces.map((workspace) => {
103
- const options = { tagPrefixes: getAllTagPrefixes(workspace), paths: workspace.paths };
103
+ const options = { tagPrefixes: getAllTagPrefixes(workspace), paths: workspace.paths, workspaceDir: workspace.dir };
104
104
  return {
105
105
  filePath: resolveOverridePath(workspace.workspacePath),
106
106
  items: flattenEntriesToItems(buildEntries(config, options)),
package/docs/api.md CHANGED
@@ -4,7 +4,7 @@ How to build a release config by hand with `deriveWorkspaceConfig()`, the script
4
4
 
5
5
  ## Using `deriveWorkspaceConfig()` for manual configuration
6
6
 
7
- If you need to build a `MonorepoReleaseConfig` manually (e.g., for the legacy script-based approach), the exported `deriveWorkspaceConfig()` helper creates a `WorkspaceConfig` from a workspace-relative path. It reads the workspace's `package.json` to derive the tag prefix from the package name:
7
+ For a `MonorepoReleaseConfig` built manually (e.g., for the legacy script-based approach), the exported `deriveWorkspaceConfig()` helper creates a `WorkspaceConfig` from a workspace-relative path. It reads the workspace's `package.json` to derive the tag prefix from the package name:
8
8
 
9
9
  ```typescript
10
10
  import { deriveWorkspaceConfig } from '@williamthorsen/release-kit';
@@ -22,9 +22,9 @@ deriveWorkspaceConfig('packages/arrays');
22
22
  // }
23
23
  ```
24
24
 
25
- `dir` is the basename of the workspace path and is the stable internal identifier used by `--only`, `WorkspaceOverride.dir`, and the dependency graph. `tagPrefix` is derived from the unscoped `package.json` `name` — any leading `@scope/` is stripped — so tags reflect the package identity rather than the directory layout. For example, a workspace at `packages/core` with `"name": "@williamthorsen/nmr-core"` produces `tagPrefix: 'nmr-core-v'`, yielding tags like `nmr-core-v1.3.0`.
25
+ `dir` is the basename of the workspace path and is the stable internal identifier used by `--only`, `WorkspaceOverride.dir`, and the dependency graph. `tagPrefix` is derived from the unscoped `package.json` `name` (with any leading `@scope/` stripped), so tags reflect the package identity rather than the directory layout. For example, a workspace at `packages/core` with `"name": "@williamthorsen/nmr-core"` produces `tagPrefix: 'nmr-core-v'`, yielding tags like `nmr-core-v1.3.0`.
26
26
 
27
- The workspace's `package.json` must declare a non-empty `name` field; `deriveWorkspaceConfig()` throws otherwise. If two workspaces produce the same `tagPrefix` (because their unscoped names collide), `mergeMonorepoConfig()` throws and names the colliding workspaces so you can rename one.
27
+ The workspace's `package.json` must declare a non-empty `name` field; `deriveWorkspaceConfig()` throws otherwise. If two workspaces produce the same `tagPrefix` (because their unscoped names collide), `mergeMonorepoConfig()` throws and names the colliding workspaces so that the developer can rename one.
28
28
 
29
29
  ## Legacy script-based approach
30
30
 
@@ -49,7 +49,7 @@ import { config } from './release.config.ts';
49
49
  runReleasePrepare(config);
50
50
  ```
51
51
 
52
- The key difference: the script-based approach requires manually listing every workspace, while the CLI auto-discovers them from `pnpm-workspace.yaml`.
52
+ The key difference: The script-based approach requires manually listing every workspace, while the CLI auto-discovers them from `pnpm-workspace.yaml`.
53
53
 
54
54
  ## `resolveReleaseTags` takes workspaces; `WorkspaceConfig` requires `workspacePath`
55
55
 
@@ -58,7 +58,7 @@ Tag resolution is now driven by workspace records rather than a caller-supplied
58
58
  - `resolveReleaseTags` signature changed from `(workspaceMap?: Map<string, string>)` to `(workspaces?: readonly WorkspaceConfig[])`.
59
59
  - `WorkspaceConfig` gained a required `workspacePath: string` field.
60
60
 
61
- Replace direct `Map`-based calls with `deriveWorkspaceConfig()`, which now populates `workspacePath` for you:
61
+ Replace direct `Map`-based calls with `deriveWorkspaceConfig()`, which now populates `workspacePath`:
62
62
 
63
63
  ```diff
64
64
  -import { resolveReleaseTags } from '@williamthorsen/release-kit';
@@ -70,4 +70,4 @@ Replace direct `Map`-based calls with `deriveWorkspaceConfig()`, which now popul
70
70
  +resolveReleaseTags([deriveWorkspaceConfig('packages/core')]);
71
71
  ```
72
72
 
73
- If you construct `WorkspaceConfig` objects directly, add `workspacePath` alongside the other required fields.
73
+ When constructing `WorkspaceConfig` objects directly, add `workspacePath` alongside the other required fields.
@@ -1,27 +1,27 @@
1
1
  # Changelogs
2
2
 
3
- What reaches a published tarball, what each `.meta/changelog.json` item contains, which commits reach a changelog, how a merge commit's change-record block becomes items, and how release notes reach a README.
3
+ What a published tarball includes, what each `.meta/changelog.json` item contains, which commits appear in a changelog, how a merge commit's change-record block becomes items, and how release notes are written into a README.
4
4
 
5
- Neither `CHANGELOG.md` nor `.meta/changelog.json` reaches a published tarball on its own. npm stopped including `CHANGELOG` automatically in npm 7, so a package that declares a `files` field ships a changelog only where that field names `CHANGELOG.md` and `.meta/changelog.json`. release-kit's readyup kit reports a publishable workspace whose `files` field omits either.
5
+ npm doesn't include either `CHANGELOG.md` or `.meta/changelog.json` in a published tarball on its own. npm stopped including `CHANGELOG` automatically in npm 7, so a package that declares a `files` field publishes a changelog only when that field names `CHANGELOG.md` and `.meta/changelog.json`. release-kit's readyup kit reports a publishable workspace whose `files` field omits either.
6
6
 
7
7
  ## `changelog.json` item schema
8
8
 
9
- Each item under a section in `.meta/changelog.json` carries one required field and five optional ones:
9
+ Each item under a section in `.meta/changelog.json` has one required field and five optional ones:
10
10
 
11
- | Field | Type | Meaning |
12
- | ------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
13
- | `description` | `string` | The bullet headline, taken from the commit subject with the ticket ID and type prefix stripped, or from a change-record entry's `text` (see [Change-record blocks](#change-record-blocks)). |
14
- | `body` | `string` | The commit body, with trailing trailer metadata and any `change-record` block stripped. Absent on an item derived from a change-record entry. |
15
- | `breaking` | `boolean` | Present and `true` where the commit subject carried the `!` prefix, or the change-record entry set `breaking: true`, and the type permits it. See [`!` (breaking change) policy](work-types.md#-breaking-change-policy). |
16
- | `migration` | `string` | The migration step for a consumer. See below. |
17
- | `hash` | `string` | The full commit SHA, which an override key matches by prefix. Absent on synthetic propagation entries. |
18
- | `entry` | `number` | The 1-based position of the change-record entry from which the item derives, which an override key `<hash>:<n>` matches. Absent on an item derived from the commit's title. |
11
+ | Field | Type | Meaning |
12
+ | ------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
13
+ | `description` | `string` | The bullet headline, taken from the commit subject with the ticket ID and type prefix stripped, or from a change-record entry's `text` (see [Change-record blocks](#change-record-blocks)). |
14
+ | `body` | `string` | The commit body, with trailing trailer metadata and any `change-record` block stripped. Absent on an item derived from a change-record entry. |
15
+ | `breaking` | `boolean` | Present and `true` when the commit subject had the `!` prefix, or the change-record entry set `breaking: true`, and the type permits it. See [`!` (breaking change) policy](work-types.md#-breaking-change-policy). |
16
+ | `migration` | `string` | The migration step for a consumer. See below. |
17
+ | `hash` | `string` | The full commit SHA, which an override key matches by prefix. Absent on synthetic propagation entries. |
18
+ | `entry` | `number` | The 1-based position of the change-record entry from which the item derives, which an override key `<hash>:<n>` matches. Absent on an item derived from the commit's title. |
19
19
 
20
20
  Every optional field is omitted rather than emitted as `null`, so a consumer tests for presence.
21
21
 
22
22
  ### The `migration` field
23
23
 
24
- A commit body states a migration step as a paragraph opening with the literal label `Migration:`. Where one is present, `migration` carries that paragraph with the label stripped. An item derived from a change-record entry takes `migration` from the entry instead, and has no `body`.
24
+ A commit body states a migration step as a paragraph opening with the literal label `Migration:`. When one is present, `migration` contains that paragraph with the label stripped. An item derived from a change-record entry takes `migration` from the entry instead, and doesn't have a `body`.
25
25
 
26
26
  A title-derived item looks like this:
27
27
 
@@ -35,68 +35,91 @@ A title-derived item looks like this:
35
35
 
36
36
  Three properties are worth knowing:
37
37
 
38
- - **It is independent of `breaking`.** A `deprecate` cannot carry `!` under the default breaking policy, and a `deprecate` still calls for a migration. Filter on `migration` to find every step; filter on `breaking` to find every breaking change.
38
+ - **It is independent of `breaking`.** A `deprecate` cannot have `!` under the default breaking policy, and a `deprecate` still calls for a migration. Filter on `migration` to find every step; filter on `breaking` to find every breaking change.
39
39
  - **`body` keeps the paragraph.** The field is an extraction, not a move, so `CHANGELOG.md` and `.meta/changelog.json` go on agreeing.
40
- - **An override cannot set it.** `migration` is not a field an override file can set (see [File shape](editorial-overrides.md#file-shape)). On a title-derived item it is re-derived from whatever `body` an override installs, and cleared where that body carries no labeled paragraph, so overriding `body` changes the migration text. An entry-derived item keeps its entry's `migration` whatever `body` an override installs.
40
+ - **An override cannot set it.** `migration` is not a field that an override file can set (see [File shape](editorial-overrides.md#file-shape)). On a title-derived item, overriding `body` changes the migration text: release-kit re-derives `migration` from whatever `body` an override installs, and clears it when that body doesn't contain a labeled paragraph. An entry-derived item keeps its entry's `migration` whatever `body` an override installs.
41
41
 
42
42
  The label match is exact: `migration:` and `**Migration:**` are not recognized.
43
43
 
44
- ## What reaches a changelog
44
+ ## Which commits appear in a changelog
45
45
 
46
46
  release-kit splits the history reachable from `HEAD` into one window per release tag that matches the scope's tag prefixes, restricted to commits that touch the scope's `paths`. The bump reads the same items: Its level is the highest that the unreleased window's items call for, so a commit that bumps is one that the changelog lists.
47
47
 
48
- A commit whose last `change-record` block records entries yields its items from those entries, as [Change-record blocks](#change-record-blocks) describes. Any other commit in a window reaches the changelog when its subject passes three checks:
48
+ A commit whose last `change-record` block records entries yields its items from those entries, as [Change-record blocks](#change-record-blocks) describes. Any other commit in a window appears in the changelog when its subject passes three checks:
49
49
 
50
50
  - **A ticket-ID prefix**, such as `#42 `, `TOOL-123 `, or `## `.
51
51
  - **A declared work type**, resolved against the merged [work types](work-types.md).
52
52
  - **A type not excluded from the changelog** by `excludedFromChangelog: true`.
53
53
 
54
- `release:` commits and merge commits whose subject git wrote (`Merge …`) never reach a changelog, whether or not they carry a block. A commit that fails a check yields no item and raises no bump, and a release left with no surviving commit gets no changelog entry. The prepare report lists a commit of the unreleased window that fails the first or the second check as unparseable, unless a malformed-block warning already reports it; it lists none that fails the third. The commit's type decides the section under which its item appears.
54
+ `release:` commits and merge commits whose subject git wrote (`Merge …`) never appear in a changelog, whether or not they contain a block. A commit that fails a check doesn't yield an item or raise a bump, so a window without an item doesn't call for a release. A release forced by `--force` or `--set-version` whose window doesn't yield an item records a "Forced version bump." entry under Notes for its version, whether or not the window has commits. Because history does not yield that entry again, `release-kit prepare` keeps it by merging each release's entries with the `changelog.json` on disk. The prepare report lists a commit of the unreleased window that fails the first or the second check as unparseable, unless a malformed-block warning already reports it; it lists none that fails the third. The commit's type decides the section under which its item appears.
55
55
 
56
- Every item of a commit reaches every workspace whose window contains the commit, because windows are selected by the paths that the commit touches.
56
+ ### Existing `CHANGELOG.md` sections
57
+
58
+ `prepare` also reads the `CHANGELOG.md` that it regenerates, so a version recorded only there, such as history written by hand before release-kit was adopted, survives. The first SemVer token in a `##` heading names the section's version: `## 1.2.0`, `## v1.2.0`, and `## [1.2.0] - 2024-01-01` all name 1.2.0, and so does `## Upgrading from 1.2.0`.
59
+
60
+ - **A version that neither the release windows nor `changelog.json` contain** keeps its section verbatim, placed among the rendered sections in version order.
61
+ - **A version that either contains** is rendered from its entries, and its old text is discarded.
62
+ - **A section whose heading doesn't name a version**, such as `## Unreleased`, is dropped.
63
+
64
+ The header above the first section and the footer comment are regenerated. The prepare report lists the versions kept for each changelog and warns about each section dropped. Kept sections never enter `changelog.json`.
65
+
66
+ ### Routing to workspaces
67
+
68
+ A workspace's window contains every commit that touches the workspace's paths. An item derived from a title is routed to every workspace whose window contains its commit. An item derived from a change-record entry is routed to a workspace in that window only when the entry's `scopes` route it there:
69
+
70
+ - **A scope that names the workspace's `dir`** routes the item to that workspace. release-kit resolves each scope through [`scopeAliases`](configuration.md) before it compares the scope with the `dir`.
71
+ - **`*`, or an empty or absent `scopes`,** routes the item to every workspace in the window.
72
+ - **`root`** doesn't name a workspace, so it routes the item nowhere and is not reported. A workspace whose `dir` is `root` is the exception: `root` then names that workspace.
73
+
74
+ A scope narrows the window and never widens it: A scope that names a workspace whose window does not contain the commit doesn't route the item to any workspace, and `prepare` reports it (see [Reported diagnostics](#reported-diagnostics)). The project changelog and a single-package repo take every entry, whatever its scopes.
75
+
76
+ A workspace's bump reads the items routed to it, so a breaking entry scoped to one workspace does not raise a major bump in another that the same commit touched. A workspace whose window has commits but lacks a routed item doesn't call for a release.
77
+
78
+ Each `prepare` rebuilds a workspace's released windows with the same routing. When an earlier release recorded an item under a workspace that the item's scopes do not name, the rebuild removes the item from that workspace's past section. A path window does not follow a renamed directory, and legacy identities do not rename scopes: An entry scoped to a workspace's old directory name is routed to the workspace only through a `scopeAliases` entry from the old name to the new one, and until one exists, `prepare` reports the scope for each such commit in the unreleased window.
57
79
 
58
80
  ## Change-record blocks
59
81
 
60
82
  A squash-merge commit composed from a pull request can end with a fenced block whose info string is exactly `change-record`. Its payload is YAML, with the grammar that codeassembly's `change-record.md` specifies. release-kit reads these keys and ignores any other:
61
83
 
62
- | Key | Type | Meaning |
63
- | --------------------- | -------- | ------------------------------------------------------------------------------- |
64
- | `entries` | list | The change entries, one per outcome of the change, in the order that they read. |
65
- | `entries[].type` | `string` | A work type, resolved through the declared types and their aliases. Required. |
66
- | `entries[].text` | `string` | The sentence that reports the outcome. Required. |
67
- | `entries[].breaking` | boolean | Whether the outcome breaks consumers. |
68
- | `entries[].scopes` | list | The scopes that the outcome touched, each a string. Validated but not yet used. |
69
- | `entries[].migration` | `string` | The migration step for a consumer. |
70
- | `pr_number` | integer | The merged pull request's number, a positive integer. |
71
- | `ticket_ref` | `string` | The ticket that the change serves. Validated but not yet used. |
84
+ | Key | Type | Meaning |
85
+ | --------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
86
+ | `entries` | list | The change entries, one per outcome of the change, in the order that they read. |
87
+ | `entries[].type` | `string` | A work type, resolved through the declared types and their aliases. Required. |
88
+ | `entries[].text` | `string` | The sentence that reports the outcome. Required. |
89
+ | `entries[].breaking` | boolean | Whether the outcome breaks consumers. |
90
+ | `entries[].scopes` | list | The scopes that the outcome touched, each a string. They decide which workspaces receive the entry's item; see [Routing to workspaces](#routing-to-workspaces). |
91
+ | `entries[].migration` | `string` | The migration step for a consumer. |
92
+ | `pr_number` | integer | The merged pull request's number, a positive integer. |
93
+ | `ticket_ref` | `string` | The ticket that the change serves. Validated but not yet used. |
72
94
 
73
95
  release-kit reads the **last** `change-record` fence in the message. A key whose value is null reads as absent.
74
96
 
75
97
  ### Items from entries
76
98
 
77
- Each entry yields one item, whether or not the commit subject carries a ticket-ID prefix or a declared type:
99
+ Each entry routed to the workspace being read yields one item, whether or not the commit subject has a ticket-ID prefix or a declared type:
78
100
 
79
- - **Section**: the header of the entry's `type`, so the section order and the audience follow as for a title.
80
- - **`description`**: the entry's `text` as written, followed by ` (#N)` when the block records `pr_number`.
81
- - **`breaking`**: set when the entry's `breaking` is `true` and the type's breaking policy permits it.
82
- - **`migration`**: the entry's `migration`, when present. There is no `body`.
83
- - **`hash`** and **`entry`**: the commit's hash, and the entry's 1-based position among all the block's entries, including those that yield no item.
101
+ - **Section**: The header of the entry's `type`, so the section order and the audience follow as for a title.
102
+ - **`description`**: The entry's `text` as written, followed by ` (#N)` when the block records `pr_number`.
103
+ - **`breaking`**: Set when the entry's `breaking` is `true` and the type's breaking policy permits it.
104
+ - **`migration`**: The entry's `migration`, when present. The item doesn't have a `body`.
105
+ - **`hash`** and **`entry`**: The commit's hash, and the entry's 1-based position among all the block's entries, including those that don't yield an item.
84
106
 
85
- An entry whose type is excluded from the changelog yields no item. An override targets one of these items by the key `<hash>:<n>`, where `n` is the item's `entry`. A bare `<hash>` key matches every item of the commit, and it fails when it sets `description` or `body` on a commit with several items; see [Keys](editorial-overrides.md#keys).
107
+ An entry whose type is excluded from the changelog doesn't yield an item. An override targets one of these items by the key `<hash>:<n>`, where `n` is the item's `entry`. A bare `<hash>` key matches every item of the commit, and it fails when it sets `description` or `body` on a commit with several items; see [Keys](editorial-overrides.md#keys).
86
108
 
87
109
  ### Fallback to the title
88
110
 
89
- A commit is read from its title, as [What reaches a changelog](#what-reaches-a-changelog) describes, when it has no block, when the block's `entries` is absent or empty, or when the block is malformed. A block is malformed when it never closes, when its payload is not valid YAML or not a mapping, when `entries` is not a list, when an entry is not a mapping, when an entry's `type` or `text` is missing, blank, or not a string, when `breaking` is not a boolean, when `scopes` is not a list of strings, when `migration` or `ticket_ref` is not a string, or when `pr_number` is not a positive integer. One defect makes the whole block malformed.
111
+ A commit is read from its title, as [Which commits appear in a changelog](#which-commits-appear-in-a-changelog) describes, when it doesn't have a block, when the block's `entries` is absent or empty, or when the block is malformed. A block is malformed when it never closes, when its payload is not valid YAML or not a mapping, when `entries` is not a list, when an entry is not a mapping, when an entry's `type` or `text` is missing, blank, or not a string, when `breaking` is not a boolean, when `scopes` is not a list of strings, when `migration` or `ticket_ref` is not a string, or when `pr_number` is not a positive integer. One defect makes the whole block malformed.
90
112
 
91
113
  ### Reported diagnostics
92
114
 
93
- `release-kit prepare` reports these as warnings for the release being prepared, from the commits that no earlier release contains:
115
+ `release-kit prepare` reports these as warnings for the release being prepared, from the commits that aren't in any earlier release:
94
116
 
95
117
  - **A malformed block**, with the defect that stopped the read.
96
- - **An entry whose type is not declared**, with its position. It yields no item.
118
+ - **An entry whose type is not declared**, with its position. It doesn't yield an item.
97
119
  - **An entry that violates its type's breaking policy**, as a policy violation at the entry's position. Its item is not marked breaking.
120
+ - **An entry scope that doesn't name a workspace in its commit's window**, with the commit's hash, the entry's position, and the scope as the entry declares it. release-kit reports it under every workspace whose window contains the commit. A scope that names nothing, or a workspace whose window does not contain the commit, is reported; `*`, `root`, and a scope that names a workspace in the window are not. Under `--only`, a scope that names a configured workspace left out of the run is not reported either. A workspace excluded with `shouldExclude: true` is not a configured workspace, so a scope that names one is reported.
98
121
 
99
- The result that `prepare` returns carries these as `malformedBlocks`, `undeclaredEntryTypes`, and `policyViolations` entries whose `surface` is `'entry'`, whether the target releases or is skipped. A block whose entries are all undeclared or excluded yields no item, so the commit raises no bump.
122
+ The result that `prepare` returns includes these as `malformedBlocks`, `undeclaredEntryTypes`, `policyViolations` entries whose `surface` is `'entry'`, and `unroutedEntryScopes`, whether the target releases or is skipped. Because a block whose entries are all undeclared or excluded doesn't yield an item, the commit doesn't raise a bump.
100
123
 
101
124
  ## Release-notes injection
102
125
 
@@ -106,4 +129,4 @@ With `releaseNotes.shouldInjectIntoReadme` set to `true`, `release-kit publish`
106
129
  <!-- section:release-notes --><!-- /section:release-notes -->
107
130
  ```
108
131
 
109
- A README without the marker pair receives the notes at its top, above its title, and release-kit's readyup kit reports a README that lacks the markers. Injection is skipped with a warning when `changelogJson.enabled` is `false`, when the version has no entry, or when the entry has no all-audience content. `release-kit prepare --with-release-notes` writes the same injected README as a [preview](releasing.md#previewing-release-notes-with---with-release-notes).
132
+ A README without the marker pair receives the notes at its top, above its title, and release-kit's readyup kit reports a README that lacks the markers. Injection is skipped with a warning when `changelogJson.enabled` is `false`, when the version doesn't have an entry, or when the entry doesn't have any all-audience content. `release-kit prepare --with-release-notes` writes the same injected README as a [preview](releasing.md#previewing-release-notes-with---with-release-notes).
@@ -6,29 +6,29 @@ A minimal config file is in the [README](../README.md#configuration).
6
6
 
7
7
  ## Config file location
8
8
 
9
- release-kit reads `.config/release-kit.config.ts`, resolved against the working directory. A run reads a different file when the subcommand is given `--config <path>`, which is also resolved against the working directory.
9
+ release-kit reads `.config/release-kit.config.ts`, resolved against the repo root, which release-kit locates from the directory from which it was invoked. A run reads a different file when the subcommand is given `--config <path>`, which is resolved against the invocation directory rather than the root, so that a relative path means what it means in the caller's shell.
10
10
 
11
- The flag is accepted by every subcommand that reads a config: `prepare`, `publish`, `create-github-release`, `show-tag-prefixes`, `overrides validate`, `sync-labels init`, and `sync-labels generate`. `commit`, `tag`, `push`, and `sync-labels sync` read no config and reject it.
11
+ The flag is accepted by every subcommand that reads a config: `prepare`, `push`, `publish`, `create-github-release`, `show-tag-prefixes`, `overrides validate`, `sync-labels init`, and `sync-labels generate`. `commit`, `tag`, and `sync-labels sync` don't read a config, and they reject the flag.
12
12
 
13
- The two paths differ in what an absent file means. An absent default path means the repo declares no config, and the run proceeds on discovered defaults. An absent `--config` path fails the command with an error naming the resolved path, because a caller who names a file expects that file to be read. A file that exists and fails validation is an error on both paths alike, and so is one that exists and fails to load.
13
+ The two paths differ in what an absent file means. An absent default path means that the repo doesn't declare a config, and the run proceeds on discovered defaults. An absent `--config` path fails the command with an error naming the resolved path, because a caller who names a file expects that file to be read. A file that exists and fails validation is an error on both paths alike, and so is one that exists and fails to load.
14
14
 
15
- `--config` names a file to read, never a write target. `release-kit init --with-config` scaffolds to `.config/release-kit.config.ts` and takes no `--config`, and `sync-labels init` writes its seeded `repoLabels` block to that same path.
15
+ `--config` names a file to read, never a write target. `release-kit init --with-config` scaffolds to `.config/release-kit.config.ts` and doesn't take `--config`, and `sync-labels init` writes its seeded `repoLabels` block to that same path.
16
16
 
17
17
  ## `ReleaseKitConfig` reference
18
18
 
19
- | Field | Type | Description |
20
- | ------------------ | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
21
- | `workspaces` | `WorkspaceOverride[]` | Override or exclude discovered workspaces (matched by `dir`) |
22
- | `formatCommand` | `string` | Shell command to run after changelog generation; modified file paths are appended as arguments |
23
- | `versionPatterns` | `VersionPatterns` | Rules for which commit types trigger major/minor bumps |
24
- | `scopeAliases` | `Record<string, string>` | Maps shorthand scope names to canonical names in commits |
25
- | `workTypes` | `Record<string, WorkTypeConfig>` | Work type definitions, merged with defaults by key |
26
- | `breakingPolicies` | `Record<string, 'forbidden' \| 'optional' \| 'required'>` | Per-type `!`-policy lookup. Defaults to `DEFAULT_BREAKING_POLICIES`. Replaces the default entirely when provided. Set to `{}` to disable enforcement |
27
- | `retiredPackages` | `RetiredPackage[]` | Packages that once lived in this repo but have been extracted or removed; suppresses undeclared-tag-prefix warnings |
28
- | `project` | `ProjectConfig` | Opt-in project-level release block. Declaring `project: {}` (even empty) enables a project-release stage in `prepare` |
29
- | `repoLabels` | `RepoLabelsConfig` | Declares the repository's label registry for `sync-labels`; see [Label configuration](labels.md#label-configuration) |
30
- | `changelogJson` | `ChangelogJsonConfig` | Controls `.meta/changelog.json`: `enabled` (default `true`), `outputPath` (default `'.meta/changelog.json'`), and `devOnlySections`; see [Custom work types](work-types.md#custom-work-types) |
31
- | `releaseNotes` | `ReleaseNotesConfig` | `shouldInjectIntoReadme` (default `false`) injects release notes into each package's README while it publishes; see [Release-notes injection](changelogs.md#release-notes-injection) |
19
+ | Field | Type | Description |
20
+ | ------------------ | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
21
+ | `workspaces` | `WorkspaceOverride[]` | Override or exclude discovered workspaces (matched by `dir`) |
22
+ | `formatCommand` | `string` | Shell command to run after changelog generation; modified file paths are appended as arguments |
23
+ | `versionPatterns` | `VersionPatterns` | Rules for which commit types trigger major/minor bumps |
24
+ | `scopeAliases` | `Record<string, string>` | Maps shorthand scope names to canonical names in commit titles and in change-record entry `scopes`, which [route items to workspaces](changelogs.md#routing-to-workspaces). A renamed workspace directory needs an entry from its old name to its new one |
25
+ | `workTypes` | `Record<string, WorkTypeConfig>` | Work type definitions, merged with defaults by key |
26
+ | `breakingPolicies` | `Record<string, 'forbidden' \| 'optional'>` | Per-type `!`-policy lookup. Defaults to `DEFAULT_BREAKING_POLICIES`. Replaces the default entirely when provided. Set to `{}` to disable enforcement |
27
+ | `retiredPackages` | `RetiredPackage[]` | Packages that once lived in this repo but have been extracted or removed; suppresses undeclared-tag-prefix warnings |
28
+ | `project` | `ProjectConfig` | Opt-in project-level release block. Declaring `project: {}` (even empty) enables a project-release stage in `prepare` |
29
+ | `repoLabels` | `RepoLabelsConfig` | Declares the repository's label registry for `sync-labels`; see [Label configuration](labels.md#label-configuration) |
30
+ | `changelogJson` | `ChangelogJsonConfig` | Controls `.meta/changelog.json`: `enabled` (default `true`), `outputPath` (default `'.meta/changelog.json'`), and `devOnlySections`; see [Custom work types](work-types.md#custom-work-types) |
31
+ | `releaseNotes` | `ReleaseNotesConfig` | `shouldInjectIntoReadme` (default `false`) injects release notes into each package's README while it publishes; see [Release-notes injection](changelogs.md#release-notes-injection) |
32
32
 
33
33
  All fields are optional.
34
34
 
@@ -47,7 +47,9 @@ interface LegacyIdentity {
47
47
  }
48
48
  ```
49
49
 
50
- `legacyIdentities` captures prior identities of a workspace as complete `(name, tagPrefix)` snapshots. The union of the current `tagPrefix` and each identity's `tagPrefix` is consulted when release-kit searches for the most recent baseline tag and when generating changelogs. Use it when a workspace's historical tags were published under a different npm name, a different tag prefix, or both — typically across a package rename. Both fields are required per identity: each entry must be a complete historical snapshot that stays valid regardless of subsequent renames. Run `release-kit show-tag-prefixes` to detect undeclared candidates and produce a paste-ready config snippet. Listing the current identity (full `(name, tagPrefix)` match) is rejected as a no-op duplicate; an identity whose `tagPrefix` matches the current but whose `name` differs is valid and documents a prior rename that reused the same tag shape. If the workspace no longer exists in this repo at all (the package was extracted or removed), use [`retiredPackages`](#retiredpackage) instead.
50
+ `prepare` never versions or tags an excluded workspace. A tag of one found on HEAD anyway, made by hand or before the exclusion, is skipped with a warning by `push`, `publish`, and `create-github-release`.
51
+
52
+ `legacyIdentities` captures prior identities of a workspace as complete `(name, tagPrefix)` snapshots. The union of the current `tagPrefix` and each identity's `tagPrefix` is consulted when release-kit searches for the most recent baseline tag and when generating changelogs. Use it when a workspace's historical tags were published under a different npm name, a different tag prefix, or both, typically across a package rename. Both fields are required per identity: Each entry must be a complete historical snapshot that stays valid regardless of subsequent renames. Run `release-kit show-tag-prefixes` to detect undeclared candidates and produce a paste-ready config snippet. Listing the current identity (full `(name, tagPrefix)` match) is rejected as a no-op duplicate; an identity whose `tagPrefix` matches the current but whose `name` differs is valid and documents a prior rename that reused the same tag shape. If the workspace no longer exists in this repo at all (the package was extracted or removed), use [`retiredPackages`](#retiredpackage) instead.
51
53
 
52
54
  ## `RetiredPackage`
53
55
 
@@ -59,9 +61,9 @@ interface RetiredPackage {
59
61
  }
60
62
  ```
61
63
 
62
- `retiredPackages` is the repo-level complement to `legacyIdentities`. Use `legacyIdentities` when the workspace still exists in this repo under a new identity; use `retiredPackages` when no workspace for this package exists in this repo anymore — the package was extracted to another repo or removed outright. Retired entries are inert: release-kit never consults them for baseline lookup or changelog attribution. Their declared `tagPrefix` values are recognized as historical, so `show-tag-prefixes` stops flagging them under "Undeclared tag prefixes."
64
+ `retiredPackages` is the repo-level complement to `legacyIdentities`. Use `legacyIdentities` when the workspace still exists in this repo under a new identity; use `retiredPackages` when this repo no longer contains a workspace for this package, because the package was extracted to another repo or removed outright. Retired entries are inert: release-kit never consults them for baseline lookup or changelog attribution. Their declared `tagPrefix` values are recognized as historical, so `show-tag-prefixes` stops flagging them under "Undeclared tag prefixes."
63
65
 
64
- Worked example — `preflight` was extracted from this monorepo and continues as the standalone `readyup` project. Its tags stay in this repo as historical anchors:
66
+ Worked example: `preflight` was extracted from this monorepo and continues as the standalone `readyup` project. Its tags stay in this repo as historical anchors:
65
67
 
66
68
  ```typescript
67
69
  import { defineConfig } from '@williamthorsen/release-kit/config';
@@ -76,7 +78,7 @@ Validation rules:
76
78
  - `name` and `tagPrefix` are required per entry and must be non-empty strings.
77
79
  - `successor` is optional; if present, it must be a non-empty string.
78
80
  - Full-tuple `(name, tagPrefix)` duplicates within `retiredPackages` are rejected.
79
- - Two entries sharing the same `tagPrefix` but different `name`s are accepted — this documents a package renamed within the repo before being retired.
81
+ - Two entries sharing the same `tagPrefix` but different `name`s are accepted: Such a pair documents a package renamed within the repo before being retired.
80
82
 
81
83
  `show-tag-prefixes` currently does not render a dedicated "Retired packages" section (deferred). Declaring a retired entry is verifiable by confirming that its `tagPrefix` stops appearing under "Undeclared tag prefixes" in the `show-tag-prefixes` output.
82
84
 
@@ -88,17 +90,17 @@ Tag prefixes from distinct owners must not be identical or be a strict prefix of
88
90
  - A `retiredPackages[]` entry (one owner per entry).
89
91
  - The `project` block, when configured.
90
92
 
91
- release-kit matches a tag to a prefix when the tag name starts with the prefix and continues with a digit, so a strict-prefix overlap between distinct owners can match the wrong owner's tags. For example, a project prefix of `v` collides with a workspace prefix of `v11y-check-v`, since `v11y-check-v1.0.0` starts with `v` followed by a digit. The rule rejects every strict-prefix overlap, including one such as `v` and `vue-helpers-v` that no tag of the longer prefix can reach.
93
+ Because release-kit matches a tag to a prefix when the tag name starts with the prefix and continues with a digit, a strict-prefix overlap between distinct owners can match the wrong owner's tags. For example, a project prefix of `v` collides with a workspace prefix of `v11y-check-v`, since `v11y-check-v1.0.0` starts with `v` followed by a digit. The rule rejects every strict-prefix overlap, including one such as `v` and `vue-helpers-v`, whose shorter prefix can't match any tag of the longer one.
92
94
 
93
95
  The rule is enforced at config load; the resulting error identifies both colliding declarations.
94
96
 
95
97
  ## `release-kit show-tag-prefixes`
96
98
 
97
- Print a per-workspace table of derived tag prefixes, tag counts, and declared legacy prefixes. Also surfaces any release-shaped tag prefix in the repo that is neither a derived prefix nor declared via `legacyIdentities`, along with a copy-pasteable `workspaces: [...]` config snippet. The snippet uses a `TODO-fill-in-legacy-npm-name` placeholder for each identity's `name`; replace it with the package's prior npm name before pasting.
99
+ Prints a per-workspace table of derived tag prefixes, tag counts, and declared legacy prefixes. Also reports any release-shaped tag prefix in the repo that is neither a derived prefix nor declared via `legacyIdentities`, along with a copy-pasteable `workspaces: [...]` config snippet. The snippet uses a `TODO-fill-in-legacy-npm-name` placeholder for each identity's `name`; replace it with the package's prior npm name before pasting.
98
100
 
99
- Exits `0` when every workspace derives a prefix and there are no cross-workspace collisions; exits `1` on any derivation failure or collision. Undeclared candidates do not affect the exit code — they surface as a warning via the `legacy tag prefixes are declared` readyup check.
101
+ Exits `0` when every workspace derives a prefix and the workspaces' prefixes don't collide; exits `1` on any derivation failure or collision. Undeclared candidates do not affect the exit code; the `legacy tag prefixes are declared` readyup check reports them as a warning.
100
102
 
101
- A config file that exists and either fails to load or fails schema validation reports the failure on stderr and exits `1`, printing no preview, because a config that cannot be read leaves the declared-versus-undeclared verdict wrong in the one dimension the command exists to report on. That holds on the default path and under `--config`, and in single-package mode as well as monorepo mode. An absent default config is a supported state and still previews against derived defaults.
103
+ When a config file exists and either fails to load or fails schema validation, the command reports the failure on stderr and exits `1` without printing a preview, because a config that cannot be read leaves the declared-versus-undeclared verdict wrong in the one dimension on which the command exists to report. That holds on the default path and under `--config`, and in single-package mode as well as monorepo mode. An absent default config is a supported state and still previews against derived defaults.
102
104
 
103
105
  In single-package mode, prints a single row with `workspacePath = .` and `derivedPrefix = v`; legacy entries and undeclared-candidate scanning are not applicable.
104
106
 
@@ -133,6 +135,6 @@ The variable outranks detection. Under `auto`, release-kit defers to detection,
133
135
 
134
136
  1. `CI` is set to anything other than an empty string or `false`.
135
137
  2. The stream is not a terminal, as when its output is piped or redirected.
136
- 3. `TERM` is `linux`, the Linux virtual console, which draws no emoji.
138
+ 3. `TERM` is `linux`, the Linux virtual console, which doesn't draw emoji.
137
139
 
138
- The style governs emoji alone. Changelog section emoji are file content rather than terminal output, and they are unaffected.
140
+ The style affects only emoji. Changelog section emoji are file content rather than terminal output, and they are unaffected.