@neocompose/cli 0.75.1 → 0.76.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.75.1",
3
+ "version": "0.76.0",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,7 @@ description: >-
9
9
  `@neocompose/cli` or `node cli/bin/neo.mjs` in the neo-compose repository.
10
10
  ---
11
11
 
12
- <!-- reviewed-through-cli: 0.75.1 -->
12
+ <!-- reviewed-through-cli: 0.76.0 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -211,8 +211,12 @@ copies, use the editor's duplicate action or copy the initializer without IDs.
211
211
  Scalars may have several owners. If those values diverge in web, pull keeps
212
212
  the different server values by inlining the differing uses. Initializer
213
213
  aliases are local source organization, not runtime globals or server records.
214
- Use their names as complete value initializers; executable expressions such as
215
- `BASE + 1` cannot access these local declarations.
214
+ Complete member and collection entries compose owned values, including inside
215
+ constructor-backed initializers. A class local used as a constructor argument or
216
+ executable operand refers to its single stored instance. Nested targets need an
217
+ existing `@id` or an authored UUID-v4; a direct static owner supports pending IDs.
218
+ Scalar executable expressions such as `BASE + 1` still require inlining the
219
+ scalar expression. Locals are unavailable inside executable closures.
216
220
  The shared editor offers extract-to-file on a selected complete initializer,
217
221
  inline on its sole use, and duplicate at a use. Move the created file freely.
218
222
 
@@ -308,5 +312,6 @@ and `insert_final_newline` there to keep regenerated source consistent with your
308
312
  editor. Defaults are two spaces, LF, and a final newline. Push preserves authored
309
313
  text when regeneration has the same tokens; pull preserves semantically unchanged
310
314
  files. Formatting-only body or source-location changes do not create a subsequent
311
- push; real code and string-content edits still do. Formatting preferences belong
315
+ push, including stored initializer recipes, computed member defaults, and inline
316
+ constructor delegates; real code and string-content edits still do. Formatting preferences belong
312
317
  to the checkout, not stored script bodies.
@@ -66,8 +66,9 @@ new GlassFloorTile { Cell = new(9, -4) }
66
66
 
67
67
  Layer-link `Tiles` lists contain these values directly. `NeoTileInstance` and
68
68
  `@tile(...)` are removed. A tile's `Name`, `Sprite`, and `SmartTile` are
69
- `readonly` declaration members. Override them on a subclass; a placement
70
- initializer cannot assign them. `Cell` is the placement's writable field.
69
+ class-level `readonly` members: tiles render per class, so override them on a
70
+ subclass. Push rejects a placement that sets them. `Cell` is the placement's
71
+ writable field.
71
72
 
72
73
  Object `PlacementTiles` is a `List<NeoPlacementTile>`. Each entry marks a
73
74
  footprint cell with its own `Cell` member. It is independent of `NeoTile`. Rendered
@@ -105,7 +105,10 @@ project-file bytes, and web editor commits use the shared client preparation pro
105
105
  Unrelated source files are not uploaded or recompiled on every push.
106
106
  Optional source maps missing from valid stored bodies do not request a backfill.
107
107
  Formatting-only source location changes do not create pushes, including when
108
- other records change. Evaluation maps the current local layout; authored edits,
108
+ other records change. Stored initializer recipes, computed member defaults, and
109
+ inline constructor delegates preserve their pulled code and compiled bodies when
110
+ source tokens are unchanged. String-literal content remains significant.
111
+ Evaluation maps the current local layout; authored edits,
109
112
  dependency changes, and explicit forced recompilation retain their normal compile scope. A CLI lowering mismatch on a normal push is exposed by the next
110
113
  canonical pull; `neo push --dry-run` remains the complete source-to-record
111
114
  rehearsal.
@@ -126,7 +129,7 @@ wrappers.
126
129
  The marker near the top of `SKILL.md` must exactly match the package version:
127
130
 
128
131
  ```html
129
- <!-- reviewed-through-cli: 0.75.1 -->
132
+ <!-- reviewed-through-cli: 0.76.0 -->
130
133
  ```
131
134
 
