@kaisers-io/refs 0.12.0 → 0.13.1
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/CHANGELOG.md +181 -1
- package/dist/refs.mjs +60 -52
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,187 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [0.13.1] - 2026-09-12
|
|
9
|
+
|
|
10
|
+
### Upgrading
|
|
11
|
+
|
|
12
|
+
**A repository declaring a workspace pattern like `crates/*/js` will suddenly report packages refs
|
|
13
|
+
could not see before.** The pattern was classified as unsupported, which also made the whole scan
|
|
14
|
+
unreliable and stood the unregistered-package pass down for that ref. `refs doctor` may therefore
|
|
15
|
+
name members on a ref that reported nothing for months. They are not new; they were invisible.
|
|
16
|
+
Register the ones worth routing to, and leave the rest.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **Every drift finding that has a repair now prints it as a command.** `unregistered` has printed
|
|
21
|
+
a runnable `refs edit --create` since the probe shipped; `missing` and `relocated` described the
|
|
22
|
+
repair in prose and left the reader to construct it. The asymmetry was not deliberate — one of
|
|
23
|
+
the two repairs did not exist as a command at all. `refs doctor` now ends those findings with
|
|
24
|
+
`refs edit --package='<name>' --remove '<ref>'` and
|
|
25
|
+
`refs edit --package='<name>' '<ref>' 'path' '<new>'`, quoted for a shell like every other
|
|
26
|
+
command refs prints. A new path the configuration could not hold is still reported, without a command
|
|
27
|
+
that would only fail validation.
|
|
28
|
+
|
|
29
|
+
Every printed `refs edit` now attaches its option values with `=` and, where a positional would
|
|
30
|
+
otherwise begin with `-`, ends the options with `--`. Quoting gets a value past the shell; refs'
|
|
31
|
+
own parser reads it next, and `refs edit '<ref>' path '-new/pkg'` — a path `zPackagePath` accepts
|
|
32
|
+
— arrived correctly quoted and exited `2` with `unknown option '-new/pkg'`. The terminator is
|
|
33
|
+
printed only where it is needed, so appending `--json` to an ordinary printed line still works.
|
|
34
|
+
|
|
35
|
+
- **`refs edit <ref> --package <name> --remove` unregisters a package.** The counterpart to
|
|
36
|
+
`--create`, and the repair the `missing` finding needed: a package that left a repository's
|
|
37
|
+
workspaces leaves an entry behind that `refs resolve` keeps answering with, pointing at a path no
|
|
38
|
+
checkout has. The only instruction refs could give was to hand-edit `config.toml`. Removal
|
|
39
|
+
touches configuration alone and never consults a checkout — it has to work while the checkout is
|
|
40
|
+
stale, absent, or still carrying the directory. Removing the last entry drops the `packages`
|
|
41
|
+
table rather than leaving an empty one, which `add` and the drift probe read differently.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- **A workspace pattern whose wildcard is not in its last segment is expanded.** `crates/*/js` —
|
|
46
|
+
the spelling a Rust/JS monorepo uses — was reported as `unsupported_pattern` and every package
|
|
47
|
+
beneath it went undetected. That pattern costs one `readdir` and a literal probe per child, the
|
|
48
|
+
same shape `packages/*` has, so the budget that refused it was measuring the wrong thing. Two
|
|
49
|
+
wildcards still mean two levels and `packages/*/nested/*` is still refused. Measured on a
|
|
50
|
+
repository declaring `crates/*/js` and `turbopack/crates/*/js`: 41 packages detected instead of
|
|
51
|
+
37, with no diagnostics; three other monorepos detect exactly what they did before.
|
|
52
|
+
|
|
53
|
+
- **`tag_format` is detected from every tag, not from the top twenty of a refname sort.** Detection
|
|
54
|
+
counted formats among the first 20 tags of `git tag --sort=-version:refname`. That sort is
|
|
55
|
+
version-aware over the whole refname, so in a monorepo that tags per package it groups by prefix:
|
|
56
|
+
those twenty are one package's tags, not a sample of the repository's. Measured against two real
|
|
57
|
+
repositories, `refs add` proposed `create-astro@{version}` for a repository with 839
|
|
58
|
+
`astro@{version}` tags, and `keystatic@{version}` — a package whose last tag is `0.0.36` and whose
|
|
59
|
+
directory no longer exists — for one where `@keystatic/core@{version}` is the live package with 183 of
|
|
60
|
+
them. Both now resolve correctly, and the two repositories whose detection was already right are
|
|
61
|
+
unchanged.
|
|
62
|
+
|
|
63
|
+
A tag list that could not be read whole now yields no candidate at all rather than one derived
|
|
64
|
+
from whatever survived. `git tag` output is capped at the subprocess stream limit, and the note
|
|
65
|
+
saying so was written to stderr and never read — which did not matter while only twenty lines
|
|
66
|
+
were needed, and does once the count is a claim about all of them.
|
|
67
|
+
|
|
68
|
+
What this establishes is historical plurality, not which package is primary or still alive: a
|
|
69
|
+
retired package with a thousand tags still outvotes its replacement's hundred. The two-phase flow
|
|
70
|
+
shows the candidate for exactly that reason.
|
|
71
|
+
|
|
72
|
+
## [0.13.0] - 2026-09-12
|
|
73
|
+
|
|
74
|
+
### Upgrading
|
|
75
|
+
|
|
76
|
+
**`refs add <url> --description "…"` now refuses a source with workspace members.** It holds one
|
|
77
|
+
description, about the repository, and none for a package — and a package's description is no
|
|
78
|
+
longer read out of its manifest, so there is nothing for it to fall back on. Such a source exits
|
|
79
|
+
`3`, listing every package the proposal will need a description for and printing the two-phase
|
|
80
|
+
commands to run instead, with the source quoted into them. The shortcut still finalizes a
|
|
81
|
+
repository with no detected packages, and one whose only detected package is the root at `.`.
|
|
82
|
+
|
|
83
|
+
If a script calls the one-shot against a monorepo, it will start failing. The two-phase flow is the
|
|
84
|
+
replacement and always was the documented agent path.
|
|
85
|
+
|
|
86
|
+
**Package descriptions already in `config.toml` are left exactly as they are.** A description
|
|
87
|
+
imported from a manifest before this release stays; nothing distinguishes it from one someone
|
|
88
|
+
wrote, so nothing rewrites it. To review them: `refs show <ref> --packages --json`, and
|
|
89
|
+
`refs edit <ref> --package <name> description "…"` to replace one.
|
|
90
|
+
|
|
91
|
+
**A ref key containing an unpaired surrogate is now rejected.** No configuration written by refs
|
|
92
|
+
can contain one — TOML has no escape for it — so this only affects a hand-built key.
|
|
93
|
+
|
|
94
|
+
### Fixed
|
|
95
|
+
|
|
96
|
+
- **`refs add` says when it could not fully inspect a repository's declared workspaces.** A
|
|
97
|
+
repository declaring `packages/**` — an ordinary spelling the pattern classifier cannot expand —
|
|
98
|
+
produced a proposal missing every member, with no indication why. That is not self-explanatory:
|
|
99
|
+
a thin `packages` record is also what an ordinary repository produces, and a detected root makes
|
|
100
|
+
even a memberless result look complete. The configuration was then built without those packages,
|
|
101
|
+
and the drift probe returns immediately for a ref that configures none, so nothing downstream
|
|
102
|
+
could recover it either. Both `--dry-run` and the `--description` one-shot now carry a warning
|
|
103
|
+
naming what stopped the scan — an unexpandable pattern, an unreadable manifest, a candidate that
|
|
104
|
+
could not be inspected — alongside any clone warning rather than instead of it.
|
|
105
|
+
|
|
106
|
+
- **A drift probe that could not finish looking says so, instead of reporting `ok`.** The pass that
|
|
107
|
+
finds packages a checkout declares and the configuration does not have stands down whenever
|
|
108
|
+
workspace detection may have missed something — an unreadable manifest, a pattern the classifier
|
|
109
|
+
cannot expand. That conservatism is right: a second declaration of the same name could be behind
|
|
110
|
+
the obstacle, and naming one path from a partial view would prescribe something registration
|
|
111
|
+
might not do. What was wrong is that it then said nothing at all, so `refs doctor` answered
|
|
112
|
+
`config-drift: ok — every configured package path resolves` while an unregistered package sat in
|
|
113
|
+
the checkout. One malformed `package.json` anywhere in a monorepo was enough, permanently.
|
|
114
|
+
|
|
115
|
+
Both discovery passes now report the obstacle. `structure` gains `discovery_incomplete`, naming
|
|
116
|
+
what stopped them (`packages/c: manifest_unreadable`), and its `status` is `unknown` rather than
|
|
117
|
+
`ok`. The findings about configured entries are unaffected — that half never needed the scan to
|
|
118
|
+
begin.
|
|
119
|
+
|
|
120
|
+
- **A ref whose key carries `@`, a space or any non-ASCII character can be locked, and therefore
|
|
121
|
+
synced.** `zRefKey` admits every character but `/`, `\`, `%` and `:`; the lock alphabet is far
|
|
122
|
+
narrower, and the derived lock name was passed through unchanged. Such a ref could be added and
|
|
123
|
+
read but never locked — so `refs sync`, `refs remove`, `refs resolve`'s package verification and
|
|
124
|
+
`refs doctor`'s drift check all failed for it, with a message naming the lock name rather than
|
|
125
|
+
the ref. A name the lock alphabet will not accept now falls back to the same bounded digest form
|
|
126
|
+
a too-long name already used. Keys the alphabet does admit keep their readable name unchanged.
|
|
127
|
+
Reachable through a self-hosted url or a hand-edited `config.toml`; no forge allows these
|
|
128
|
+
characters in a repository path.
|
|
129
|
+
- **A ref key carrying an unpaired surrogate is rejected.** Such a character is a valid JavaScript
|
|
130
|
+
string and has no UTF-8 encoding, so everything that writes the key out — the checkout directory,
|
|
131
|
+
the digest a lock name can fall back to — silently substituted U+FFFD for it. Keys ending
|
|
132
|
+
U+D800, U+D801 and U+FFFD therefore shared one directory and one lock name, and two unrelated
|
|
133
|
+
refs would have serialized against each other. No real config could carry one: TOML has no
|
|
134
|
+
escape for an unpaired surrogate.
|
|
135
|
+
- **A failed sync no longer persists an unbounded amount of remote output into `state.json`.**
|
|
136
|
+
`last_error` holds the failure's message, most often git's own output — which quotes what git was
|
|
137
|
+
working on, ref names among them, chosen by the tracked repository. No persistence-specific limit
|
|
138
|
+
applied: an upstream with 120 conflicting tags produced a 47,816-character error, all of it
|
|
139
|
+
written into a file refs reads back on every command. A newly recorded message now keeps 1500
|
|
140
|
+
characters from the start and 500 from the end, with an exact count of what was dropped in
|
|
141
|
+
between — so both the command that failed and git's own closing hint survive. The result of the
|
|
142
|
+
run that produced the failure still carries the message as it arrived.
|
|
143
|
+
|
|
144
|
+
### Changed
|
|
145
|
+
|
|
146
|
+
- **Dependencies refreshed.** `zod` 4.5.2 → 4.6.2 — the only production dependency, and bundled
|
|
147
|
+
into the published CLI. The development toolchain moved too: `vitest` 4 → 5, `tsdown` 0.22 →
|
|
148
|
+
0.23 (rolldown 1.2.8), `oxlint`, `oxfmt` and `vite`. No behaviour depends on any of it.
|
|
149
|
+
- **A package's description is never read out of a manifest.** Workspace detection carried each
|
|
150
|
+
package's `description` from its `package.json`, and `refs add <source> --description "…"`
|
|
151
|
+
persisted it into `config.toml` — text written by whoever owns the upstream repository, crossing
|
|
152
|
+
into a file refs reads back as its own configuration and replays on every `refs list` and
|
|
153
|
+
`refs show`. Detection now carries a package's name and path and nothing else, which is what
|
|
154
|
+
`refs sync`'s `unregistered` finding has done since 0.12.0. Every description in a config entry
|
|
155
|
+
is written by someone who read the source.
|
|
156
|
+
|
|
157
|
+
The one-shot `refs add <source> --description "…"` therefore no longer registers workspace
|
|
158
|
+
members: it has one description, about the repository, and none for a package. It still
|
|
159
|
+
finalizes a source with no detected packages, or one whose only detected package is the
|
|
160
|
+
repository root at `.` — and there the text now wins over whatever the root manifest said about
|
|
161
|
+
itself. For anything else it exits `3`, naming every package and printing the two-phase commands
|
|
162
|
+
to run instead, with the source it was given quoted into them, and listing every package the
|
|
163
|
+
proposal will need a description for — the repository root included, which is not the set the
|
|
164
|
+
refusal is about but is the set `refs add --proposal` requires.
|
|
165
|
+
|
|
166
|
+
A refusal registers no ref. The checkout it cloned stays, and is now recorded as a pending add —
|
|
167
|
+
so for the next 24 hours `doctor` reports it as one instead of offering to delete a checkout the
|
|
168
|
+
printed recovery is about to reuse, and the clone mode actually used survives into the finalize.
|
|
169
|
+
That mode cannot be recovered from the checkout afterwards: git records
|
|
170
|
+
`remote.origin.promisor` and `partialclonefilter` from the requested filter, whether or not the
|
|
171
|
+
server honoured it.
|
|
172
|
+
|
|
173
|
+
Existing entries are untouched. A description imported from a manifest before this release stays
|
|
174
|
+
exactly as it is — nothing distinguishes it from one written by hand, so nothing rewrites it. To
|
|
175
|
+
review them: `refs show <ref> --packages --json`, and `refs edit <ref> --package <name>
|
|
176
|
+
description "…"` to replace one.
|
|
177
|
+
|
|
178
|
+
### Security
|
|
179
|
+
|
|
180
|
+
- `last_error` is documented as what it is — a failure message, usually git's own output, quoting
|
|
181
|
+
ref names the tracked repository chose — and `skills/refs/COMMANDS.md` now tells agents to read
|
|
182
|
+
it as untrusted evidence rather than as something refs vouches for.
|
|
183
|
+
- `SECURITY.md` claimed refs "never reads checkout content as configuration", which was not true of
|
|
184
|
+
manifest descriptions and is still not true of package names, paths, the default branch, or a
|
|
185
|
+
`tag_format` derived from real tags. The claim is now stated as what the code enforces: refs never
|
|
186
|
+
imports a description out of a manifest. The values that do still cross are named, along with the
|
|
187
|
+
fact that being structural does not make them safe to read — a package name is whatever the
|
|
188
|
+
manifest declares.
|
|
9
189
|
|
|
10
190
|
## [0.12.0] - 2026-09-08
|
|
11
191
|
|