@neocompose/cli 0.87.2 → 0.89.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +14 -0
  3. package/dist/neo-runtime.mjs +11153 -3060
  4. package/dist/test-worker.mjs +15214 -9707
  5. package/package.json +1 -1
  6. package/skills/neocompose-cli/SKILL.md +95 -279
  7. package/skills/neocompose-cli/references/cli-development.md +101 -117
  8. package/skills/neocompose-cli/references/declarations/annotations-and-storage.md +112 -0
  9. package/skills/neocompose-cli/references/declarations/constructors.md +164 -0
  10. package/skills/neocompose-cli/references/declarations/declarations.md +103 -0
  11. package/skills/neocompose-cli/references/declarations/extensions.md +46 -0
  12. package/skills/neocompose-cli/references/declarations/initializers.md +100 -0
  13. package/skills/neocompose-cli/references/declarations/member-kinds.md +107 -0
  14. package/skills/neocompose-cli/references/declarations/overloads.md +50 -0
  15. package/skills/neocompose-cli/references/dialogues.md +145 -0
  16. package/skills/neocompose-cli/references/neoscript/collections.md +138 -0
  17. package/skills/neocompose-cli/references/neoscript/control-flow.md +123 -0
  18. package/skills/neocompose-cli/references/neoscript/delegates-and-listeners.md +107 -0
  19. package/skills/neocompose-cli/references/neoscript/math-and-random.md +114 -0
  20. package/skills/neocompose-cli/references/neoscript/neoscript.md +113 -0
  21. package/skills/neocompose-cli/references/neoscript/strings.md +47 -0
  22. package/skills/neocompose-cli/references/neoscript/testing-and-migrations.md +73 -0
  23. package/skills/neocompose-cli/references/neoscript/types-and-narrowing.md +111 -0
  24. package/skills/neocompose-cli/references/sync/branches-and-history.md +69 -0
  25. package/skills/neocompose-cli/references/sync/conflicts-and-recovery.md +68 -0
  26. package/skills/neocompose-cli/references/sync/sync.md +96 -0
  27. package/skills/neocompose-cli/references/sync/unity-export.md +63 -0
  28. package/skills/neocompose-cli/references/values/defaults-and-localization.md +86 -0
  29. package/skills/neocompose-cli/references/values/files.md +71 -0
  30. package/skills/neocompose-cli/references/values/identities.md +97 -0
  31. package/skills/neocompose-cli/references/values/references.md +151 -0
  32. package/skills/neocompose-cli/references/values/values.md +110 -0
  33. package/skills/neocompose-cli/references/world/animation.md +133 -0
  34. package/skills/neocompose-cli/references/world/grids-and-cell-patterns.md +55 -0
  35. package/skills/neocompose-cli/references/world/variants.md +112 -0
  36. package/skills/neocompose-cli/references/world/world.md +105 -0
  37. package/skills/neocompose-cli/references/animation-and-world-authoring.md +0 -582
  38. package/skills/neocompose-cli/references/commands-and-sync.md +0 -408
  39. package/skills/neocompose-cli/references/declarations-and-construction.md +0 -773
  40. package/skills/neocompose-cli/references/neoscript.md +0 -990
  41. package/skills/neocompose-cli/references/values-identities-and-references.md +0 -421
