@neocompose/cli 0.56.0 → 0.56.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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.56.1] - 2026-09-23
4
+
5
+ - Changing a member that a variant's `initialize` or `apply` closure uses, such as its storage, recompiles the closure. The closure used to keep the code compiled against the old member, so after a member changed from `.Session` to `.Writable`, a variant that writes it failed in the game with "Member '…' is not Session-owned." To repair closures an earlier push left stale, add `--force-recompile` to your next push.
6
+
3
7
  ## [0.56.0] - 2026-09-23
4
8
 
5
9
  - `@storage(allowed: .Writable)` declares a member that is always writable at runtime and persists wherever its owner persists: Save under a Save parent, Session otherwise. NeoScript can write it through any receiver, including one read from Assets, and animation clips can target it. `neo pull` writes it back in the same form. Earlier CLIs fail on a project that uses it, so upgrade before pulling.
package/dist/neo.mjs CHANGED
@@ -95076,6 +95076,121 @@ var init_member_access_modifier_validation = __esm({
95076
95076
  }
95077
95077
  });
95078
95078
 
95079
+ // ../src/database/project-version-write-changes.ts
95080
+ function applyProjectVersionWriteChanges(document, changes) {
95081
+ return projectVersionDocumentWithChanges(document, changes, false);
95082
+ }
95083
+ function projectVersionDocumentWithChanges(document, changes, reuseUnchangedBuckets) {
95084
+ const projectRows = (rows, kind) => {
95085
+ const relevant = changes.filter((change) => change.recordKind === kind);
95086
+ if (reuseUnchangedBuckets && relevant.length === 0) return rows;
95087
+ return applyArrayChanges(rows, relevant);
95088
+ };
95089
+ const hasGranularDialogueRecords = document.dialogueRecords !== void 0 || changes.some((change) => change.recordKind === "dialogue");
95090
+ const hasGranularDialogueNodes = document.dialogueNodes !== void 0 || changes.some((change) => change.recordKind === "dialogue-node");
95091
+ const dialogueRecords = hasGranularDialogueRecords ? projectRows(document.dialogueRecords ?? [], "dialogue") : void 0;
95092
+ const dialogueNodes = hasGranularDialogueNodes ? projectRows(document.dialogueNodes ?? [], "dialogue-node") : void 0;
95093
+ const projected = {
95094
+ localizedTexts: projectRows(
95095
+ document.localizedTexts ?? [],
95096
+ "localized-text"
95097
+ ),
95098
+ localizationConfig: applySingletonChange(
95099
+ document.localizationConfig,
95100
+ changes,
95101
+ "localization-config"
95102
+ ),
95103
+ project: applySingletonChange(document.project, changes, "project"),
95104
+ members: projectRows(document.members, "member"),
95105
+ classes: projectRows(document.classes, "class"),
95106
+ // P43 §6. Declared constructors participate in the post-transaction
95107
+ // document so the whole-graph validator and every body compile see the
95108
+ // same constructor surface the transaction is about to persist.
95109
+ constructors: projectRows(document.constructors ?? [], "constructor"),
95110
+ // P67 §2.1. Same reason as constructors: without these buckets the
95111
+ // whole-graph validator, the variant ownership roots, and every body
95112
+ // compile would grade the transaction against the pre-transaction
95113
+ // variant set — so a newly pushed variant's closures would compile
95114
+ // against a document that does not contain it.
95115
+ variants: projectRows(document.variants ?? [], "variant"),
95116
+ variantFolders: projectRows(
95117
+ document.variantFolders ?? [],
95118
+ "variant-folder"
95119
+ ),
95120
+ values: projectRows(document.values ?? [], "value"),
95121
+ // Project-file staging finalizes pending uploads before schema
95122
+ // preparation. Defaults and authored instance graphs may reference a file
95123
+ // created by the same source transaction, so every construction pass must
95124
+ // see that finalized row in its post-write document.
95125
+ projectFiles: projectRows(document.projectFiles ?? [], "project-file"),
95126
+ // Import-setting templates are part of the same file graph. A new file
95127
+ // may select a new template in one source transaction, and default-value
95128
+ // materialization validates that relationship immediately against this
95129
+ // post-write document.
95130
+ textureTemplates: projectRows(
95131
+ document.textureTemplates ?? [],
95132
+ "unity-texture-template"
95133
+ ),
95134
+ audioClipTemplates: projectRows(
95135
+ document.audioClipTemplates ?? [],
95136
+ "unity-audio-clip-template"
95137
+ ),
95138
+ internalRecordRelations: projectRows(
95139
+ document.internalRecordRelations ?? [],
95140
+ "internal-record-relation"
95141
+ ),
95142
+ enums: projectRows(document.enums, "enum"),
95143
+ interfaces: projectRows(document.interfaces, "interface"),
95144
+ dialogues: dialogueRecords === void 0 || dialogueNodes === void 0 || dialogueRecords === document.dialogueRecords && dialogueNodes === document.dialogueNodes ? document.dialogues : materializeDialogues(
95145
+ dialogueRecords,
95146
+ dialogueNodes
95147
+ ),
95148
+ dialogueGroups: projectRows(
95149
+ document.dialogueGroups ?? [],
95150
+ "dialogue-group"
95151
+ ),
95152
+ migrations: projectRows(document.migrations ?? [], "migration")
95153
+ };
95154
+ return {
95155
+ ...document,
95156
+ ...projected,
95157
+ ...dialogueRecords === void 0 ? {} : { dialogueRecords },
95158
+ ...dialogueNodes === void 0 ? {} : { dialogueNodes }
95159
+ // The document spread above carries every bucket the caller's view declares
95160
+ // beyond the projected ones, so the result is the same view; TypeScript
95161
+ // cannot prove that of an object literal built over a type parameter.
95162
+ };
95163
+ }
95164
+ function applySingletonChange(current, changes, recordKind) {
95165
+ const change = changes.find(
95166
+ (candidate) => candidate.recordKind === recordKind
95167
+ );
95168
+ if (change === void 0) return current;
95169
+ if (change.operation === "delete" || change.nextData === void 0) {
95170
+ throw new Error(`Schema commit cannot delete the ${recordKind} record.`);
95171
+ }
95172
+ return change.nextData;
95173
+ }
95174
+ function applyArrayChanges(current, changes) {
95175
+ const byId = new Map(current.map((record6) => [record6.id, record6]));
95176
+ for (const change of changes) {
95177
+ if (change.operation === "delete") {
95178
+ byId.delete(change.recordId);
95179
+ continue;
95180
+ }
95181
+ if (change.nextData !== void 0) {
95182
+ byId.set(change.recordId, change.nextData);
95183
+ }
95184
+ }
95185
+ return [...byId.values()];
95186
+ }
95187
+ var init_project_version_write_changes = __esm({
95188
+ "../src/database/project-version-write-changes.ts"() {
95189
+ "use strict";
95190
+ init_projectDocumentDialogueMaterialization();
95191
+ }
95192
+ });
95193
+
95079
95194
  // ../src/database/member-localization-normalization.ts
