@@ -7,6 +7,16 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
77
88## [ Unreleased]
99
10+ ## [ 0.24.0] — npm ` 0.24.0 ` · PyPI ` 0.24.0 ` · NuGet ` 0.24.0 ` · Maven ` 7.24.0 `
11+
12+ A coordinated ** MINOR** across all four registries. It is a MINOR rather than a PATCH
13+ because two of its changes are DEFAULT FLIPS, not corrections of previously-wrong
14+ behaviour: the Java Maven plugin now fails a build that a silently-empty model used to
15+ let pass, and Java and Python now follow symlinked directories where they previously did
16+ not. Pre-1.0, ` ^0.23.x ` resolves ` <0.24.0 ` , so a MINOR is adopted deliberately while a
17+ PATCH would be taken automatically on a routine update — the same call, for the same
18+ reason, as ` 0.21.0 ` .
19+
1020### Added — ` sources ` is read by all four CLI surfaces, plus ` meta init --config-only `
1121
1222` .metaobjects/config.json ` 's ` sources ` key stops being a Node-only concern. Adopter
@@ -51,8 +61,21 @@ guide: [`docs/features/metadata-sources.md`](docs/features/metadata-sources.md).
5161 including when a declared ` sources ` path is itself a symlink, or a symlink
5262 sits partway through a walked tree. TypeScript and C# already did; Java and
5363 Python now match (a symlinked ` sources ` path previously resolved to zero
54- files in Java, silently, exit 0). A symlink cycle is a loud error rather
55- than a hang. Gated by two new ` symlinks ` -bearing corpus cases.
64+ files in Java, silently, exit 0). A symlink CYCLE is a loud error in every
65+ port. Gated by three new ` symlinks ` -bearing corpus cases.
66+
67+ That last sentence was written before it was true, and the gap is worth
68+ recording because "a loud error rather than a hang" understated what C# did.
69+ ` Directory.EnumerateFiles(dir, "*", AllDirectories) ` follows symlinks but has
70+ no loop guard, and its ` EnumerationOptions ` default of ` IgnoreInaccessible `
71+ SWALLOWS the kernel's own ELOOP refusal — so a self-referential directory
72+ symlink neither hung nor threw. It ** completed normally, returning ~ 40 copies
73+ of one real file** at ever-deeper phantom paths, and because source
74+ de-duplication keys on the LEXICAL path every phantom was admitted as its own
75+ source, loading the same metadata once per level. C# now walks with a
76+ per-branch real-ancestor guard and raises on revisit, matching the three ports
77+ that already did. The claim is now carried by a corpus case
78+ (` a-symlink-cycle-is-an-error ` ) rather than by prose.
5679- ** Behavior change (Java/Maven only): a ` <loader> ` naming neither
5780 ` <sourceDir> ` nor ` <sources> ` , with no ` .metaobjects/config.json ` ` sources `
5881 and no default ` metaobjects/ ` directory, now FAILS the build**
@@ -209,6 +232,23 @@ asserts its target equals the configured registry, checks it against a deny-list
209232public registries, ** parses ` bun publish --dry-run ` ** rather than trusting bun, and runs with
210233` HOME ` redirected so a fall-back has no credential to use.
211234
235+ ### Fixed — ` meta init --print-only ` wrote the files it was previewing
236+
237+ ` --print-only ` is documented as "print what would be written, don't write". It honoured
238+ that on the full scaffold, and — since the fix that moved ` --config-only ` below the
239+ guard — on ` --config-only ` . The two agent-context paths, ** ` --docs-only ` and
240+ ` --refresh-docs ` ** , return from ` init() ` ABOVE that guard and were missed, so both
241+ documented dry runs scaffolded for real: the docs, every stack-scoped skill reference,
242+ the manifest, and, with ` --wire-root ` , a newly created root ` CLAUDE.md ` .
243+
244+ The guard now sits at the I/O rather than at the report. ` writeAgentContext ` already
245+ computes the complete plan before performing a single write, so a dry run reports
246+ exactly the set a real run would touch, with no second hardcoded list to drift out of
247+ step. Reporting is future-tense under a dry run: the old code announced `wired
248+ @.metaobjects/AGENTS.md into CLAUDE.md` and ` refreshed version written to <path >.new`
249+ for edits it had not made — a side effect on a file you own, which you could go looking
250+ for and never find.
251+
212252### Fixed — ` scripts/release.mjs ` preflighted only one package
213253
214254The target-version check ran ` npm view @metaobjectsdev/cli@<version> ` and nothing else, so a
@@ -266,11 +306,19 @@ the same migrations. Three changes are visible even to that project. Adopter gui
266306 naming the target it cannot find, never a half-resolved model — and the
267307 replacement is an explicit ` { "path": "../shared-model/metaobjects" } ` source,
268308 which works in any layout and needs no topological ordering.
269- - ** ` .metaobjects/config.json ` rejects unknown keys.** ` ConfigSchema ` is ` .strict() `
270- at every level, so a key that was previously stripped in silence is now a load
271- error naming the key. Silently dropping a key means the setting you wrote does not
272- exist: ` { "migrate": { "scopee": [...] } } ` used to mean * unscoped* , governing
273- every table in a database you were trying to share.
309+ - ** ` .metaobjects/config.json ` rejects unknown keys — in the Node CLI only, and that
310+ asymmetry is deliberate.** ` ConfigSchema ` is ` .strict() ` at every level, so a key
311+ that was previously stripped in silence is now a load error naming the key. Silently
312+ dropping a key means the setting you wrote does not exist: `{ "migrate": { "scopee":
313+ [ ...] } }` used to mean * unscoped* , governing every table in a database you were
314+ trying to share. The C#, Python and Java CLIs read the neutral subset and IGNORE an
315+ unknown key, so the same config loads there and fails here. That is intended and now
316+ ruled: this file is TypeScript's, and TypeScript is the only port that models its
317+ whole vocabulary — so it is the only one that CAN tell a typo from a key a sibling
318+ owns. A partial reader could imitate strictness only by carrying TypeScript's key
319+ list in lockstep, at which point a port one release behind would reject a config a
320+ newer ` meta ` had just written. Consequence to plan for: a config written by a NEWER
321+ ` meta ` hard-fails an OLDER one, which is what ` schema_version ` is for.
274322- ** ` ExpectedView.fqn ` is required.** On the public ` @metaobjectsdev/codegen-ts `
275323 export, the declaring object's fully-qualified name is no longer optional —
276324 ` migrate.scope ` decides ownership on that name, and a view arriving without one
@@ -302,10 +350,12 @@ the same migrations. Three changes are visible even to that project. Adopter gui
302350 it a config: ` meta init ` writes one, and a ` "sources": [] ` config is enough to
303351 claim the directory and take the default.
304352
305- ## [ 0.23.3 ] — npm ` 0.23.3 ` · PyPI ` 0.23.3 ` · NuGet ` 0.23.3 ` · Maven ` 7 .23.3`
353+ ### Fixed — ` meta gen ` silently destroyed hand edits (was drafted as ` 0 .23.3` )
306354
307- A coordinated ** PATCH** across all four registries, and every one of them carries a real
308- changed product file — the same defect had to be fixed four times, once per write path.
355+ Drafted as a standalone ` 0.23.3 ` PATCH and never released — no registry ever carried
356+ ` 0.23.3 ` . It ships here instead, because the ` sources ` work above forces this cut to a
357+ MINOR and a release cannot be two versions at once. Every registry carries a real changed
358+ product file for it: the same defect had to be fixed four times, once per write path.
309359
310360** ` meta gen ` was silently destroying hand edits, and the case it happened in was the
311361normal one.** A generated file that existed, differed from fresh output, and had no
0 commit comments