@williamthorsen/release-kit 10.7.0 โ†’ 10.8.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.
@@ -1,4 +1,32 @@
1
1
  [
2
+ {
3
+ "version": "10.8.0",
4
+ "date": "2026-09-15",
5
+ "sections": [
6
+ {
7
+ "title": "๐ŸŽ‰ Features",
8
+ "audience": "all",
9
+ "items": [
10
+ {
11
+ "description": "Allow the breaking marker on fix and perf commits (#818)",
12
+ "body": "- Makes `release-kit prepare` bump the major version for a `fix!` or `perf!` commit under the default breaking policies, instead of reporting a policy violation and bumping only the patch version while the changelog marked the entry breaking.\n- Corrects `docs/work-types.md` to state that a `breakingPolicies` map replaces the default policies rather than overriding individual types, and that release-kit treats every type omitted by the map as `optional`.",
13
+ "hash": "9d7bbe89deef6cdcedd999336e1cb8118bbf739f"
14
+ }
15
+ ]
16
+ },
17
+ {
18
+ "title": "๐Ÿ› Bug fixes",
19
+ "audience": "all",
20
+ "items": [
21
+ {
22
+ "description": "Omit the breaking marker from changelog entries whose type forbids it (#819)",
23
+ "body": "- Makes the configured `breakingPolicies` map decide whether the changelog marks a commit breaking, as it already decides the version bump.",
24
+ "hash": "6ff7414f6f826fc85ae3337c4ac6bd9e38fe6ef7"
25
+ }
26
+ ]
27
+ }
28
+ ]
29
+ },
2
30
  {
3
31
  "version": "10.7.0",
4
32
  "date": "2026-09-15",
@@ -98,7 +98,7 @@ function translateGlob(pattern) {
98
98
 
99
99
  // .readyup/kits/default.ts
100
100
  function getMinVersion() {
101
- const picked = { "version": "10.7.0" };
101
+ const picked = { "version": "10.8.0" };
102
102
  if (typeof picked["version"] !== "string") {
103
103
  throw new TypeError("release-kit/package.json: 'version' must be a string");
104
104
  }
@@ -13,7 +13,7 @@
13
13
  "path": "kits/default.ts"
14
14
  },
15
15
  {
16
- "hash": "a373c49d",
16
+ "hash": "a8611365",
17
17
  "kind": "inline",
18
18
  "path": "../package.json",
19
19
  "paths": [
@@ -46,7 +46,7 @@
46
46
  "readyupVersion": "0.36.0",
47
47
  "source": "kits/default.ts",
48
48
  "sourceHash": "55ff5731",
49
- "targetHash": "68976fc8"
49
+ "targetHash": "be5f29ad"
50
50
  },
51
51
  {
52
52
  "checklists": [
package/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 10.8.0 โ€” 2026-09-15
6
+
7
+ ### ๐ŸŽ‰ Features
8
+
9
+ - Allow the breaking marker on fix and perf commits (#818)
10
+
11
+ - Makes `release-kit prepare` bump the major version for a `fix!` or `perf!` commit under the default breaking policies, instead of reporting a policy violation and bumping only the patch version while the changelog marked the entry breaking.
12
+ - Corrects `docs/work-types.md` to state that a `breakingPolicies` map replaces the default policies rather than overriding individual types, and that release-kit treats every type omitted by the map as `optional`.
13
+
14
+ ### ๐Ÿ› Bug fixes
15
+
16
+ - Omit the breaking marker from changelog entries whose type forbids it (#819)
17
+
18
+ - Makes the configured `breakingPolicies` map decide whether the changelog marks a commit breaking, as it already decides the version bump.
19
+
5
20
  ## 10.7.0 โ€” 2026-09-15
6
21
 
7
22
  ### ๐ŸŽ‰ Features
package/README.md CHANGED
@@ -7,30 +7,20 @@ Version-bumping and changelog-generation toolkit for release workflows.
7
7
  Provides a self-contained CLI that auto-discovers workspaces from `pnpm-workspace.yaml`, parses conventional commits, determines version bumps, updates `package.json` files, and generates changelogs from `git-cliff --context` output rendered in-process (with optional [editorial overrides](docs/editorial-overrides.md)).
8
8
 
9
9
  <!-- section:release-notes -->
10
- ## Release notes โ€” v10.7.0 (2026-09-15)
10
+ ## Release notes โ€” v10.8.0 (2026-09-15)
11
11
 
12
12
  ### ๐ŸŽ‰ Features
13
13
 
14
- - Fix scaffold error handling and cover every scaffolded workflow (#806)
14
+ - Allow the breaking marker on fix and perf commits (#818)
15
15
 
16
- - Adds a `create-github-release.yaml matches template` check to release-kit's ReadyUp kit, which warns when `.github/workflows/create-github-release.yaml` differs from the template for the detected repo type and skips in a repo that has not installed that workflow.
17
- - Stops `v11y init --force` from replacing an existing `.config/v11y-check.config.json` with the defaults, which discarded the repo's thresholds and the allowlist maintained by `v11y sync`.
18
-
19
- Migration: To reset `.config/v11y-check.config.json` to its defaults, delete the file and run `v11y init`, because `v11y init --force` now overwrites only `.github/workflows/audit.yaml`.
16
+ - Makes `release-kit prepare` bump the major version for a `fix!` or `perf!` commit under the default breaking policies, instead of reporting a policy violation and bumping only the patch version while the changelog marked the entry breaking.
17
+ - Corrects `docs/work-types.md` to state that a `breakingPolicies` map replaces the default policies rather than overriding individual types, and that release-kit treats every type omitted by the map as `optional`.
20
18
 
21
19
  ### ๐Ÿ› Bug fixes
22
20
 
23
- - Gate the trusted-publisher checks on trust-query capability (#786)
24
-
25
- - Fixes the issue that the `npm-auto-publish` kit advised `npm trust github โ€ฆ` for every package when the npm session lacked the two-factor authentication that trust queries need.
26
-
27
- - Treat label names that differ only in case as one label (#808)
28
-
29
- - Fixes the issue that `sync-labels generate` wrote a preset's `bug` and a local `Bug` to `.github/labels.yaml` as two labels, which made the first sync fail to create the second and every later sync rename the existing label back and forth.
30
- - Lets a `labels` entry change a preset label's casing: `Bug` replaces a preset's `bug` and appears in the output as `Bug`, and `Bug: null` removes it.
31
- - Makes `labels` keys that differ only in case a config error that names each group, such as `'bug' and 'Bug'`, since folding them would discard all but one without any sign in the output.
21
+ - Omit the breaking marker from changelog entries whose type forbids it (#819)
32
22
 
33
- Migration: Keep one of any keys in the `labels` record that differ only in case, and delete the others.
23
+ - Makes the configured `breakingPolicies` map decide whether the changelog marks a commit breaking, as it already decides the version bump.
34
24
  <!-- /section:release-notes -->
35
25
 
36
26
  ## Installation
@@ -209,7 +209,7 @@ Subcommands:
209
209
  sync Overwrite the local work-types.json with the upstream contents
210
210
 
211
211
  Exit codes (check):
212
- 0 Match (or upstream missing โ€” transitional warning printed)
212
+ 0 Match (or upstream not found, with a warning)
213
213
  1 Drift detected
214
214
  2 Network error
215
215
  3 Schema mismatch
@@ -223,8 +223,8 @@ function showWorkTypesCheckHelp() {
223
223
  Usage: release-kit work-types check
224
224
 
225
225
  Compare the local work-types.json against the upstream codeassembly canonical and report
226
- drift. Exit 0 on match, 1 on drift, 0 + warning when upstream is missing (transitional),
227
- 2 on network error, 3 on schema mismatch.
226
+ drift. Exit 0 on match, 1 on drift, 0 + warning when upstream is not found, 2 on network
227
+ error, 3 on schema mismatch.
228
228
 
229
229
  Options:
230
230
  --help, -h Show this help message
@@ -23,4 +23,4 @@ export declare function stripGroupDecorations(group: string): string;
23
23
  * not whether git-cliff runs). This means dry-run exercises the full git-cliff toolchain and surfaces missing-binary,
24
24
  * malformed-config, and template-resolution failures earlier than before.
25
25
  */
26
- export declare function buildChangelogEntries(config: Pick<ReleaseConfig, 'cliffConfigPath' | 'changelogJson'>, tag: string, options?: GenerateChangelogOptions): ChangelogEntry[];
26
+ export declare function buildChangelogEntries(config: Pick<ReleaseConfig, 'breakingPolicies' | 'changelogJson' | 'cliffConfigPath' | 'workTypes'>, tag: string, options?: GenerateChangelogOptions): ChangelogEntry[];
@@ -1,8 +1,8 @@
1
1
  import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
2
2
  import { extractVersion } from "./changelogJsonUtils.js";
3
- import { DEFAULT_WORK_TYPES } from "./defaults.js";
3
+ import { DEFAULT_BREAKING_POLICIES, DEFAULT_WORK_TYPES } from "./defaults.js";
4
4
  import { extractMigration } from "./extractMigration.js";
5
- import { COMMIT_PREPROCESSOR_PATTERNS, PIPE_SCOPE_SOURCE } from "./parseCommitMessage.js";
5
+ import { COMMIT_PREPROCESSOR_PATTERNS, parseCommitMessage, PIPE_SCOPE_SOURCE } from "./parseCommitMessage.js";
6
6
  import { resolveCliffConfigPath } from "./resolveCliffConfigPath.js";
7
7
  import { runGitCliff } from "./runGitCliff.js";
8
8
  import { stripEmojiPrefix } from "./stripEmojiPrefix.js";
@@ -30,7 +30,9 @@ export function buildChangelogEntries(config, tag, options) {
30
30
  const contextJson = runGitCliff(resolvedConfigPath, cliffArgs);
31
31
  const releases = parseCliffContext(contextJson);
32
32
  const devOnlySections = new Set(config.changelogJson.devOnlySections);
33
- return transformReleases(releases, devOnlySections);
33
+ const workTypes = config.workTypes ?? DEFAULT_WORK_TYPES;
34
+ const breakingPolicies = config.breakingPolicies ?? DEFAULT_BREAKING_POLICIES;
35
+ return transformReleases(releases, devOnlySections, workTypes, breakingPolicies);
34
36
  }
35
37
  catch (error) {
36
38
  throw chainError(`Failed to build changelog entries for tag ${tag}`, error);
@@ -74,7 +76,7 @@ function toCliffContextCommit(value) {
74
76
  }
75
77
  return commit;
76
78
  }
77
- function transformReleases(releases, devOnlySections) {
79
+ function transformReleases(releases, devOnlySections, workTypes, breakingPolicies) {
78
80
  const entries = [];
79
81
  const devOnlyNormalised = new Set([...devOnlySections].map(stripGroupDecorations));
80
82
  for (const release of releases) {
@@ -89,7 +91,7 @@ function transformReleases(releases, devOnlySections) {
89
91
  const group = stripCommentPrefix(commit.group ?? 'Other');
90
92
  const description = extractDescription(commit.message);
91
93
  const body = extractBody(commit.message);
92
- const breaking = subjectHasBreakingMarker(commit.message);
94
+ const breaking = isBreakingUnderPolicy(commit, workTypes, breakingPolicies);
93
95
  let items = sectionMap.get(group);
94
96
  if (items === undefined) {
95
97
  items = [];
@@ -132,6 +134,13 @@ function transformReleases(releases, devOnlySections) {
132
134
  function stripCommentPrefix(group) {
133
135
  return group.replace(HTML_COMMENT_PREFIX_PATTERN, '');
134
136
  }
137
+ function isBreakingUnderPolicy(commit, workTypes, breakingPolicies) {
138
+ if (!subjectHasBreakingMarker(commit.message)) {
139
+ return false;
140
+ }
141
+ const parsed = parseCommitMessage(commit.message, commit.id ?? '', workTypes, undefined, { breakingPolicies });
142
+ return parsed?.breaking ?? true;
143
+ }
135
144
  const SUBJECT_BREAKING_MARKER_PATTERN = new RegExp(String.raw `^(?:${PIPE_SCOPE_SOURCE}\|)?\w+(?:\([^)]+\))?!:`);
136
145
  function subjectHasBreakingMarker(message) {
137
146
  let subject = message.split('\n', 1)[0] ?? '';
@@ -4,7 +4,7 @@ export declare const UPSTREAM_WORK_TYPES_URL = "https://raw.githubusercontent.co
4
4
  export interface DriftCheckResult {
5
5
  /**
6
6
  * Process exit code semantics:
7
- * - `0` โ€” match (or upstream missing/transitional, with a warning).
7
+ * - `0` โ€” match (or upstream not found, with a warning).
8
8
  * - `1` โ€” drift detected.
9
9
  * - `2` โ€” network error.
10
10
  * - `3` โ€” schema mismatch (upstream JSON does not parse or fails the schema invariants).
@@ -27,8 +27,7 @@ export interface CheckWorkTypesDriftDependencies {
27
27
  *
28
28
  * Failure modes:
29
29
  * - Network error โ†’ exit 2 with a diagnostic.
30
- * - Upstream 404 โ†’ exit 0 with a transitional warning (codeassembly hasn't published the
31
- * JSON yet; the check is informational while migration is in flight).
30
+ * - Upstream 404 โ†’ exit 0 with a warning.
32
31
  * - Schema mismatch โ†’ exit 3 with a diagnostic.
33
32
  * - Drift โ†’ exit 1 with a unified-diff-style message.
34
33
  * - Match โ†’ exit 0.
@@ -38,7 +38,7 @@ export async function checkWorkTypesDrift(dependencies = {}) {
38
38
  if (response.status === 404) {
39
39
  return {
40
40
  exitCode: 0,
41
- message: `Upstream work-types.json not yet published at ${url}; skipping drift check.`,
41
+ message: `Upstream work-types.json not found at ${url}; skipping drift check.`,
42
42
  };
43
43
  }
44
44
  if (!response.ok) {
@@ -15,10 +15,9 @@ export type PolicyViolationSurface = 'prefix' | 'body';
15
15
  /**
16
16
  * Callback invoked when `parseCommitMessage` detects a `!`-policy violation.
17
17
  *
18
- * Two-tier policy: at write-time (commit-msg hook, OOS for ticket #355), violations are
19
- * rejected outright; at release-time (this parser), violations are warn-and-continue so
20
- * legacy log entries don't block releases. Callers (`decideRelease`, `releasePrepareMono`,
21
- * etc.) collect callback invocations and surface them in the release report.
18
+ * The parser warns and continues on a violation, so legacy log entries don't block releases.
19
+ * Callers (`decideRelease`, `releasePrepareMono`, etc.) collect callback invocations and
20
+ * surface them in the release report.
22
21
  */
23
22
  export type PolicyViolationHandler = (commit: Commit, type: string, surface: PolicyViolationSurface) => void;
24
23
  /** Optional configuration for `parseCommitMessage` beyond the core inputs. */
@@ -47,6 +46,6 @@ export interface ParseCommitMessageOptions {
47
46
  * `!`-policy enforcement is **release-time tolerant**: when the resolved type's policy
48
47
  * forbids `!`, the marker is dropped from the parse (`breaking: false`) and
49
48
  * `onPolicyViolation` is invoked. When the policy requires `!`, a bare type triggers the
50
- * same warning path. The strict write-time gate (commit-msg hook) is out of scope.
49
+ * same warning path.
51
50
  */
52
51
  export declare function parseCommitMessage(message: string, hash: string, workTypes: Record<string, WorkTypeConfig>, scopeAliases?: Record<string, string>, options?: ParseCommitMessageOptions): ParsedCommit | undefined;
@@ -11,19 +11,20 @@ export interface ChangelogItem {
11
11
  /**
12
12
  * Whether this item represents a breaking change.
13
13
  *
14
- * `true` when the commit subject carries the `!` prefix (e.g. `feat!:` or `drop(scope)!:`).
15
- * The `BREAKING CHANGE:` body footer is intentionally NOT considered here โ€” only the prefix
16
- * `!` marks a changelog item as breaking. Renderers prefix breaking-item bullets with the
17
- * marker constructed from `WORK_TYPES_DATA.markers.breaking` (rendered as `๐Ÿšจ **Breaking:** `
18
- * with the canonical SSOT values) to surface them prominently in release notes.
14
+ * `true` when the commit subject carries the `!` prefix (e.g. `feat!:` or `drop(scope)!:`) and
15
+ * the commit's work-type policy permits it, so the item agrees with the version bump.
16
+ * The `BREAKING CHANGE:` body footer is intentionally NOT considered here. Renderers prefix
17
+ * breaking-item bullets with the marker constructed from `WORK_TYPES_DATA.markers.breaking`
18
+ * (rendered as `๐Ÿšจ **Breaking:** ` with the canonical SSOT values) to surface them prominently
19
+ * in release notes.
19
20
  */
20
21
  breaking?: boolean;
21
22
  /**
22
23
  * Migration instruction for a consumer, taken from the `Migration:` paragraph of `body`.
23
24
  *
24
25
  * Derived from `body` wherever `body` is set, and absent where the body carries no labeled
25
- * paragraph. Independent of `breaking`: a `fix` that tightens validation cannot carry `!` under
26
- * the default breaking policy and still imposes a migration. `body` keeps the paragraph, so
26
+ * paragraph. Independent of `breaking`: a `deprecate` commit cannot carry `!` under the default
27
+ * breaking policy and still calls for a migration. `body` keeps the paragraph, so
27
28
  * `CHANGELOG.md` goes on rendering it in place.
28
29
  */
29
30
  migration?: string;
@@ -1,133 +1,149 @@
1
1
  {
2
2
  "$schema": "./work-types.schema.json",
3
+ "markers": {
4
+ "breaking": {
5
+ "emoji": "๐Ÿšจ",
6
+ "label": "Breaking"
7
+ }
8
+ },
3
9
  "tiers": ["public", "internal", "process"],
4
10
  "types": [
5
11
  {
6
- "tier": "public",
7
- "key": "feat",
8
12
  "aliases": ["feature"],
13
+ "breakingPolicy": "optional",
14
+ "description": "A change that gives consumers a new capability or extends an existing one.",
9
15
  "emoji": "๐ŸŽ‰",
16
+ "key": "feat",
10
17
  "label": "Features",
11
- "breakingPolicy": "optional"
18
+ "tier": "public"
12
19
  },
13
20
  {
14
- "tier": "public",
15
- "key": "drop",
16
21
  "aliases": [],
22
+ "breakingPolicy": "required",
23
+ "description": "A change that removes a capability or surface on which consumers depend.",
17
24
  "emoji": "๐Ÿชฆ",
25
+ "key": "drop",
18
26
  "label": "Removed",
19
- "breakingPolicy": "required"
27
+ "tier": "public"
20
28
  },
21
29
  {
22
- "tier": "public",
23
- "key": "deprecate",
24
30
  "aliases": [],
31
+ "breakingPolicy": "forbidden",
32
+ "description": "A change that marks a capability or surface for removal while the capability or surface keeps working.",
25
33
  "emoji": "๐Ÿ—‘๏ธ",
34
+ "key": "deprecate",
26
35
  "label": "Deprecated",
27
- "breakingPolicy": "forbidden"
36
+ "tier": "public"
28
37
  },
29
38
  {
30
- "tier": "public",
31
- "key": "fix",
32
39
  "aliases": ["bugfix"],
40
+ "breakingPolicy": "optional",
41
+ "description": "A change that corrects behavior that consumers meet and that differs from what was intended.",
33
42
  "emoji": "๐Ÿ›",
43
+ "key": "fix",
34
44
  "label": "Bug fixes",
35
- "breakingPolicy": "forbidden"
45
+ "tier": "public"
36
46
  },
37
47
  {
38
- "tier": "public",
39
- "key": "sec",
40
48
  "aliases": ["security"],
49
+ "breakingPolicy": "optional",
50
+ "description": "A change that closes a vulnerability or hardens a consumer-facing surface against attack.",
41
51
  "emoji": "๐Ÿ”’",
52
+ "key": "sec",
42
53
  "label": "Security",
43
- "breakingPolicy": "optional"
54
+ "tier": "public"
44
55
  },
45
56
  {
46
- "tier": "public",
47
- "key": "perf",
48
57
  "aliases": ["performance"],
58
+ "breakingPolicy": "optional",
59
+ "description": "A change that reduces the time, memory, or other resources that consumer-facing work uses, without changing its result.",
49
60
  "emoji": "โšก",
61
+ "key": "perf",
50
62
  "label": "Performance",
51
- "breakingPolicy": "forbidden"
63
+ "tier": "public"
52
64
  },
53
65
  {
54
- "tier": "internal",
55
- "key": "internal",
56
66
  "aliases": ["utility"],
67
+ "breakingPolicy": "forbidden",
68
+ "description": "A change that adds or extends a capability that consumers do not use directly, such as a helper or an internal module.",
57
69
  "emoji": "๐Ÿ—๏ธ",
70
+ "key": "internal",
58
71
  "label": "Internal features",
59
- "breakingPolicy": "forbidden"
72
+ "tier": "internal"
60
73
  },
61
74
  {
62
- "tier": "internal",
63
- "key": "refactor",
64
75
  "aliases": [],
76
+ "breakingPolicy": "forbidden",
77
+ "description": "A change that restructures existing code without changing its behavior.",
65
78
  "emoji": "โ™ป๏ธ",
79
+ "key": "refactor",
66
80
  "label": "Refactoring",
67
- "breakingPolicy": "forbidden"
81
+ "tier": "internal"
68
82
  },
69
83
  {
70
- "tier": "internal",
71
- "key": "tests",
72
84
  "aliases": ["test"],
85
+ "breakingPolicy": "forbidden",
86
+ "description": "A change confined to tests, test fixtures, or test helpers, including one that unblocks a build or a type check.",
73
87
  "emoji": "๐Ÿงช",
88
+ "key": "tests",
74
89
  "label": "Tests",
75
- "breakingPolicy": "forbidden"
90
+ "tier": "internal"
76
91
  },
77
92
  {
78
- "tier": "process",
79
- "key": "tooling",
80
93
  "aliases": [],
94
+ "breakingPolicy": "forbidden",
95
+ "description": "A change to the tools used to develop the project: build, lint, formatting, and release configuration, and development scripts.",
81
96
  "emoji": "โš™๏ธ",
97
+ "key": "tooling",
82
98
  "label": "Tooling",
83
- "breakingPolicy": "forbidden"
99
+ "tier": "process"
84
100
  },
85
101
  {
86
- "tier": "process",
87
- "key": "ci",
88
102
  "aliases": [],
103
+ "breakingPolicy": "forbidden",
104
+ "description": "A change to continuous-integration workflows and their configuration.",
89
105
  "emoji": "๐Ÿ‘ท",
106
+ "key": "ci",
90
107
  "label": "CI",
91
- "breakingPolicy": "forbidden"
108
+ "tier": "process"
92
109
  },
93
110
  {
94
- "tier": "process",
95
- "key": "deps",
96
111
  "aliases": ["dep"],
112
+ "breakingPolicy": "forbidden",
113
+ "description": "A change that adds, removes, or upgrades a dependency, including its lockfile and version-catalog entries.",
97
114
  "emoji": "๐Ÿ“ฆ",
115
+ "key": "deps",
98
116
  "label": "Dependencies",
99
- "breakingPolicy": "forbidden"
117
+ "tier": "process"
100
118
  },
101
119
  {
102
- "tier": "process",
103
- "key": "ai",
104
120
  "aliases": [],
121
+ "breakingPolicy": "forbidden",
122
+ "description": "A change to guidance on working in the repository itself, such as its `AGENTS.md` or a repo-local skill, or to the repository's agent configuration.",
105
123
  "emoji": "๐Ÿค–",
124
+ "key": "ai",
106
125
  "label": "Agentic support",
107
- "breakingPolicy": "forbidden"
126
+ "tier": "process"
108
127
  },
109
128
  {
110
- "tier": "process",
111
- "key": "docs",
112
129
  "aliases": ["doc"],
130
+ "breakingPolicy": "forbidden",
131
+ "description": "A change to documentation for human readers, such as a README, a guide, or a code comment.",
113
132
  "emoji": "๐Ÿ“š",
133
+ "key": "docs",
114
134
  "label": "Documentation",
115
- "breakingPolicy": "forbidden"
135
+ "tier": "process"
116
136
  },
117
137
  {
118
- "tier": "process",
119
- "key": "fmt",
120
138
  "aliases": [],
139
+ "breakingPolicy": "forbidden",
140
+ "description": "A change to formatting alone, such as whitespace or line wrapping, that leaves the content unchanged.",
121
141
  "emoji": "๐ŸŽจ",
142
+ "excludedFromChangelog": true,
143
+ "key": "fmt",
122
144
  "label": "Formatting",
123
- "breakingPolicy": "forbidden",
124
- "excludedFromChangelog": true
145
+ "tier": "process"
125
146
  }
126
147
  ],
127
- "markers": {
128
- "breaking": {
129
- "emoji": "๐Ÿšจ",
130
- "label": "Breaking"
131
- }
132
- }
148
+ "version": "1.1.0"
133
149
  }
@@ -4,13 +4,18 @@
4
4
  "title": "Work types",
5
5
  "description": "Canonical taxonomy of commit work types used by release-kit. Drives section ordering, audience classification, and the `!` (breaking) policy. The structured single-source-of-truth for everything that previously lived in `DEFAULT_WORK_TYPES` and `cliff.toml.template` group definitions.",
6
6
  "type": "object",
7
- "required": ["tiers", "types", "markers"],
7
+ "required": ["tiers", "types", "markers", "version"],
8
8
  "additionalProperties": false,
9
9
  "properties": {
10
10
  "$schema": {
11
11
  "type": "string",
12
12
  "description": "Optional reference to this schema for IDE validation."
13
13
  },
14
+ "version": {
15
+ "type": "string",
16
+ "description": "Semantic version of the data shape. Bumped when the structure changes in a way that breaks downstream consumers.",
17
+ "pattern": "^\\d+\\.\\d+\\.\\d+$"
18
+ },
14
19
  "tiers": {
15
20
  "type": "array",
16
21
  "description": "Render tiers in canonical order. Each entry's `tier` field must reference one of these names.",
@@ -55,7 +60,7 @@
55
60
  },
56
61
  "workType": {
57
62
  "type": "object",
58
- "required": ["tier", "key", "aliases", "emoji", "label", "breakingPolicy"],
63
+ "required": ["tier", "key", "aliases", "emoji", "label", "breakingPolicy", "description"],
59
64
  "additionalProperties": false,
60
65
  "properties": {
61
66
  "tier": {
@@ -84,6 +89,11 @@
84
89
  "description": "Human-readable section label (rendered as `${emoji} ${label}` in changelogs and release notes).",
85
90
  "minLength": 1
86
91
  },
92
+ "description": {
93
+ "type": "string",
94
+ "description": "The changes that the type covers, read by an agent choosing a type for a change.",
95
+ "minLength": 1
96
+ },
87
97
  "breakingPolicy": {
88
98
  "type": "string",
89
99
  "description": "Policy governing the `!` (breaking) marker for this type. `forbidden`: `!` is a policy violation. `optional`: `!` is allowed. `required`: bare type is a policy violation; only the `!` form is accepted.",
@@ -1,15 +1,13 @@
1
1
  /**
2
- * Canonical work-types data โ€” runtime mirror of `work-types.json`.
2
+ * Canonical work-types data: the runtime mirror of `work-types.json`.
3
3
  *
4
- * `work-types.json` is the canonical SSOT for the taxonomy and is the file
5
- * `release-kit work-types check` / `:sync` operate on. This `.ts` mirror exists for
6
- * runtime consumption: ESM JSON imports require an `import attributes` clause that
7
- * esbuild's `bundle: false` mode strips, so a plain TS module is the most reliable way
8
- * to ship the data to consumers.
4
+ * `work-types.json` is release-kit's copy of the codeassembly canonical. This `.ts` mirror exists for
5
+ * runtime consumption: The compiler baseline does not enable JSON module imports, and a TS constant keeps
6
+ * `breakingPolicy` typed as its three-value union.
9
7
  *
10
- * A drift test (`workTypesData.unit.test.ts`) asserts that this constant deep-equals the
11
- * parsed contents of `work-types.json`. To change the taxonomy, edit BOTH files (or run
12
- * `nmr work-types:sync` to pull from upstream).
8
+ * `nmr work-types:sync` updates only the JSON, so copy its change here by hand. A drift test
9
+ * (`workTypesData.unit.test.ts`) asserts that this constant deep-equals the parsed contents of
10
+ * `work-types.json`.
13
11
  */
14
12
  /** Schema for a single entry. */
15
13
  export interface WorkTypeEntry {
@@ -18,6 +16,7 @@ export interface WorkTypeEntry {
18
16
  aliases: string[];
19
17
  emoji: string;
20
18
  label: string;
19
+ description: string;
21
20
  breakingPolicy: 'forbidden' | 'optional' | 'required';
22
21
  excludedFromChangelog?: boolean;
23
22
  }
@@ -34,6 +33,8 @@ export interface MarkerEntry {
34
33
  }
35
34
  /** Schema for the full data. */
36
35
  export interface WorkTypesData {
36
+ /** Semantic version of the data shape, as declared by the upstream canonical. */
37
+ version: string;
37
38
  tiers: string[];
38
39
  types: WorkTypeEntry[];
39
40
  /**
@@ -1,18 +1,60 @@
1
1
  export const WORK_TYPES_DATA = {
2
+ version: '1.1.0',
2
3
  tiers: ['public', 'internal', 'process'],
3
4
  types: [
4
- { tier: 'public', key: 'feat', aliases: ['feature'], emoji: '๐ŸŽ‰', label: 'Features', breakingPolicy: 'optional' },
5
- { tier: 'public', key: 'drop', aliases: [], emoji: '๐Ÿชฆ', label: 'Removed', breakingPolicy: 'required' },
6
- { tier: 'public', key: 'deprecate', aliases: [], emoji: '๐Ÿ—‘๏ธ', label: 'Deprecated', breakingPolicy: 'forbidden' },
7
- { tier: 'public', key: 'fix', aliases: ['bugfix'], emoji: '๐Ÿ›', label: 'Bug fixes', breakingPolicy: 'forbidden' },
8
- { tier: 'public', key: 'sec', aliases: ['security'], emoji: '๐Ÿ”’', label: 'Security', breakingPolicy: 'optional' },
5
+ {
6
+ tier: 'public',
7
+ key: 'feat',
8
+ aliases: ['feature'],
9
+ emoji: '๐ŸŽ‰',
10
+ label: 'Features',
11
+ description: 'A change that gives consumers a new capability or extends an existing one.',
12
+ breakingPolicy: 'optional',
13
+ },
14
+ {
15
+ tier: 'public',
16
+ key: 'drop',
17
+ aliases: [],
18
+ emoji: '๐Ÿชฆ',
19
+ label: 'Removed',
20
+ description: 'A change that removes a capability or surface on which consumers depend.',
21
+ breakingPolicy: 'required',
22
+ },
23
+ {
24
+ tier: 'public',
25
+ key: 'deprecate',
26
+ aliases: [],
27
+ emoji: '๐Ÿ—‘๏ธ',
28
+ label: 'Deprecated',
29
+ description: 'A change that marks a capability or surface for removal while the capability or surface keeps working.',
30
+ breakingPolicy: 'forbidden',
31
+ },
32
+ {
33
+ tier: 'public',
34
+ key: 'fix',
35
+ aliases: ['bugfix'],
36
+ emoji: '๐Ÿ›',
37
+ label: 'Bug fixes',
38
+ description: 'A change that corrects behavior that consumers meet and that differs from what was intended.',
39
+ breakingPolicy: 'optional',
40
+ },
41
+ {
42
+ tier: 'public',
43
+ key: 'sec',
44
+ aliases: ['security'],
45
+ emoji: '๐Ÿ”’',
46
+ label: 'Security',
47
+ description: 'A change that closes a vulnerability or hardens a consumer-facing surface against attack.',
48
+ breakingPolicy: 'optional',
49
+ },
9
50
  {
10
51
  tier: 'public',
11
52
  key: 'perf',
12
53
  aliases: ['performance'],
13
54
  emoji: 'โšก',
14
55
  label: 'Performance',
15
- breakingPolicy: 'forbidden',
56
+ description: 'A change that reduces the time, memory, or other resources that consumer-facing work uses, without changing its result.',
57
+ breakingPolicy: 'optional',
16
58
  },
17
59
  {
18
60
  tier: 'internal',
@@ -20,6 +62,7 @@ export const WORK_TYPES_DATA = {
20
62
  aliases: ['utility'],
21
63
  emoji: '๐Ÿ—๏ธ',
22
64
  label: 'Internal features',
65
+ description: 'A change that adds or extends a capability that consumers do not use directly, such as a helper or an internal module.',
23
66
  breakingPolicy: 'forbidden',
24
67
  },
25
68
  {
@@ -28,19 +71,61 @@ export const WORK_TYPES_DATA = {
28
71
  aliases: [],
29
72
  emoji: 'โ™ป๏ธ',
30
73
  label: 'Refactoring',
74
+ description: 'A change that restructures existing code without changing its behavior.',
75
+ breakingPolicy: 'forbidden',
76
+ },
77
+ {
78
+ tier: 'internal',
79
+ key: 'tests',
80
+ aliases: ['test'],
81
+ emoji: '๐Ÿงช',
82
+ label: 'Tests',
83
+ description: 'A change confined to tests, test fixtures, or test helpers, including one that unblocks a build or a type check.',
84
+ breakingPolicy: 'forbidden',
85
+ },
86
+ {
87
+ tier: 'process',
88
+ key: 'tooling',
89
+ aliases: [],
90
+ emoji: 'โš™๏ธ',
91
+ label: 'Tooling',
92
+ description: 'A change to the tools used to develop the project: build, lint, formatting, and release configuration, and development scripts.',
93
+ breakingPolicy: 'forbidden',
94
+ },
95
+ {
96
+ tier: 'process',
97
+ key: 'ci',
98
+ aliases: [],
99
+ emoji: '๐Ÿ‘ท',
100
+ label: 'CI',
101
+ description: 'A change to continuous-integration workflows and their configuration.',
102
+ breakingPolicy: 'forbidden',
103
+ },
104
+ {
105
+ tier: 'process',
106
+ key: 'deps',
107
+ aliases: ['dep'],
108
+ emoji: '๐Ÿ“ฆ',
109
+ label: 'Dependencies',
110
+ description: 'A change that adds, removes, or upgrades a dependency, including its lockfile and version-catalog entries.',
111
+ breakingPolicy: 'forbidden',
112
+ },
113
+ {
114
+ tier: 'process',
115
+ key: 'ai',
116
+ aliases: [],
117
+ emoji: '๐Ÿค–',
118
+ label: 'Agentic support',
119
+ description: "A change to guidance on working in the repository itself, such as its `AGENTS.md` or a repo-local skill, or to the repository's agent configuration.",
31
120
  breakingPolicy: 'forbidden',
32
121
  },
33
- { tier: 'internal', key: 'tests', aliases: ['test'], emoji: '๐Ÿงช', label: 'Tests', breakingPolicy: 'forbidden' },
34
- { tier: 'process', key: 'tooling', aliases: [], emoji: 'โš™๏ธ', label: 'Tooling', breakingPolicy: 'forbidden' },
35
- { tier: 'process', key: 'ci', aliases: [], emoji: '๐Ÿ‘ท', label: 'CI', breakingPolicy: 'forbidden' },
36
- { tier: 'process', key: 'deps', aliases: ['dep'], emoji: '๐Ÿ“ฆ', label: 'Dependencies', breakingPolicy: 'forbidden' },
37
- { tier: 'process', key: 'ai', aliases: [], emoji: '๐Ÿค–', label: 'Agentic support', breakingPolicy: 'forbidden' },
38
122
  {
39
123
  tier: 'process',
40
124
  key: 'docs',
41
125
  aliases: ['doc'],
42
126
  emoji: '๐Ÿ“š',
43
127
  label: 'Documentation',
128
+ description: 'A change to documentation for human readers, such as a README, a guide, or a code comment.',
44
129
  breakingPolicy: 'forbidden',
45
130
  },
46
131
  {
@@ -49,6 +134,7 @@ export const WORK_TYPES_DATA = {
49
134
  aliases: [],
50
135
  emoji: '๐ŸŽจ',
51
136
  label: 'Formatting',
137
+ description: 'A change to formatting alone, such as whitespace or line wrapping, that leaves the content unchanged.',
52
138
  breakingPolicy: 'forbidden',
53
139
  excludedFromChangelog: true,
54
140
  },
@@ -8,13 +8,13 @@ Neither `CHANGELOG.md` nor `.meta/changelog.json` reaches a published tarball on
8
8
 
9
9
  Each item under a section in `.meta/changelog.json` carries one required field and four 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. |
14
- | `body` | `string` | The commit body, with trailing trailer metadata stripped. |
15
- | `breaking` | `boolean` | Present and `true` where the commit subject carried the `!` prefix. 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, and the key on which an override entry matches. Absent on synthetic propagation entries. |
11
+ | Field | Type | Meaning |
12
+ | ------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
13
+ | `description` | `string` | The bullet headline, taken from the commit subject with the ticket ID and type prefix stripped. |
14
+ | `body` | `string` | The commit body, with trailing trailer metadata stripped. |
15
+ | `breaking` | `boolean` | Present and `true` where the commit subject carried the `!` prefix and the commit's 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, and the key on which an override entry matches. Absent on synthetic propagation entries. |
18
18
 
19
19
  Every optional field is omitted rather than emitted as `null`, so a consumer tests for presence.
20
20
 
@@ -32,7 +32,7 @@ A commit body states a migration step as a paragraph opening with the literal la
32
32
 
33
33
  Three properties are worth knowing:
34
34
 
35
- - **It is independent of `breaking`.** A `fix` cannot carry `!` under the default breaking policy, and a `fix` that tightens validation still imposes a migration. Filter on `migration` to find every step; filter on `breaking` to find every breaking change.
35
+ - **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.
36
36
  - **`body` keeps the paragraph.** The field is an extraction, not a move, so `CHANGELOG.md` and `.meta/changelog.json` go on agreeing.
37
37
  - **It is derived, not authored.** `migration` is not a field an override file can set (see [File shape](editorial-overrides.md#file-shape)); it is re-derived from whatever `body` an override installs, and cleared where that body carries no labeled paragraph. To change the migration text, override `body`.
38
38
 
@@ -2,16 +2,16 @@
2
2
 
3
3
  The taxonomy against which release-kit parses commits: its tiers, the breaking-change policy, section markers, customization, and the commands that compare it with its upstream.
4
4
 
5
- The canonical taxonomy lives in `packages/release-kit/src/work-types.json` and is split into three tiers that drive section rendering and audience classification.
5
+ release-kit bundles a copy of the codeassembly canonical taxonomy in `packages/release-kit/src/work-types.json`, kept level by `release-kit work-types sync`. The taxonomy is split into three tiers that drive section rendering and audience classification.
6
6
 
7
7
  | Tier | Key | Header | Aliases | `!` policy |
8
8
  | -------- | ----------- | ------------------------- | ------------- | ------------ |
9
9
  | public | `feat` | ๐ŸŽ‰ Features | `feature` | optional |
10
10
  | public | `drop` | ๐Ÿชฆ Removed | | **required** |
11
11
  | public | `deprecate` | ๐Ÿ—‘๏ธ Deprecated | | forbidden |
12
- | public | `fix` | ๐Ÿ› Bug fixes | `bugfix` | forbidden |
12
+ | public | `fix` | ๐Ÿ› Bug fixes | `bugfix` | optional |
13
13
  | public | `sec` | ๐Ÿ”’ Security | `security` | optional |
14
- | public | `perf` | โšก Performance | `performance` | forbidden |
14
+ | public | `perf` | โšก Performance | `performance` | optional |
15
15
  | internal | `internal` | ๐Ÿ—๏ธ Internal features | `utility` | forbidden |
16
16
  | internal | `refactor` | โ™ป๏ธ Refactoring | | forbidden |
17
17
  | internal | `tests` | ๐Ÿงช Tests | `test` | forbidden |
@@ -38,18 +38,15 @@ Section render order is **tier order (`public` โ†’ `internal` โ†’ `process`), th
38
38
 
39
39
  Each work-type carries a `breakingPolicy` value:
40
40
 
41
- - `optional` (`feat`, `sec`) โ€” `!` is allowed; both `type:` and `type!:` parse cleanly.
42
- - `forbidden` (most types) โ€” `!` is a policy violation. The premise: types like `internal!`, `perf!`, `fix!` are contradictory; an internal change cannot break a consumer contract, a pure perf change preserves the contract, and a bug-fix is by definition not a contract change.
43
- - `required` (`drop`) โ€” bare `drop:` is a policy violation; only `drop!:` is accepted. The premise: removing a feature always breaks consumers; the `!` form makes that explicit.
41
+ - `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.
42
+ - `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.
43
+ - `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.
44
44
 
45
- ### Two-tier policy enforcement
45
+ ### Policy enforcement
46
46
 
47
- The `!` policy operates at two distinct levels with different semantics:
47
+ `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. Callers (`decideRelease` etc.) can collect these warnings and surface them in the release report. A single legacy `internal!` in a year-old log does not block releases.
48
48
 
49
- - **Write-time** (commit-msg hook) โ€” strict rejection. Policy violations are blocked at the gate where the author can act on them immediately. _Hook-based enforcement is tracked separately and is not yet shipped._
50
- - **Release-time** (`parseCommitMessage`) โ€” tolerant warn-and-continue. 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 a `onPolicyViolation` callback fires. Callers (`decideRelease` etc.) can collect these warnings and surface them in the release report. A single legacy `internal!` in a year-old log does not block releases.
51
-
52
- A `BREAKING CHANGE:` body footer on a `forbidden`-policy type triggers the same warning path as the prefix `!` does โ€” the spirit of the policy is "internal/perf/etc. cannot be breaking", which must apply to both surfaces.
49
+ 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.
53
50
 
54
51
  The release-prepare orchestrators (`releasePrepare`, `releasePrepareMono`, `releasePrepareProject`) apply `DEFAULT_BREAKING_POLICIES` automatically. Violations encountered while parsing each workspace's or project's commit window are collected onto the corresponding result's `policyViolations` field and rendered under the section in the prepare report:
55
52
 
@@ -62,17 +59,23 @@ arrays
62
59
  ๐Ÿ“ฆ 1.0.0 โ†’ 1.0.1 (patch)
63
60
  ```
64
61
 
65
- To customize, set `breakingPolicies` in `release-kit.config.ts` โ€” provide a partial map to override individual types, or `{}` to disable enforcement entirely (the parser falls back to `'optional'` for any missing type). Violations remain warnings, never failures.
62
+ To customize, set `breakingPolicies` in `release-kit.config.ts`. The map replaces the default policies rather than merging with them, and the parser treats any type that the map omits as `'optional'`: A config that changes one type lists every policy that it keeps, and `{}` disables enforcement entirely. Violations remain warnings, never failures.
66
63
 
67
64
  ## `๐Ÿšจ **Breaking:**` bullet marker
68
65
 
69
- Items whose commit subject carries the `!` prefix (e.g. `feat!`, `drop!`, `feat(api)!`) are rendered with a `๐Ÿšจ **Breaking:** ` prefix on the bullet:
66
+ 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:
70
67
 
71
68
  ```markdown
72
69
  - ๐Ÿšจ **Breaking:** Drop legacy /v1 endpoint
73
70
  ```
74
71
 
75
- Only the prefix `!` triggers this marker. A `BREAKING CHANGE:` body footer on its own does **not** retroactively mark a changelog item as breaking โ€” the changelog signal is tied to the commit-prefix policy. This avoids surprise breaking-marker appearances for older commits written under earlier conventions.
72
+ The marker agrees with the version bump:
73
+
74
+ - 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.
75
+ - The configured `breakingPolicies` map, `{}` included, decides which types permit the marker.
76
+ - A commit whose type the parser cannot resolve has no policy, so its `!` prefix alone decides.
77
+
78
+ 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.
76
79
 
77
80
  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.
78
81
 
@@ -104,18 +107,10 @@ The default `devOnlySections` (excluded from public release notes but still writ
104
107
 
105
108
  Manage the canonical work-types taxonomy used by changelog and release-notes generation.
106
109
 
107
- The check is non-blocking initially: until codeassembly publishes its `work-types.json`, the upstream URL returns 404 and `check` exits 0 with a warning. CI flip to a blocking check is tracked as a follow-up once the upstream ships.
110
+ `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.
108
111
 
109
112
  These commands are also exposed as `nmr work-types:check` / `nmr work-types:sync` from any package directory.
110
113
 
111
114
  ### Authenticated fetches
112
115
 
113
- When the upstream codeassembly repo is private, both `check` and `sync` need a GitHub token to fetch the canonical `work-types.json`. Set `GITHUB_TOKEN` in the environment and the commands send `Authorization: Bearer <token>` automatically; without it, requests are unauthenticated and a private upstream will return 404.
114
-
115
- ```sh
116
- # Source from `gh auth` for local runs:
117
- export GITHUB_TOKEN=$(gh auth token)
118
- pnpm exec release-kit work-types check
119
- ```
120
-
121
- The token needs `contents: read` on the codeassembly repo (fine-grained PAT scope) or the equivalent classic-PAT scope. A token without sufficient scope still produces a 404 โ€” same response as a missing upstream โ€” so a misconfigured token degrades to the transitional-warning path rather than failing loudly. CI wiring against private upstream is deferred until either codeassembly is publicly readable or a cross-repo PAT is provisioned as a workflow secret.
116
+ The upstream codeassembly repo is public, so `check` and `sync` need no token. When `GITHUB_TOKEN` is set in the environment, both commands send it as `Authorization: Bearer <token>`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@williamthorsen/release-kit",
3
- "version": "10.7.0",
3
+ "version": "10.8.0",
4
4
  "description": "Version-bumping and changelog-generation toolkit for release workflows",
5
5
  "keywords": [
6
6
  "changelog",