@@ -0,0 +1,111 @@
1
+ # Types, narrowing, and tuples
2
+
3
+ Null and type narrowing, patterns, equality, generic functions, and tuples.
4
+
5
+ ## Null narrowing
6
+
7
+ - Null propagation continues through later required accesses in the same
8
+ chain: `config.Hat?.Head.Idle.Down.Frames` is null when `Hat` is null. A
9
+ nullable descendant still needs its own `?.`, `??`, `!`, or guard.
10
+ - `Member` and `this.Member` share narrowing. A local or parameter named
11
+ `Member` shadows the member; `this.Member` still names the field.
12
+ - Reassigning a narrowed path to a nullable value clears its facts and its
13
+ descendants' facts. Writes to unrelated members keep guards, also in loops.
14
+ A false earlier condition narrows later `else if` conditions.
15
+ - `a ?? b` is non-null when `b` is non-null, including in constructor
16
+ arguments such as `new Sprite(item.Sprite ?? SpriteInfo.Empty)`.
17
+ - Where a reference is expected, `first ?? fallback` picks the reference; a
18
+ present reference whose target is null does not select the fallback. In a
19
+ scalar context the reference reads its target first.
20
+ - Lookup results follow the same rules.
21
+
22
+ ## Type patterns
23
+
24
+ ```neo
25
+ if (item is not BedItem bedItem) {
26
+ throw "Expected a bed item";
27
+ }
28
+ return bedItem.Name;
29
+ ```
30
+
31
+ - A binding stays in scope after a guard when every path that falls through
32
+ matched, including when other paths return from a nested block.
33
+ - `value is not T name` binds only where the test is known false: the `else`
34
+ branch, the right of `||`, or after a rejecting guard. The operand evaluates
35
+ once. `not` is an ordinary identifier elsewhere.
36
+ - Patterns follow the possible runtime type. An open class can match an
37
+ unrelated interface through a subclass that implements it; a sealed class
38
+ without it is a compile-time impossible pattern. A class implements each
39
+ interface once, so a value known to be `IBox<int>` never passes
40
+ `is IBox<string>`.
41
+ - An interface guard such as `if (item is not IReceiver) continue;` narrows
42
+ `item` itself: it keeps its declared class or generic `T` members and gains
43
+ `IReceiver` members. Later class guards accumulate (`IReceiver` then
44
+ `Furnace` exposes both). Guards that return, throw, or break work the same.
45
+ - A declaration pattern keeps the original receiver's type too: after
46
+ `if (item is not IReceiver receiver) return;`, `item` is unchanged and
47
+ `receiver` exposes `IReceiver`.
48
+ - Generic `is` tests check whether the bound can overlap the tested type.
49
+ - Guards also narrow named globals and static member paths, qualified or not,
50
+ inherited included. Assignment clears the proof. Closures must guard again
51
+ because the stored value can change before they run.
52
+ - A generic receiver may call members of its class or interface constraint;
53
+ nullable receivers still need narrowing.
54
+
55
+ ## Equality
56
+
57
+ `==`, `!=`, `Contains`, `IndexOf`, and `Remove` compare class, interface,
58
+ List, Set, and Dictionary values by identity. Primitives, enums, vectors, and
59
+ media references compare by value. The static type decides, so an
60
+ unconstrained `T` compares by value.
61
+
62
+ `Equals(other)` on a non-null generic value calls the runtime class's
63
+ one-argument `bool Equals` (including overrides) when it has one, otherwise
64
+ falls back to value equality. Local sets fall back to comparing current
65
+ entries in insertion order.
66
+
67
+ ## Generic functions
68
+
69
+ Functions, natives, and interface functions may declare type parameters with
70
+ optional constraints and defaults:
71
+
72
+ ```neo
73
+ public static T? FirstOf<T extends Item = Item>(List<T> items) => items.FirstOrDefault();
74
+ ```
75
+
76
+ - Call with every type argument, omit a defaulted tail
77
+ (`FirstOf<Seed>(items)`), or omit `<...>` to infer from arguments.
78
+ - Lambda inputs bind first so their returns can infer outputs, as in
79
+ `Map(seed, (value) => value.Price)`. Defaults fill parameters with no
80
+ evidence. The expected return type is not evidence. Explicit `<...>`
81
+ never infers the omitted tail. Conflicting types need explicit arguments;
82
+ inference does not invent a common ancestor.
83
+ - Function type parameters take no `@id`. Overrides declare the same count,
84
+ constraints, and defaults but may rename them.
85
+
86
+ Generic class arguments are invariant through inheritance: `IntBox : Box<int>`
87
+ assigns to `Box<int>`, never `Box<string>`. Typed locals accept generic
88
+ classes (`Box<int> box = new(1);`). Explicit lambda parameter types accept
89
+ the full declaration grammar, including `decimal`, generics, collections, and
90
+ nullables.
91
+
92
+ ## Tuples
93
+
94
+ ```neo
95
+ (int Row, int Col) position = (Row: 2, Col: 3);
96
+ var (row, col) = position;
97
+ (int first, (bool enabled, int _)) = (1, (true, 9));
98
+ var score = positions.Select(((r, c)) => r + c);
99
+ ```
100
+
101
+ - Two or more elements. Labels are optional and don't affect type identity;
102
+ `Item1`, `Item2`, ... always work. Slots are immutable: replace the tuple.
103
+ - `((r, c)) =>` takes one tuple; `(r, c) =>` takes two parameters. Recursive
104
+ patterns work in declarations, loop bindings, and lambda parameters. `_`
105
+ discards. Assignment deconstruction is unsupported.
106
+ - Equality is recursive by element; decimal scale is ignored, classes and
107
+ collections compare by identity.
108
+ - `(int, int)?` is a nullable tuple; `(int?, int)` has a nullable element.
109
+
110
+ Stored tuple members are covered in
111
+ [member kinds](../declarations/member-kinds.md#stored-tuples).
@@ -0,0 +1,69 @@
1
+ # Branches, releases, and access
2
+
3
+ ## Branches and releases
4
+
5
+ ```sh
6
+ neo branch list
7
+ neo branch create <name> [--from <ref>]
8
+ neo branch switch <nameOrId>
9
+ neo branch refresh [--dry-run]
10
+ neo merge <branch> [--dry-run] [--migrate]
11
+ neo release cut [--bump major|minor|patch] [--dry-run]
12
+ ```
13
+
14
+ - Branches are copy-on-write forks; releases are immutable snapshots.
15
+ - Switching needs a clean checkout. Unpushed source or binaries, conflicts,
16
+ source errors, and pending push recovery stop it before anything changes.
17
+ Push or back up first; `neo pull --force` only when discarding is intended.
18
+ - Creation waits until the durable copy publishes. The source stays readable
19
+ while copying.
20
+ - The server derives the minimum compatibility bump; a caller may only raise
21
+ it. Releases need the `release` login profile.
22
+ - In Unity projects using `unityConfigPath`, branch and version switches
23
+ update `NeoComposeConfig.asset`.
24
+
25
+ ## History
26
+
27
+ ```sh
28
+ neo history inspect
29
+ neo history log
30
+ neo history prune [--project <id|@latest|@current>]
31
+ neo history flatten [--project <id|@latest|@current>] --version <id|@latest|@current>
32
+ neo history apply --plan <plan-id> [--confirm-scope <project-id>[/<version-id>]]
33
+ neo history status --run <run-id>
34
+ neo history resume --run <run-id>
35
+ ```
36
+
37
+ Rewrites need project owner or admin. `prune` and `flatten` only create
38
+ immutable plans. `apply` starts the destructive run once the plan seals;
39
+ production also needs the exact scope confirmation the plan shows.
40
+
41
+ ## Login and credentials
42
+
43
+ ```sh
44
+ neo login [--api <url>] [--profile editor|release] [--save-project <id>]
45
+ neo whoami
46
+ neo logout [--api <url>]
47
+ ```
48
+
49
+ - Inside a workspace these use its `apiBaseUrl`; `--api` wins; outside, the
50
+ build's default origin. Login prints the origin before storing anything.
51
+ - Tokens use the OS credential store, falling back to a protected file. In CI
52
+ use `NEO_COMPOSE_TOKEN` or `--token-stdin`. The CLI refreshes its
53
+ short-lived JWT during long commands.
54
+ - The editor profile can't publish releases; server scopes are the security
55
+ boundary.
56
+ - `logout` deletes the credential for exactly one origin and revokes the
57
+ session best-effort.
58
+ - `NEO_COMPOSE_CONFIG_HOME` (absolute path replacing
59
+ `$XDG_CONFIG_HOME/neo-compose`) and `NEO_COMPOSE_CREDENTIAL_NAMESPACE`
60
+ (keychain account scope) isolate concurrent environments on one OS user.
61
+
62
+ ## Automation
63
+
64
+ - Pass explicit project/version IDs and flags, prefer `--json`, and never
65
+ depend on pickers or confirms. Non-TTY runs never wait for them.
66
+ - Boolean flags are bare switches; `--dry-run=true` is rejected.
67
+ - `--dry-run` exists on push, merge, refresh, branch refresh, release cut, and
68
+ migrate run; elsewhere it is rejected before any action. `branch create`
69
+ has none.
@@ -0,0 +1,68 @@
1
+ # Conflicts and recovery
2
+
3
+ ## Conflicts
4
+
5
+ Pull merges disjoint fields and children, and merges collection and graph
6
+ children by ID, never by index. Conflicting scalars, edges, order,
7
+ delete/edit pairs, and binaries become explicit conflicts whose source fails
8
+ compilation on purpose.
9
+
10
+ - Edit the source to the intended final state, or run `neo resolve --mine` or
11
+ `--theirs` for a deliberate whole-side choice. Then pull, review, dry-run,
12
+ and push. There is no force-CAS path.
13
+ - Either side adopts the server as the new base for the conflicts it
14
+ settles: `--theirs` then reads clean, `--mine` reads as an ordinary local
15
+ update.
16
+ - One record is one conflict however many files declare it. `neo resolve`
17
+ settles all of its files together and refuses a partly resolved record.
18
+ - Resolution keeps the chosen lines and their line endings (LF, CRLF, or
19
+ mixed). Choosing a deleted side removes the file; deletion placeholders are
20
+ display text. Empty and comment-only files stay files.
21
+ - Against a server-side deletion, keeping the record restores it as a create
22
+ guarded by an absent-record check; removing it accepts the deletion.
23
+ - Pull warns when a conflicted record's local base strands newly downloaded
24
+ descendant rows. Resolve it with `--mine` or `--theirs` before pushing.
25
+ - Compiled initializer IR and other server-derived metadata never cause a
26
+ conflict by themselves.
27
+ - Binary conflicts keep local bytes; the server copy is under
28
+ `.neo/conflicts/files/<file-id>/`.
29
+
30
+ `value-base-desync` means the workspace base no longer names value rows its
31
+ own files project, so a plan would delete live rows. Nothing is planned; run
32
+ `neo pull` and retry. As a warning, it reports without blocking.
33
+
34
+ ## Interrupted pushes
35
+
36
+ If a push committed but rewriting source failed, `neo pull` resumes the saved
37
+ result before checking the remote head. Status and push require that
38
+ recovery first. Interrupted file writes resume without parsing a half-written
39
+ checkout. If a file changed again after the plan was saved, recovery stops and
40
+ names it; save that edit separately, restore the file to its pre-rewrite or
41
+ generated content, then run `neo pull`.
42
+
43
+ ## .neo/state.json
44
+
45
+ It holds the merge baseline, source placements, and the recovery journal.
46
+ Keep it. Normal pull refuses existing source without a baseline. To replace a
47
+ checkout whose state is gone, back it up and run `neo pull --force`, which
48
+ recovers custom paths from the remaining declarations and replaces local
49
+ source with the server version.
50
+
51
+ ## neo doctor
52
+
53
+ `neo doctor` checks format, compiler, extension cache, source, files, and the
54
+ last pull's document integrity, and fails on malformed records. Pull first to
55
+ audit the current head.
56
+
57
+ - The partition audit follows ownership: a lookup may reference a value in a
58
+ different partition.
59
+ - It also reports base rows that no file projects and no record names. These
60
+ are invisible to `neo status`.
61
+ - `--json` includes a `repair` entry per finding for targeted repair.
62
+ - `--fix` repairs only `unprojected-value-row` findings: it lists each orphan
63
+ root with its row count, confirms, and deletes them in batches. Each row
64
+ carries the last pull's CAS base and each transaction expects the previous
65
+ head, so a stale audit fails instead of deleting a row that gained an
66
+ owner; a workspace with no recorded head is refused. `--yes` skips the
67
+ prompt. `--fix` has no `--json` form.
68
+ - After a reviewed `--fix`, pull and run doctor again.
@@ -0,0 +1,96 @@
1
+ # Sync commands
2
+
3
+ What `pull`, `status`, `diff`, and `push` do beyond the workflow in
4
+ [SKILL.md](../../SKILL.md). Open a leaf from [References](#references) for
5
+ conflicts and recovery, branches and releases, or Unity export.
6
+
7
+ ## Pull
8
+
9
+ - Normal pull fetches only changed records and merges them three-way by
10
+ record identity. If the remote head is unchanged it leaves source alone
11
+ without compiling; use `neo status` for local diagnostics.
12
+ - It skips records whose hashes match the saved baseline, including echoes of
13
+ this checkout's last push, and refreshes records whose remote change touches
14
+ only compiler-owned bodies or timestamps without rewriting authored files.
15
+ - It keeps declaration placement, spelling, comments, and file grouping by
16
+ stable ID. New server declarations use the default layout; system
17
+ declarations default to a `System` subfolder under their category.
18
+ Ownership comes from the reserved system identity, not the name.
19
+ - `--reset` regenerates canonical source and managed binaries, keeping
20
+ recorded declaration paths. `--force` discards local edits for the server
21
+ version. Both bypass push recovery. Use them only when that loss is
22
+ intended.
23
+ - `--regenerate-source-names` replaces generated dialogue symbols with
24
+ readable names and rewrites references.
25
+ - Pull refuses a baseline holding a compiled body this CLI rejects and names
26
+ the record. If pull can't refresh it, back up local edits and run
27
+ `neo pull --force`.
28
+
29
+ ## Status and diff
30
+
31
+ `neo status` and `neo diff` show every pending change, grouped by owning
32
+ source, including newly authored value seeds. Diagnostics carry
33
+ file:line:column, such as invalid constructor parameter types. A failed class
34
+ or constructor declaration blocks candidate preparation.
35
+
36
+ Deleting a class or collection declaration also deletes its hidden entry
37
+ members, nested lists and dictionaries included; replacing a collection with
38
+ a scalar deletes abandoned entries. Entries used by surviving declarations
39
+ stay. If you deleted a member before pulling remote instance values, pull
40
+ keeps them in the deletion plan. Review these derived deletions before
41
+ pushing.
42
+
43
+ ## Dry-run
44
+
45
+ `neo push --dry-run` rehearses the whole commit locally: source emission,
46
+ pending-ID assignment, transport shapes, hashes, repeat verification,
47
+ compilation, schema-commit materialization, seeded structural rows, and
48
+ whole-graph validation. It never prompts, uploads, commits, or rewrites
49
+ source. Pending getters and functions compile before initializers evaluate,
50
+ even on a first push, and initializers resolve pending dependencies first;
51
+ cycles report the participating values. It also checks derived-edge extractor compatibility against metadata
52
+ from the last pull; run `neo pull` if that metadata is missing.
53
+
54
+ A clean dry-run doesn't guarantee a clean push. The server still checks the
55
+ live head, per-change CAS bases, authorization, row shapes, file staging, and
56
+ admission.
57
+
58
+ ## Push
59
+
60
+ - Push recompiles only bodies affected by changed declarations, prepares the
61
+ transaction once before confirmation, and reuses it after.
62
+ `--force-recompile` (with or without `--dry-run`) sweeps every body; use it
63
+ only to diagnose stale derived IR.
64
+ - One accepted push atomically commits records, main-locale text, bindings,
65
+ and staged file metadata. Pending IDs and source rewrites become real only
66
+ after acceptance.
67
+ - Normal push hashes the authored source it read and does not re-lower the
68
+ whole tree. A lowering mismatch therefore surfaces on the next canonical
69
+ pull; dry-run is the full source-to-record proof.
70
+ - `non-fast-forward`: someone committed after your pull. Pull, then push.
71
+ - `base-hash-conflict`: pull and resolve.
72
+ - `version-bump-required`: inspect the classification and pass
73
+ `--accept-bump` only when intended.
74
+ - Completion arrives through a live subscription, with progress every five
75
+ seconds. A disconnect doesn't cancel the durable job.
76
+
77
+ ## Output and diagnostics
78
+
79
+ Interactive commands show phase spinners; `NO_COLOR` removes color only. JSON
80
+ and piped output contain no animation. Set `NEO_COMPOSE_TRACE_PERFORMANCE=1`
81
+ to write `neo-performance` JSON spans to stderr (`startedMs` relative to the
82
+ process, `durationMs` per operation; nested spans overlap, so don't sum
83
+ them).
84
+
85
+ ## References
86
+
87
+ - [Conflicts and recovery](conflicts-and-recovery.md): merge conflicts,
88
+ `neo resolve`, `value-base-desync`, interrupted pushes, `.neo/state.json`,
89
+ `neo doctor` and `--fix`.
90
+ - [Branches, releases, and access](branches-and-history.md): branches, merge,
91
+ releases, history rewrites, login profiles, tokens, flags.
92
+ - [Unity export](unity-export.md): `neo export`, `neo dev`, local exports,
93
+ workspace lock.
94
+ - [Testing](../neoscript/testing-and-migrations.md): `neo test`,
95
+ `neo script`, `neo migrate`.
96
+ - [Localization](../values/defaults-and-localization.md#neo-loc): `neo loc`.
@@ -0,0 +1,63 @@
1
+ # Unity export
2
+
3
+ `neo export` builds the Unity export from the working copy, offline, into the
4
+ directories the Unity project's `NeoComposeConfig` asset names: `project.json`
5
+ with a partition index, `Partitions/*.json` value rows, localization files,
6
+ and generated C# under `<generatedTypesDirectory>/Generated/`.
7
+ `Library/NeoCompose/export.json` is written last for Unity to ingest.
8
+
9
+ - Only changed files are written. Codegen is skipped when schema inputs are
10
+ unchanged, but content is still validated. Repeated exports, undos, and
11
+ reverts reuse cached results after verifying output bytes.
12
+ - The Unity project comes from `unityConfigPath`. Without it, an interactive
13
+ export offers to link the one config asset naming this project; scripts get
14
+ an error with the line to add. `--unity-project <dir>` exports elsewhere
15
+ without linking (agent rigs use it).
16
+ - Export stops before writing on a source error, a file conflict, a
17
+ `download` file action, an error diagnostic, or an unchanged project file
18
+ whose bytes differ from the pulled file.
19
+ - Keep `NeoGeneratedFiles.json` with the generated files; it preserves
20
+ `.meta` GUIDs across type renames.
21
+ - Export reports an error when an animation clip target doesn't match its
22
+ owner or one of its constructed base types.
23
+
24
+ Status and export stay clean when animation defaults use initializers or
25
+ constructor arguments select collection rows through Lookups.
26
+
27
+ ## Local exports
28
+
29
+ A clean working copy exports the pulled head; diagnostic location shifts
30
+ alone don't count as local changes. Local edits, `Files/` bytes included, make
31
+ the export local: `project.json` gets `metadata.localExport`, Unity fails
32
+ player builds and keeps saves under `NeoCompose/LocalExport`, and a push or a
33
+ clean export ends it.
34
+
35
+ IDs minted for local declarations, including getters used in NeoFlow
36
+ interpolation, are kept in `.neo/export.json`'s `identityLedger`, so the next
37
+ push commits the IDs Unity already saw. A push re-exports into a Unity project
38
+ that received a local export.
39
+
40
+ ## neo dev
41
+
42
+ `neo dev` watches source, binaries, ignore rules, workspace state, and config,
43
+ then exports in batches (20 ms quiet period, capped at 250 ms). Edits during
44
+ an export go into the next batch. It never pulls or pushes.
45
+
46
+ Pull, push, branch, merge, resolve, init, and export take turns through
47
+ `.neo/workspace.lock`; a lock whose process died is taken over.
48
+
49
+ ## C# target and extension wrappers
50
+
51
+ Set `codegen.csharpVersion` in `neo.json` to a numeric language version such as
52
+ `"14"`. `latest` and `preview` are rejected. Without an override, export uses
53
+ the supported Unity compiler mapping or the SDK's C# 9 baseline. It reads
54
+ `codegen.unityVersion` first, then the linked Unity project's
55
+ `ProjectSettings/ProjectVersion.txt`. `neo doctor` reports the effective target
56
+ and its source. An unrecognized Unity version produces a baseline warning.
57
+
58
+ Closed instance extension functions can generate classic C# extension methods.
59
+ Interface receivers, open receiver types, static extensions, and computed
60
+ extension properties require C# 14. Export omits an unavailable wrapper and
61
+ reports its member ID, receiver, and missing capability. It keeps the member
62
+ available to the NeoScript evaluators and SDK direct invocation API. Changing
63
+ the target regenerates wrappers and removes obsolete owned wrapper files.
@@ -0,0 +1,86 @@
1
+ # Defaults, overrides, and localization
2
+
3
+ ## Virtual members and sparse overrides
4
+
5
+ Omitted members are virtual projections of the instance's recorded
6
+ constructor or variant and follow later edits to that source.
7
+
8
+ - Writing a member creates a sparse override with a deterministic projected
9
+ ID. Removing it from an existing initializer resets the override and
10
+ resumes tracking. Both transitions must show in `neo diff`.
11
+ - Spelling a declared default verbatim is the same as omitting it: a leaf
12
+ equal to its declaration, or a bare construction the declaration wholly
13
+ settles, mints no override. Spelling an unchanged child default such as
14
+ `FPS = 8` is a no-op even when siblings own rows. A stored row that already
15
+ spells its default becomes sparse on its next authored edit.
16
+ - Adding a different child default keeps sibling row identities.
17
+ - A stored class with no construction record still inherits literal
18
+ declaration defaults (`FoodInfo.Quality = .OneStar` fills an existing row
19
+ that stores only its price). Don't add an override to repair that. Required
20
+ fields with no applicable default remain errors.
21
+ - Defaulted members can be added without recreating existing instances.
22
+ - Editing an inline child keeps the outer construction's call-site values for
23
+ its other fields while its variant selection matches; a different variant
24
+ gives it an independent recipe.
25
+
26
+ ## Emitted defaults
27
+
28
+ A member whose default constructs its declared type emits `= new()`; a
29
+ subclass default keeps its type (`Base Value = new Derived();`). Edit the
30
+ declaration to change the shared default, or assign inside an instance
31
+ initializer to override one instance. A nullable member with no default
32
+ stays bare; an explicit null default emits `= null`.
33
+
34
+ Generic arguments are types, not defaults: a required generic member needs a
35
+ value at construction unless a member declaration supplies a default. Pull
36
+ keeps incomplete stored objects for repair; add the initializer or default
37
+ before testing or pushing. An override without a local initializer inherits
38
+ the base default; an explicit `= null` is a literal nullable default.
39
+
40
+ ## Construction rebuilds
41
+
42
+ Source is authoritative for a construction. Editing its constructor
43
+ arguments, a static's included, rebuilds it in place on push:
44
+
45
+ - the value keeps its ID and rows at unchanged positions keep theirs,
46
+ - added entries create rows and removed entries delete them,
47
+ - overrides the new construction no longer produces are deleted, so keep
48
+ intended overrides in the construction's `{ ... }` block.
49
+
50
+ Switching to another overload or shared argument rebuilds even when argument
51
+ values compare equal; owned inline arguments still compare by contents. A
52
+ construction whose class changed fails with `construction-arguments-conflict`;
53
+ delete and recreate the value instead.
54
+
55
+ ## Localized text
56
+
57
+ - Write main-locale text directly in localizable string initializers. Never
58
+ author localized-text IDs in place of prose.
59
+ - Equal main-locale text on the same declared member shares one localization
60
+ record across overrides and instances. Different members keep separate
61
+ contexts.
62
+ - Editing a class default updates inheriting instances; editing one instance
63
+ doesn't change the default. Reverting an instance to its default restores
64
+ the shared record and archives an unreferenced unique one.
65
+ - An empty string stores no localization ID. Nullability, not text length,
66
+ decides whether the member is required.
67
+
68
+ ## neo loc
69
+
70
+ Source files hold only main-locale text. Use `neo loc` for other locales,
71
+ statuses, comments, archives, and repair:
72
+
73
+ ```sh
74
+ neo loc locales
75
+ neo loc list
76
+ neo loc set <textId> <locale> "text"
77
+ neo loc status-set <textId> <locale> <statusId>
78
+ neo loc comment-set <textId> <locale> "comment"
79
+ neo loc archive <textId>
80
+ neo loc restore <textId>
81
+ ```
82
+
83
+ Batches go on stdin or `--file` as a JSON array where supported. `neo loc`
84
+ doesn't rewrite source; run `neo pull` after it (or any web edit) before
85
+ further edits or a push. A write refused because another commit landed is
86
+ run again.
@@ -0,0 +1,71 @@
1
+ # Managed files
2
+
3
+ Images (PNG, JPEG, GIF, SVG) and audio clips are declared in typed registries
4
+ and stored as tracked binaries. SVG keeps its original bytes everywhere; the
5
+ CLI never rasterizes it.
6
+
7
+ ## Registries
8
+
9
+ ```neo
10
+ ImageRegistry Images = new() {
11
+ @id("sword-image-file-id")
12
+ @settings(template: PixelArt)
13
+ NeoImage Sword = new("Files/Images/Sword.png");
14
+ }
15
+
16
+ AudioClipRegistry AudioClips = new() {
17
+ @id("sword-hit-audio-file-id")
18
+ @settings(template: SoundEffect)
19
+ NeoAudioClip SwordHit = new("Files/AudioClips/SwordHit.wav");
20
+ }
21
+ ```
22
+
23
+ - `Images.Sword.Slice(0)` is a sprite; `AudioClips.SwordHit` is a clip. A bare
24
+ `Images.Sword` is only a file ID, valid in file-valued contexts such as
25
+ `SpriteInfo.FileId`.
26
+ - Registries and binaries may sit in any non-ignored folder. Update the path
27
+ when moving a binary.
28
+ - Renaming the symbol or replacing bytes keeps the file ID when `@id` stays.
29
+ - Symbols derive from the persisted file name (`tree.cherryblossom.png`
30
+ becomes `TreeCherryblossom`). Keep the symbol aligned or set
31
+ `@settings(name: "TreeCherryBlossom.png")`. The name must end with the
32
+ binary's extension because pull rebuilds the path from it.
33
+ - Without `@settings(template: ...)`, the CLI stores the complete built-in
34
+ Unity import defaults for the file kind. Template-backed settings may hold
35
+ sparse leaf paths such as `platformSettings.standalone.maxTextureSize` or
36
+ `spriteSettings.pivot.x`; keep them so untouched settings inherit.
37
+
38
+ A declaration can live in its own file, listed by name in the registry. The
39
+ declaration carries `@id`, `@settings`, and the path, and must be listed
40
+ exactly once:
41
+
42
+ ```neo
43
+ // Values/Audio/SWORD_HIT.neo
44
+ @id("sword-hit-audio-file-id")
45
+ @settings(template: SoundEffect)
46
+ NeoAudioClip SWORD_HIT = new("Files/AudioClips/SwordHit.wav");
47
+
48
+ // Files/AudioClips.neo
49
+ AudioClipRegistry AudioClips = new() {
50
+ NeoAudioClip SwordHit = SWORD_HIT;
51
+ }
52
+ ```
53
+
54
+ ## Adding, replacing, removing
55
+
56
+ - Drop a supported binary under `Files/Images/` or `Files/AudioClips/` to
57
+ create a pending file with a provisional symbol. Status, diff, and dry-run
58
+ don't rewrite the registry; a successful push creates the record, uploads
59
+ verified bytes, and writes its declaration and ID.
60
+ - Remove the declaration to delete a file. A declaration whose binary is
61
+ missing is an error.
62
+ - Pull keeps unchanged entries, comments, and order; new remote entries
63
+ append, deleted ones are removed in place.
64
+
65
+ ## Transfers and conflicts
66
+
67
+ Pull and push compare server-verified SHA-256, byte length, and MIME type.
68
+ Pull downloads up to eight files at once, reuses matching local bytes, and
69
+ verifies every download before replacing anything; a failed transfer leaves
70
+ files intact. A diverged binary keeps local bytes and writes the server copy
71
+ under `.neo/conflicts/files/<file-id>/`.
@@ -0,0 +1,97 @@
1
+ # Identities
2
+
3
+ ## Keep, omit, or author
4
+
5
+ - Keep every existing `@id` through rename, reorder, and file moves. Renaming
6
+ a member with its ID keeps its value graph; update initializer keys to the
7
+ new name and keep child IDs.
8
+ - A copy gets a new ID.
9
+ - New records omit `@id`. A successful real push assigns and inserts it;
10
+ dry-run and failed pushes leave source unchanged.
11
+ - Never match an existing ordered row by index or payload. A bare row is a
12
+ pending create.
13
+ - Never invent IDs for rows whose identity derives from an owner role: root
14
+ and static bindings, projected values, and constructor arguments (named or
15
+ positional), whose identity comes from the schema key the parameter
16
+ settles.
17
+
18
+ Nested class and list rows keep inline IDs:
19
+
20
+ ```neo
21
+ Tags = [
22
+ @id("story-tag-item-id")
23
+ "story",
24
+ ];
25
+ ```
26
+
27
+ ## Authored UUIDs
28
+
29
+ When a same-push structural reference has no symbol, path, or unique-key
30
+ form, give the new row a never-used UUID-v4 `@id` and reference it in the same
31
+ edit. The server checks shape and uniqueness. Reusing an existing ID means
32
+ update, not create. Dry-run keeps a valid authored UUID-v4 on a new nested
33
+ member or dictionary value. A malformed unknown ID is an error at its
34
+ annotation: remove it or supply an unused UUID-v4. Prefer generated IDs.
35
+
36
+ ## Moving a nested value
37
+
38
+ A stored nested class value may keep its UUID-v4 when moved to an empty,
39
+ compatible class field on another existing parent, if all of these hold:
40
+
41
+ - the old owning declaration is removed in the same push,
42
+ - both fields declare the same non-generic class and storage rules,
43
+ - exactly one ownership path remains to the same root or static binding
44
+ through ordinary class fields, and
45
+ - both paths use inherited storage and automatic distribution, with no member
46
+ overrides.
47
+
48
+ Moves of projected values, constructor-owned slots, partitioned values,
49
+ generic instances, unordered-list entries, or into newly constructed parents
50
+ are refused. Omit the ID to create a new value there instead.
51
+
52
+ ## Local initializer IDs
53
+
54
+ An existing row ID may precede a local declaration. It names the initializer
55
+ row, not a stored global:
56
+
57
+ ```neo
58
+ @id("existing-quest-row-id")
59
+ Quest HardAsStone = new Quest() { Name = "Hard as stone" };
60
+ ```
61
+
62
+ `Quest HardAsStone = @id("...") new Quest();` is accepted and formatted to the
63
+ form above. Never put an ID in both places. A local keeps its concrete type
64
+ when a base-typed member consumes it.
65
+
66
+ ## Locals as constructor arguments
67
+
68
+ In constructor arguments and executable operands, a class local names its one
69
+ stored instance rather than building a copy. Its owning placement keeps it,
70
+ so a constructor that stores it needs a `Reference` field:
71
+
72
+ ```neo
73
+ @id("86ac7c7d-8d3b-4642-9def-cdf32643bdc5")
74
+ DeliveryQuest HARD_AS_STONE = new();
75
+ // The core quest list owns HARD_AS_STONE.
76
+ WorkflowStep UNLOCK = new QuestUnlockStep(HARD_AS_STONE);
77
+
78
+ class QuestUnlockStep(DeliveryQuest quest) : WorkflowStep {
79
+ public Reference<DeliveryQuest> Quest = quest;
80
+ }
81
+ ```
82
+
83
+ - The local needs exactly one owning placement. Unowned locals, ambiguous
84
+ placements, and cycles are source errors.
85
+ - A nested target needs its existing `@id`. A structurally owned list entry
86
+ may use an unused UUID-v4 in the same push.
87
+ - A local nested inside an evaluated constructor stays idless on first push;
88
+ push assigns its identity before it can be referenced. Don't give it a fresh
89
+ UUID.
90
+ - A direct static owner supplies a pending identity for an idless local, and
91
+ aliases of that static local work too.
92
+ - A member default is a construction template, so its local is not a single
93
+ stored instance.
94
+ - Owned locals inside evaluated initializers need directly addressable fields,
95
+ list entries, or literal dictionary keys. Inline the construction when a
96
+ computed key hides the owning path.
97
+ - Canonical construction arguments may use `Reference<Type>(id: "...")`.