@neocompose/cli 0.62.2 → 0.63.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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.63.0] - 2026-10-02
4
+
5
+ - Add `@effect` for class member functions. The Unity runtime runs an effect for every live instance, then reruns it whenever data it read changes. `@effect(kind: .None)` stops an inherited effect. An effect must be an immediate, parameterless `void` instance function with a NeoScript or abstract body; anything else reports `invalid-effect` at its source.
6
+ - Pull functions with empty bodies instead of failing on their stored empty source.
7
+
8
+ ## [0.62.3] - 2026-10-02
9
+
10
+ - `neo export unity` saves the server codegen revision in the generated-file manifest, matching Unity synchronization. Server codegen changes now invalidate incremental Unity exports even when project records have not changed. Unchanged generated C# files retain their contents and timestamps.
11
+
3
12
  ## [0.62.2] - 2026-10-02
4
13
 
5
14
  - Speed up `neo test` after production edits by sharing one indexed compiler context across specs and omitting the push-only source serialization round trip. Production compilation and whole-graph validation still run.
package/dist/neo.mjs CHANGED
@@ -19678,6 +19678,7 @@ var init_project_schema_contract_generated = __esm({
19678
19678
  "argumentTypes",
19679
19679
  "dispatch",
19680
19680
  "bodyMode",
19681
+ "effect",
19681
19682
  "uiAction",
19682
19683
  "templateId",
19683
19684
  "genericParamId"
@@ -19881,6 +19882,10 @@ var init_project_schema_contract_generated = __esm({
19881
19882
  "Sprite",
19882
19883
  "Audio"
19883
19884
  ],
19885
+ "NeoEffectKind": [
19886
+ "None",
19887
+ "Auto"
19888
+ ],
19884
19889
  "NeoProjectRecordKind": [
19885
19890
  "Project",
19886
19891
  "Member",
@@ -24245,6 +24250,9 @@ function fieldsForAnnotation(name, context, environment) {
24245
24250
  if (context.kind === "type") return null;
24246
24251
  if (name === "storage") return storageFields(context);
24247
24252
  if (name === "distribution") return distributionFields(context);
24253
+ if (name === "effect") {
24254
+ return context.kind === "member" && context.memberKind === "function" ? [{ name: "kind", type: "NeoEffectKind" }] : null;
24255
+ }
24248
24256
  if (name === "relations") return relationFields(context, environment);
24249
24257
  if (name === "system") {
24250
24258
  return context.kind === "class" || context.kind === "schema" || context.kind === "member" ? markerFields("NeoSystemAttribute") : null;
@@ -27804,11 +27812,23 @@ function validateMembers(environment, diagnostics) {
27804
27812
  );
27805
27813
  }
27806
27814
  }
27815
+ for (const member of ref.declaration.members) {
27816
+ const effect = effectAnnotation(member);
27817
+ if (effect === void 0) continue;
27818
+ diagnose(
27819
+ diagnostics,
27820
+ ref.uri,
27821
+ effect.nameRange,
27822
+ "invalid-effect",
27823
+ `Interface member '${ref.declaration.name}.${member.name}' cannot be an @effect; only class member functions can.`
27824
+ );
27825
+ }
27807
27826
  validateInterfaceMemberConflicts(ref, environment, diagnostics);
27808
27827
  }
27809
27828
  for (const ref of environment.classes.values()) {
27810
27829
  validateClassMemberModifiers(ref, environment, diagnostics);
27811
27830
  validateClassOverrides(ref, environment, diagnostics);
27831
+ validateEffects(ref, environment, diagnostics);
27812
27832
  if (ref.declaration.modifiers.includes("abstract")) {
27813
27833
  validateAbstractInterfaceMembers(ref, environment, diagnostics);
27814
27834
  } else {
@@ -28234,6 +28254,70 @@ function validateClassOverrides(ref, environment, diagnostics) {
28234
28254
  }
28235
28255
  }
28236
28256
  }
28257
+ function validateEffects(ref, environment, diagnostics) {
28258
+ let inherited;
28259
+ for (const member of ref.declaration.members) {
28260
+ if (member.kind !== "function") continue;
28261
+ const annotation2 = effectAnnotation(member);
28262
+ let kind = annotation2 === void 0 ? void 0 : effectKind(annotation2);
28263
+ if (annotation2 === void 0) {
28264
+ if (!member.modifiers.includes("override") || !member.modifiers.includes("native")) {
28265
+ continue;
28266
+ }
28267
+ inherited ??= inheritedClassMembers(ref, environment);
28268
+ kind = inheritedEffectKind(inherited, member.name);
28269
+ }
28270
+ if (kind !== "Auto") continue;
28271
+ const problem = effectIneligibility(member);
28272
+ if (problem === null) continue;
28273
+ diagnose(
28274
+ diagnostics,
28275
+ ref.uri,
28276
+ annotation2?.nameRange ?? member.nameRange,
28277
+ "invalid-effect",
28278
+ `'${ref.declaration.name}.${member.name}' cannot be an @effect: ${problem}.`
28279
+ );
28280
+ }
28281
+ }
28282
+ function effectAnnotation(member) {
28283
+ return member.annotations.find((entry) => entry.name === "effect");
28284
+ }
28285
+ function effectKind(annotation2) {
28286
+ if (annotation2.arguments.length === 0) return "Auto";
28287
+ const argument2 = annotation2.arguments.find((entry) => entry.name === "kind") ?? annotation2.arguments.find((entry) => entry.name === void 0);
28288
+ if (argument2 === void 0) return void 0;
28289
+ let expression;
28290
+ try {
28291
+ expression = parseExpression(argument2.text);
28292
+ } catch (error) {
28293
+ if (error instanceof CompileError) return void 0;
28294
+ throw error;
28295
+ }
28296
+ return expression.kind === "contextualEnum" ? expression.name : void 0;
28297
+ }
28298
+ function inheritedEffectKind(inherited, name) {
28299
+ for (let index = inherited.length - 1; index >= 0; index -= 1) {
28300
+ const view = inherited[index];
28301
+ if (!namesEqual(view.member.name, name)) continue;
28302
+ const annotation2 = effectAnnotation(view.member);
28303
+ if (annotation2 !== void 0) return effectKind(annotation2);
28304
+ }
28305
+ return void 0;
28306
+ }
28307
+ function effectIneligibility(member) {
28308
+ if (member.modifiers.includes("static")) {
28309
+ return "a static function has no instance to run for";
28310
+ }
28311
+ if (member.modifiers.includes("native")) {
28312
+ return "a native function has no NeoScript reads to track";
28313
+ }
28314
+ if (member.modifiers.includes("async")) {
28315
+ return "an effect runs immediately and cannot be async";
28316
+ }
28317
+ if (member.type.name !== "void") return "an effect must return void";
28318
+ if (member.parameters.length > 0) return "an effect takes no parameters";
28319
+ return null;
28320
+ }
28237
28321
  function isPlatformImplementedSystemMember(member) {
28238
28322
  if (!member.modifiers.includes("protected")) return false;
28239
28323
  return member.annotations.some((entry) => entry.name === "system");
@@ -28699,6 +28783,8 @@ var init_project_source_type_checker = __esm({
28699
28783
  init_language_spec();
28700
28784
  init_project_source_ast();
28701
28785
  init_lexer();
28786
+ init_executable_parser();
28787
+ init_strict_compile_error();
28702
28788
  init_project_source_accessors();
28703
28789
  init_project_source_identifiers();
28704
28790
  BUILTIN_ARITY = /* @__PURE__ */ new Map([
@@ -30106,6 +30192,7 @@ function projectDeclarationAnnotationNames(declaration) {
30106
30192
  }
30107
30193
  function projectMemberAnnotationNames(member) {
30108
30194
  const names = ["id", "settings", "storage", "distribution"];
30195
+ if (member?.kind === "function") names.push("effect");
30109
30196
  if (member?.type.name === "List") names.push("index", "column");
30110
30197
  return names;
30111
30198
  }
@@ -31770,6 +31857,8 @@ function annotationSignatureParameters(name) {
31770
31857
  return ["StorageKind allowed"];
31771
31858
  case "distribution":
31772
31859
  return ["NeoSubtreeDistribution kind"];
31860
+ case "effect":
31861
+ return ["NeoEffectKind kind"];
31773
31862
  case "relations":
31774
31863
  return ["relation descriptors"];
31775
31864
  case "incomplete":
@@ -40021,7 +40110,7 @@ function assertMemberInvariants(member, path) {
40021
40110
  fail2("script", "is required for a concrete scripted member.");
40022
40111
  }
40023
40112
  }
40024
- var nonEmptyString, nullableString2, nullableId, finiteNumber, nullableNumber, nullableInteger, jsonValueSchema, schemaSourcePositionSchema, schemaSourceSpanSchema, schemaSourceSymbolKinds, schemaSourceIdentitySchema, memberSourceIdentitySchema, systemProtectionKindSchema, worldSystemClassKindSchema, schemaSystemMetadataSchema, schemaGenericBindingSchema, schemaGenericBindingRecordSchema, genericConstraintSchema, schemaGenericParameterSchema, primitiveTypeKinds, schemaTypeCache, returnTypeCache, schemaTypeSchema, schemaFunctionReturnTypeSchema, schemaParameterDefaultSchema, schemaFunctionArgumentSchema, memberOwnerSchema, schemaDefaultValueSchema, propertyScriptSchema, functionScriptSchema, commonMemberEntries, schemaMemberCommonSchema, simpleMemberSchemas, dictionaryKeySchema, schemaListIndexSchema, schemaListColumnSchema, callableEntries, schemaMemberSchema, SchemaMemberContractError;
40113
+ var nonEmptyString, nullableString2, nullableId, finiteNumber, nullableNumber, nullableInteger, jsonValueSchema, schemaSourcePositionSchema, schemaSourceSpanSchema, schemaSourceSymbolKinds, schemaSourceIdentitySchema, memberSourceIdentitySchema, systemProtectionKindSchema, worldSystemClassKindSchema, schemaSystemMetadataSchema, schemaGenericBindingSchema, schemaGenericBindingRecordSchema, genericConstraintSchema, schemaGenericParameterSchema, primitiveTypeKinds, schemaTypeCache, returnTypeCache, schemaTypeSchema, schemaFunctionReturnTypeSchema, schemaParameterDefaultSchema, schemaFunctionArgumentSchema, memberOwnerSchema, schemaDefaultValueSchema, propertyScriptSchema, functionScriptSchema, commonMemberEntries, schemaMemberCommonSchema, simpleMemberSchemas, dictionaryKeySchema, schemaListIndexSchema, schemaListColumnSchema, callableEntries, functionEffectSchema, schemaMemberSchema, SchemaMemberContractError;
40025
40114
  var init_schema_member = __esm({
40026
40115
  "../packages/manifest/src/schema-member.ts"() {
40027
40116
  "use strict";
@@ -40346,6 +40435,7 @@ var init_schema_member = __esm({
40346
40435
  returnType: schemaFunctionReturnTypeSchema,
40347
40436
  arguments: array2(schemaFunctionArgumentSchema)
40348
40437
  };
40438
+ functionEffectSchema = optional(picklist(["none", "auto"]));
40349
40439
  schemaMemberSchema = variant("kind", [
40350
40440
  ...simpleMemberSchemas,
40351
40441
  memberSchema("int", {
@@ -40408,11 +40498,13 @@ var init_schema_member = __esm({
40408
40498
  }),
40409
40499
  memberSchema("function", {
40410
40500
  ...callableEntries,
40411
- deferred: boolean2()
40501
+ deferred: boolean2(),
40502
+ effect: functionEffectSchema
40412
40503
  }),
40413
40504
  memberSchema("scriptFunction", {
40414
40505
  ...callableEntries,
40415
40506
  deferred: boolean2(),
40507
+ effect: functionEffectSchema,
40416
40508
  bodyMode: optional(literal2("ui")),
40417
40509
  uiAction: optional(jsonValueSchema),
40418
40510
  script: nullable(functionScriptSchema)
@@ -40503,6 +40595,7 @@ var init_document_contracts = __esm({
40503
40595
  "argumentTypes",
40504
40596
  "dispatch",
40505
40597
  "bodyMode",
40598
+ "effect",
40506
40599
  "uiAction",
40507
40600
  "templateId",
40508
40601
  "genericParamId"
@@ -42207,12 +42300,24 @@ function memberFromDocument(record6, members, owners, classNames, context) {
42207
42300
  FUNCTION_DISPATCH,
42208
42301
  record6,
42209
42302
  "dispatch"
42210
- ) === "asynchronous"
42303
+ ) === "asynchronous",
42304
+ // The row's own value, not the chain's: an override without `@effect`
42305
+ // inherits, and resolving here would emit the ancestor's kind back
42306
+ // into the override's source as phantom drift.
42307
+ ...data.effect === void 0 || data.effect === null ? {} : {
42308
+ effect: ordinalValue(
42309
+ data.effect,
42310
+ FUNCTION_EFFECT,
42311
+ record6,
42312
+ "effect"
42313
+ )
42314
+ }
42211
42315
  };
42212
42316
  if (kind === MEMBER_KIND.Function) {
42213
42317
  return { ...common, kind: "function", ...signature };
42214
42318
  }
42215
- const code = optionalStringOrNull(field("code", null));
42319
+ const codeValue = field("code", null);
42320
+ const code = codeValue === "" ? "" : optionalStringOrNull(codeValue);
42216
42321
  const bodyModeValue = ordinalValue(
42217
42322
  field("bodyMode", 0),
42218
42323
  FUNCTION_BODY,
@@ -42458,6 +42563,9 @@ function memberToDocumentFields(member, baseData3) {
42458
42563
  if (member.deferred || isOverride) {
42459
42564
  fields.dispatch = member.deferred ? 1 : 0;
42460
42565
  }
42566
+ if (member.effect !== void 0) {
42567
+ fields.effect = FUNCTION_EFFECT.indexOf(member.effect);
42568
+ }
42461
42569
  if (member.kind === "scriptFunction" && member.script !== null && member.bodyMode !== "ui") {
42462
42570
  fields.code = member.script.sourceText;
42463
42571
  }
@@ -43252,7 +43360,7 @@ function invalidDocument(record6, path, message) {
43252
43360
  `${record6.recordKind}:${record6.recordId}.${path} ${message}.`
43253
43361
  );
43254
43362
  }
43255
- var MEMBER_KIND, MEMBER_MODIFIERS, MEMBER_ACCESS, MEMBER_STORAGE, MEMBER_DISTRIBUTION, STRING_FORMAT, MEMBER_SEARCH_BY, MEMBER_SELECTION, FUNCTION_DISPATCH, FUNCTION_BODY, MEMBER_PAYLOAD, DICTIONARY_KEY, LIST_KIND, LIST_INDEX, COLUMN_VISIBILITY, COLUMN_PIN, COLUMN_OVERFLOW, GENERIC_BINDING, TYPE_KIND_BY_MEMBER_KIND, SIMPLE_MEMBER_KIND_BY_KIND, MEMBER_KIND_BY_KIND, TYPE_MEMBER_KIND_BY_KIND, MEMBER_IDENTITY_FIELDS, INHERITED_MEMBER_ORDINAL_FIELDS, NULLABLE_OVERRIDE_MEMBER_FIELDS;
43363
+ var MEMBER_KIND, MEMBER_MODIFIERS, MEMBER_ACCESS, MEMBER_STORAGE, MEMBER_DISTRIBUTION, STRING_FORMAT, MEMBER_SEARCH_BY, MEMBER_SELECTION, FUNCTION_DISPATCH, FUNCTION_BODY, FUNCTION_EFFECT, MEMBER_PAYLOAD, DICTIONARY_KEY, LIST_KIND, LIST_INDEX, COLUMN_VISIBILITY, COLUMN_PIN, COLUMN_OVERFLOW, GENERIC_BINDING, TYPE_KIND_BY_MEMBER_KIND, SIMPLE_MEMBER_KIND_BY_KIND, MEMBER_KIND_BY_KIND, TYPE_MEMBER_KIND_BY_KIND, MEMBER_IDENTITY_FIELDS, INHERITED_MEMBER_ORDINAL_FIELDS, NULLABLE_OVERRIDE_MEMBER_FIELDS;
43256
43364
  var init_codecs = __esm({
43257
43365
  "src/project-manifest/record-adapters/codecs.ts"() {
43258
43366
  "use strict";
@@ -43303,6 +43411,7 @@ var init_codecs = __esm({
43303
43411
  MEMBER_SELECTION = ["single", "multi"];
43304
43412
  FUNCTION_DISPATCH = ["synchronous", "asynchronous"];
43305
43413
  FUNCTION_BODY = ["code", "ui"];
43414
+ FUNCTION_EFFECT = ["none", "auto"];
43306
43415
  MEMBER_PAYLOAD = ["full", "partial"];
43307
43416
  DICTIONARY_KEY = ["string", "enum"];
43308
43417
  LIST_KIND = ["ordered", "unordered"];
@@ -49170,6 +49279,9 @@ var init_structured_leaf_fields = __esm({
49170
49279
  });
49171
49280
 
49172
49281
  // ../src/models/members/member-kinds.ts
49282
+ function isFunctionEffectKind(value) {
49283
+ return value === 0 /* None */ || value === 1 /* Auto */;
49284
+ }
49173
49285
  function isMemberAccessKind(value) {
49174
49286
  return MEMBER_ACCESS_KINDS.includes(value);
49175
49287
  }
@@ -49918,6 +50030,11 @@ function hasValidStorageField(v, allowNull) {
49918
50030
  if (typeof v.kind !== "number") return false;
49919
50031
  return memberKindSupportsStorage(v.kind);
49920
50032
  }
50033
+ function hasValidEffectField(v) {
50034
+ if (v.effect === void 0) return true;
50035
+ if (!isFunctionEffectKind(v.effect)) return false;
50036
+ return v.kind === 13 /* Function */ || v.kind === 23 /* NSFunction */;
50037
+ }
49921
50038
  function hasValidDistributionField(v) {
49922
50039
  if (v.distribution === void 0) return true;
49923
50040
  if (!isNeoSubtreeDistributionKind(v.distribution)) return false;
@@ -49948,6 +50065,7 @@ function isMemberBase(value) {
49948
50065
  }
49949
50066
  if (!hasValidStorageField(v, true)) return false;
49950
50067
  if (!hasValidDistributionField(v)) return false;
50068
+ if (!hasValidEffectField(v)) return false;
49951
50069
  const storageKey = getField(value, "storageKey");
49952
50070
  if (storageKey !== void 0 && storageKey !== null && typeof storageKey !== "string")
49953
50071
  return false;
@@ -50047,6 +50165,7 @@ function isMemberOverrideBase(value) {
50047
50165
  }
50048
50166
  if (!hasValidStorageField(v, false)) return false;
50049
50167
  if (!hasValidDistributionField(v)) return false;
50168
+ if (!hasValidEffectField(v)) return false;
50050
50169
  if (v.defaultValue !== void 0 && v.defaultValue !== null && !isMemberValueBase(v.defaultValue)) {
50051
50170
  return false;
50052
50171
  }
@@ -59706,6 +59825,7 @@ function emitMember(context, member, enclosingClassId) {
59706
59825
  // P76 §1 is never materialized into source, so a member that omits the
59707
59826
  // annotation round-trips as an omission.
59708
59827
  ...member.distribution ? [`@distribution(kind: .${enumCase(member.distribution)})`] : [],
59828
+ ...(member.kind === "function" || member.kind === "scriptFunction") && member.effect !== void 0 ? [member.effect === "auto" ? "@effect" : "@effect(kind: .None)"] : [],
59709
59829
  ...memberSettings(context, member),
59710
59830
  ...listAnnotations(member),
59711
59831
  ...member.system ? [systemAnnotation2(member.system)] : []
@@ -62578,6 +62698,11 @@ function lowerMemberKind(context, ownerClass, declaration, id2, owner) {
62578
62698
  };
62579
62699
  }
62580
62700
  );
62701
+ const effect = functionEffectValue(
62702
+ annotation(declaration.annotations, "effect"),
62703
+ ownerClass,
62704
+ declaration
62705
+ );
62581
62706
  const settings = annotation(declaration.annotations, "settings");
62582
62707
  const logicMode = contextualEnumArgument(settings, "logic");
62583
62708
  if (logicMode !== null && logicMode !== "UI") {
@@ -62604,6 +62729,7 @@ function lowerMemberKind(context, ownerClass, declaration, id2, owner) {
62604
62729
  returnType,
62605
62730
  arguments: argumentsValue,
62606
62731
  deferred: declaration.modifiers.includes("async"),
62732
+ ...effect,
62607
62733
  ...logicMode === "UI" ? { bodyMode: "ui", uiAction } : {},
62608
62734
  script: {
62609
62735
  kind: "function",
@@ -62621,6 +62747,7 @@ function lowerMemberKind(context, ownerClass, declaration, id2, owner) {
62621
62747
  returnType,
62622
62748
  arguments: argumentsValue,
62623
62749
  deferred: declaration.modifiers.includes("async"),
62750
+ ...effect,
62624
62751
  bodyMode: "ui",
62625
62752
  ...uiAction === void 0 ? {} : { uiAction },
62626
62753
  script: null
@@ -62633,6 +62760,7 @@ function lowerMemberKind(context, ownerClass, declaration, id2, owner) {
62633
62760
  returnType,
62634
62761
  arguments: argumentsValue,
62635
62762
  deferred: declaration.modifiers.includes("async"),
62763
+ ...effect,
62636
62764
  script: null
62637
62765
  };
62638
62766
  }
@@ -62641,7 +62769,8 @@ function lowerMemberKind(context, ownerClass, declaration, id2, owner) {
62641
62769
  kind: "function",
62642
62770
  returnType,
62643
62771
  arguments: argumentsValue,
62644
- deferred: declaration.modifiers.includes("async")
62772
+ deferred: declaration.modifiers.includes("async"),
62773
+ ...effect
62645
62774
  };
62646
62775
  }
62647
62776
  return lowerFieldMember(
@@ -62771,6 +62900,16 @@ function distributionValue(value, ownerClass, declaration) {
62771
62900
  `@distribution on ${site} has unknown kind ".${kind}". Valid kinds are .Sparse and .Packed.`
62772
62901
  );
62773
62902
  }
62903
+ function functionEffectValue(value, ownerClass, declaration) {
62904
+ if (value === void 0) return {};
62905
+ if (value.arguments.length === 0) return { effect: "auto" };
62906
+ const kind = contextualEnumArgument(value, "kind");
62907
+ if (kind === "None") return { effect: "none" };
62908
+ if (kind === "Auto") return { effect: "auto" };
62909
+ throw new Error(
62910
+ `@effect on ${ownerClass.name}.${declaration.name} has unknown kind${kind === null ? "" : ` ".${kind}"`}. Valid kinds are .None and .Auto.`
62911
+ );
62912
+ }
62774
62913
  function resolvedMemberStorage(context, memberId, visited = /* @__PURE__ */ new Set()) {
62775
62914
  if (visited.has(memberId)) return null;
62776
62915
  visited.add(memberId);
@@ -147462,7 +147601,7 @@ var init_registry2 = __esm({
147462
147601
  "../packages/manifest/schema-contract/registry.mjs"() {
147463
147602
  "use strict";
147464
147603
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
147465
- cliVersion: "0.62.2",
147604
+ cliVersion: "0.63.0",
147466
147605
  projectFileUploadBatchSize: 32,
147467
147606
  documentRecords: {
147468
147607
  member: {
@@ -147514,6 +147653,10 @@ var init_registry2 = __esm({
147514
147653
  "argumentTypes",
147515
147654
  "dispatch",
147516
147655
  "bodyMode",
147656
+ // P97 §1.1. No normalization rule, like `distribution`: absence means
147657
+ // unset (a declaration resolves to `.None`, an override inherits) and
147658
+ // explicit ordinal 0 is an authored `.None` that stops inheritance.
147659
+ "effect",
147517
147660
  "uiAction",
147518
147661
  "templateId",
147519
147662
  "genericParamId"
@@ -147816,7 +147959,8 @@ var init_registry2 = __esm({
147816
147959
  NeoColumnVisibility: ["Visible", "Hidden"],
147817
147960
  NeoColumnPin: ["None", "Leading"],
147818
147961
  NeoColumnOverflow: ["Clip", "Wrap"],
147819
- NeoFileKind: ["Sprite", "Audio"]
147962
+ NeoFileKind: ["Sprite", "Audio"],
147963
+ NeoEffectKind: ["None", "Auto"]
147820
147964
  },
147821
147965
  markers: {
147822
147966
  NeoSchemaClassAttribute: {
@@ -155535,7 +155679,7 @@ function readManifest(path) {
155535
155679
  });
155536
155680
  return { projectId: data.projectId, files };
155537
155681
  }
155538
- function writeUnityGeneratedFiles(directory, projectId, files) {
155682
+ function writeUnityGeneratedFiles(directory, projectId, files, codegenRevision) {
155539
155683
  if (!Array.isArray(files) || files.length === 0)
155540
155684
  throw new Error(
155541
155685
  "The export returned no generated files. Update the server before exporting Unity code."
@@ -155599,6 +155743,7 @@ function writeUnityGeneratedFiles(directory, projectId, files) {
155599
155743
  {
155600
155744
  schemaVersion: 1,
155601
155745
  projectId,
155746
+ codegenRevision,
155602
155747
  files: [...files].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0).map((file) => ({
155603
155748
  id: file.id,
155604
155749
  path: file.path,
@@ -155680,7 +155825,8 @@ async function runExportUnity(workspace, outDir) {
155680
155825
  writeUnityGeneratedFiles(
155681
155826
  scriptsDir,
155682
155827
  response.projectId,
155683
- response.generatedFiles
155828
+ response.generatedFiles,
155829
+ response.codegenRevision
155684
155830
  );
155685
155831
  writeFileSync16(join19(resourcesDir, "project.json"), response.projectJson);
155686
155832
  for (const file of response.localizationFiles ?? []) {
@@ -156229,7 +156375,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
156229
156375
  async function main() {
156230
156376
  const args = parseArgs(process.argv.slice(2));
156231
156377
  if (args.command === "--version") {
156232
- console.log("0.62.2");
156378
+ console.log("0.63.0");
156233
156379
  return;
156234
156380
  }
156235
156381
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
@@ -15569,6 +15569,9 @@ var NSFunctionArgumentReservedNames = /* @__PURE__ */ new Set([
15569
15569
  ...FunctionArgumentReservedNames,
15570
15570
  "value"
15571
15571
  ]);
15572
+ function isFunctionEffectKind(value) {
15573
+ return value === 0 /* None */ || value === 1 /* Auto */;
15574
+ }
15572
15575
  var CHAIN_RESOLVED_OPTIONAL_MEMBER_FIELDS = [
15573
15576
  "storageKey",
15574
15577
  "minValue",
@@ -16310,6 +16313,11 @@ function hasValidStorageField(v, allowNull) {
16310
16313
  if (typeof v.kind !== "number") return false;
16311
16314
  return memberKindSupportsStorage(v.kind);
16312
16315
  }
16316
+ function hasValidEffectField(v) {
16317
+ if (v.effect === void 0) return true;
16318
+ if (!isFunctionEffectKind(v.effect)) return false;
16319
+ return v.kind === 13 /* Function */ || v.kind === 23 /* NSFunction */;
16320
+ }
16313
16321
  function hasValidDistributionField(v) {
16314
16322
  if (v.distribution === void 0) return true;
16315
16323
  if (!isNeoSubtreeDistributionKind(v.distribution)) return false;
@@ -16340,6 +16348,7 @@ function isMemberBase(value) {
16340
16348
  }
16341
16349
  if (!hasValidStorageField(v, true)) return false;
16342
16350
  if (!hasValidDistributionField(v)) return false;
16351
+ if (!hasValidEffectField(v)) return false;
16343
16352
  const storageKey = getField(value, "storageKey");
16344
16353
  if (storageKey !== void 0 && storageKey !== null && typeof storageKey !== "string")
16345
16354
  return false;
@@ -16439,6 +16448,7 @@ function isMemberOverrideBase(value) {
16439
16448
  }
16440
16449
  if (!hasValidStorageField(v, false)) return false;
16441
16450
  if (!hasValidDistributionField(v)) return false;
16451
+ if (!hasValidEffectField(v)) return false;
16442
16452
  if (v.defaultValue !== void 0 && v.defaultValue !== null && !isMemberValueBase(v.defaultValue)) {
16443
16453
  return false;
16444
16454
  }
@@ -20142,6 +20152,7 @@ var SCHEMA_DOCUMENT_FIELD_CONTRACTS = {
20142
20152
  "argumentTypes",
20143
20153
  "dispatch",
20144
20154
  "bodyMode",
20155
+ "effect",
20145
20156
  "uiAction",
20146
20157
  "templateId",
20147
20158
  "genericParamId"
@@ -44757,6 +44768,7 @@ var MEMBER_SEARCH_BY = ["none", "memberKey"];
44757
44768
  var MEMBER_SELECTION = ["single", "multi"];
44758
44769
  var FUNCTION_DISPATCH = ["synchronous", "asynchronous"];
44759
44770
  var FUNCTION_BODY = ["code", "ui"];
44771
+ var FUNCTION_EFFECT = ["none", "auto"];
44760
44772
  var MEMBER_PAYLOAD = ["full", "partial"];
44761
44773
  var DICTIONARY_KEY = ["string", "enum"];
44762
44774
  var LIST_KIND = ["ordered", "unordered"];
@@ -45094,12 +45106,24 @@ function memberFromDocument(record2, members, owners, classNames, context) {
45094
45106
  FUNCTION_DISPATCH,
45095
45107
  record2,
45096
45108
  "dispatch"
45097
- ) === "asynchronous"
45109
+ ) === "asynchronous",
45110
+ // The row's own value, not the chain's: an override without `@effect`
45111
+ // inherits, and resolving here would emit the ancestor's kind back
45112
+ // into the override's source as phantom drift.
45113
+ ...data.effect === void 0 || data.effect === null ? {} : {
45114
+ effect: ordinalValue(
45115
+ data.effect,
45116
+ FUNCTION_EFFECT,
45117
+ record2,
45118
+ "effect"
45119
+ )
45120
+ }
45098
45121
  };
45099
45122
  if (kind === MEMBER_KIND.Function) {
45100
45123
  return { ...common, kind: "function", ...signature };
45101
45124
  }
45102
- const code = optionalStringOrNull(field("code", null));
45125
+ const codeValue = field("code", null);
45126
+ const code = codeValue === "" ? "" : optionalStringOrNull(codeValue);
45103
45127
  const bodyModeValue = ordinalValue(
45104
45128
  field("bodyMode", 0),
45105
45129
  FUNCTION_BODY,
@@ -45365,6 +45389,9 @@ function memberToDocumentFields(member, baseData) {
45365
45389
  if (member.deferred || isOverride) {
45366
45390
  fields.dispatch = member.deferred ? 1 : 0;
45367
45391
  }
45392
+ if (member.effect !== void 0) {
45393
+ fields.effect = FUNCTION_EFFECT.indexOf(member.effect);
45394
+ }
45368
45395
  if (member.kind === "scriptFunction" && member.script !== null && member.bodyMode !== "ui") {
45369
45396
  fields.code = member.script.sourceText;
45370
45397
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.62.2",
3
+ "version": "0.63.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.62.2 -->
12
+ <!-- reviewed-through-cli: 0.63.0 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -110,7 +110,7 @@ wrappers.
110
110
  The marker near the top of `SKILL.md` must exactly match the package version:
111
111
 
112
112
  ```html
113
- <!-- reviewed-through-cli: 0.62.2 -->
113
+ <!-- reviewed-through-cli: 0.63.0 -->
114
114
  ```
115
115
 
116
116
  The quoted version above is checked too, so this instruction cannot go stale
@@ -105,6 +105,12 @@ Use context-aware typed annotations:
105
105
  explicitly stored. A packed subtree shares its parent's document-size limit,
106
106
  so a write that would exceed the hard limit fails instead of falling back.
107
107
  `.Packed` is rejected on a List whose `listKind` is unordered.
108
+ - `@effect` only on a class member function. The Unity runtime runs it for
109
+ every live instance and reruns it whenever data it read changes; the web
110
+ editor, `neo test`, and dry runs never run effects. It must be an immediate,
111
+ parameterless `void` instance function with a NeoScript or abstract body.
112
+ An override without the annotation inherits; `@effect(kind: .None)` stops
113
+ an inherited effect. Use it instead of polling or relaying changes by hand.
108
114
 
109
115
  Use numeric literals for numeric limits. Keep annotations separate instead of
110
116
  inventing a generic metadata bag.