@sacho/sacho 0.1.0-dev.11 → 0.1.0-dev.13

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
@@ -55,6 +55,41 @@ Without mise, npm, or Cargo, download the archive for your platform from
55
55
  [GitHub Releases]: https://github.com/dahlia/sacho/releases
56
56
 
57
57
 
58
+ Agent Skill
59
+ -----------
60
+
61
+ Sacho ships an [Agent Skill] that teaches AI coding agents, such as Claude Code,
62
+ how to adopt and operate Sacho: writing fragments for users, cutting releases,
63
+ and forward-porting fixes across maintenance branches.
64
+
65
+ In Claude Code, install it as a plugin. Add this repository as a plugin
66
+ marketplace, then install the `sacho` plugin:
67
+
68
+ ~~~~ text
69
+ /plugin marketplace add dahlia/sacho
70
+ /plugin install sacho@sacho
71
+ ~~~~
72
+
73
+ For other agents, or to install the skill without the plugin system, use the
74
+ [`skills`] CLI, which reads the skill straight from this repository:
75
+
76
+ ~~~~ sh
77
+ npx skills add dahlia/sacho
78
+ ~~~~
79
+
80
+ Add `-a claude-code` to target one agent, `-g` to install it globally, and
81
+ `--skill sacho` to select the skill by name.
82
+
83
+ The skill is also bundled inside the `@sacho/sacho` npm package, following the
84
+ [`skills-npm`] layout. In a project that depends on that package, run
85
+ `npx skills-npm setup` once to link bundled skills from *node\_modules* into
86
+ your agent on every install.
87
+
88
+ [Agent Skill]: https://agentskills.io/
89
+ [`skills`]: https://github.com/vercel-labs/skills
90
+ [`skills-npm`]: https://github.com/antfu/skills-npm
91
+
92
+
58
93
  Documentation
59
94
  -------------
60
95
 
@@ -138,15 +173,27 @@ staged Git changes or `sacho check --base <revision>` in CI.
138
173
 
139
174
  When the version is ready, compile and consume its fragments. This command
140
175
  releases the version stored by `sacho next`, uses the current local date, and
141
- starts the next version:
176
+ closes the materialized unreleased region:
142
177
 
143
178
  ~~~~ sh
144
- sacho release --next 1.3.0
179
+ sacho release
145
180
  ~~~~
146
181
 
147
182
  If no next version has been set, pass the release version explicitly, for
148
183
  example `sacho release 1.2.0`. Use `--date YYYY-MM-DD` when the release date
149
- must be supplied rather than taken from the local clock.
184
+ must be supplied rather than taken from the local clock. A repository with no
185
+ released changelog sections can cut its first release without fragments. Later
186
+ empty releases require an explicit `--allow-empty`.
187
+
188
+ Commit and tag that released-only state. When development resumes, start the
189
+ next cycle separately:
190
+
191
+ ~~~~ sh
192
+ sacho next 1.3.0
193
+ ~~~~
194
+
195
+ Pass `--next 1.3.0` to `sacho release` only when the release commit should also
196
+ open the next cycle and retain its empty materialized region.
150
197
 
151
198
  After committing a release, print its frozen Markdown section with `show`:
152
199
 
@@ -225,3 +272,13 @@ is the same insistence this tool makes against generating changelogs from
225
272
  commit messages after the fact.
226
273
 
227
274
  [1]: https://en.wikipedia.org/wiki/Veritable_Records_of_the_Joseon_Dynasty#Compilation_process
275
+
276
+
277
+ License
278
+ -------
279
+
280
+ Copyright (C) 2026 Hong Minhee.
281
+
282
+ Sacho is free software licensed under the GNU General Public License version 3
283
+ only. It comes with no warranty, to the extent permitted by law. See
284
+ [*LICENSE*](./LICENSE) for the full terms.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sacho/sacho",
3
- "version": "0.1.0-dev.11+f8249e8",
3
+ "version": "0.1.0-dev.13+e6331b9",
4
4
  "description": "An opinionated changelog manager",
5
5
  "license": "GPL-3.0-only",
