@williamthorsen/release-kit 12.0.0 โ†’ 13.0.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 +212 -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 +58 -0
  6. package/README.md +55 -48
  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 +11 -8
  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 +1 -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 +1 -1
  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 +116 -91
  146. package/dist/esm/types.js +1 -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 +21 -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
package/docs/labels.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # Labels
2
2
 
3
- How to declare the repository's labels in `.config/release-kit.config.ts`, how the scaffolded workflow applies them, and the published schema for `.meta/label-map.json`.
3
+ How to declare the repository's labels in `.config/release-kit.config.ts`, how the scaffolded workflow applies them, and where the schema for `.meta/label-map.json` is published.
4
4
 
5
5
  ## `release-kit sync-labels`
6
6
 
7
- Manage GitHub label definitions via the `repoLabels` block of `.config/release-kit.config.ts`.
7
+ Manages GitHub label definitions via the `repoLabels` block of `.config/release-kit.config.ts`.
8
8
 
9
- `init` scaffolds the `.github/workflows/sync-labels.yaml` caller workflow and seeds a `repoLabels` block with scope labels discovered from workspaces and declared `retiredPackages`. When `.config/release-kit.config.ts` does not exist, `init` writes it; when it does, `init` prints the block for manual paste โ€” a hand-authored config is never rewritten. `generate` resolves the block and writes `.github/labels.yaml`; with `--check` it regenerates in memory and exits non-zero if the committed file is stale or missing, writing nothing. `sync` triggers the workflow remotely โ€” it requires the `gh` CLI and an existing workflow file.
9
+ `init` scaffolds the `.github/workflows/sync-labels.yaml` caller workflow and seeds a `repoLabels` block with scope labels discovered from workspaces and declared `retiredPackages`. When `.config/release-kit.config.ts` does not exist, `init` writes it; when it does, `init` prints the block for manual paste; a hand-authored config is never rewritten. `generate` resolves the block and writes `.github/labels.yaml`; with `--check` it regenerates in memory and exits non-zero if the committed file is stale or missing, writing nothing. `sync` triggers the workflow remotely; it requires the `gh` CLI and an existing workflow file.
10
10
 
