@sacho/sacho 0.1.0-dev.9 → 0.2.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.
package/README.md CHANGED
@@ -1,6 +1,27 @@
1
1
  Sacho: an opinionated changelog manager
2
2
  =======================================
3
3
 
4
+ [![GitHub Releases][GitHub Releases badge]][GitHub Releases]
5
+ [![crates.io][crates.io badge]][crates.io]
6
+ [![npm][npm badge]][npm]
7
+ [![GitHub Actions][GitHub Actions badge]][GitHub Actions]
8
+ [![License: GPL-3.0-only][GPL-3.0 badge]][GPL-3.0]
9
+ [![GitHub Sponsors][GitHub Sponsors badge]][GitHub Sponsors]
10
+
11
+ [GitHub Releases badge]: https://img.shields.io/github/v/release/dahlia/sacho?logo=github
12
+ [GitHub Releases]: https://github.com/dahlia/sacho/releases
13
+ [crates.io badge]: https://img.shields.io/crates/v/sacho?logo=rust
14
+ [crates.io]: https://crates.io/crates/sacho
15
+ [npm badge]: https://img.shields.io/npm/v/@sacho/sacho?logo=npm
16
+ [npm]: https://www.npmjs.com/package/@sacho/sacho
17
+ [GitHub Actions badge]: https://github.com/dahlia/sacho/actions/workflows/main.yaml/badge.svg
18
+ [GitHub Actions]: https://github.com/dahlia/sacho/actions/workflows/main.yaml
19
+ [GPL-3.0 badge]: https://img.shields.io/github/license/dahlia/sacho
20
+ [GPL-3.0]: https://www.gnu.org/licenses/gpl-3.0.html
21
+ [GitHub Sponsors badge]: https://img.shields.io/github/sponsors/dahlia?logo=githubsponsors
22
+ [GitHub Sponsors]: https://github.com/sponsors/dahlia
23
+
24
+
4
25
  Motivation
5
26
  ----------
6
27
 
@@ -18,7 +39,7 @@ towncrier assumes Python; changesets assumes an npm monorepo. Sacho is a single
18
39
  static binary with no runtime dependencies, usable in any repository regardless
19
40
  of language, and it does not even assume Git.
20
41
 
21
- [Sacho is opinionated.](./PHILOSOPHY.md) It enforces one fragment format,
42
+ [*Sacho is opinionated.*](./PHILOSOPHY.md) It enforces one fragment format,
22
43
  one output style, and one set of invariants. Configuration exists to describe
23
44
  your repository, not to customize the philosophy.
24
45
 
@@ -52,7 +73,51 @@ Without mise, npm, or Cargo, download the archive for your platform from
52
73
  *sacho* (*sacho.exe* on Windows) into a directory on your `PATH`.
53
74
 
54
75
  [mise]: https://mise.jdx.dev/
55
- [GitHub Releases]: https://github.com/dahlia/sacho/releases
76
+
77
+
78
+ Agent Skill
79
+ -----------
80
+
81
+ Sacho ships an [Agent Skill] that teaches AI coding agents, such as Claude Code,
82
+ how to adopt and operate Sacho: writing fragments for users, cutting releases,
83
+ and forward-porting fixes across maintenance branches.
84
+
85
+ In Claude Code, install it as a plugin. Add this repository as a plugin
86
+ marketplace, then install the `sacho` plugin:
87
+
88
+ ~~~~ text
89
+ /plugin marketplace add dahlia/sacho
90
+ /plugin install sacho@sacho
91
+ ~~~~
92
+
93
+ For other agents, or to install the skill without the plugin system, use the
94
+ [`skills`] CLI, which reads the skill straight from this repository:
95
+
96
+ ~~~~ sh
97
+ npx skills add dahlia/sacho
98
+ ~~~~
99
+
100
+ Add `-a claude-code` to target one agent, `-g` to install it globally, and
101
+ `--skill sacho` to select the skill by name.
102
+
103
+ The skill is also bundled inside the `@sacho/sacho` npm package, following the
104
+ [`skills-npm`] layout. In a project that depends on that package, run
105
+ `npx skills-npm setup` once to link bundled skills from *node\_modules* into
106
+ your agent on every install.
107
+
108
+ [Agent Skill]: https://agentskills.io/
109
+ [`skills`]: https://github.com/vercel-labs/skills
110
+ [`skills-npm`]: https://github.com/antfu/skills-npm
111
+
112
+
113
+ Documentation
114
+ -------------
115
+
116
+ The [*Sacho documentation*] walks through adoption, everyday use, releases,
117
+ CI, and version-control integration. It also explains Sacho's core concepts and
118
+ provides complete command and configuration references.
119
+
120
+ [*Sacho documentation*]: https://sacho.dev/guide/getting-started
56
121
 