6
6
  "author": "Hong Minhee <hong@minhee.org>",
@@ -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.11+f8249e8",
27
- "@sacho/sacho-aarch64-pc-windows-msvc": "0.1.0-dev.11+f8249e8",
28
- "@sacho/sacho-aarch64-unknown-linux-musl": "0.1.0-dev.11+f8249e8",
29
- "@sacho/sacho-x86_64-apple-darwin": "0.1.0-dev.11+f8249e8",
30
- "@sacho/sacho-x86_64-pc-windows-msvc": "0.1.0-dev.11+f8249e8",
31
- "@sacho/sacho-x86_64-unknown-linux-musl": "0.1.0-dev.11+f8249e8"
27
+ "@sacho/sacho-aarch64-apple-darwin": "0.1.0-dev.13+e6331b9",
28
+ "@sacho/sacho-aarch64-pc-windows-msvc": "0.1.0-dev.13+e6331b9",
29
+ "@sacho/sacho-aarch64-unknown-linux-musl": "0.1.0-dev.13+e6331b9",
30
+ "@sacho/sacho-x86_64-apple-darwin": "0.1.0-dev.13+e6331b9",
31
+ "@sacho/sacho-x86_64-pc-windows-msvc": "0.1.0-dev.13+e6331b9",
32
+ "@sacho/sacho-x86_64-unknown-linux-musl": "0.1.0-dev.13+e6331b9"
32
33
  },