95080
95195
  function planMemberLocalizationNormalization(input, now = 0) {
95081
95196
  const before = expandPackedDocumentValues(input);
@@ -95366,7 +95481,7 @@ var init_member_localization_normalization = __esm({
95366
95481
  init_constructor_argument_ownership();
95367
95482
  init_variant_value_graph();
95368
95483
  init_packed_value_write_fold();
95369
- init_project_version_schema_commit();
95484
+ init_project_version_write_changes();
95370
95485
  init_packed_value_encoding();
95371
95486
  init_inheritance();
95372
95487
  init_members();
@@ -100590,16 +100705,38 @@ function prepareServerOwnedValueInitializerBodies(args) {
100590
100705
  }
100591
100706
  }
100592
100707
  function collectDelegateSweepRows(args) {
100593
- if (args.targetIds.size === 0) return [];
100708
+ const closureRows = [];
100709
+ const inlineRows = [];
100710
+ if (args.targetIds.size === 0) return { closureRows, inlineRows };
100594
100711
  const preparedValueIds = indexChangesByRecordId(args.prepared, "value");
100595
- const sweepRows = [];
100596
100712
  for (const value of args.postDocument.values) {
100597
100713
  if (!args.targetIds.has(value.id)) continue;
100598
100714
  if (preparedValueIds.has(value.id)) continue;
100599
100715
  const row = delegateValueRow(value);
100600
- if (row !== null) sweepRows.push(row);
100716
+ if (row !== null) {
100717
+ closureRows.push(row);
100718
+ continue;
100719
+ }
100720
+ const inlineRow = inlineDelegateSweepRow(value);
100721
+ if (inlineRow !== null) inlineRows.push(inlineRow);
100722
+ }
100723
+ return { closureRows, inlineRows };
100724
+ }
100725
+ function inlineDelegateSweepRow(value) {
100726
+ if (!isMemberValue(value) || !isLiteralValueContent(value)) return null;
100727
+ const constructorArgs = asRecord4(
100728
+ value.constructorArgs
100729
+ );
100730
+ if (constructorArgs === null) return null;
100731
+ let sourceArgs = null;
100732
+ for (const [key, argument2] of Object.entries(constructorArgs)) {
100733
+ const closure = asRecord4(argument2);
100734
+ if (typeof closure?.code !== "string") continue;
100735
+ if (!("action" in closure)) continue;
100736
+ sourceArgs ??= { ...constructorArgs };
100737
+ sourceArgs[key] = { code: closure.code };
100601
100738
  }
100602
- return sweepRows;
100739
+ return sourceArgs === null ? null : { ...value, constructorArgs: sourceArgs };
100603
100740
  }
100604
100741
  function delegateValueRow(value) {
100605
100742
  if (!isMemberValue(value) || !isLiteralValueContent(value)) return null;
@@ -100746,8 +100883,8 @@ function prepareServerOwnedDelegateValueBodies(args) {
100746
100883
  const withInlineArgs = inlineDelegateArgumentRow(change.nextData);
100747
100884
  if (withInlineArgs !== null) inlineArgumentRows.set(index, withInlineArgs);
100748
100885
  }
100749
- const sweepRows = collectDelegateSweepRows(args);
100750
- if (explicitRows.size === 0 && sweepRows.length === 0 && inlineArgumentRows.size === 0) {
100886
+ const { closureRows: sweepRows, inlineRows: inlineSweepRows } = collectDelegateSweepRows(args);
100887
+ if (explicitRows.size === 0 && sweepRows.length === 0 && inlineArgumentRows.size === 0 && inlineSweepRows.length === 0) {
100751
100888
  return;
100752
100889
  }
100753
100890
  const committedDocument = applyProjectVersionWriteChanges(
@@ -100756,7 +100893,7 @@ function prepareServerOwnedDelegateValueBodies(args) {
100756
100893
  );
100757
100894
  const targetIds = valueIdsFromRows(
100758
100895
  [...explicitRows.values(), ...inlineArgumentRows.values()],
100759
- sweepRows
100896
+ [...sweepRows, ...inlineSweepRows]
100760
100897
  );
100761
100898
  const rootOwnerByValueId = /* @__PURE__ */ new Map();
100762
100899
  const delegateScope = withVariantOwnershipRoots(
@@ -100831,7 +100968,7 @@ function prepareServerOwnedDelegateValueBodies(args) {
100831
100968
  };
100832
100969
  }
100833
100970
  }
100834
- if (sweepRows.length === 0) return;
100971
+ if (sweepRows.length === 0 && inlineSweepRows.length === 0) return;
100835
100972
  const currentById = new Map(
100836
100973
  args.document.values.map((value) => [value.id, value])
100837
100974
  );
@@ -100841,40 +100978,65 @@ function prepareServerOwnedDelegateValueBodies(args) {
100841
100978
  prepared: args.prepared,
100842
100979
  contentHashHeads: args.contentHashHeads
100843
100980
  });
100981
+ const refresh = (id2, compiled) => {
100982
+ const current = currentById.get(id2);
100983
+ if (current === void 0 || canonicallyEqual2(current, compiled)) return;
100984
+ const contentHash = baseHashFor(id2);
100985
+ if (contentHash === void 0) {
100986
+ throw new Error(
100987
+ `Cannot safely refresh the compiled NeoDelegate closure for value "${id2}": its current content hash is unavailable.`
100988
+ );
100989
+ }
100990
+ args.prepared.push({
100991
+ recordKind: "value",
100992
+ recordId: id2,
100993
+ operation: "update",
100994
+ nextData: compiled,
100995
+ intent: createProjectVersionIntent("value.update", {
100996
+ source: "server-neoscript-recompile"
100997
+ }),
100998
+ ...contentHash === null ? {} : { expectedBaseContentHash: contentHash }
100999
+ });
101000
+ };
100844
101001
  for (const row of sweepRows) {
100845
101002
  const compiled = { ...row, value: { ...row.value } };
100846
101003
  try {
100847
101004
  compileOne(delegateScope.document, compiled);
100848
101005
  } catch (postWriteError) {
100849
- const current2 = delegateValueRow(
101006
+ const current = delegateValueRow(
100850
101007
  structuredClone(currentById.get(row.id))
100851
101008
  );
100852
- if (current2 === null) throw postWriteError;
101009
+ if (current === null) throw postWriteError;
100853
101010
  try {
100854
- compileOne(args.document, current2);
101011
+ compileOne(args.document, current);
100855
101012
  } catch (preWriteError) {
100856
101013
  if (sameCompileFailure(preWriteError, postWriteError)) continue;
100857
101014
  }
100858
101015
  throw postWriteError;
100859
101016
  }
100860
- const current = currentById.get(row.id);
100861
- if (current === void 0 || canonicallyEqual2(current, compiled)) continue;
100862
- const contentHash = baseHashFor(row.id);
100863
- if (contentHash === void 0) {
100864
- throw new Error(
100865
- `Cannot safely refresh the compiled NeoDelegate closure for value "${row.id}": its current content hash is unavailable.`
100866
- );
101017
+ refresh(row.id, compiled);
101018
+ }
101019
+ const compileInlineOne = (document, row) => compileInlineDelegateConstructorArguments({
101020
+ document,
101021
+ row,
101022
+ ownerMember: ownerByValueId.get(row.id),
101023
+ rootOwner: rootOwnerByValueId.get(row.id) ?? null
101024
+ });
101025
+ for (const row of inlineSweepRows) {
101026
+ let compiledArgs;
101027
+ try {
101028
+ compiledArgs = compileInlineOne(delegateScope.document, row);
101029
+ } catch (postWriteError) {
101030
+ const current = inlineDelegateSweepRow(currentById.get(row.id));
101031
+ if (current === null) throw postWriteError;
101032
+ try {
101033
+ compileInlineOne(args.document, current);
101034
+ } catch (preWriteError) {
101035
+ if (sameCompileFailure(preWriteError, postWriteError)) continue;
101036
+ }
101037
+ throw postWriteError;
100867
101038
  }
100868
- args.prepared.push({
100869
- recordKind: "value",
100870
- recordId: row.id,
100871
- operation: "update",
100872
- nextData: compiled,
100873
- intent: createProjectVersionIntent("value.update", {
100874
- source: "server-neoscript-recompile"
100875
- }),
100876
- ...contentHash === null ? {} : { expectedBaseContentHash: contentHash }
100877
- });
101039
+ refresh(row.id, { ...row, constructorArgs: compiledArgs });
100878
101040
  }
100879
101041
  }
100880
101042
  function prepareServerOwnedConstructorBodies(args) {
@@ -101129,113 +101291,6 @@ function assertUniqueChanges(changes) {
101129
101291
  }
101130
101292
  }
101131
101293
  }
101132
- function applyProjectVersionWriteChanges(document, changes) {
101133
- return projectVersionDocumentWithChanges(document, changes, false);
101134
- }
101135
- function projectVersionDocumentWithChanges(document, changes, reuseUnchangedBuckets) {
101136
- const projectRows = (rows, kind) => {
101137
- const relevant = changes.filter((change) => change.recordKind === kind);
101138
- if (reuseUnchangedBuckets && relevant.length === 0) return rows;
101139
- return applyArrayChanges(rows, relevant);
101140
- };
101141
- const hasGranularDialogueRecords = document.dialogueRecords !== void 0 || changes.some((change) => change.recordKind === "dialogue");
101142
- const hasGranularDialogueNodes = document.dialogueNodes !== void 0 || changes.some((change) => change.recordKind === "dialogue-node");
101143
- const dialogueRecords = hasGranularDialogueRecords ? projectRows(document.dialogueRecords ?? [], "dialogue") : void 0;
101144
- const dialogueNodes = hasGranularDialogueNodes ? projectRows(document.dialogueNodes ?? [], "dialogue-node") : void 0;
101145
- const projected = {
101146
- localizedTexts: projectRows(
101147
- document.localizedTexts ?? [],
101148
- "localized-text"
101149
- ),
101150
- localizationConfig: applySingletonChange(
101151
- document.localizationConfig,
101152
- changes,
101153
- "localization-config"
101154
- ),
101155
- project: applySingletonChange(document.project, changes, "project"),
101156
- members: projectRows(document.members, "member"),
101157
- classes: projectRows(document.classes, "class"),
101158
- // P43 §6. Declared constructors participate in the post-transaction
101159
- // document so the whole-graph validator and every body compile see the
101160
- // same constructor surface the transaction is about to persist.
101161
- constructors: projectRows(document.constructors ?? [], "constructor"),
101162
- // P67 §2.1. Same reason as constructors: without these buckets the
101163
- // whole-graph validator, the variant ownership roots, and every body
101164
- // compile would grade the transaction against the pre-transaction
101165
- // variant set — so a newly pushed variant's closures would compile
101166
- // against a document that does not contain it.
101167
- variants: projectRows(document.variants ?? [], "variant"),
101168
- variantFolders: projectRows(
101169
- document.variantFolders ?? [],
101170
- "variant-folder"
101171
- ),
101172
- values: projectRows(document.values ?? [], "value"),
101173
- // Project-file staging finalizes pending uploads before schema
101174
- // preparation. Defaults and authored instance graphs may reference a file
101175
- // created by the same source transaction, so every construction pass must
101176
- // see that finalized row in its post-write document.
101177
- projectFiles: projectRows(document.projectFiles ?? [], "project-file"),
101178
- // Import-setting templates are part of the same file graph. A new file
101179
- // may select a new template in one source transaction, and default-value
101180
- // materialization validates that relationship immediately against this
101181
- // post-write document.
101182
- textureTemplates: projectRows(
101183
- document.textureTemplates ?? [],
101184
- "unity-texture-template"
101185
- ),
101186
- audioClipTemplates: projectRows(
101187
- document.audioClipTemplates ?? [],
101188
- "unity-audio-clip-template"
101189
- ),
101190
- internalRecordRelations: projectRows(
101191
- document.internalRecordRelations ?? [],
101192
- "internal-record-relation"
101193
- ),
101194
- enums: projectRows(document.enums, "enum"),
101195
- interfaces: projectRows(document.interfaces, "interface"),
101196
- dialogues: dialogueRecords === void 0 || dialogueNodes === void 0 || dialogueRecords === document.dialogueRecords && dialogueNodes === document.dialogueNodes ? document.dialogues : materializeDialogues(
101197
- dialogueRecords,
101198
- dialogueNodes
101199
- ),
101200
- dialogueGroups: projectRows(
101201
- document.dialogueGroups ?? [],
101202
- "dialogue-group"
101203
- ),
101204
- migrations: projectRows(document.migrations ?? [], "migration")
101205
- };
101206
- return {
101207
- ...document,
101208
- ...projected,
101209
- ...dialogueRecords === void 0 ? {} : { dialogueRecords },
101210
- ...dialogueNodes === void 0 ? {} : { dialogueNodes }
101211
- // The document spread above carries every bucket the caller's view declares
101212
- // beyond the projected ones, so the result is the same view; TypeScript
101213
- // cannot prove that of an object literal built over a type parameter.
101214
- };
101215
- }
101216
- function applySingletonChange(current, changes, recordKind) {
101217
- const change = changes.find(
101218
- (candidate) => candidate.recordKind === recordKind
101219
- );
101220
- if (change === void 0) return current;
101221
- if (change.operation === "delete" || change.nextData === void 0) {
101222
- throw new Error(`Schema commit cannot delete the ${recordKind} record.`);
101223
- }
101224
- return change.nextData;
101225
- }
101226
- function applyArrayChanges(current, changes) {
101227
- const byId = new Map(current.map((record6) => [record6.id, record6]));
101228
- for (const change of changes) {
101229
- if (change.operation === "delete") {
101230
- byId.delete(change.recordId);
101231
- continue;
101232
- }
101233
- if (change.nextData !== void 0) {
101234
- byId.set(change.recordId, change.nextData);
101235
- }
101236
- }
101237
- return [...byId.values()];
101238
- }
101239
101294
  function asRecord4(value) {
101240
101295
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
101241
101296
  return null;
@@ -101430,7 +101485,6 @@ var init_project_version_schema_commit = __esm({
101430
101485
  init_evaluateInitializer();
101431
101486
  init_virtual_instance_graph();
101432
101487
  init_virtual_instance_values();
101433
- init_projectDocumentDialogueMaterialization();
101434
101488
  init_valueHeadPlacementGraph();
101435
101489
  init_canonical_json();
101436
101490
  init_compile_ns_property();
@@ -101455,6 +101509,7 @@ var init_project_version_schema_commit = __esm({
101455
101509
  init_project_version_relation_sweep_trigger();
101456
101510
  init_project_version_static_value_writes();
101457
101511
  init_project_version_transaction_planning();
101512
+ init_project_version_write_changes();
101458
101513
  init_value_row_owner_members();
101459
101514
  init_variant_value_graph();
101460
101515
  SERVER_COMPILATION_PROJECT_CACHE_LIMIT = 4;
@@ -107537,7 +107592,7 @@ var init_project_version_whole_graph_validation = __esm({
107537
107592
  init_project_root_members();
107538
107593
  init_project_schema_identifier_validation();
107539
107594
  init_packed_value_write_fold();
107540
- init_project_version_schema_commit();
107595
+ init_project_version_write_changes();
107541
107596
  init_project_version_system_protection_writes();
107542
107597
  init_reserved_record_id_validation();
107543
107598
  init_members();
@@ -139485,7 +139540,7 @@ var init_registry2 = __esm({
139485
139540
  "schema-contract/registry.mjs"() {
139486
139541
  "use strict";
139487
139542
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
139488
- cliVersion: "0.56.0",
139543
+ cliVersion: "0.56.1",
139489
139544
  projectFileUploadBatchSize: 32,
139490
139545
  documentRecords: {
139491
139546
  member: {
@@ -147306,7 +147361,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
147306
147361
  async function main() {
147307
147362
  const args = parseArgs(process.argv.slice(2));
147308
147363
  if (args.command === "--version") {
147309
- console.log("0.56.0");
147364
+ console.log("0.56.1");
147310
147365
  return;
147311
147366
  }
147312
147367
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.56.0",
3
+ "version": "0.56.1",
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.56.0 -->
12
+ <!-- reviewed-through-cli: 0.56.1 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -97,7 +97,7 @@ wrappers.
97
97
  The marker near the top of `SKILL.md` must exactly match the package version:
98
98
 
99
99
  ```html
100
- <!-- reviewed-through-cli: 0.56.0 -->
100
+ <!-- reviewed-through-cli: 0.56.1 -->
101
101
  ```
102
102
 
103
103
  The quoted version above is checked too, so this instruction cannot go stale