57
122
 
58
123
  How it works
@@ -82,21 +147,37 @@ state that never shipped.
82
147
  Basic workflow
83
148
  --------------
84
149
 
85
- With `sacho` on your `PATH`, initialize it at the repository root and choose
86
- the version you are preparing:
150
+ With `sacho` on your `PATH`, initialize it at the repository root:
87
151
 
88
152
  ~~~~ sh
89
153
  sacho init
90
- sacho next 1.2.0
91
154
  ~~~~
92
155
 
93
156
  Initialization creates *sacho.toml*, *changes.d/*, and *CHANGES.md*. It also
94
157
  sets up the merge integration supported by the detected version-control
95
158
  system. The interactive setup can infer an issue-link template from the
96
- repository URL and offer to install a Git pre-commit hook. Repository
159
+ repository URL, offer headings found across an existing changelog as section
160
+ configuration, and offer to install a Git pre-commit hook. Repository
97
161
  integrations use the executable that ran `init`; pass
98
162
  `--integration-executable PATH` to use another executable.
99
163
 
164
+ If an existing *CHANGES.md* already has entries under `Unreleased` or
165
+ `Version X`, import them before creating new fragments:
166
+
167
+ ~~~~ sh
168
+ sacho import-unreleased
169
+ ~~~~
170
+
171
+ Sacho shows any normalization diff before writing. The import creates one root
172
+ fragment, or one fragment per populated configured section, records `X` as the
173
+ next version when present, and leaves every released section unchanged.
174
+
175
+ If the import did not infer a version, choose the version you are preparing:
176
+
177
+ ~~~~ sh
178
+ sacho next 1.2.0
179
+ ~~~~
180
+
100
181
  Create one fragment for each user-visible change, then edit the path printed by
101
182
  the command:
102
183
 
@@ -116,6 +197,7 @@ before committing:
116
197
 
117
198
  ~~~~ sh
118
199
  sacho fmt
200
+ sacho resolve-links # optional: pin final redirected reference URLs
119
201
  sacho preview
120
202
  sacho check
121
203
  ~~~~
@@ -128,15 +210,27 @@ staged Git changes or `sacho check --base <revision>` in CI.
128
210
 
129
211
  When the version is ready, compile and consume its fragments. This command
130
212
  releases the version stored by `sacho next`, uses the current local date, and
131
- starts the next version:
213
+ closes the materialized unreleased region:
132
214
 
133
215
  ~~~~ sh
134
- sacho release --next 1.3.0
216
+ sacho release
135
217
  ~~~~
136
218
 
137
219
  If no next version has been set, pass the release version explicitly, for
138
220
  example `sacho release 1.2.0`. Use `--date YYYY-MM-DD` when the release date
139
- must be supplied rather than taken from the local clock.
221
+ must be supplied rather than taken from the local clock. A repository with no
222
+ released changelog sections can cut its first release without fragments. Later
223
+ empty releases require an explicit `--allow-empty`.
224
+
225
+ Commit and tag that released-only state. When development resumes, start the
226
+ next cycle separately:
227
+
228
+ ~~~~ sh
229
+ sacho next 1.3.0
230
+ ~~~~
231
+
232
+ Pass `--next 1.3.0` to `sacho release` only when the release commit should also
233
+ open the next cycle and retain its empty materialized region.
140
234
 
141
235
  After committing a release, print its frozen Markdown section with `show`:
142
236
 
@@ -161,7 +255,8 @@ Version-control integration
161
255
  ---------------------------
162
256
 
163
257
  Sacho can enforce that commits changing configured source paths also carry a
164
- changelog fragment. Select the repository's VCS in *sacho.toml*:
258
+ changelog fragment. Select the repository's VCS and the paths to check in
259
+ *sacho.toml*:
165
260
 
166
261
  ~~~~ toml
167
262
  [vcs]
@@ -176,41 +271,6 @@ Git also supports `sacho check --staged`. The `none` preset explicitly skips
176
271
  VCS-backed checks while leaving fragment validation and changelog consistency
177
272
  checks enabled.
178
273
 
179
- Each preset supplies three subprocess commands. Repositories can override one
180
- command without repeating the other two:
181
-
182
- ~~~~ toml
183
- [vcs]
184
- preset = "git"
185
-
186
- [vcs.commands]
187
- message = ["my-vcs-wrapper", "message", "${commit}"]
188
- ~~~~
189
-
190
- The first array element is the executable and every remaining element is one
191
- literal argument; Sacho never invokes a shell. The commits command uses
192
- `${base}`, while changed-paths and message use `${commit}`. An overridden query
193
- is the complete query and does not invoke commands from its preset behind the
194
- scenes. The built-in Mercurial preset handles parent comparisons internally.
195
-
196
- The query output contracts are:
197
-
198
- - `commits` emits zero or more nonempty UTF-8 commit identifiers, oldest
199
- first and one per line. The final line may end in LF; CRLF is also
200
- accepted.
201
- - `changed-paths` emits concatenated NUL-terminated name-status records.
202
- Ordinary records are `A\0PATH\0`, `D\0PATH\0`, `M\0PATH\0`, or
203
- `T\0PATH\0`, where `T` denotes a file type change. Copy and rename records
204
- are `C[SIMILARITY]\0OLD_PATH\0NEW_PATH\0` and
205
- `R[SIMILARITY]\0OLD_PATH\0NEW_PATH\0`, where the optional similarity is a
206
- decimal percentage from 0 through 100. Status fields are UTF-8 and paths
207
- are nonempty repository-relative platform paths; path bytes need not be
208
- UTF-8 on Unix. Nonempty output ends in NUL. A copied or renamed fragment
209
- counts as new content only when similarity is present and below 100;
210
- omitting it does not satisfy fragment coverage.
211
- - `message` emits the complete UTF-8 commit message verbatim. Sacho does not
212
- trim it.
213
-
214
274
  `sacho init` installs Git merge attributes and drivers in Git repositories. In
215
275
  Mercurial repositories it installs an idempotent block in *.hg/hgrc* containing
216
276
  a successful-merge update hook and, when changelog materialization is enabled,
@@ -219,6 +279,12 @@ the changelog merge driver. These integrations invoke the executable that ran
219
279
  per-path merge-driver hook; after resolving a concurrent fragment merge, run
220
280
  `sacho sync --force`.
221
281
 
282
+ See [*CI and hooks*] for fragment coverage and [*Version control*] for preset,
283
+ merge-driver, and custom-query details.
284
+
285
+ [*CI and hooks*]: https://sacho.dev/guide/ci-and-hooks
286
+ [*Version control*]: https://sacho.dev/guide/version-control
287
+
222
288
 
223
289
  Etymology
224
290
  ---------
@@ -243,3 +309,13 @@ is the same insistence this tool makes against generating changelogs from
243
309
  commit messages after the fact.
244
310
 
245
311
  [1]: https://en.wikipedia.org/wiki/Veritable_Records_of_the_Joseon_Dynasty#Compilation_process
312
+
313
+
314
+ License
315
+ -------
316
+
317
+ Copyright (C) 2026 Hong Minhee.
318
+
319
+ Sacho is free software licensed under the GNU General Public License version 3
320
+ only. It comes with no warranty, to the extent permitted by law. See
321
+ [*LICENSE*](./LICENSE) for the full terms.
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@sacho/sacho",
3
- "version": "0.1.0-dev.9+eaee87c",
3
+ "version": "0.2.0",
4
4
  "description": "An opinionated changelog manager",
5
5
  "license": "GPL-3.0-only",
6
6
  "author": "Hong Minhee <hong@minhee.org>",
7
- "homepage": "https://github.com/dahlia/sacho",
7
+ "homepage": "https://sacho.dev/",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/dahlia/sacho.git"
@@ -17,18 +17,19 @@
17
17
  },
18
18
  "files": [
19
19
  "bin",
20
- "lib"
20
+ "lib",
21
+ "skills"
21
22
  ],
22
23
  "engines": {
23
24
  "node": ">=18"
24
25
  },
25
26
  "optionalDependencies": {
26
- "@sacho/sacho-aarch64-apple-darwin": "0.1.0-dev.9+eaee87c",
27
- "@sacho/sacho-aarch64-pc-windows-msvc": "0.1.0-dev.9+eaee87c",
28
- "@sacho/sacho-aarch64-unknown-linux-musl": "0.1.0-dev.9+eaee87c",
29
- "@sacho/sacho-x86_64-apple-darwin": "0.1.0-dev.9+eaee87c",
30
- "@sacho/sacho-x86_64-pc-windows-msvc": "0.1.0-dev.9+eaee87c",
31
- "@sacho/sacho-x86_64-unknown-linux-musl": "0.1.0-dev.9+eaee87c"
27
+ "@sacho/sacho-aarch64-apple-darwin": "0.2.0",
28
+ "@sacho/sacho-aarch64-pc-windows-msvc": "0.2.0",
29
+ "@sacho/sacho-aarch64-unknown-linux-musl": "0.2.0",
30
+ "@sacho/sacho-x86_64-apple-darwin": "0.2.0",
31
+ "@sacho/sacho-x86_64-pc-windows-msvc": "0.2.0",
32
+ "@sacho/sacho-x86_64-unknown-linux-musl": "0.2.0"
32
33
  },
33
34
  "publishConfig": {
34
35
  "access": "public",
@@ -0,0 +1,322 @@
1
+ ---
2
+ name: sacho
3
+ description: >-
4
+ Help a maintainer adopt and operate Sacho, the opinionated changelog manager
5
+ that keeps user-facing release notes as one small Markdown fragment per change
6
+ under changes.d/ and compiles them into CHANGES.md at release time. Use this
7
+ skill whenever a repository has a sacho.toml, a changes.d/ directory, or a
8
+ CHANGES.md built from fragments; whenever the user wants to add Sacho to a
9
+ project, write or edit a changelog entry for a change they just made, cut a
10
+ release, publish release notes, or wire `sacho check` into CI or a pre-commit
11
+ hook; whenever forward-porting a bugfix release across maintenance branches or
12
+ resolving a changelog merge conflict; and whenever `sacho check` or the
13
+ changelog merge driver reports a problem. Also use it any time the user
14
+ mentions Sacho, changes.d fragments, or asks how to write a changelog entry
15
+ for users instead of generating one from commit messages.
16
+ metadata:
17
+ type: reference
18
+ ---
19
+
20
+ Managing changelogs with Sacho
21
+ ==============================
22
+
23
+ Sacho keeps a project's release notes as one small Markdown file per change,
24
+ called a fragment, under `changes.d/`. At release time it sorts and compiles the
25
+ fragments into a dated section of `CHANGES.md`, then deletes the consumed
26
+ fragments. It works in any repository regardless of language, ships as a single
27
+ static binary, and does not even assume Git.
28
+
29
+ The single most important thing to hold onto: Sacho never generates entries from
30
+ commit messages, and neither should you. A commit message explains to
31
+ collaborators why a change was made; a changelog entry tells users what changed
32
+ and what to do when they upgrade. Those are different documents for different
33
+ readers. When you draft an entry, do not transform a diff or a `git log` line
34
+ into prose. Read the change, work out its effect on someone who only sees the
35
+ public surface, and write that. When a change has no user-visible effect, the
36
+ honest entry is no entry.
37
+
38
+
39
+ Delegate the details to the live docs
40
+ -------------------------------------
41
+
42
+ The commands and flags below are a working map, not the source of truth. For the
43
+ exact syntax of the version installed in this repository, trust `sacho --help`
44
+ and `sacho <command> --help` over anything written here or online, because the
45
+ docs track the latest release while a project may pin an older binary. Use the
46
+ Markdown documentation for concepts, workflow, and reasoning; reach for `--help`
47
+ before you depend on a specific flag or output format.
48
+
49
+ - `https://sacho.dev/llms.txt` lists every page.
50
+ `https://sacho.dev/llms-full.txt` is the whole documentation in one file
51
+ when you want to read broadly.
52
+ - Every page has a `.md` twin. Fetch these directly:
53
+ - `https://sacho.dev/why-sacho.md` and `https://sacho.dev/philosophy.md`
54
+ for the reasoning, worth reading before you write entries for a project.
55
+ - `https://sacho.dev/guide/getting-started.md` to adopt Sacho.
56
+ - `https://sacho.dev/guide/everyday-workflow.md` for the per-change loop.
57
+ - `https://sacho.dev/guide/releases.md` for cutting releases and
58
+ forward-porting.
59
+ - `https://sacho.dev/guide/ci-and-hooks.md` for coverage enforcement.
60
+ - `https://sacho.dev/guide/version-control.md` for merge drivers and
61
+ presets.
62
+ - `https://sacho.dev/concepts/fragments.md`,
63
+ `https://sacho.dev/concepts/changelog-lifecycle.md`, and
64
+ `https://sacho.dev/concepts/sections.md` for the model.
65
+ - `https://sacho.dev/reference/commands.md` and
66
+ `https://sacho.dev/reference/configuration.md` for exhaustive
67
+ references.
68
+ - `https://sacho.dev/troubleshooting.md` when something misbehaves.
69
+
70
+
71
+ Orient yourself before acting
72
+ -----------------------------
73
+
74
+ Check what the repository already has. Is there a `sacho.toml` at the root, a
75
+ `changes.d/` directory, a `CHANGES.md`? Is `sacho` on the `PATH`
76
+ (`sacho --version`)? A repository with `sacho.toml` is already set up, so skip
77
+ adoption and go to the workflow that fits the request. A repository without it
78
+ needs `sacho init` first, but only after confirming the maintainer wants to
79
+ adopt Sacho rather than asking about it.
80
+
81
+ If the binary is missing, the recommended install is
82
+ `mise use -g github:dahlia/sacho`; `npm install -g @sacho/sacho` and
83
+ `cargo install sacho` work too. Read `getting-started.md` for the full list.
84
+
85
+
86
+ Adopting Sacho in a project
87
+ ---------------------------
88
+
89
+ Run `sacho init` from the repository root. It creates `sacho.toml`,
90
+ `changes.d/`, and `CHANGES.md`, and in a Git repository registers the merge
91
+ drivers. Interactive setup infers issue-link templates from the remote and can
92
+ install commit hooks. When an existing changelog is present, it also offers
93
+ level-three headings found across the changelog as section candidates and
94
+ suggests directories and source path globs. `sacho init --interactive` forces
95
+ the questions and `--no-interactive` suppresses them.
96
+
97
+ Leave old released sections exactly as they are. If the current `Unreleased` or
98
+ `Version X` region contains entries, run `sacho import-unreleased` before
99
+ creating fragments. Inspect and confirm its normalization diff; in a
100
+ noninteractive workflow, review the diff from the refused run before using
101
+ `--force`. The command creates deterministic *imported-unreleased.md* fragments
102
+ and infers the next version from `Version X`. If there are no current entries,
103
+ skip the import and set the version with `sacho next 1.2.0`.
104
+
105
+ Two configuration decisions matter early, both covered in
106
+ `reference/configuration.md`: whether to enforce fragment coverage
107
+ (`[check].paths`), and, for a monorepo, whether to define `[[sections]]` so each
108
+ package's changes require a fragment in that package's section.
109
+
110
+
111
+ Writing a fragment for a change
112
+ -------------------------------
113
+
114
+ The everyday loop is four commands. Create the fragment, write it, format, and
115
+ check:
116
+
117
+ ~~~~ sh
118
+ sacho add clear-function # add --section <id> <name> in a sectioned repo
119
+ sacho fmt
120
+ sacho preview
121
+ sacho check
122
+ ~~~~
123
+
124
+ `sacho add` creates `changes.d/<name>.md` holding an empty list item and prints
125
+ the path. Name the file after the change itself (`clear-function`), never after
126
+ an issue or PR number. Topic names make the right follow-up natural: when a
127
+ later commit reworks the same feature, you edit the existing fragment instead of
128
+ adding a second one that documents a state users never saw.
129
+
130
+ Keep the fragment in the same commit series as the implementation. Stored in the
131
+ repository, it travels with the change through merges, rebases, cherry-picks,
132
+ and reverts for free. That coupling is the whole point of fragments, so never
133
+ park changelog text in an issue, a wiki, or a draft release note.
134
+
135
+
136
+ Writing entries users can actually read
137
+ ---------------------------------------
138
+
139
+ This is the part no tool can do for the maintainer, and where you earn your
140
+ keep. Fetch `philosophy.md` when you want the full argument; the working rules:
141
+
142
+ Address the person upgrading, in the vocabulary of the public surface. A
143
+ refactor that touches forty files but changes nothing users can observe is a
144
+ real commit and a nonexistent entry. A one-line dependency bump that closes a
145
+ vulnerability is a trivial commit and a critical entry.
146
+
147
+ Write one entry per user-visible change. If a release adds `remove_all()` and a
148
+ later commit in the same cycle renames it to `clear()`, the released changelog
149
+ contains a single line, `Added clear() function`, not the two-step history. Edit
150
+ the existing fragment; never narrate intermediate states that never shipped.
151
+
152
+ Be concise, and keep internal names out. Private type names, module paths, and
153
+ subsystem nicknames tell the reader this document is not for them. If a
154
+ mechanism must be mentioned, describe what it does rather than naming a thing
155
+ the reader cannot look up.
156
+
157
+ Start each entry with a past-tense verb: Added, Changed, Deprecated, Fixed,
158
+ Removed, Security. Sacho sorts by the entry text, so these verbs fall into the
159
+ familiar Keep a Changelog order without any category metadata.
160
+
161
+ Reference issues and PRs with shortcut links in trailing brackets, house style,
162
+ at the end of the first paragraph: `[[#842], [#848]]`. Attribution is inline
163
+ prose in the same bracket: `[[#857] by Lee Hoyeon]`. The compiler resolves the
164
+ numbers into link definitions using the templates in `[links]`, and `check`
165
+ fails on a reference no template can resolve.
166
+
167
+ Because Sacho asks a human to write, present your drafts to the maintainer for
168
+ approval rather than committing prose in their voice unreviewed. You are helping
169
+ them write for their users, not automating the writing away.
170
+
171
+
172
+ The fragment file format
173
+ ------------------------
174
+
175
+ A fragment is a UTF-8 Markdown file whose body is exactly one top-level
176
+ unordered list, with optional YAML frontmatter. Nothing else at the top level:
177
+ no headings, no stray paragraphs, no second list. Inside a list item any block
178
+ content is fine, including nested lists and fenced code. One item is usual;
179
+ related changes from one pull request can share a fragment as several items.
180
+
181
+ ~~~~ markdown
182
+ ---
183
+ priority: -10
184
+ ---
185
+
186
+ - Added `clear()` to remove every entry at once. The function accepts an
187
+ optional predicate to remove entries selectively. [[#842], [#848]]
188
+ ~~~~
189
+
190
+ `priority` (integer, default `0`) is the only frontmatter key; lower sorts
191
+ earlier, and most fragments need no frontmatter at all. See
192
+ `concepts/fragments.md` for the exact constraints.
193
+
194
+
195
+ Cutting a release
196
+ -----------------
197
+
198
+ Read the compiled result as a user would before you freeze it:
199
+
200
+ ~~~~ sh
201
+ sacho next 1.2.0
202
+ sacho preview
203
+ sacho check
204
+ ~~~~
205
+
206
+ In the preview, merge entries that describe the same change, drop development
207
+ history, and confirm each entry names public behavior. Then compile:
208
+
209
+ ~~~~ sh
210
+ sacho release --next 1.3.0 # reads the version from changes.d/next
211
+ sacho release 1.2.0 --next 1.3.0 # or state the version explicitly
212
+ sacho release 1.2.0 --date 2026-07-19 --next 1.3.0 # when the date must be fixed
213
+ ~~~~
214
+
215
+ `release` stamps the dated section, deletes the consumed fragments, and sets the
216
+ next version. The first release may proceed without fragments when the changelog
217
+ has no released version sections. Later releases refuse to proceed when the
218
+ fragments compile to nothing unless `--allow-empty` is passed. Empty list items
219
+ and HTML comments are scaffolding, not release notes, and scaffold-only
220
+ fragments require the explicit option even on the first release. Commit the
221
+ changed `CHANGES.md`, the removed fragments, and the updated `changes.d/next`
222
+ together.
223
+
224
+
225
+ Publishing release notes
226
+ ------------------------
227
+
228
+ After the release commit, `sacho show` prints one frozen section:
229
+
230
+ ~~~~ sh
231
+ sacho show 1.2.0
232
+ sacho show 1.2.0 --skip-heading --output-file release-notes.md
233
+ ~~~~
234
+
235
+ A tag-triggered GitHub Actions job can hand that file to the GitHub CLI:
236
+
237
+ ~~~~ sh
238
+ version="${GITHUB_REF_NAME#v}"
239
+ sacho show "$version" --skip-heading --output-file release-notes.md
240
+ gh release create "$GITHUB_REF_NAME" --notes-file release-notes.md
241
+ ~~~~
242
+
243
+ `show` reads only released sections; it never compiles current fragments.
244
+
245
+
246
+ Enforcing coverage in CI and hooks
247
+ ----------------------------------
248
+
249
+ Configure the paths whose changes require a fragment, then let a machine catch
250
+ the omissions reviewers miss:
251
+
252
+ ~~~~ toml
253
+ [check]
254
+ paths = ["src/**", "packages/**"]
255
+ ~~~~
256
+
257
+ Locally, `sacho check --staged` inspects the Git index;
258
+ `sacho init --install-hook` installs a pre-commit hook chain. In CI, check a
259
+ branch commit-by-commit against its base:
260
+
261
+ ~~~~ sh
262
+ sacho check --base origin/main
263
+ ~~~~
264
+
265
+ Make sure the checkout history includes the base revision; a shallow clone that
266
+ omits it cannot support the comparison. A commit that genuinely needs no entry
267
+ opts out with a `Changelog: none` trailer in its message (or the
268
+ `[skip changelog]` family of substrings). The exemption applies only to the
269
+ commit that carries it. Details in `guide/ci-and-hooks.md`.
270
+
271
+
272
+ Forward-porting a bugfix release across maintenance branches
273
+ ------------------------------------------------------------
274
+
275
+ The scenario: a fix lands on `1.1-maintenance`, `1.1.5` ships, and the fix needs
276
+ to reach `1.2-maintenance` and `main`. Two paths, and which one you are on
277
+ decides the work.
278
+
279
+ Merge before releasing, and there is nothing to do. If you merge
280
+ `1.1-maintenance` into `1.2-maintenance` while the fix's fragment still exists,
281
+ the VCS carries the fragment along, and each branch later consumes its own copy.
282
+ Prefer this ordering when you can; it needs no Sacho command.
283
+
284
+ Merge the release tag, and the fragment arrives already consumed. The incoming
285
+ `1.1.5` release commit deleted the fragment, so the merge brings only the frozen
286
+ `1.1.5` section. The merge driver inserts that section at its version position
287
+ in `CHANGES.md`, keeps the receiving branch's `changes.d/next`, and prints a
288
+ `sacho carry` hint on stderr. Now it is policy, not mechanics: if the receiving
289
+ branch's own next release should also list the fix, run `sacho carry 1.1.5` to
290
+ decompile that section back into `carried-from-1.1.5.md` fragments, edit them
291
+ if this branch presents the change differently, and treat them as ordinary
292
+ unreleased entries. If the imported `1.1.5` section is enough, do nothing.
293
+
294
+ `carry` is idempotent by design: it always writes the same
295
+ `carried-from-<version>.md` name and overwrites any file already there. That is
296
+ safe on a fresh carry, but if you have already edited those carried fragments,
297
+ re-running `carry` discards your edits, so check for existing
298
+ `carried-from-*.md` files before repeating it.
299
+
300
+ Under Jujutsu there is no per-path merge driver, so after resolving a concurrent
301
+ fragment merge run `sacho sync --force` and inspect the result.
302
+ `guide/releases.md` and `guide/version-control.md` cover both.
303
+
304
+
305
+ When something goes wrong
306
+ -------------------------
307
+
308
+ `sacho check` reports across three layers: fragment validity and formatting,
309
+ agreement between the materialized unreleased region and the fragments, and
310
+ fragment coverage for changed paths.
311
+
312
+ A formatting or drift failure is mechanically repairable. `sacho check --fix`
313
+ runs `fmt` and re-syncs the generated region; it will not write prose. If
314
+ `CHANGES.md` was hand-edited, remember that fragments are the source of truth:
315
+ move the edited text into a fragment, then `sacho sync`. Sacho asks before
316
+ discarding hand edits, and `sacho sync --force` skips that prompt, so read the
317
+ diff first.
318
+
319
+ A missing-fragment failure names the affected section and suggests `sacho add`.
320
+ Either write the entry or, if the commit truly has no user-visible effect, use
321
+ the `Changelog: none` exemption. For anything stranger, fetch
322
+ `troubleshooting.md`.