11
11
  `init` and `generate` accept `--config <path>` to read a config elsewhere; `sync` reads none and rejects the flag. Under `--config`, `init` reads the named file and prints the block for manual paste, and fails when that file does not exist rather than scaffolding one, so its write target stays `.config/release-kit.config.ts`. See [Config file location](configuration.md#config-file-location).
12
12
 
13
- Every `sync-labels` subcommand refuses to run while the retired `.config/sync-labels.config.ts` is present, so custom labels cannot be silently dropped mid-migration, and the refusal names what to move and where.
13
+ Every `sync-labels` subcommand refuses to run while the retired `.config/sync-labels.config.ts` is present, so that custom labels cannot be silently dropped mid-migration, and the refusal names what to move and where.
14
14
 
15
15
  ### Label configuration
16
16
 
@@ -29,20 +29,22 @@ export default defineConfig({
29
29
  });
30
30
  ```
31
31
 
32
- The block declares the repository's label registry โ€” the set of labels defined on the GitHub repo, distinct from labels applied to PRs and issues. Resolution is an ordered fold with last-writer-wins:
32
+ The block declares the repository's label registry: the set of labels defined on the GitHub repo, distinct from labels applied to PRs and issues. Resolution is an ordered fold with last-writer-wins:
33
33
 
34
- 1. Presets, in `extends` order โ€” a later preset wins on a shared name.
35
- 2. The `labels` record โ€” an entry adds a label, replaces one an earlier layer defined, or removes it (`null`). Replacement is wholesale: an entry omitting `description` resolves to no description rather than inheriting the one an earlier layer supplied.
34
+ 1. Presets, in `extends` order: A later preset wins on a shared name.
35
+ 2. The `labels` record: An entry adds a label, replaces one that an earlier layer defined, or removes it (`null`). Replacement is wholesale: An entry omitting `description` resolves to a label without a description rather than inheriting the one that an earlier layer supplied.
36
+
37
+ The `common` preset defines a label for every work type in change-grammar's taxonomy, named by the type's `trackerLabel`, in addition to the labels in its bundled YAML file.
36
38
 
37
39
  Names match case-insensitively, as GitHub matches them: `Bug` in the `labels` record replaces a preset's `bug`, and `Bug: null` removes it. A label takes the spelling of the last layer to write it.
38
40
 
39
41
  `description` is optional throughout, in a preset as in the `labels` record, and `sync-labels init` generates none for a scope label. The generated file spells an absent description `''` because `github-label-sync` reads an omitted description as "leave the label's current one alone"; the empty form clears it.
40
42
 
41
- Overlaps between layers are never errors; order resolves them, and the committed `.github/labels.yaml` diff is where an unexpected change surfaces at review. Two misstatements are config errors, because neither is visible in the output diff: a dangling `null`, which removes a name that no preset defines, and `labels` keys that differ only in case, all but one of which the fold would discard.
43
+ Overlaps between layers are never errors; order resolves them, and the committed `.github/labels.yaml` diff shows any unexpected change at review. Two misstatements are config errors, because neither is visible in the output diff: a dangling `null`, which removes a name not defined by any preset, and `labels` keys that differ only in case, all but one of which the fold would discard.
42
44
 
43
45
  ### When labels are applied
44
46
 
45
- The scaffolded workflow carries three triggers:
47
+ The scaffolded workflow declares three triggers:
46
48
 
47
49
  | Trigger | Job | Token | Effect |
48
50
  | --------------------------------------------------------- | ------- | --------------- | --------------------------------- |
@@ -50,36 +52,36 @@ The scaffolded workflow carries three triggers:
50
52
  | Manual dispatch (`release-kit sync-labels sync`) | `sync` | `issues: write` | Applies the labels |
51
53
  | Pull request touching `.github/labels.yaml` | `check` | `issues: read` | Logs the diff without applying it |
52
54
 
53
- Applying on merge closes the window in which a regenerated `.github/labels.yaml` sits unapplied โ€” a window in which a later manual dispatch would apply a label set nobody reviewed.
55
+ Applying on merge closes the window in which a regenerated `.github/labels.yaml` remains unapplied. In that window, a later manual dispatch would apply an unreviewed label set.
54
56
 
55
- The `check` job runs the same sync in dry-run. Its **Label diff** log group lists every create, edit, rename, and deletion the sync would perform, including deletions of labels the file does not declare โ€” the destructive edits a diff of `.github/labels.yaml` alone cannot show, because a label created by hand or by another workflow is absent from the file both before and after. Read that log; the check reports success whether or not the diff is destructive, so a green check is not evidence that nothing will be deleted.
57
+ The `check` job runs the same sync in dry-run. Its **Label diff** log group lists every create, edit, rename, and deletion that the sync would perform, including deletions of labels that the file does not declare: the destructive edits that a diff of `.github/labels.yaml` alone cannot show, because a label created by hand or by another workflow is absent from the file both before and after. Read that log; the check reports success whether or not the diff is destructive, so a green check is not evidence that nothing will be deleted.
56
58
 
57
- The two jobs are split because a job's permissions are fixed when the run is created and cannot vary by trigger. The split is what keeps a write-scoped token out of pull-request runs.
59
+ The two jobs are split because a job's permissions are fixed when the run is created and cannot vary by trigger. The split keeps a write-scoped token out of pull-request runs.
58
60
 
59
- The push trigger filters on the path alone; the `sync` job compares `github.ref_name` against the repository's default branch. The workflow therefore needs no per-repo edit whatever that branch is named โ€” and a push touching `.github/labels.yaml` on any other branch produces a run whose jobs all skip.
61
+ The push trigger filters on the path alone; the `sync` job compares `github.ref_name` against the repository's default branch. The workflow therefore doesn't need a per-repo edit, whatever that branch is named, and a push touching `.github/labels.yaml` on any other branch produces a run whose jobs all skip.
60
62
 
61
63
  Manual dispatch is not a preview. It matches the `sync` job, so it applies the labels, deletions included; only the pull-request path runs in dry-run.
62
64
 
63
65
  ### Dry-run checks on fork pull requests
64
66
 
65
- GitHub issues a read-only `GITHUB_TOKEN` to pull requests from forks, and by default holds runs from first-time contributors until a maintainer approves them. The dry-run reads labels and writes nothing, so the check normally runs once approved. A repo that disables workflows on fork pull requests gets no check at all; to preview such a change, push the branch to the base repo and open the pull request from there.
67
+ GitHub issues a read-only `GITHUB_TOKEN` to pull requests from forks, and by default pauses runs from first-time contributors until a maintainer approves them. Because the dry-run reads labels and writes nothing, the check normally runs once approved. A repo that disables workflows on fork pull requests doesn't get a check at all; to preview such a change, push the branch to the base repo and open the pull request from there.
66
68
 
67
- ### Published JSON Schema for `.meta/label-map.json`
69
+ ### JSON Schema for `.meta/label-map.json`
68
70
 
69
- release-kit publishes a JSON Schema for `.meta/label-map.json` โ€” a separate, generic data file that maps commit-prefix scopes and types to GitHub label names. The schema lives at `packages/release-kit/schemas/label-map.json` in this repo and is reachable via the stable raw URL:
71
+ `.meta/label-map.json` is a separate, generic data file that maps commit-prefix scopes and types to GitHub label names. `@williamthorsen/change-grammar` publishes its JSON Schema, reachable via the stable raw URL:
70
72
 
71
73
  ```
72
- https://github.com/williamthorsen/node-monorepo-tools/raw/release-kit-v<version>/packages/release-kit/schemas/label-map.json
74
+ https://github.com/williamthorsen/node-monorepo-tools/raw/change-grammar-v<version>/packages/change-grammar/schemas/label-map.json
73
75
  ```
74
76
 
75
77
  Consumers reference it from the top of their `.meta/label-map.json`:
76
78
 
77
79
  ```json
78
80
  {
79
- "$schema": "https://github.com/williamthorsen/node-monorepo-tools/raw/release-kit-v<version>/packages/release-kit/schemas/label-map.json",
81
+ "$schema": "https://github.com/williamthorsen/node-monorepo-tools/raw/change-grammar-v<version>/packages/change-grammar/schemas/label-map.json",
80
82
  "types": { "feat": "feature", "fix": "fix" },
81
83
  "scopes": { "audit": "scope:audit" }
82
84
  }
83
85
  ```
84
86
 
85
- release-kit publishes the schema only; it does not generate `.meta/label-map.json`. Generation requires commit-prefix knowledge that lives outside release-kit (in agent-conventions tooling), and is owned by those consumers.
87
+ Earlier releases published the same schema from release-kit, and URLs under existing `release-kit-v*` tags keep resolving. release-kit does not generate `.meta/label-map.json`; change-grammar's `deriveLabelMap` derives one from the taxonomy and the workspace scopes.
@@ -2,7 +2,7 @@
2
2
 
3
3
  How to add a project-level release to a monorepo, what the `project` block accepts, and how that release interacts with `prepare`'s flags.
4
4
 
5
- Some monorepos ship a single combined deliverable โ€” a Chrome extension, a CLI binary, a packaged desktop app โ€” for which the per-workspace tags and changelogs alone do not describe what the user actually receives. Declare the optional `project` block to add a project-level release stage that runs alongside the per-workspace pipeline.
5
+ Some monorepos release a single combined deliverable, such as a Chrome extension, a CLI binary, or a packaged desktop app, for which the per-workspace tags and changelogs alone do not describe what the user actually receives. Declare the optional `project` block to add a project-level release stage that runs alongside the per-workspace pipeline.
6
6
 
7
7
  ```typescript
8
8
  import type { ReleaseKitConfig } from '@williamthorsen/release-kit/config';
@@ -19,12 +19,12 @@ When configured, each `release-kit prepare` run additionally:
19
19
 
20
20
  - Computes commits since the last project tag (`<tagPrefix><version>`), filtered to `paths` (by default, the union of every contributing workspace's paths).
21
21
  - Bumps the root `package.json`'s `version` field using the same bump-derivation rules as workspaces (or the `--bump=...` override).
22
- - Regenerates the root `./CHANGELOG.md` from the structured `ChangelogEntry[]` built from the project's release history (scoped to the project's `tagPrefix` and the same `paths`) and any matching editorial overrides.
22
+ - Regenerates the root `./CHANGELOG.md` from the structured `ChangelogEntry[]` built from the project's release history (scoped to the project's `tagPrefix` and the same `paths`) and any matching editorial overrides, keeping the [existing sections](changelogs.md#existing-changelogmd-sections) whose versions those entries lack.
23
23
  - Emits `./.meta/changelog.json` (when `changelogJson.enabled`).
24
24
  - With `--with-release-notes`, additionally emits `./docs/RELEASE_NOTES.v<version>.md`.
25
- - Appends the project tag to `tmp/.release-tags` so `release-kit commit` and `release-kit tag` pick it up alongside per-workspace tags.
25
+ - Appends the project tag to `tmp/.release-tags` so that `release-kit commit` and `release-kit tag` pick it up alongside per-workspace tags.
26
26
 
27
- If no commit under `paths` has landed since the last project tag, the project release is silently skipped โ€” same behavior as a per-workspace skip.
27
+ If the history since the last project tag doesn't contain any commit under `paths`, the project release is silently skipped, as a per-workspace release is.
28
28
 
29
29
  ## `ProjectConfig`
30
30
 
@@ -37,14 +37,14 @@ interface ProjectConfig {
37
37
 
38
38
  | Field | Default | Description |
39
39
  | ----------- | ----------------------- | -------------------------------------------------------------------- |
40
- | `paths` | Contributing-path union | Patterns selecting the commits the project release considers |
40
+ | `paths` | Contributing-path union | Patterns selecting the commits that the project release considers |
41
41
  | `tagPrefix` | `'v'` | Prefix for project tags. The full tag is `${tagPrefix}${newVersion}` |
42
42
 
43
- By default the contributing workspaces are implicit: every non-excluded discovered workspace contributes its `<dir>/**` glob, and the project release considers commits under their union.
43
+ By default the contributing workspaces are implicit: Every non-excluded discovered workspace contributes its `<dir>/**` glob, and the project release considers commits under their union.
44
44
 
45
- Declare `paths` to choose the window yourself. Each entry is a git pathspec, which both the bump and the changelog read. A declared value **replaces** the workspace union rather than extending it: `paths: ['docs/**']` drops every workspace commit from the project release.
45
+ Declare `paths` to choose the window explicitly. Each entry is a git pathspec, which both the bump and the changelog read. A declared value **replaces** the workspace union rather than extending it: `paths: ['docs/**']` drops every workspace commit from the project release.
46
46
 
47
- A repo whose content lives at the root โ€” where no commit matches any `<dir>/**` glob, so the project release would skip every run โ€” declares the whole tree:
47
+ In a repo whose content lives at the root, commits don't match any `<dir>/**` glob, so the project release would skip every run. Such a repo declares the whole tree:
48
48
 
49
49
  ```typescript
50
50
  const config: ReleaseKitConfig = {
@@ -60,15 +60,15 @@ Validation rules:
60
60
 
61
61
  - The root `package.json` must exist and declare a `version` field. release-kit reports an error at config-load time if either is missing.
62
62
  - The `project` block is rejected in single-package mode (the package's own release already covers the whole repo, so a project tier would only duplicate it).
63
- - `paths`, when declared, must hold at least one non-empty string. An empty array is rejected: it reaches git as no filter at all, which is the inverse of how it reads.
63
+ - `paths`, when declared, must contain at least one non-empty string. An empty array is rejected, because git would read it as the absence of a filter: the inverse of how the array reads.
64
64
  - Unknown fields inside `project` are rejected.
65
65
 
66
66
  CLI flag interactions:
67
67
 
68
- - `--dry-run` previews project artifacts alongside workspace artifacts; no files are written.
69
- - `--bump=major|minor|patch` propagates to the project release as a level chooser. It does not trigger a release on its own when there are no commits or no bump-worthy commits.
70
- - `--force` runs the project release even when no commits or no bump-worthy commits exist since the last project tag. Defaults to patch when `--bump` is not given; combine with `--bump=X` to release at a different level.
71
- - `--only` narrows the run to the named workspaces and skips the project release, which is reported as a warning. The project release rolls up every contributing workspace, and `--only` changes which workspaces those are, so rolling up a narrowed set would release a project version covering work the run excluded. Run a full `prepare` (no `--only`) to include the project release.
68
+ - `--dry-run` previews project artifacts alongside workspace artifacts; it doesn't write any files.
69
+ - `--bump=major|minor|patch` propagates to the project release as a level chooser. It does not trigger a release on its own when the window doesn't have any commits or any bump-worthy commits.
70
+ - `--force` runs the project release even when the history since the last project tag doesn't have any commits or any bump-worthy commits. Defaults to patch when `--bump` is not given; combine with `--bump=X` to release at a different level.
71
+ - `--only` narrows the run to the named workspaces and skips the project release, which is reported as a warning. The project release rolls up every contributing workspace, and `--only` changes which workspaces those are, so rolling up a narrowed set would release a project version covering work that the run excluded. Run a full `prepare` (without `--only`) to include the project release.
72
72
  - `--set-version` is rejected with an error when `project` is configured. `--set-version` operates on a single workspace, but a project release rolls up every contributing workspace; the two semantics don't compose. To use `--set-version`, run on a config without a `project` block.
73
73
 
74
74
  How `--bump` and `--force` combine for every target is in [Releasing](releasing.md#release-kit-prepare).
@@ -4,16 +4,16 @@ How `release-kit publish` and `release-kit create-github-release` choose which p
4
4
 
5
5
  ## `release-kit publish`
6
6
 
7
- Publish packages that have release tags on HEAD. The publish workflow's reusable workflow `publish.reusable.yaml` invokes this command in CI.
7
+ Publishes packages that have release tags on HEAD. The publish workflow's reusable workflow `publish.reusable.yaml` invokes this command in CI.
8
8
 
9
9
  ### Publishability filter
10
10
 
11
- `publish` and `create-github-release` operate only on workspaces where `package.json#private` is absent or `false`. A workspace marked `private: true` is "versioned but not published": it can still be tagged by `release-kit tag` and get a `CHANGELOG.md` entry, but it is skipped by both `release-kit publish` (no registry publish) and `release-kit create-github-release` (no GitHub Release). `private: true` alone routes a workspace out of publish and announce โ€” no extra release config is required. Other commands ignore this filter and operate on private workspaces unchanged.
11
+ `publish` and `create-github-release` operate only on workspaces whose `package.json#private` is absent or `false`. A workspace marked `private: true` is "versioned but not published": It can still be tagged by `release-kit tag` and get a `CHANGELOG.md` entry, but it is skipped by both `release-kit publish` (no registry publish) and `release-kit create-github-release` (no GitHub Release). `private: true` alone excludes a workspace from publish and announce; it doesn't require any extra release config. Other commands ignore this filter and operate on private workspaces unchanged.
12
12
 
13
13
  An unpublishable tag is always skipped cleanly, never fatal. For `release-kit publish`, whether the skip is announced depends on how the tag was resolved:
14
14
 
15
- - **Without `--tags`** (implicit resolution): unpublishable tags on HEAD are silently filtered. The pre-publish listing shows only the publishable subset. If the filter empties the set, `release-kit publish` prints `Nothing to publish.` and exits 0.
16
- - **With `--tags`** (explicit naming): each named tag pointing at an unpublishable workspace is skipped with a warning, and any publishable tags named in the same command still publish.
15
+ - **Without `--tags`** (implicit resolution): Unpublishable tags on HEAD are silently filtered. The pre-publish listing shows only the publishable subset. If the filter empties the set, `release-kit publish` prints `Nothing to publish.` and exits 0.
16
+ - **With `--tags`** (explicit naming): Each named tag pointing at an unpublishable workspace is skipped with a warning, and any publishable tags named in the same command are still published.
17
17
 
18
18
  `release-kit create-github-release` skips unpublishable workspaces with a warning regardless of `--tags`; an all-private tag set is a clean no-op.
19
19
 
@@ -25,6 +25,6 @@ Skipping basic-v1.0.0 (packages/basic): package.json#private is true.
25
25
 
26
26
  ## `release-kit create-github-release`
27
27
 
28
- Create GitHub Releases from `changelog.json` for tags on HEAD. Independent of `npm publish`: invoking this command creates Releases for publishable packages whether or not they were published to a registry. Private workspaces are skipped (see [Publishability filter](#publishability-filter)).
28
+ Creates GitHub Releases from `changelog.json` for tags on HEAD. Independent of `npm publish`: Invoking this command creates Releases for publishable packages whether or not they were published to a registry. Private workspaces are skipped (see [Publishability filter](#publishability-filter)).
29
29
 
30
30
  When `--tags` is omitted, every release tag pointing at HEAD is processed. The CLI requires the `gh` CLI on `PATH` and `contents: write` permission. The bundled `create-github-release.reusable.yaml` GitHub Actions workflow runs this command in CI.
@@ -2,7 +2,7 @@
2
2
 
3
3
  What release-kit's two readyup kits check, how to run each, and what `npm-auto-publish` needs from the npm session.
4
4
 
5
- release-kit publishes two [readyup](https://www.npmjs.com/package/readyup) kits that check a consuming repo against the release it has installed: the `default` kit covers release-kit's own setup โ€” workflows matching the current templates, config free of removed fields, sync-labels wiring โ€” and `npm-auto-publish` covers a repo's OIDC-based npm publishing setup. Both ship inside the package, so they check against the version you installed rather than whatever a repository ref happens to point at, and a check added in a release reaches your repo on upgrade.
5
+ release-kit publishes two [readyup](https://www.npmjs.com/package/readyup) kits that check a consuming repo against the release that it has installed: The `default` kit covers release-kit's own setup (workflows matching the current templates, config free of removed fields, sync-labels wiring), and `npm-auto-publish` covers a repo's OIDC-based npm publishing setup. Both are included in the package, so they check against the installed version rather than whatever a repository ref happens to point at, and a check added in a release becomes available to a repo when it upgrades.
6
6
 
7
7
  The [README](../README.md#readiness-checks) shows how to name release-kit in the readyup config.
8
8
 
@@ -13,6 +13,6 @@ rdy run --from npm:@williamthorsen/release-kit npm-auto-publish # the npm-auto-
13
13
  rdy list --from npm:@williamthorsen/release-kit # what release-kit publishes
14
14
  ```
15
15
 
16
- `--packages` is the form that survives release-kit publishing further kits.
16
+ `--packages` is the form that keeps working when release-kit publishes further kits.
17
17
 
18
- `npm-auto-publish` queries the npm registry for each package's trusted publisher, so it needs an npm session elevated by two-factor authentication, and is meant to be invoked deliberately rather than swept up by an unattended run. A session that cannot answer those queries, whether it is missing a login, missing the elevation, or facing an unreachable registry, is reported once by the `npm session can answer trust queries` gate. The rows that would repeat the failed query then stand down: the trusted-publisher rows in every case, and `published to npm` where the registry itself is unreachable. Each package's `package.json` checks report throughout, since they read no registry.
18
+ Because `npm-auto-publish` queries the npm registry for each package's trusted publisher, it needs an npm session elevated by two-factor authentication, and is meant to be invoked deliberately rather than included in an unattended run. A session that cannot answer those queries, whether it is missing a login, missing the elevation, or facing an unreachable registry, is reported once by the `npm session can answer trust queries` gate. The rows that would repeat the failed query are then skipped: the trusted-publisher rows in every case, and `published to npm` when the registry itself is unreachable. Each package's `package.json` checks report throughout, since they don't read a registry.
package/docs/releasing.md CHANGED
@@ -1,31 +1,41 @@
1
1
  # Releasing
2
2
 
3
- How to scaffold the release workflows, what `release-kit prepare` does with its flags, and how the release workflow runs a release.
3
+ How to scaffold the release workflows and keep them up to date, what `release-kit prepare` does with its flags, and how the release workflow runs a release.
4
4
 
5
5
  The [README](../README.md#quick-start) shows the first run.
6
6
 
7
7
  ## `release-kit init`
8
8
 
9
- Initialize release-kit in the current repository. By default, scaffolds only the GitHub Actions workflow files. Use `--with-config` to also scaffold configuration files.
9
+ Initializes release-kit in the current repository. By default, scaffolds only the GitHub Actions workflow files. Use `--with-config` to also scaffold configuration files.
10
10
 
11
11
  Scaffolded files:
12
12
 
13
- - `.github/workflows/create-github-release.yaml` โ€” workflow that creates a GitHub Release on tag push, independent of npm publish
14
- - `.github/workflows/publish.yaml` โ€” workflow that delegates to a reusable publish workflow
15
- - `.github/workflows/release.yaml` โ€” workflow that delegates to a reusable release workflow
16
- - `.config/release-kit.config.ts` โ€” starter config with commented-out customization examples (with `--with-config`)
13
+ - `.github/workflows/create-github-release.yaml`: Workflow that creates a GitHub Release on tag push, independent of npm publish
14
+ - `.github/workflows/publish.yaml`: Workflow that delegates to a reusable publish workflow
15
+ - `.github/workflows/release.yaml`: Workflow that delegates to a reusable release workflow
16
+ - `.config/release-kit.config.ts`: Starter config with commented-out customization examples (with `--with-config`)
17
+
18
+ ## `release-kit update-templates`
19
+
20
+ Updates the scaffolded workflows to the templates of the installed release-kit version, and prints what changed in each file. Run it after upgrading release-kit; `--dry-run` previews the changes without writing them.
21
+
22
+ The workflows are managed files: release-kit renders them, and a change made by hand is overwritten on the next update. The config files are the repository's own, and the command never reads or writes them.
23
+
24
+ - It creates a missing workflow that `init` would create.
25
+ - It refreshes `.github/workflows/sync-labels.yaml` only when the file exists, because `sync-labels init` scaffolds it on request.
26
+ - It keeps `publish.yaml`'s provenance setting: When the file does not set `provenance: true`, as in a private repository, the updated file does not either.
17
27
 
18
28
  ## `release-kit prepare`
19
29
 
20
- Run release preparation with automatic workspace discovery.
30
+ Runs release preparation with automatic workspace discovery.
21
31
 
22
32
  Workspace names for `--only` match the package directory name (e.g., `arrays`, `release-kit`).
23
33
 
24
34
  `prepare` reads each target's history once and builds the changelog items of its unreleased commits. The bump is the highest level that those items call for, under [`versionPatterns`](configuration.md#versionpatterns), so the bump and the changelog describe the same release: A `feat` entry in a [change-record block](changelogs.md#change-record-blocks) bumps minor under a `docs:` title, and a breaking item bumps major.
25
35
 
26
- - A commit that yields no item bumps nothing: one without a ticket-ID prefix, one of a type excluded from the changelog (such as `fmt`), and one whose change-record entries are all undeclared or excluded.
27
- - A `BREAKING CHANGE:` footer raises no major bump. Only a subject's `!` and an entry's `breaking: true` mark an item breaking; on a type whose policy forbids `!`, the footer is reported as a [policy violation](work-types.md#policy-enforcement).
28
- - A target whose commits yield no item is skipped. The prepare report lists the commits that could not be parsed and the change-record diagnostics, skipped targets included.
36
+ - A commit that doesn't yield an item doesn't bump anything: one without a ticket-ID prefix, one of a type excluded from the changelog (such as `fmt`), and one whose change-record entries are all undeclared or excluded.
37
+ - A `BREAKING CHANGE:` footer doesn't raise a major bump. Only a subject's `!` and an entry's `breaking: true` mark an item breaking; on a type whose policy forbids `!`, the footer is reported as a [policy violation](work-types.md#policy-enforcement).
38
+ - A target whose commits don't yield an item is skipped. The prepare report lists the commits that could not be parsed and the change-record diagnostics, skipped targets included.
29
39
 
30
40
  `--bump` and `--force` are orthogonal in every mode, single-package included: `--bump` is purely a level chooser; `--force` is purely a release trigger. Examples:
31
41
 
@@ -33,16 +43,17 @@ Workspace names for `--only` match the package directory name (e.g., `arrays`, `
33
43
  # Release every target at its natural bump level (no flags).
34
44
  release-kit prepare
35
45
 
36
- # Force a release even when no commit yields a changelog item; defaults to
37
- # patch per target, with each target keeping its natural bump if it has one.
46
+ # Force a release even when the commits don't yield any changelog item;
47
+ # defaults to patch per target, with each target keeping its natural bump
48
+ # if it has one.
38
49
  release-kit prepare --force
39
50
 
40
51
  # Force a release at a uniform level across every releasing target.
41
52
  release-kit prepare --force --bump=minor
42
53
 
43
- # --bump=X alone is a level chooser, NOT a trigger. If no commit of a target
44
- # yields a changelog item, it skips with a "Pass --force..." reason. Otherwise
45
- # the override applies.
54
+ # --bump=X alone is a level chooser, NOT a trigger. If a target's commits
55
+ # don't yield any changelog item, it skips with a "Pass --force..." reason.
56
+ # Otherwise the override applies.
46
57
  release-kit prepare --bump=minor
47
58
  ```
48
59
 
@@ -50,10 +61,10 @@ release-kit prepare --bump=minor
50
61
 
51
62
  `--with-release-notes` writes two versioned files per workspace after each workspace's `changelog.json` is produced:
52
63
 
53
- - `<workspacePath>/docs/README.v<version>.md` โ€” the workspace `README.md` with release notes injected at the `<!-- section:release-notes -->` marker.
54
- - `<workspacePath>/docs/RELEASE_NOTES.v<version>.md` โ€” the standalone release notes for this version.
64
+ - `<workspacePath>/docs/README.v<version>.md`: The workspace `README.md` with release notes injected at the `<!-- section:release-notes -->` marker.
65
+ - `<workspacePath>/docs/RELEASE_NOTES.v<version>.md`: The standalone release notes for this version.
55
66
 
56
- The publish-time inject-and-revert lifecycle is unchanged; previews are additive, deterministic, and safe to regenerate. When `changelogJson.enabled` is `false`, prepare reports a warning and skips preview generation. In dry-run mode, planned writes are logged and no files are created.
67
+ The publish-time inject-and-revert lifecycle is unchanged; previews are additive, deterministic, and safe to regenerate. When `changelogJson.enabled` is `false`, prepare reports a warning and skips preview generation. In dry-run mode, prepare logs the planned writes and doesn't create any files.
57
68
 
58
69
  Because preview filenames are versioned, committing them will accumulate files over time. The recommended `.gitignore` entry for monorepos is:
59
70
 
@@ -69,7 +80,7 @@ docs/*.v*.md
69
80
 
70
81
  ### Setting an explicit version with `--set-version`
71
82
 
72
- The `--set-version` flag is a first-class escape hatch for the cases where the derived bump produces the wrong version โ€” most notably, promoting a pre-1.0 package to 1.0.0. Pre-1.0 packages collapse a `feat!` breaking change to a minor bump (matching semantic-release's `initialMajor: false` and release-please's `bump-minor-pre-major`), so a deliberate promotion to 1.0.0 must be requested explicitly.
83
+ The `--set-version` flag is a first-class escape hatch for the cases in which the derived bump produces the wrong version, most notably when promoting a pre-1.0 package to 1.0.0. Pre-1.0 packages collapse a `feat!` breaking change to a minor bump (matching semantic-release's `initialMajor: false` and release-please's `bump-minor-pre-major`), so a deliberate promotion to 1.0.0 must be requested explicitly.
73
84
 
74
85
  The flag validates that:
75
86
 
@@ -85,17 +96,31 @@ Promoting a pre-1.0 package to 1.0.0 in a monorepo:
85
96
  release-kit prepare --only arrays --set-version 1.0.0
86
97
  ```
87
98
 
88
- An empty changelog section is expected for a bare promotion, because the changelog is generated from commits since the last tag. To include a narrative entry, land a descriptive release commit (e.g., a `feat!` describing the stable API) before running `prepare`.
99
+ An empty changelog section is expected for a bare promotion, because the changelog is generated from commits since the last tag. To include a narrative entry, merge a descriptive release commit (e.g., a `feat!` describing the stable API) before running `prepare`.
100
+
101
+ ### Adopting release-kit in a repo with untagged releases
102
+
103
+ `prepare` finds a target's previous release by its tag. When a target's current version was released and recorded in its `CHANGELOG.md` or `changelog.json` but never tagged, the unreleased window would extend back to the start of history: The new version would absorb every earlier commit, and its bump would be derived from all of them. `prepare` stops before writing anything instead, under `--force` and `--set-version` too, and names each target, its version, and the tag to create:
104
+
105
+ ```text
106
+ The current version is recorded in the changelog, but its tag is not the previous tag reachable from HEAD:
107
+ - workspace 'api': 1.2.0 (create tag api-v1.2.0)
108
+ Tag the commit that released each version, then run prepare again.
109
+ ```
110
+
111
+ Tag each named version at the commit that released it, then run `prepare` again. In a monorepo, `prepare` checks the project only after every workspace passes, so a second run can name the project's tag. The check skips a first release, whose version isn't recorded in any changelog, and a workspace that releases only through propagation.
112
+
113
+ Each new tag closes a release window. When that window's commits yield items, `prepare` renders the tagged version from them, in place of its hand-written section; it keeps every other hand-written version as [Existing `CHANGELOG.md` sections](changelogs.md#existing-changelogmd-sections) describes.
89
114
 
90
115
  ## GitHub Actions workflow
91
116
 
92
117
  The `init` command scaffolds a release workflow at `.github/workflows/release.yaml` that delegates to a reusable release workflow. The scaffolded workflow accepts these inputs:
93
118
 
94
- | Input | Type | Description |
95
- | ------- | ------- | --------------------------------------------------------------------------------------------------------------------------- |
96
- | `only` | string | Workspaces to release (comma-separated, leave empty for all) |
97
- | `bump` | choice | Override bump type: `patch`, `minor`, `major` (empty = auto-detect) |
98
- | `force` | boolean | Release even when no commits or no bump-worthy commits exist (defaults to patch; combine with `bump` for a different level) |
119
+ | Input | Type | Description |
120
+ | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
121
+ | `only` | string | Workspaces to release (comma-separated, leave empty for all) |
122
+ | `bump` | choice | Bump type: `auto` (default, detects it from commits), `patch`, `minor`, or `major` |
123
+ | `force` | boolean | Release even when the window doesn't have any commits or any bump-worthy commits (defaults to patch; combine with `bump` for a different level) |
99
124
 
100
125
  For repos that need a self-contained workflow instead of the reusable one, the scaffolded file can be expanded. The key steps are: checkout with full history (`fetch-depth: 0`), run `release-kit prepare` with optional `--only`, `--bump`, and `--force` flags, check for changes, read tags from `tmp/.release-tags`, then commit, tag, and push.
101
126
 
@@ -1,56 +1,55 @@
1
1
  # Work types and tiers
2
2
 
3
- The taxonomy against which release-kit parses commits: its tiers, the breaking-change policy, section markers, customization, and the maintainer scripts that keep release-kit's copy level with its upstream.
4
-
5
- release-kit bundles a copy of the codeassembly canonical taxonomy in `packages/release-kit/src/work-types.json`, kept level by the [maintainer scripts](#maintaining-the-bundled-taxonomy). The taxonomy is split into three tiers that drive section rendering and audience classification.
6
-
7
- | Tier | Key | Header | Aliases | `!` policy |
8
- | -------- | ----------- | ------------------------- | ------------- | ------------ |
9
- | public | `feat` | ๐ŸŽ‰ Features | `feature` | optional |
10
- | public | `drop` | ๐Ÿชฆ Removed | | **required** |
11
- | public | `deprecate` | ๐Ÿ—‘๏ธ Deprecated | | forbidden |
12
- | public | `fix` | ๐Ÿ› Bug fixes | `bugfix` | optional |
13
- | public | `sec` | ๐Ÿ”’ Security | `security` | optional |
14
- | public | `perf` | โšก Performance | `performance` | optional |
15
- | internal | `internal` | ๐Ÿ—๏ธ Internal features | `utility` | forbidden |
16
- | internal | `refactor` | โ™ป๏ธ Refactoring | | forbidden |
17
- | internal | `tests` | ๐Ÿงช Tests | `test` | forbidden |
18
- | process | `tooling` | โš™๏ธ Tooling | | forbidden |
19
- | process | `ci` | ๐Ÿ‘ท CI | | forbidden |
20
- | process | `deps` | ๐Ÿ“ฆ Dependencies | `dep` | forbidden |
21
- | process | `ai` | ๐Ÿค– Agentic support | | forbidden |
22
- | process | `docs` | ๐Ÿ“š Documentation | `doc` | forbidden |
23
- | process | `fmt` | (excluded from changelog) | | forbidden |
3
+ The taxonomy against which release-kit parses commits: its tiers, the breaking-change policy, section markers, and customization.
4
+
5
+ release-kit reads the taxonomy from `CANONICAL_TAXONOMY` in [`@williamthorsen/change-grammar`](https://github.com/williamthorsen/node-monorepo-tools/tree/main/packages/change-grammar#readme), which also parses commit subjects. The taxonomy is split into three tiers that determine section rendering and audience classification.
6
+
7
+ | Tier | Key | Header | Aliases | `!` policy |
8
+ | -------- | ----------- | ------------------------- | ------------- | ---------- |
9
+ | public | `feat` | ๐ŸŽ‰ Features | `feature` | optional |
10
+ | public | `drop` | ๐Ÿชฆ Removed | | optional |
11
+ | public | `deprecate` | ๐Ÿ—‘๏ธ Deprecated | | forbidden |
12
+ | public | `fix` | ๐Ÿ› Bug fixes | `bugfix` | optional |
13
+ | public | `sec` | ๐Ÿ”’ Security | `security` | optional |
14
+ | public | `perf` | โšก Performance | `performance` | optional |
15
+ | internal | `internal` | ๐Ÿ—๏ธ Internal features | `utility` | forbidden |
16
+ | internal | `refactor` | โ™ป๏ธ Refactoring | | forbidden |
17
+ | internal | `tests` | ๐Ÿงช Tests | `test` | forbidden |
18
+ | process | `tooling` | โš™๏ธ Tooling | | forbidden |
19
+ | process | `ci` | ๐Ÿ‘ท CI | | forbidden |
20
+ | process | `deps` | ๐Ÿ“ฆ Dependencies | `dep` | forbidden |
21
+ | process | `ai` | ๐Ÿค– Agentic support | | forbidden |
22
+ | process | `docs` | ๐Ÿ“š Documentation | `doc` | forbidden |
23
+ | process | `fmt` | (excluded from changelog) | | forbidden |
24
24
 
25
25
  ## Tier semantics
26
26
 
27
- - **`public`** โ€” visible to all audiences. `public`-tier sections appear in both public release notes and dev changelogs.
28
- - **`internal`** โ€” dev-only. `internal`-tier sections appear in dev changelogs but not in public-facing release notes.
29
- - **`process`** โ€” dev-only. Same audience treatment as `internal`.
27
+ - **`public`**: Visible to all audiences. `public`-tier sections appear in both public release notes and dev changelogs.
28
+ - **`internal`**: Dev-only. `internal`-tier sections appear in dev changelogs but not in public-facing release notes.
29
+ - **`process`**: Dev-only. Same audience treatment as `internal`.
30
30
 
31
- Section render order is **tier order (`public` โ†’ `internal` โ†’ `process`), then row order within tier**. It comes from the declaration order of `work-types.json`: `CANONICAL_SECTION_ORDER` in `buildChangelogEntries.ts` indexes each header by its row, and `transformReleases` sorts a release's sections by that index whatever order the commits arrived in.
31
+ Section render order is **tier order (`public` โ†’ `internal` โ†’ `process`), then row order within tier**. It comes from the declaration order of `CANONICAL_TAXONOMY`: `CANONICAL_SECTION_ORDER` in `buildChangelogEntries.ts` indexes each header by its row, and `transformReleases` sorts a release's sections by that index whatever order the commits arrived in.
32
32
 
33
33
  ## Subject forms
34
34
 
35
- `classifyChangelogCommit` resolves a commit's type through `parseCommitMessage`, so a subject reaches its section under any form that parser accepts: `type:`, `scope|type:`, and the conventional-commit `type(scope):`. The type is matched case-insensitively, so `Feat:` and `FEAT:` resolve like `feat:`. The ticket-ID prefix is still required, and an alias resolves before the section is chosen. A commit whose [change-record block](changelogs.md#change-record-blocks) records entries takes its sections from the entries' types instead of from its subject.
35
+ `classifyChangelogCommit` resolves a commit's type through `parseCommitMessage`, so a subject maps to its section under any form that the parser accepts: `type:`, `scope|type:`, and the conventional-commit `type(scope):`, each followed by a space and a title. A subject combining both scope forms, such as `web|feat(api):`, does not parse. The type is matched case-insensitively: `Feat:` and `FEAT:` resolve like `feat:`. The ticket-ID prefix is still required, and an alias resolves before the section is chosen. A commit whose [change-record block](changelogs.md#change-record-blocks) records entries takes its sections from the entries' types instead of from its subject.
36
36
 
37
37
  ## `utility` alias
38
38
 
39
- `utility:` is a backward-compat alias for `internal:`. Both forms parse to the same canonical type, route to the same `๐Ÿ—๏ธ Internal features` section, and are subject to the same `!` policy.
39
+ `utility:` is a backward-compat alias for `internal:`. Both forms parse to the same canonical type, appear under the same `๐Ÿ—๏ธ Internal features` section, and are subject to the same `!` policy.
40
40
 
41
41
  ## `!` (breaking change) policy
42
42
 
43
- Each work-type carries a `breakingPolicy` value:
43
+ Each work-type has a `breakingPolicy` value:
44
44
 
45
- - `optional` (`feat`, `fix`, `sec`, `perf`): `!` is allowed; both `type:` and `type!:` parse cleanly. Any of these can break consumers, and the marker records when one does: A fix can break consumers who relied on the defective behavior, and a performance change can break a contract to achieve its gain.
45
+ - `optional` (`feat`, `drop`, `fix`, `sec`, `perf`): `!` is allowed; both `type:` and `type!:` parse cleanly. Any of these can break consumers, and the marker records when one does: A removal breaks consumers only when the removed surface was published, a fix can break consumers who relied on the defective behavior, and a performance change can break a contract to achieve its gain.
46
46
  - `forbidden` (`deprecate` and every `internal`- and `process`-tier type): `!` is a policy violation. Deprecating a surface keeps it working, and removing it is a `drop`. Internal- and process-tier work does not face consumers; a change that breaks consumers faces them and therefore takes a public-tier type.
47
- - `required` (`drop`): Bare `drop:` is a policy violation; only `drop!:` is accepted. Removing a public surface always breaks consumers, and the `!` form makes that explicit.
48
47
 
49
48
  ### Policy enforcement
50
49
 
51
50
  `parseCommitMessage` enforces the `!` policy at release time and treats a violation as a warning. Commits already in the log cannot be rewritten, so a policy-violating commit is parsed using its canonical type with `breaking: false` (the `!` is dropped from the parse) and an `onPolicyViolation` callback fires. `readReleaseHistory` collects these warnings while it builds the items of the unreleased window, and the release report lists them. A single legacy `internal!` in a year-old log does not block releases.
52
51
 
53
- A `BREAKING CHANGE:` body footer on a `forbidden`-policy type triggers the same warning path as the prefix `!` does: A type that cannot be breaking is parsed as non-breaking whichever surface carries the signal. The warning is all that a footer triggers; it raises no bump (see [`๐Ÿšจ **Breaking:**` bullet marker](#-breaking-bullet-marker)).
52
+ A `BREAKING CHANGE:` body footer on a `forbidden`-policy type triggers the same warning path as the prefix `!` does: A type that cannot be breaking is parsed as non-breaking whichever surface has the signal. The warning is all that a footer triggers; it doesn't raise a bump (see [`๐Ÿšจ **Breaking:**` bullet marker](#-breaking-bullet-marker)).
54
53
 
55
54
  The release-prepare orchestrators (`releasePrepare`, `releasePrepareMono`, `releasePrepareProject`) apply `DEFAULT_BREAKING_POLICIES` automatically. Violations in the titles and change-record entries of each workspace's or project's unreleased window are collected onto the corresponding result's `policyViolations` field, on a skipped result as on a released one, and rendered under the section in the prepare report:
56
55
 
@@ -58,7 +57,7 @@ The release-prepare orchestrators (`releasePrepare`, `releasePrepareMono`, `rele
58
57
  arrays
59
58
  Found 1 commits since arrays-v1.0.0
60
59
  ๐ŸŸ  1 policy violation:
61
- ยท def5678 'internal!: refactor cache' โ€” type 'internal' at prefix surface
60
+ ยท def5678 'internal!: refactor cache': type 'internal' at prefix surface
62
61
  Bumping versions (patch)...
63
62
  ๐Ÿ“ฆ 1.0.0 โ†’ 1.0.1 (patch)
64
63
  ```
@@ -67,7 +66,7 @@ To customize, set `breakingPolicies` in `release-kit.config.ts`. The map replace
67
66
 
68
67
  ## `๐Ÿšจ **Breaking:**` bullet marker
69
68
 
70
- Items whose commit subject carries the `!` prefix (e.g. `feat!`, `drop!`, `feat(api)!`) on a type whose policy permits it are rendered with a `๐Ÿšจ **Breaking:** ` prefix on the bullet:
69
+ Items whose commit subject has the `!` prefix (e.g. `feat!`, `drop!`, `feat(api)!`) on a type whose policy permits it are rendered with a `๐Ÿšจ **Breaking:** ` prefix on the bullet:
71
70
 
72
71
  ```markdown
73
72
  - ๐Ÿšจ **Breaking:** Drop legacy /v1 endpoint
@@ -75,17 +74,17 @@ Items whose commit subject carries the `!` prefix (e.g. `feat!`, `drop!`, `feat(
75
74
 
76
75
  The marker agrees with the version bump:
77
76
 
78
- - A `forbidden`-policy type carrying `!`, such as `refactor!`, gets no marker, just as its `!` raises no bump; the prepare report lists it as a policy violation. An [editorial override](editorial-overrides.md) that sets `breaking: true` restores the marker for one entry.
77
+ - A `forbidden`-policy type with `!`, such as `refactor!`, doesn't get a marker, just as its `!` doesn't raise a bump; the prepare report lists it as a policy violation. An [editorial override](editorial-overrides.md) that sets `breaking: true` restores the marker for one entry.
79
78
  - The configured `breakingPolicies` map, `{}` included, decides which types permit the marker.
80
- - A commit whose type the parser cannot resolve reaches no changelog at all, so no marker question arises.
79
+ - A commit whose type the parser cannot resolve doesn't appear in any changelog, so the question of a marker doesn't arise.
81
80
 
82
- A `BREAKING CHANGE:` body footer on its own does **not** retroactively mark a changelog item as breaking, even on a type whose policy permits `!`; the changelog signal is tied to the commit prefix. This avoids surprise breaking-marker appearances for older commits written under earlier conventions. The bump follows the items, so a footer raises no major bump either: A consumer that marks breaking changes with the footer alone gets the bump of the commit's type.
81
+ A `BREAKING CHANGE:` body footer on its own does **not** retroactively mark a changelog item as breaking, even on a type whose policy permits `!`; the changelog signal is tied to the commit prefix. This avoids surprise breaking-marker appearances for older commits written under earlier conventions. Because the bump follows the items, a footer doesn't raise a major bump either: A consumer that marks breaking changes with the footer alone gets the bump of the commit's type.
83
82
 
84
- The emoji and label of this marker are sourced from the `markers.breaking` entry in `work-types.json` (see [Section markers](#section-markers)) so consumers that render their own breaking-changes section draw from the same SSOT.
83
+ The emoji and label of this marker come from the `markers.breaking` entry of `CANONICAL_TAXONOMY` (see [Section markers](#section-markers)), which is also the source for consumers that render their own breaking-changes section.
85
84
 
86
85
  ## Section markers
87
86
 
88
- Alongside `tiers` and `types`, `work-types.json` exposes a top-level `markers` object for cross-cutting section markers โ€” visual indicators that aren't tied to a specific work type. Today the canonical entry is `breaking`; additional keys (e.g., security advisories, migration notices) can be added without a schema change.
87
+ Alongside `tiers` and `types`, `CANONICAL_TAXONOMY` declares a top-level `markers` object for cross-cutting section markers: visual indicators that aren't tied to a specific work type. Today its only entry is `breaking`.
89
88
 
90
89
  ```jsonc
91
90
  {
@@ -95,29 +94,14 @@ Alongside `tiers` and `types`, `work-types.json` exposes a top-level `markers` o
95
94
  }
96
95
  ```
97
96
 
98
- Entries store plain text only โ€” the SSOT is format-agnostic, so consumers apply their own emphasis (Markdown bold, ANSI escape, HTML `<strong>`) when constructing the rendered form. release-kit's own renderer constructs the per-bullet prefix as `${emoji} **${label}:** ` from this entry.
97
+ Entries store plain text only, so that the taxonomy stays format-agnostic: Consumers apply their own emphasis (Markdown bold, ANSI escape, HTML `<strong>`) when constructing the rendered form. release-kit's own renderer constructs the per-bullet prefix as `${emoji} **${label}:** ` from this entry.
99
98
 
100
99
  ## `fmt`
101
100
 
102
- `fmt:` commits are recognized by `parseCommitMessage`, but `fmt` carries `excludedFromChangelog: true`, which `DEFAULT_WORK_TYPES` carries onto its `WorkTypeConfig`. `classifyChangelogCommit` excludes a commit whose type sets the flag, so a `fmt:` commit never appears in `CHANGELOG.md`, `changelog.json`, or release notes. It yields no item, so it raises no bump, and the release report does not list it as unparseable. The label and emoji are present in `work-types.json` for schema parity with the codeassembly upstream but never render.
101
+ `fmt:` commits are recognized by `parseCommitMessage`, but `fmt` sets `excludedFromChangelog: true`, which `DEFAULT_WORK_TYPES` copies onto its `WorkTypeConfig`. `classifyChangelogCommit` excludes a commit whose type sets the flag, so a `fmt:` commit never appears in `CHANGELOG.md`, `changelog.json`, or release notes. Because it doesn't yield an item, it doesn't raise a bump, and the release report does not list it as unparseable. The taxonomy declares a label and emoji for `fmt` like any other type, but they never render.
103
102
 
104
103
  ## Custom work types
105
104
 
106
- Work types from a config are merged with these defaults by key: a consumer entry overrides or extends, it does not replace the full set. `classifyChangelogCommit` reads the merged record, so an added type reaches the changelog under the `header` it declares, and an entry setting `excludedFromChangelog: true` keeps its commits out of the changelog and the bump. Release-notes sections are rendered in the declaration order of the merged work-types record, with any unknown titles trailing the known ones.
105
+ Work types from a config are merged with these defaults by key: A consumer entry overrides or extends, it does not replace the full set. `classifyChangelogCommit` reads the merged record, so an added type appears in the changelog under the `header` that it declares, and an entry setting `excludedFromChangelog: true` keeps its commits out of the changelog and the bump. Release-notes sections are rendered in the declaration order of the merged work-types record, with any unknown titles trailing the known ones.
107
106
 
108
- The default `devOnlySections` (excluded from public release notes but still written to `CHANGELOG.md`) are derived from the `internal` and `process` tiers (excluding `fmt`). Override via `changelogJson.devOnlySections`; matching is decorator-tolerant, so a bare-name override like `['Internal features']` keeps working against the emoji-prefixed default titles.
109
-
110
- ## Maintaining the bundled taxonomy
111
-
112
- `src/workTypesData.ts` mirrors `src/work-types.json` for runtime use. Two maintainer scripts of the release-kit package compare that JSON with its codeassembly upstream and update it. They exist only in a checkout of this repository and are not part of the published `release-kit` CLI.
113
-
114
- - `nmr work-types:check` compares the content of `src/work-types.json` with the upstream, ignoring the local `$schema` hint.
115
- - `nmr work-types:sync` overwrites `src/work-types.json` with the upstream when their content differs.
116
-
117
- Both run from `packages/release-kit`, or from anywhere in the repository as `nmr -F @williamthorsen/release-kit work-types:check` and `nmr -F @williamthorsen/release-kit work-types:sync`. After `sync` writes the file, update `src/workTypesData.ts` to match; `workTypesData.unit.test.ts` fails until the two agree.
118
-
119
- `check` exits 0 on a match, 1 on drift, 2 when the upstream fetch fails, and 3 when either file is not valid JSON or the upstream fails the shape check. When the upstream URL returns 404, `check` exits 0 with a warning.
120
-
121
- ### Authenticated fetches
122
-
123
- The upstream codeassembly repo is public, so `check` and `sync` need no token. When `GITHUB_TOKEN` is set in the environment, both scripts send it as `Authorization: Bearer <token>`.
107
+ The default `devOnlySections` (excluded from public release notes but still written to `CHANGELOG.md`) are derived from the `internal` and `process` tiers (excluding `fmt`). Override via `changelogJson.devOnlySections`; matching is decorator-tolerant: A bare-name override like `['Internal features']` keeps working against the emoji-prefixed default titles.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@williamthorsen/release-kit",
3
- "version": "12.0.0",
3
+ "version": "13.0.0",
4
4
  "description": "Version-bumping and changelog-generation toolkit for release workflows",
5
5
  "keywords": [
6
6
  "changelog",
@@ -50,7 +50,8 @@
50
50
  "schemas/**"
51
51
  ],
52
52
  "dependencies": {
53
- "@williamthorsen/nmr-core": "0.13.0",
53
+ "@williamthorsen/change-grammar": "0.1.0",
54
+ "@williamthorsen/nmr-core": "0.13.1",
54
55
  "@williamthorsen/toolbelt.errors": "0.8.1",
55
56
  "json-stringify-pretty-compact": "4.0.0",
56
57
  "semver": "7.8.5",
@@ -62,8 +63,10 @@
62
63
  "@williamthorsen/toolbelt.filesystem": "0.9.1",
63
64
  "@williamthorsen/toolbelt.testing": "0.6.1",
64
65
  "@williamthorsen/toolbelt.vitest": "0.10.1",
66
+ "actionlint": "2.0.6",
67
+ "esbuild": "0.28.2",
65
68
  "readyup": "0.38.0",
66
- "smol-toml": "1.8.0",
69
+ "smol-toml": "1.9.0",
67
70
  "vitest": "4.1.11"
68
71
  },
69
72
  "engines": {
@@ -75,8 +78,6 @@
75
78
  },
76
79
  "scripts": {
77
80
  "build:post": "rdy compile",
78
- "build:pre": "rdy verify",
79
- "work-types:check": "node scripts/check-work-types.ts",
80
- "work-types:sync": "node scripts/sync-work-types.ts"
81
+ "build:pre": "rdy verify"
81
82
  }
82
83
  }