132
135
  The quoted version above is checked too, so this instruction cannot go stale
@@ -166,6 +166,24 @@ a User class reads only `root.User`. Other code reads `root.User` but can't
166
166
  assign, mutate, construct, or invoke User data. Lookups and listeners don't
167
167
  cross between User and Save data; Session lookups may target `root.User`.
168
168
 
169
+ `readonly` is init-only, as in C#, and independent of storage. A field
170
+ initializer, a constructor body, an object initializer block, or an authored
171
+ value sets it; nothing writes it after construction. It is valid on Save,
172
+ Session, Immutable, and User classes, but not with Writable storage. It may
173
+ read a header parameter:
174
+
175
+ ```neo
176
+ class MixerGroup(string name) {
177
+ public readonly string Name = name;
178
+ public readonly List<MixerChannel> Children = [];
179
+ public float Volume = 1;
180
+ }
181
+ ```
182
+
183
+ Readonly is shallow. A readonly collection's membership is fixed, while its
184
+ entries' members follow their own storage and mutability. User class members
185
+ still declare no storage.
186
+
169
187
  `@index(member: Field, kind: .Unique)` and
170
188
  `@column(member: Field, width: ..., visibility: ..., pin: ..., overflow: ...)`
171
189
  are repeatable List annotations. `member:` is a bare field name on the entry
@@ -369,8 +387,8 @@ Accept settlement only from:
369
387
 
370
388
  1. The member's own initializer expression.
371
389
  2. Assignment on every reachable path in `init` or a constructor body.
372
- 3. A call-site initializer entry when using an otherwise valid construction
373
- path.
390
+ 3. A call-site initializer entry, in value source and NeoScript bodies alike,
391
+ when using an otherwise valid construction path.
374
392
 
375
393
  Do not infer settlement from a side effect inside another function. Require
376
394
  full `if`/`else` coverage when an assignment is conditional.
@@ -434,7 +452,21 @@ class PlacedItem<
434
452
  }
435
453
  ```
436
454
 
437
- Executable object initializer blocks can set immutable stored members on the newly constructed instance, including nested `new` expressions. Ordinary assignments after construction remain subject to storage restrictions. `readonly` declarations still change only through class defaults.
455
+ Object initializer blocks can set `readonly` members of the newly constructed
456
+ instance, including nested `new` expressions. Ordinary assignments after
457
+ construction remain subject to storage and `readonly` restrictions.
458
+
459
+ Runtime construction never supplies an Immutable member: an initializer block,
460
+ constructor body, base-clause argument, or generated C# factory cannot set one.
461
+ The value is never stored with the instance. When the declaration, including
462
+ every row it owns, reads neither `this` nor a constructor argument, the member
463
+ is a constant and every instance shares one value. Otherwise each instance
464
+ evaluates the declared initializer with its own `this` and constructor
465
+ arguments, again after a reload. A method group such as `selector: SelectTorso`
466
+ binds `this`. `Clone()` shares an authored or constant Immutable value and
467
+ re-evaluates a per-instance one. A class with a required Immutable member and
468
+ no initializer cannot be constructed at runtime; only authored values construct
469
+ it.
438
470
 
439
471
  Populated unordered member defaults, such as `@settings(kind: .Unordered)` with
440
472
  `List<Entry> Entries = [new Entry()]`, are constructed by an initializer for
@@ -446,3 +478,28 @@ A lookup's declared class must be assignable to the entry class of its
446
478
  selection. Constructor arguments and member initializers must select compatible
447
479
  targets too. An `NPC` lookup cannot select from `List<Quest>`, even when its
448
480
  initializer is typed `NPC`; status reports the conflicting collection annotation.
481
+
482
+ Schema export retains a constructed default's generic arguments, including
483
+ nested collection references. For example, a `NeoAnimationClip<ArmPart>`
484
+ default constructs `new NeoAnimationClip<ArmPart>` and its frames reference
485
+ `List<NeoAnimationFrame<ArmPart>>`.
486
+
487
+ When a construction leaves a required member unsettled, CLI diagnostics point to
488
+ `new` and include the member declaration as a related location. Fix that
489
+ construction before pushing. Valid root initializers still participate in
490
+ reference checking, so an unrelated root error does not imply their values are
491
+ missing or deleted. Independent reference errors remain visible.
492
+
493
+ Object initializer assignments use commas. The last comma is optional; the
494
+ semicolon terminating the enclosing declaration is valid:
495
+
496
+ ```neo
497
+ AudioSettings AUDIO_SETTINGS = new() {
498
+ Main = new AudioMixerGroupSettingsFolder("Main", "Master", "MasterVolume", []),
499
+ };
500
+ ```
501
+
502
+ A semicolon after `Main = ...` inside the initializer is invalid. Editor and CLI
503
+ diagnostics highlight that separator in the authored file, including global,
504
+ static member, and protected root initializers. Statement semicolons inside
505
+ function or delegate bodies remain valid.
@@ -154,9 +154,10 @@ Object initializers are available in method bodies and `.spec.neo` tests:
154
154
  var tile = new DirtWetTile { Cell = Vector2Int(3, -2) };
155
155
  ```