33
34
  "publishConfig": {
34
35
  "access": "public",
@@ -0,0 +1,317 @@
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; `sacho init --interactive` forces the questions and
93
+ `--no-interactive` suppresses them.
94
+
95
+ Adoption needs no migration. Sacho never rewrites existing released sections, so
96
+ leave the old `CHANGES.md` history exactly as it is and start using fragments
97
+ for the next version. Set that version with `sacho next 1.2.0` so the unreleased
98
+ heading and `release` know what is being prepared.
99
+
100
+ Two configuration decisions matter early, both covered in
101
+ `reference/configuration.md`: whether to enforce fragment coverage
102
+ (`[check].paths`), and, for a monorepo, whether to define `[[sections]]` so each
103
+ package's changes require a fragment in that package's section.
104
+
105
+
106
+ Writing a fragment for a change
107
+ -------------------------------
108
+
109
+ The everyday loop is four commands. Create the fragment, write it, format, and
110
+ check:
111
+
112
+ ~~~~ sh
113
+ sacho add clear-function # add --section <id> <name> in a sectioned repo
114
+ sacho fmt
115
+ sacho preview
116
+ sacho check
117
+ ~~~~
118
+
119
+ `sacho add` creates `changes.d/<name>.md` holding an empty list item and prints
120
+ the path. Name the file after the change itself (`clear-function`), never after
121
+ an issue or PR number. Topic names make the right follow-up natural: when a
122
+ later commit reworks the same feature, you edit the existing fragment instead of
123
+ adding a second one that documents a state users never saw.
124
+
125
+ Keep the fragment in the same commit series as the implementation. Stored in the
126
+ repository, it travels with the change through merges, rebases, cherry-picks,
127
+ and reverts for free. That coupling is the whole point of fragments, so never
128
+ park changelog text in an issue, a wiki, or a draft release note.
129
+
130
+
131
+ Writing entries users can actually read
132
+ ---------------------------------------
133
+
134
+ This is the part no tool can do for the maintainer, and where you earn your
135
+ keep. Fetch `philosophy.md` when you want the full argument; the working rules:
136
+
137
+ Address the person upgrading, in the vocabulary of the public surface. A
138
+ refactor that touches forty files but changes nothing users can observe is a
139
+ real commit and a nonexistent entry. A one-line dependency bump that closes a
140
+ vulnerability is a trivial commit and a critical entry.
141
+
142
+ Write one entry per user-visible change. If a release adds `remove_all()` and a
143
+ later commit in the same cycle renames it to `clear()`, the released changelog
144
+ contains a single line, `Added clear() function`, not the two-step history. Edit
145
+ the existing fragment; never narrate intermediate states that never shipped.
146
+
147
+ Be concise, and keep internal names out. Private type names, module paths, and
148
+ subsystem nicknames tell the reader this document is not for them. If a
149
+ mechanism must be mentioned, describe what it does rather than naming a thing
150
+ the reader cannot look up.
151
+
152
+ Start each entry with a past-tense verb: Added, Changed, Deprecated, Fixed,
153
+ Removed, Security. Sacho sorts by the entry text, so these verbs fall into the
154
+ familiar Keep a Changelog order without any category metadata.
155
+
156
+ Reference issues and PRs with shortcut links in trailing brackets, house style,
157
+ at the end of the first paragraph: `[[#842], [#848]]`. Attribution is inline
158
+ prose in the same bracket: `[[#857] by Lee Hoyeon]`. The compiler resolves the
159
+ numbers into link definitions using the templates in `[links]`, and `check`
160
+ fails on a reference no template can resolve.
161
+
162
+ Because Sacho asks a human to write, present your drafts to the maintainer for
163
+ approval rather than committing prose in their voice unreviewed. You are helping
164
+ them write for their users, not automating the writing away.
165
+
166
+
167
+ The fragment file format
168
+ ------------------------
169
+
170
+ A fragment is a UTF-8 Markdown file whose body is exactly one top-level
171
+ unordered list, with optional YAML frontmatter. Nothing else at the top level:
172
+ no headings, no stray paragraphs, no second list. Inside a list item any block
173
+ content is fine, including nested lists and fenced code. One item is usual;
174
+ related changes from one pull request can share a fragment as several items.
175
+
176
+ ~~~~ markdown
177
+ ---
178
+ priority: -10
179
+ ---
180
+
181
+ - Added `clear()` to remove every entry at once. The function accepts an
182
+ optional predicate to remove entries selectively. [[#842], [#848]]
183
+ ~~~~
184
+
185
+ `priority` (integer, default `0`) is the only frontmatter key; lower sorts
186
+ earlier, and most fragments need no frontmatter at all. See
187
+ `concepts/fragments.md` for the exact constraints.
188
+
189
+
190
+ Cutting a release
191
+ -----------------
192
+
193
+ Read the compiled result as a user would before you freeze it:
194
+
195
+ ~~~~ sh
196
+ sacho next 1.2.0
197
+ sacho preview
198
+ sacho check
199
+ ~~~~
200
+
201
+ In the preview, merge entries that describe the same change, drop development
202
+ history, and confirm each entry names public behavior. Then compile:
203
+
204
+ ~~~~ sh
205
+ sacho release --next 1.3.0 # reads the version from changes.d/next
206
+ sacho release 1.2.0 --next 1.3.0 # or state the version explicitly
207
+ sacho release 1.2.0 --date 2026-07-19 --next 1.3.0 # when the date must be fixed
208
+ ~~~~
209
+
210
+ `release` stamps the dated section, deletes the consumed fragments, and sets the
211
+ next version. The first release may proceed without fragments when the changelog
212
+ has no released version sections. Later releases refuse to proceed when the
213
+ fragments compile to nothing unless `--allow-empty` is passed. Empty list items
214
+ and HTML comments are scaffolding, not release notes, and scaffold-only
215
+ fragments require the explicit option even on the first release. Commit the
216
+ changed `CHANGES.md`, the removed fragments, and the updated `changes.d/next`
217
+ together.
218
+
219
+
220
+ Publishing release notes
221
+ ------------------------
222
+
223
+ After the release commit, `sacho show` prints one frozen section:
224
+
225
+ ~~~~ sh
226
+ sacho show 1.2.0
227
+ sacho show 1.2.0 --skip-heading --output-file release-notes.md
228
+ ~~~~
229
+
230
+ A tag-triggered GitHub Actions job can hand that file to the GitHub CLI:
231
+
232
+ ~~~~ sh
233
+ version="${GITHUB_REF_NAME#v}"
234
+ sacho show "$version" --skip-heading --output-file release-notes.md
235
+ gh release create "$GITHUB_REF_NAME" --notes-file release-notes.md
236
+ ~~~~
237
+
238
+ `show` reads only released sections; it never compiles current fragments.
239
+
240
+
241
+ Enforcing coverage in CI and hooks
242
+ ----------------------------------
243
+
244
+ Configure the paths whose changes require a fragment, then let a machine catch
245
+ the omissions reviewers miss:
246
+
247
+ ~~~~ toml
248
+ [check]
249
+ paths = ["src/**", "packages/**"]
250
+ ~~~~
251
+
252
+ Locally, `sacho check --staged` inspects the Git index;
253
+ `sacho init --install-hook` installs a pre-commit hook chain. In CI, check a
254
+ branch commit-by-commit against its base:
255
+
256
+ ~~~~ sh
257
+ sacho check --base origin/main
258
+ ~~~~
259
+
260
+ Make sure the checkout history includes the base revision; a shallow clone that
261
+ omits it cannot support the comparison. A commit that genuinely needs no entry
262
+ opts out with a `Changelog: none` trailer in its message (or the
263
+ `[skip changelog]` family of substrings). The exemption applies only to the
264
+ commit that carries it. Details in `guide/ci-and-hooks.md`.
265
+
266
+
267
+ Forward-porting a bugfix release across maintenance branches
268
+ ------------------------------------------------------------
269
+
270
+ The scenario: a fix lands on `1.1-maintenance`, `1.1.5` ships, and the fix needs
271
+ to reach `1.2-maintenance` and `main`. Two paths, and which one you are on
272
+ decides the work.
273
+
274
+ Merge before releasing, and there is nothing to do. If you merge
275
+ `1.1-maintenance` into `1.2-maintenance` while the fix's fragment still exists,
276
+ the VCS carries the fragment along, and each branch later consumes its own copy.
277
+ Prefer this ordering when you can; it needs no Sacho command.
278
+
279
+ Merge the release tag, and the fragment arrives already consumed. The incoming
280
+ `1.1.5` release commit deleted the fragment, so the merge brings only the frozen
281
+ `1.1.5` section. The merge driver inserts that section at its version position
282
+ in `CHANGES.md`, keeps the receiving branch's `changes.d/next`, and prints a
283
+ `sacho carry` hint on stderr. Now it is policy, not mechanics: if the receiving
284
+ branch's own next release should also list the fix, run `sacho carry 1.1.5` to
285
+ decompile that section back into `carried-from-1.1.5.md` fragments, edit them
286
+ if this branch presents the change differently, and treat them as ordinary
287
+ unreleased entries. If the imported `1.1.5` section is enough, do nothing.
288
+
289
+ `carry` is idempotent by design: it always writes the same
290
+ `carried-from-<version>.md` name and overwrites any file already there. That is
291
+ safe on a fresh carry, but if you have already edited those carried fragments,
292
+ re-running `carry` discards your edits, so check for existing
293
+ `carried-from-*.md` files before repeating it.
294
+
295
+ Under Jujutsu there is no per-path merge driver, so after resolving a concurrent
296
+ fragment merge run `sacho sync --force` and inspect the result.
297
+ `guide/releases.md` and `guide/version-control.md` cover both.
298
+
299
+
300
+ When something goes wrong
301
+ -------------------------
302
+
303
+ `sacho check` reports across three layers: fragment validity and formatting,
304
+ agreement between the materialized unreleased region and the fragments, and
305
+ fragment coverage for changed paths.
306
+
307
+ A formatting or drift failure is mechanically repairable. `sacho check --fix`
308
+ runs `fmt` and re-syncs the generated region; it will not write prose. If
309
+ `CHANGES.md` was hand-edited, remember that fragments are the source of truth:
310
+ move the edited text into a fragment, then `sacho sync`. Sacho asks before
311
+ discarding hand edits, and `sacho sync --force` skips that prompt, so read the
312
+ diff first.
313
+
314
+ A missing-fragment failure names the affected section and suggests `sacho add`.
315
+ Either write the entry or, if the commit truly has no user-visible effect, use
316
+ the `Changelog: none` exemption. For anything stranger, fetch
317
+ `troubleshooting.md`.