@neocompose/cli 0.19.2 → 0.19.4

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.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Neo Compose CLI"
3
+ short_description: "Author and synchronize Neo Compose projects"
4
+ default_prompt: "Use $neocompose-cli to edit and safely synchronize this Neo Compose project."
@@ -0,0 +1,218 @@
1
+ # Animation and world authoring
2
+
3
+ Use this reference for world objects, structured leaves, animation, and
4
+ NeoFlow dialogue graphs.
5
+
6
+ ## Contents
7
+
8
+ - World object contracts
9
+ - Sorting and optional children
10
+ - Structured leaves
11
+ - Animation overrides
12
+ - Segments and tracks
13
+ - NeoFlow dialogues
14
+
15
+ ## World object contracts
16
+
17
+ Extend visible system world classes with ordinary project-authored classes and
18
+ overrides. Never copy the system schema's `@system` provenance annotation into
19
+ project source.
20
+
21
+ For world layer links, derive a concrete project class from
22
+ `NeoTileLayerLink` or `NeoObjectLayerLink` and resolve exactly one
23
+ `targetLayer` relation. Do not add value-level target metadata or ask painting
24
+ operations to create/repair relation targets.
25
+
26
+ ## Sorting and optional children
27
+
28
+ Attach `NeoSortingGroup` as an optional nested member on the project object
29
+ classes that need it:
30
+
31
+ ```neo
32
+ class Tree : NeoObject {
33
+ public virtual NeoSortingGroup? SortingGroup = null;
34
+ }
35
+ ```
36
+
37
+ Presence of a non-null group makes the object and descendants sort as one unit.
38
+ `SortAtRoot` hoists a nested group against the scene root.
39
+
40
+ `NeoSpriteObject` exposes `FlipX`, `FlipY`, `MaskInteraction`, and optional
41
+ `SortingOrder`. Treat `SortingOrder` as an offset added to the order derived
42
+ from layer, placement, and composition; it is not an absolute replacement.
43
+
44
+ Use `NeoObjectBase.Enabled` to hide an object and its entire subtree without
45
+ removing its authored `Children` row. A disabled object is not rendered or
46
+ collided with, but its live values and running animation continue. Prefer this
47
+ for fixed optional equipment/content slots so their provenance and animation
48
+ targets remain stable.
49
+
50
+ Do not treat P45 runtime-child provenance as available. Runtime structural
51
+ children remain deferred; author known slots and toggle `Enabled`.
52
+
53
+ ## Structured leaves
54
+
55
+ Address fields of these leaf values:
56
+
57
+ - `SpriteInfo`: `FileId`, `SliceIndex`
58
+ - Vector values: lowercase components such as `x`, `y`, `z`, `w` where
59
+ supported by the concrete vector type
60
+ - Color: lowercase `r`, `g`, `b`, `a`
61
+
62
+ Keep the casing exact. `FileId` and `SliceIndex` are PascalCase; vector and
63
+ color components are lowercase.
64
+
65
+ Use complete values in normal member initialization:
66
+
67
+ ```neo
68
+ Sprite = Images.Hero.Slice(0);
69
+ Position = new(0, 0, 0);
70
+ Tint = Color(1, 1, 1, 1);
71
+ ```
72
+
73
+ A bare `Images.Hero` yields a file ID only in a file-valued field context.
74
+ Use `.Slice(index)` for a complete `SpriteInfo`.
75
+
76
+ NeoScript may read or write structured fields when the receiver's effective
77
+ storage permits it:
78
+
79
+ ```neo
80
+ root.Session.Debug.Slice = Leg.Sprite.SliceIndex;
81
+ Leg.Sprite.SliceIndex = 2;
82
+ Leg.Position.y = 0.25;
83
+ Leg.Tint.a = 0.5;
84
+ ```
85
+
86
+ Treat writes as read-modify-write against the current whole leaf. Reject
87
+ unknown fields, vector components unsupported by the concrete type, non-integer
88
+ integer-vector values, and color channels outside `[0, 1]`.
89
+
90
+ ## Animation overrides
91
+
92
+ Use partial structured literals only inside animation override graphs:
93
+
94
+ ```neo
95
+ Overrides = new {
96
+ Sprite = new { SliceIndex = 1 },
97
+ Position = new { y = 0.25 },
98
+ Tint = new { a = 0.5 },
99
+ };
100
+ ```
101
+
102
+ An empty partial `new { }` means no change. A partial override changes only
103
+ the named fields and preserves the leaf's other current fields. A whole-leaf
104
+ override replaces the whole leaf.
105
+
106
+ Frame overrides may change `Enabled`, `FlipX`, and `SortingOrder`; they may
107
+ not add, remove, or reorder `Children`.
108
+
109
+ Address direct children with stable row IDs. Missing optional child slots are
110
+ skipped with diagnostics when the authored slot is absent, while stale
111
+ pre-provenance placements may still fail closed.
112
+
113
+ A placement row carries its `assetClassId` binding and optional `assetValueId`
114
+ override beside its schema keys. They are row provenance, not declared schema:
115
+ never declare them as members on a project class, and never strip them from a
116
+ row you rewrite.
117
+
118
+ ## Segments and tracks
119
+
120
+ Use `NeoAnimationSegment<T>` for a frame-indexed value lane. The shipped
121
+ sprite specialization is `NeoSpriteAnimationSegment`, whose
122
+ `NeoAnimationSegmentFrame<SpriteInfo>` rows each provide `Index` and
123
+ `Value`. Sparse frame values hold until the next row or `Duration`.
124
+
125
+ Use one polymorphic `NeoAnimationClip.Tracks` list:
126
+
127
+ - `NeoAnimationChildTrack` schedules a child clip selected by `Child` and
128
+ `ClipKey`.
129
+ - A project subclass of
130
+ `NeoAnimationSegmentTrack<TChild, TValue>` schedules a segment onto one
131
+ target member. Implement its abstract `Segment` member as a stored value,
132
+ lookup, or computed getter.
133
+ - The shipped
134
+ `NeoSpriteAnimationSegmentTrack<TChild extends NeoSpriteObject>` pairing
135
+ targets `NeoSpriteObject.Sprite`.
136
+
137
+ Declare a concrete project track by implementing `Segment`, then construct its
138
+ row with the target child ID:
139
+
140
+ ```neo
141
+ class StoredPantsTrack : NeoSpriteAnimationSegmentTrack<PantsSprite> {
142
+ public override NeoSpriteAnimationSegment Segment = new();
143
+ }
144
+
145
+ Tracks = [
146
+ @id("pants-track-row-id")
147
+ new StoredPantsTrack(id: "pants-child-row-id") {
148
+ StartFrame = 0,
149
+ Direction = .Reverse,
150
+ OffsetEndIndex = 3,
151
+ },
152
+ ];
153
+ ```
154
+
155
+ Each track has `StartFrame`, `Direction` (`.Forward` or `.Reverse`), and a
156
+ crop window `OffsetStartIndex`/`OffsetEndIndex`. Crop before reversing, then
157
+ schedule on the owning clip. Content beyond the owning clip truncates. Reject
158
+ empty/inverted crop windows and tracks that can never enter the clip.
159
+
160
+ Treat track target selection as class metadata, not row data. A target must be
161
+ compatible with `TChild` and `TValue`. Track reads and segment getters are
162
+ re-resolved for the next applied frame after a watched dependency changes.
163
+
164
+ When multiple tracks write the same member in one frame, apply list order and
165
+ let the last write win.
166
+
167
+ ## NeoFlow dialogues
168
+
169
+ Put exactly one top-level `sealed class Name : Dialogue` in each `.neoflow`
170
+ file. Make it non-generic, derive directly from compiler-owned `Dialogue`, and
171
+ override `Trigger Trigger` exactly once. Do not add any other type declaration.
172
+
173
+ ```neoflow
174
+ @id("capitol-dialogue-id")
175
+ @settings(name: "Capitol: cold boot", saveOptionChoices: true)
176
+ sealed class CapitolColdBoot : Dialogue {
177
+ @primary Outpost capitol = Assets.Capitol;
178
+ Player player = Player.Current;
179
+
180
+ @id("trigger-id")
181
+ override Trigger Trigger = new(group: CapitolDialogues.High) => Welcome;
182
+
183
+ @id("welcome-node-id")
184
+ Text Welcome = new(name: "Welcome!") {
185
+ """
186
+ Hello, {player.Name}.
187
+ """
188
+
189
+ @id("continue-option-id")
190
+ Option Continue = new() {
191
+ """
192
+ Continue.
193
+ """
194
+ return Finish;
195
+ }
196
+ }
197
+
198
+ @id("finish-node-id")
199
+ Text Finish = new() {
200
+ """
201
+ Until next time.
202
+ """
203
+ }
204
+ }
205
+ ```
206
+
207
+ Use `@primary` explicitly; never infer it. Keep dialogue bindings flow-wide and
208
+ node bindings scoped to their node/children. Give each persisted condition use,
209
+ action invocation, mutation, pause, option, and node its own owner-scoped ID.
210
+
211
+ Use `=> Next` or a trailing block with `return Next;`; do not author a `to:`
212
+ argument. Require destinations for triggers, options, and outcomes. Permit
213
+ terminal text/actions to fall through. Keep all statements for
214
+ `Actions Empty = new();` inside its body.
215
+
216
+ After editing, run `neo dialogue dryrun <dialogue-ref>`. Exit 1 means the graph
217
+ would fail on device. Use `neo dialogue compile '<logic-block-json>'` only for
218
+ low-level inspection or repair of one logic block.
@@ -0,0 +1,110 @@
1
+ # CLI development
2
+
3
+ Use this reference only when changing the CLI, language service, package, or
4
+ the skill itself. Project authors usually do not need it.
5
+
6
+ ## Contents
7
+
8
+ - Source of truth
9
+ - Feature-spec status
10
+ - Shared implementation contract
11
+ - Package and release maintenance
12
+ - Verification
13
+
14
+ ## Source of truth
15
+
16
+ Treat the current CLI compiler, shared-language implementation, contract
17
+ registry, tests, and generated corpus as the executable source of truth. Use
18
+ the feature specs to understand intent and edge cases, but do not call the old
19
+ umbrella project-source document complete: it predates P38–P52 and contains
20
+ obsolete authoring examples.
21
+
22
+ When a spec status header disagrees with released implementation and tests,
23
+ verify the shipped package and implementation PR before teaching the feature.
24
+
25
+ ## Feature-spec status
26
+
27
+ Read the relevant specs in full:
28
+
29
+ - [P38 complete schema declarations](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/complete/p38-neo-source-complete-schema-declarations.md)
30
+ - [P39 authored system schema](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/complete/p39-neo-authored-system-schema.md)
31
+ - [P40 sorting and sprite metadata](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/complete/p40-sorting-groups-and-sprite-renderer-metadata.md)
32
+ - [P41 optional object children](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/complete/p41-optional-object-children.md)
33
+ - [P42 structured leaf fields](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/complete/p42-addressable-structured-leaf-fields.md)
34
+ - [P43 initializers and constructors](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/complete/p43-neoscript-initializers-and-constructors.md)
35
+ - [P44 nested-row provenance](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/complete/p44-authored-provenance-for-nested-class-rows.md)
36
+ - [P45 deferred runtime-child provenance](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p45-provenance-for-runtime-children.md)
37
+ - [P46 proposed provenance hardening](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p46-provenance-hardening.md)
38
+ - [P47 same-push references](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p47-same-push-value-references.md)
39
+ - [P48 animation segments](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p48-sprite-animation-segments.md)
40
+ - [P49 required construction](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p49-required-constructors-and-settled-members.md)
41
+ - [P50 loops](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p50-neoscript-for-and-foreach-loops.md)
42
+ - [P51 switch](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p51-neoscript-switch-statements.md)
43
+ - [P52 try/catch](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p52-neoscript-try-catch-blocks.md)
44
+
45
+ P38–P44 and P47–P51 are implemented. P45 remains deliberately deferred; do not
46
+ build or teach runtime-child provenance. P46 is a hardening proposal, not an
47
+ authoring capability. P52's document header still says proposed, but try/catch
48
+ shipped in CLI 0.18; implementation and parity tests take precedence over that
49
+ stale header.
50
+
51
+ ## Shared implementation contract
52
+
53
+ Keep parsing, strict resolution, formatting, diagnostics, symbols, and
54
+ NeoScript compilation in the browser-safe `@neocompose/neoscript-language`
55
+ service. Monaco, VS Code, CLI, trusted server, and corpus tooling must consume
56
+ that shared implementation. Do not add language intelligence to one adapter.
57
+
58
+ Keep project context refreshable without accumulating stale Monaco providers or
59
+ LSP state. Preserve diagnostics, completion, hover, signatures, definition,
60
+ references, rename, symbols, semantic tokens, code actions, and formatting
61
+ parity for `.neo` and `.neoflow`.
62
+
63
+ Keep trusted server recompilation and verification independent of client
64
+ outputs. The CLI emits a complete source bundle; client-generated IR, structural
65
+ rows, hashes, storage stamps, and pending IDs are not trusted.
66
+
67
+ ## Package and release maintenance
68
+
69
+ Keep the npm artifact native JavaScript. Do not publish `.csproj`, `.cs`,
70
+ `.dll`, Roslyn host metadata, generated C# SDK output, or trusted server-only
71
+ wrappers.
72
+
73
+ The marker near the top of `SKILL.md` must exactly match the package version:
74
+
75
+ ```html
76
+ <!-- reviewed-through-cli: 0.19.4 -->
77
+ ```
78
+
79
+ The quoted version above is checked too, so this instruction cannot go stale
80
+ while the skill advances.
81
+
82
+ For every CLI version bump, audit all behavior and public syntax added since the
83
+ marker, update the skill/references where needed, then advance the marker. Never
84
+ bump it mechanically. `npm run doctor` and package verification both fail when
85
+ it drifts, so the audit lands in the same change as the bump rather than
86
+ blocking a publish after the release has merged.
87
+
88
+ Every version bump also gets a `CHANGELOG.md` entry, newest first, under a
89
+ `## [version] - date` heading. The same check grades it, so an undocumented
90
+ release fails alongside a stale marker.
91
+
92
+ Keep `agents/openai.yaml` and every directly linked reference inside the
93
+ published `skills/neocompose-cli/` tree. Keep the frontmatter limited to
94
+ `name` and `description`.
95
+
96
+ ## Verification
97
+
98
+ Run focused language/compiler tests for changed behavior, then:
99
+
100
+ ```sh
101
+ python3 <skill-creator>/scripts/quick_validate.py cli/skills/neocompose-cli
102
+ npm --prefix cli run test:package
103
+ npm run typecheck
104
+ npm run doctor
105
+ ```
106
+
107
+ Also run contract/corpus checks, Monaco/browser parity, VSIX packaging, and
108
+ interactive sample no-op verification when the corresponding implementation
109
+ surface changes. Run `npm run doctor` last; do not rerun successful suites only
110
+ because doctor formatted files.
@@ -0,0 +1,130 @@
1
+ # Commands and synchronization
2
+
3
+ Use this reference for workspace synchronization, conflicts, repair commands,
4
+ branches, releases, history, and authentication.
5
+
6
+ ## Contents
7
+
8
+ - Pull, inspect, and push
9
+ - Conflicts and files
10
+ - Low-level operations
11
+ - Branches, releases, and history
12
+ - Authentication and automation
13
+
14
+ ## Pull, inspect, and push
15
+
16
+ Use the high-level source workflow:
17
+
18
+ ```sh
19
+ neo pull
20
+ neo status
21
+ neo diff
22
+ neo dialogue dryrun <dialogue-ref>
23
+ neo push --dry-run
24
+ neo push
25
+ ```
26
+
27
+ Use `neo pull --regenerate-source-names` to refresh readable naming while
28
+ retaining normal merge behavior. Use `--reset` to regenerate canonical source
29
+ and managed binaries. Use `--force` only to discard local edits for the server
30
+ version.
31
+
32
+ Treat `neo push --dry-run` as a full local commit rehearsal: bundle emission,
33
+ pending-ID assignment on a clone, transport shapes, hashes, and repeat
34
+ verification all run. It also runs the server's preparation phase locally —
35
+ schema-commit materialization, seeded structural rows, and whole-graph
36
+ validation — so a rejection that used to cost a real push and return an opaque
37
+ `preparation-failed` now reports before anything is sent. It does not prompt,
38
+ upload, commit, or rewrite source.
39
+
40
+ A clean dry-run does not promise a clean push. The workspace holds its
41
+ last-pulled state, so everything that grades the live head is still server-only:
42
+ re-verification of the uploaded bundle bytes, project-file staging, the
43
+ snapshot-id and content-head cross-check, admission classification, chunk
44
+ planning, and finalization. Pull again before pushing if time has passed.
45
+
46
+ One accepted push atomically commits records, main-locale text, stored bindings,
47
+ and staged file metadata under CAS. Pending durable IDs and source rewrites
48
+ become real only after acceptance.
49
+
50
+ ## Conflicts and files
51
+
52
+ Resolve source conflicts by editing the final intended source. Use
53
+ `neo resolve --mine` or `neo resolve --theirs` only for a deliberate whole-side
54
+ choice. Pull, inspect, dry-run, and retry; there is no force-CAS path.
55
+
56
+ For `base-hash-conflict`, pull and merge. For `version-bump-required`, inspect
57
+ the classification and use `neo push --accept-bump` only when the bump is
58
+ intended.
59
+
60
+ Inspect and stage files with:
61
+
62
+ ```sh
63
+ neo files list
64
+ neo files add <path> [--template <Name>]
65
+ ```
66
+
67
+ Keep local bytes during binary conflict resolution. Inspect the server copy
68
+ under `.neo/conflicts/files/<file-id>/`.
69
+
70
+ ## Low-level operations
71
+
72
+ Prefer checked-in source for normal authoring. Use low-level commands only for
73
+ automation, repair, or inspection:
74
+
75
+ ```sh
76
+ neo records query [--kind <recordKind>]
77
+ neo records get <kind> <id>
78
+ neo values list [memberId]
79
+ neo values get <valueId>
80
+ neo values set <valueId> '<raw-json-value>'
81
+ neo values bind <staticMemberId> <valueId>
82
+ neo values unbind <staticMemberId>
83
+ neo values create '<raw-json-value>' [--class <classId>] --bind <staticMemberId>
84
+ neo loc locales
85
+ neo loc list
86
+ neo loc set <textId> <locale> "text"
87
+ neo dialogue list
88
+ neo dialogue show <ref>
89
+ neo dialogue compile '<logic-block-json>'
90
+ ```
91
+
92
+ Pass a JSON-array batch on stdin or `--file` where supported so related writes
93
+ remain atomic. A `values set` payload is the raw value, not
94
+ `{ "value": ... }`. Static bind/unbind changes the live binding.
95
+
96
+ Low-level commands do not rewrite local project source. Run `neo pull` after
97
+ any low-level or web mutation before further source edits or push.
98
+
99
+ ## Branches, releases, and history
100
+
101
+ ```sh
102
+ neo branch list
103
+ neo branch create <name> [--from <ref>]
104
+ neo branch switch <nameOrId>
105
+ neo branch refresh [--dry-run]
106
+ neo merge <branch> [--dry-run] [--migrate]
107
+ neo release cut [--bump major|minor|patch] [--dry-run]
108
+ neo history inspect
109
+ neo history log
110
+ ```
111
+
112
+ Treat branches as copy-on-write forks and releases as immutable snapshots. The
113
+ server derives the minimum compatibility bump; a caller may only raise it.
114
+ Require the release login profile for release operations.
115
+
116
+ ## Authentication and automation
117
+
118
+ ```sh
119
+ neo login [--api <url>] [--profile editor|release] [--save-project <id>]
120
+ neo doctor
121
+ ```
122
+
123
+ The Node CLI talks directly to authenticated Convex APIs through the
124
+ session-gated CAS boundary. Tokens use the OS credential store when available
125
+ and a protected file only as fallback. Use `NEO_COMPOSE_TOKEN` or
126
+ `--token-stdin` in CI. The editor profile cannot publish releases; server
127
+ scopes remain the security boundary.
128
+
129
+ Pass explicit project/version IDs and flags in automation. Prefer `--json` for
130
+ machine-readable output and do not depend on interactive pickers or confirms.
@@ -0,0 +1,217 @@
1
+ # Declarations and construction
2
+
3
+ Use this reference for schema declarations, annotations, member contracts,
4
+ constructors, and construction-time settlement.
5
+
6
+ ## Contents
7
+
8
+ - Declaration rules
9
+ - Annotations and settings
10
+ - Member and relation rules
11
+ - Computed initializers and declared constructors
12
+ - Required constructors and settlement
13
+ - Inheritance construction
14
+
15
+ ## Declaration rules
16
+
17
+ Declare classes, interfaces, and enums only at the top level of definition
18
+ `.neo` files. Do not nest them in a type, body, accessor, lambda, initializer,
19
+ migration, or `.neoflow` file. NeoFlow has its own single-dialogue exception.
20
+
21
+ Persist the declared identifier as the schema name; there is no separate
22
+ technical/display name. Keep existing IDs stable.
23
+
24
+ ```neo
25
+ @id("interface-named-id")
26
+ interface INamed {
27
+ @id("named-name-id")
28
+ string Name { get; }
29
+
30
+ @id("named-use-id")
31
+ void Use(SomeClass context);
32
+ }
33
+
34
+ @id("class-item-id")
35
+ @storage(allowed: .Immutable)
36
+ abstract class Item<TContext extends SomeClass> : INamed {
37
+ @id("item-name-id")
38
+ @settings(localizable: true, searchKey: true)
39
+ public virtual string Name = "";
40
+
41
+ @id("item-use-id")
42
+ public abstract void Use(TContext context);
43
+
44
+ @id("item-load-id")
45
+ public native async bool Load(TContext context);
46
+ }
47
+ ```
48
+
49
+ Use ordinary `public`, `protected`, `private`, `static`, `virtual`, `abstract`,
50
+ and `override` semantics. Interfaces are non-generic. Do not put `@id` on
51
+ inferred list/dictionary type entries or on derived structural descriptors.
52
+
53
+ A bodyless function must be `abstract`, `native`, or an interface contract.
54
+ `async` alone does not make it valid.
55
+
56
+ ## Annotations and settings
57
+
58
+ Never author `@system`. It marks platform-owned system records and is rejected
59
+ on project-authored declarations, members, overrides, values, and files. The
60
+ system types visible in project source may themselves have system provenance;
61
+ that does not authorize copying the annotation onto authored records.
62
+
63
+ Use context-aware typed annotations:
64
+
65
+ - `@settings(...)` for localizable/search/index/editor constraints.
66
+ - `@storage(allowed: ...)` on classes and storage keys on members.
67
+ - `@locked` for author-protected declarations supported by the contract.
68
+ - `@hidden` only on a class; absence means visible.
69
+ - `@relations(...)` only on the source class of a specialized direct relation.
70
+
71
+ Use numeric literals for numeric limits. Keep annotations separate instead of
72
+ inventing a generic metadata bag.
73
+
74
+ `@index(member: Field, unique: ...)` and
75
+ `@column(member: Field, width: ..., hidden: ..., frozen: ..., wrapContent: ...)`
76
+ are repeatable List annotations. `member:` is a bare field name on the entry
77
+ class. An index accepts a non-localizable string or single-select enum. A column
78
+ may target `__other__` for the default layout of subclass-added fields.
79
+
80
+ Use `.EnumCase` only when context fixes one enum type; otherwise write
81
+ `EnumType.Case`. Defaults and settings must be statically analyzable.
82
+
83
+ ## Member and relation rules
84
+
85
+ Infer member kind and optionality from its type and nullability. Treat the
86
+ initializer as its default. Omitted fields materialize their current defaults
87
+ once when an instance is created; later default edits do not mutate existing
88
+ instances.
89
+
90
+ A lookup member's arity is its declared type. `public Config Config` selects one
91
+ entry and its declared type is the entry type; `public Set<Config> Configs`
92
+ selects several. An initializer body for a single-select lookup returns one
93
+ entry, never a set.
94
+
95
+ Keep generic project relations as typed top-level declarations in
96
+ `Relations.neo`. Let the compiler derive identities for owned descriptors and
97
+ concrete generic bindings.
98
+
99
+ For world layer links, instantiate a project-authored descendant of
100
+ `NeoTileLayerLink` or `NeoObjectLayerLink` that resolves exactly one
101
+ `targetLayer` relation. The relation is the complete binding; do not add
102
+ value-level target metadata.
103
+
104
+ ## Computed initializers and declared constructors
105
+
106
+ Use an expression initializer when a member default must be evaluated during
107
+ construction rather than stored as a closed literal. It runs for every newly
108
+ constructed instance in push, web, and SDK contexts; it does not rewrite
109
+ existing saved instances.
110
+
111
+ Declare overloadable constructors as members when each constructor body owns
112
+ its parameter scope:
113
+
114
+ ```neo
115
+ class Foo {
116
+ public string Bar;
117
+ public bool IsFun;
118
+
119
+ public Foo(string bar, bool isFun) {
120
+ this.Bar = bar;
121
+ this.IsFun = isFun;
122
+ }
123
+ }
124
+
125
+ Foo value = new(bar: "BAR", isFun: true) { IsFun = false };
126
+ ```
127
+
128
+ Constructor parameters are visible only in that constructor body. Do not read
129
+ them from member initializers. Each declared constructor must settle every
130
+ required member on its own; a call-site block cannot repair an invalid
131
+ constructor declaration.
132
+
133
+ ## Required constructors and settlement
134
+
135
+ Put one required constructor on the class header when the entire class body
136
+ needs the parameters:
137
+
138
+ ```neo
139
+ class Foo(string bar, bool isFun) {
140
+ public string Bar = bar;
141
+ public bool IsFun;
142
+
143
+ init {
144
+ if (isFun) {
145
+ IsFun = true;
146
+ } else {
147
+ IsFun = false;
148
+ }
149
+ }
150
+ }
151
+
152
+ Foo value = new(bar: "BAR", isFun: true) { IsFun = false };
153
+ ```
154
+
155
+ Header parameters are in scope throughout the class. Permit at most one `init`
156
+ block. A header constructor disables implicit parameterless `new` and forbids
157
+ additional constructor overloads. Its derived constructor identity is not
158
+ authored with `@id`.
159
+
160
+ Treat a non-null member with no default as required. Treat a nullable member
161
+ with no default as implicitly `null` and therefore settled:
162
+
163
+ ```neo
164
+ class Result {
165
+ public string Value; // required
166
+ public bool Ok = true; // settled by default
167
+ public string? Note; // settled by implicit null
168
+ }
169
+ ```
170
+
171
+ Accept settlement only from:
172
+
173
+ 1. The member's own initializer expression.
174
+ 2. Assignment on every reachable path in `init` or a constructor body.
175
+ 3. A call-site initializer entry when using an otherwise valid construction
176
+ path.
177
+
178
+ Do not infer settlement from a side effect inside another function. Require
179
+ full `if`/`else` coverage when an assignment is conditional.
180
+
181
+ Apply construction in this order: base construction, member initializers,
182
+ `init` or declared-constructor body, then the call-site initializer block. The
183
+ call-site block wins when it refines an earlier value.
184
+
185
+ ## Inheritance construction
186
+
187
+ Treat the base clause as a construction expression:
188
+
189
+ ```neo
190
+ class Base(string name) {
191
+ public string Name = name;
192
+ }
193
+
194
+ class Child(string name, int rank) : Base(name) {
195
+ public int Rank = rank;
196
+ }
197
+ ```
198
+
199
+ Call a declared base constructor with `: Base(args)`. When a base has no
200
+ constructor, use a base initializer block to settle required inherited members:
201
+
202
+ ```neo
203
+ class Base {
204
+ public string Name;
205
+ public bool Enabled = true;
206
+ }
207
+
208
+ class Child(string name) : Base {
209
+ Name = name,
210
+ Enabled = false
211
+ } {
212
+ }
213
+ ```
214
+
215
+ Bind generic base arguments explicitly when needed. Reject a subclass that
216
+ omits required base arguments or leaves any inherited required member
217
+ unsettled.