156
156
 
157
- Construction runs first. Each initializer entry then uses ordinary assignment
158
- rules in source order, including property setters, access checks, and readonly
159
- restrictions. Initializer expressions read the caller's scope.
157
+ An entry that settles a required member is evaluated with construction, as the
158
+ constructor argument it stands in for. The remaining entries then use ordinary
159
+ assignment rules in source order, including property setters, access checks,
160
+ and readonly restrictions. Initializer expressions read the caller's scope.
160
161
 
161
162
  ## Nullability and ownership
162
163
 
@@ -90,6 +90,45 @@ formatting moves its ID above the declaration. Do not put an ID in both places.
90
90
  A local initializer such as `Delivery DeliveryRow = new()` retains `Delivery`
91
91
  when consumed by a base-class member. Aliases retain that same concrete type.
92
92
 
93
+ Complete local member and collection initializers also work inside constructions
94
+ that execute a constructor. Each local composes its owned subtree and preserves
95
+ its concrete class:
96
+
97
+ ```neo
98
+ AudioFolder SFX_FOLDER = new("Sound effects") {
99
+ Children = [UI_VOLUME, GAMEPLAY_VOLUME]
100
+ };
101
+ AudioSettings AUDIO_SETTINGS = new() { Main = SFX_FOLDER };
102
+ ```
103
+
104
+ In constructor arguments and executable operands, a class local names its one
105
+ stored instance instead of constructing a copy:
106
+
107
+ ```neo
108
+ @id("86ac7c7d-8d3b-4642-9def-cdf32643bdc5")
109
+ DeliveryQuest HARD_AS_STONE = new();
110
+ // The core quest list owns HARD_AS_STONE.
111
+ WorkflowStep UNLOCK = new QuestUnlockStep(HARD_AS_STONE);
112
+ ```
113
+
114
+ The referenced local must have exactly one owning placement. A nested target
115
+ needs its existing `@id`. A structurally owned list entry can also use an unused
116
+ UUID-v4 in the same push. For a local nested inside an evaluated constructor,
117
+ leave it idless on the first push; push assigns its actual identity before it can
118
+ be referenced. Do not assign a fresh UUID to that nested construction. A direct static owning member can supply an idless local's pending identity.
119
+ Aliases of that static local work too. A member default is a construction template,
120
+ so its local does not identify a single stored instance. Unowned locals, ambiguous
121
+ placements, and cycles are errors at source. Constructor argument type checking
122
+ still applies. Canonical construction arguments may use `Reference<Type>(id: "…")`.
123
+
124
+ Owned locals inside evaluated initializers need directly addressable fields, list
125
+ entries, or literal dictionary keys; inline the construction when a computed key
126
+ or other executable expression prevents tracking its owning path.
127
+
128
+ Scalar locals remain complete-initializer bindings; inline their expressions when
129
+ using them in arithmetic or constructor arguments. Closure bodies cannot capture
130
+ project authoring locals.
131
+
93
132
  Nested class/list rows remain independently addressable and keep inline IDs:
94
133
 
95
134
  ```neo