@neocompose/cli 0.65.0 → 0.66.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,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.66.0] - 2026-10-03
4
+
5
+ - Add the 18 script runtime lifecycle interfaces from P98, such as `INeoOnLoad`, `INeoUnityUpdate`, and `INeoUnityOnTriggerEnter2D`. Every project gets them as protected system records, and pull writes them under `Interfaces/System/` with `@system(kind: .ScriptRuntime)`.
6
+ - Reserve the lifecycle member names, such as `OnLoad` and `Update`. A member with one must be the public, immediate instance method implementing its interface, or it reports `reserved-lifecycle-member`. This tightens valid source: a project with an unrelated member by one of these names must rename it. Push enforces the same rule on records authored in the web editor.
7
+ - Report `lifecycle-hook-without-object` as a warning when a class reaches an `INeoUnity*` interface without extending `NeoObjectBase`. Push still succeeds.
8
+ - A lifecycle hook cannot be `@effect`. It reports `invalid-effect`.
9
+
10
+ ## [0.65.1] - 2026-10-03
11
+
12
+ - Pull and push reconciliation emit a stored `new NeoAnimationSegmentFrame<NeoCollider?>(value: null)`. A null constructor argument typed by a class's generic parameter now checks nullability against the row's type argument. Previously, a pushed segment holding a null frame committed, but source reconciliation failed with "Required constructor argument stores null".
13
+ - Pulled type arguments keep a nullable binding's `?`, in constructions, references, and declared member types. Previously `FrameBox<NeoCollider?> Box` pulled as `FrameBox<NeoCollider> Box`, and the next push made the binding required.
14
+ - Code compiled from stored records keeps a nullable binding's `?`. A member declared `NeoAnimationSegmentFrame<NeoCollider?>` keeps that type, so passing it where that type is expected compiles, and reading its value as a required `NeoCollider` reports an error. Previously, every bound type argument was typed as required.
15
+
3
16
  ## [0.65.0] - 2026-10-03
4
17
 
5
18
  - Move `Collider` from `NeoObject` to `NeoObjectBase`, so sprite objects and layer groups can carry a box collider too. Unity codegen implements `INeoColliderSource` on `NeoObjectBase`.
package/dist/neo.mjs CHANGED
@@ -5959,6 +5959,10 @@ var init_neoflow_graph_parser = __esm({
5959
5959
  });
5960
5960
 
5961
5961
  // ../packages/neoscript-language/src/project-source-ast.ts
5962
+ function neoSourceTypeText(type) {
5963
+ const args = type.typeArguments.length > 0 ? `<${type.typeArguments.map(neoSourceTypeText).join(", ")}>` : "";
5964
+ return `${type.name}${args}${type.nullable ? "?" : ""}`;
5965
+ }
5962
5966
  function isNeoSourceAccessModifier(value) {
5963
5967
  return value === "public" || value === "protected" || value === "private";
5964
5968
  }
@@ -7892,6 +7896,9 @@ function tripleInterpolationEnd(text, start, limit) {
7892
7896
  }
7893
7897
  return null;
7894
7898
  }
7899
+ function rangesEqual(left, right) {
7900
+ return positionCompare(left.start, right.start) === 0 && positionCompare(left.end, right.end) === 0;
7901
+ }
7895
7902
  function positionCompare(left, right) {
7896
7903
  return left.line === right.line ? left.character - right.character : left.line - right.line;
7897
7904
  }
@@ -8367,7 +8374,8 @@ var init_project_schema_contract_generated = __esm({
8367
8374
  "DialogueMemoryContainer",
8368
8375
  "DialoguePriorityDefault",
8369
8376
  "PolicyImmutable",
8370
- "PolicyDeleteBlocked"
8377
+ "PolicyDeleteBlocked",
8378
+ "ScriptRuntime"
8371
8379
  ],
8372
8380
  "NeoMemberRequirement": [
8373
8381
  "Optional",
@@ -26424,6 +26432,40 @@ var init_project_source_script_compiler = __esm({
26424
26432
  }
26425
26433
  });
26426
26434
 
26435
+ // ../packages/neoscript-language/src/project-source-lifecycle-hooks.ts
26436
+ function isScriptRuntimeInterface(declaration) {
26437
+ const kind = declaration.annotations.find((annotation2) => annotation2.name === "system")?.arguments.find((argument2) => argument2.name === "kind")?.text.trim();
26438
+ return kind === ".ScriptRuntime";
26439
+ }
26440
+ function lifecycleHooksByMemberName(interfaces) {
26441
+ const hooks = /* @__PURE__ */ new Map();
26442
+ for (const declaration of interfaces) {
26443
+ if (!isScriptRuntimeInterface(declaration)) continue;
26444
+ for (const member of declaration.members) {
26445
+ if (member.kind !== "function") continue;
26446
+ hooks.set(member.name.toLowerCase(), {
26447
+ interfaceName: declaration.name,
26448
+ member,
26449
+ ...declaration.docsText === void 0 ? {} : { docsText: declaration.docsText },
26450
+ unity: declaration.name.startsWith(UNITY_LIFECYCLE_INTERFACE_PREFIX)
26451
+ });
26452
+ }
26453
+ }
26454
+ return hooks;
26455
+ }
26456
+ function reservedLifecycleMemberMessage(memberName, interfaceName, owner) {
26457
+ const fix = owner === "class" ? "Implement the interface with a public method" : "Extend the interface";
26458
+ return `'${memberName}' is reserved for '${interfaceName}'. ${fix}, or rename the member.`;
26459
+ }
26460
+ var NEO_LIFECYCLE_OBJECT_CLASS_NAME, UNITY_LIFECYCLE_INTERFACE_PREFIX;
26461
+ var init_project_source_lifecycle_hooks = __esm({
26462
+ "../packages/neoscript-language/src/project-source-lifecycle-hooks.ts"() {
26463
+ "use strict";
26464
+ NEO_LIFECYCLE_OBJECT_CLASS_NAME = "NeoObjectBase";
26465
+ UNITY_LIFECYCLE_INTERFACE_PREFIX = "INeoUnity";
26466
+ }
26467
+ });
26468
+
26427
26469
  // ../packages/neoscript-language/src/project-source-type-checker.ts
26428
26470
  function validateProjectSourceTypes(documents) {
26429
26471
  const diagnostics = [];
@@ -26433,6 +26475,7 @@ function validateProjectSourceTypes(documents) {
26433
26475
  validateInheritanceCycles(environment, diagnostics);
26434
26476
  validateAllTypeApplications(documents, environment, diagnostics);
26435
26477
  validateMembers(environment, diagnostics);
26478
+ validateLifecycleHooks(environment, diagnostics);
26436
26479
  return diagnostics;
26437
26480
  }
26438
26481
  function createEnvironment2(documents) {
@@ -26489,7 +26532,16 @@ function createEnvironment2(documents) {
26489
26532
  }
26490
26533
  }
26491
26534
  }
26492
- return { declarations, classes, interfaces, memberOwners, typeKinds };
26535
+ return {
26536
+ declarations,
26537
+ classes,
26538
+ interfaces,
26539
+ memberOwners,
26540
+ typeKinds,
26541
+ lifecycleHooks: lifecycleHooksByMemberName(
26542
+ [...interfaces.values()].map((ref) => ref.declaration)
26543
+ )
26544
+ };
26493
26545
  }
26494
26546
  function validatePersistedNames(documents, diagnostics) {
26495
26547
  for (const [uri, document] of documents) {
@@ -27500,7 +27552,7 @@ function validateEffects(ref, environment, diagnostics) {
27500
27552
  kind = inheritedEffectKind(inherited, member.name);
27501
27553
  }
27502
27554
  if (kind !== "Auto") continue;
27503
- const problem = effectIneligibility(member);
27555
+ const problem = effectIneligibility(member) ?? (environment.lifecycleHooks.has(member.name.toLowerCase()) ? "the runtime already calls a lifecycle hook, so its first run would happen twice" : null);
27504
27556
  if (problem === null) continue;
27505
27557
  diagnose(
27506
27558
  diagnostics,
@@ -27550,6 +27602,110 @@ function effectIneligibility(member) {
27550
27602
  if (member.parameters.length > 0) return "an effect takes no parameters";
27551
27603
  return null;
27552
27604
  }
27605
+ function validateLifecycleHooks(environment, diagnostics) {
27606
+ const hooks = environment.lifecycleHooks;
27607
+ if (hooks.size === 0) return;
27608
+ for (const ref of environment.interfaces.values()) {
27609
+ if (isScriptRuntimeInterface(ref.declaration)) continue;
27610
+ for (const member of ref.declaration.members) {
27611
+ const hook = hooks.get(member.name.toLowerCase());
27612
+ if (hook === void 0) continue;
27613
+ diagnose(
27614
+ diagnostics,
27615
+ ref.uri,
27616
+ member.nameRange,
27617
+ "reserved-lifecycle-member",
27618
+ reservedLifecycleMemberMessage(
27619
+ member.name,
27620
+ hook.interfaceName,
27621
+ "interface"
27622
+ )
27623
+ );
27624
+ }
27625
+ }
27626
+ const unityHookByInterface = /* @__PURE__ */ new Map();
27627
+ for (const ref of environment.classes.values()) {
27628
+ if (ref.sourceKind === "flow") continue;
27629
+ let implemented;
27630
+ for (const member of ref.declaration.members) {
27631
+ const hook = hooks.get(member.name.toLowerCase());
27632
+ if (hook === void 0) continue;
27633
+ implemented ??= new Set(
27634
+ implementedInterfaces(ref, environment).map(
27635
+ (view) => view.owner.declaration.name
27636
+ )
27637
+ );
27638
+ if (member.kind === "function" && effectiveSourceAccessModifier(member.modifiers, "class") === "public" && !member.modifiers.includes("static") && !member.modifiers.includes("async") && implemented.has(hook.interfaceName)) {
27639
+ continue;
27640
+ }
27641
+ diagnose(
27642
+ diagnostics,
27643
+ ref.uri,
27644
+ member.nameRange,
27645
+ "reserved-lifecycle-member",
27646
+ reservedLifecycleMemberMessage(
27647
+ member.name,
27648
+ hook.interfaceName,
27649
+ "class"
27650
+ )
27651
+ );
27652
+ }
27653
+ let rendersObjects;
27654
+ for (const base of ref.declaration.baseTypes) {
27655
+ const target = environment.interfaces.get(base.name);
27656
+ if (target === void 0) continue;
27657
+ const unityHook = unityHookReachedBy(
27658
+ target,
27659
+ environment,
27660
+ unityHookByInterface
27661
+ );
27662
+ if (unityHook === null) continue;
27663
+ rendersObjects ??= extendsLifecycleObjectClass(ref, environment);
27664
+ if (rendersObjects) break;
27665
+ diagnostics.push({
27666
+ uri: ref.uri,
27667
+ range: base.range,
27668
+ severity: "warning",
27669
+ source: "neo-project",
27670
+ code: "lifecycle-hook-without-object",
27671
+ message: `'${unityHook}' does nothing on '${ref.declaration.name}'. Only classes that extend ${NEO_LIFECYCLE_OBJECT_CLASS_NAME} get Unity lifecycle calls.`
27672
+ });
27673
+ }
27674
+ }
27675
+ }
27676
+ function unityHookReachedBy(ref, environment, memo) {
27677
+ const name = ref.declaration.name;
27678
+ const known = memo.get(name);
27679
+ if (known !== void 0) return known;
27680
+ memo.set(name, null);
27681
+ let reached = null;
27682
+ if (isScriptRuntimeInterface(ref.declaration) && ref.declaration.members.some(
27683
+ (member) => environment.lifecycleHooks.get(member.name.toLowerCase())?.unity === true
27684
+ )) {
27685
+ reached = name;
27686
+ }
27687
+ for (const base of ref.declaration.baseTypes) {
27688
+ if (reached !== null) break;
27689
+ const target = environment.interfaces.get(base.name);
27690
+ if (target !== void 0) {
27691
+ reached = unityHookReachedBy(target, environment, memo);
27692
+ }
27693
+ }
27694
+ memo.set(name, reached);
27695
+ return reached;
27696
+ }
27697
+ function extendsLifecycleObjectClass(ref, environment) {
27698
+ const visited = /* @__PURE__ */ new Set();
27699
+ let current = ref;
27700
+ while (current !== null && !visited.has(current.declaration.name)) {
27701
+ if (current.declaration.name === NEO_LIFECYCLE_OBJECT_CLASS_NAME) {
27702
+ return true;
27703
+ }
27704
+ visited.add(current.declaration.name);
27705
+ current = classBase(current, identitySubstitution(current), environment)?.owner ?? null;
27706
+ }
27707
+ return false;
27708
+ }
27553
27709
  function isPlatformImplementedSystemMember(member) {
27554
27710
  if (!member.modifiers.includes("protected")) return false;
27555
27711
  return member.annotations.some((entry) => entry.name === "system");
@@ -28019,6 +28175,7 @@ var init_project_source_type_checker = __esm({
28019
28175
  init_strict_compile_error();
28020
28176
  init_project_source_accessors();
28021
28177
  init_project_source_identifiers();
28178
+ init_project_source_lifecycle_hooks();
28022
28179
  BUILTIN_ARITY = /* @__PURE__ */ new Map([
28023
28180
  ["List", 1],
28024
28181
  ["Set", 1],
@@ -28208,12 +28365,12 @@ function classifyConstantExpression(expression) {
28208
28365
  function defaultTypeMismatch(type, constant, enumOptions, parameterName) {
28209
28366
  if (constant.kind === "null") {
28210
28367
  if (type.nullable) return null;
28211
- return `Parameter '${parameterName}' defaults to null, but its type '${formatSourceType(type)}' is not nullable.`;
28368
+ return `Parameter '${parameterName}' defaults to null, but its type '${neoSourceTypeText(type)}' is not nullable.`;
28212
28369
  }
28213
28370
  const declaredEnumOptions = enumOptions.get(type.name);
28214
28371
  if (constant.kind === "enumOption") {
28215
28372
  if (declaredEnumOptions === void 0) {
28216
- return `Parameter '${parameterName}' defaults to enum option '.${constant.optionName}', but its type '${formatSourceType(type)}' is not an enum.`;
28373
+ return `Parameter '${parameterName}' defaults to enum option '.${constant.optionName}', but its type '${neoSourceTypeText(type)}' is not an enum.`;
28217
28374
  }
28218
28375
  if (!declaredEnumOptions.has(constant.optionName)) {
28219
28376
  return `Parameter '${parameterName}' defaults to '.${constant.optionName}', which enum '${type.name}' does not declare.`;
@@ -28222,9 +28379,9 @@ function defaultTypeMismatch(type, constant, enumOptions, parameterName) {
28222
28379
  }
28223
28380
  if (constantKindMatchesPrimitive(type.name, constant.kind)) return null;
28224
28381
  if (PRIMITIVE_PARAMETER_TYPES.has(type.name) || declaredEnumOptions !== void 0) {
28225
- return `Parameter '${parameterName}' default is a ${describeConstant(constant)}, which does not match its declared type '${formatSourceType(type)}'.`;
28382
+ return `Parameter '${parameterName}' default is a ${describeConstant(constant)}, which does not match its declared type '${neoSourceTypeText(type)}'.`;
28226
28383
  }
28227
- return `Parameter '${parameterName}' of type '${formatSourceType(type)}' can only default to null${type.nullable ? "" : ", and only when the type is nullable"}.`;
28384
+ return `Parameter '${parameterName}' of type '${neoSourceTypeText(type)}' can only default to null${type.nullable ? "" : ", and only when the type is nullable"}.`;
28228
28385
  }
28229
28386
  function constantKindMatchesPrimitive(typeName, kind) {
28230
28387
  if (typeName === "bool") return kind === "bool";
@@ -28240,15 +28397,12 @@ function describeConstant(constant) {
28240
28397
  if (constant.kind === "int") return "whole-number literal";
28241
28398
  return "fractional number literal";
28242
28399
  }
28243
- function formatSourceType(type) {
28244
- const argumentsText = type.typeArguments.length === 0 ? "" : `<${type.typeArguments.map(formatSourceType).join(", ")}>`;
28245
- return `${type.name}${argumentsText}${type.nullable ? "?" : ""}`;
28246
- }
28247
28400
  var PARAMETER_DEFAULT_DIAGNOSTIC_CODES, DEFAULT_BEFORE_REQUIRED_PARAMETER, PARAMETER_DEFAULT_TYPE_MISMATCH, NON_CONSTANT_PARAMETER_DEFAULT, PARAMETER_DEFAULT_ON_DEFERRED_FUNCTION, PRIMITIVE_PARAMETER_TYPES;
28248
28401
  var init_project_source_parameter_defaults = __esm({
28249
28402
  "../packages/neoscript-language/src/project-source-parameter-defaults.ts"() {
28250
28403
  "use strict";
28251
28404
  init_language_spec();
28405
+ init_project_source_ast();
28252
28406
  init_executable_parser();
28253
28407
  PARAMETER_DEFAULT_DIAGNOSTIC_CODES = {
28254
28408
  defaultBeforeRequiredParameter: "default-before-required-parameter",
@@ -29955,7 +30109,7 @@ var init_registry = __esm({
29955
30109
  "../packages/manifest/schema-contract/registry.mjs"() {
29956
30110
  "use strict";
29957
30111
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
29958
- cliVersion: "0.65.0",
30112
+ cliVersion: "0.66.0",
29959
30113
  projectFileUploadBatchSize: 32,
29960
30114
  documentRecords: {
29961
30115
  member: {
@@ -30660,7 +30814,8 @@ var init_registry = __esm({
30660
30814
  "DialogueMemoryContainer",
30661
30815
  "DialoguePriorityDefault",
30662
30816
  "PolicyImmutable",
30663
- "PolicyDeleteBlocked"
30817
+ "PolicyDeleteBlocked",
30818
+ "ScriptRuntime"
30664
30819
  ],
30665
30820
  NeoMemberRequirement: ["Optional", "Required"],
30666
30821
  NeoMemberMutability: ["Mutable", "ReadOnly"],
@@ -34170,7 +34325,8 @@ var init_schema_member = __esm({
34170
34325
  "dialogueMemoryContainer",
34171
34326
  "dialoguePriorityDefault",
34172
34327
  "policyImmutable",
34173
- "policyDeleteBlocked"
34328
+ "policyDeleteBlocked",
34329
+ "scriptRuntime"
34174
34330
  ]);
34175
34331
  worldSystemClassKindSchema = picklist([
34176
34332
  "cellPattern",
@@ -35728,7 +35884,8 @@ function isSchemaSystemMetadata(value) {
35728
35884
  "dialogueMemoryContainer",
35729
35885
  "dialoguePriorityDefault",
35730
35886
  "policyImmutable",
35731
- "policyDeleteBlocked"
35887
+ "policyDeleteBlocked",
35888
+ "scriptRuntime"
35732
35889
  ]);
35733
35890
  const worlds = /* @__PURE__ */ new Set([
35734
35891
  "cellPattern",
@@ -40146,7 +40303,7 @@ function isEpochMillis(value) {
40146
40303
  return Number.isFinite(value);
40147
40304
  }
40148
40305
  function isSystemProtectionKind(value) {
40149
- return value === SystemProtectionKind.WorldAuthoring || value === SystemProtectionKind.DialogueMemorySchema || value === SystemProtectionKind.DialogueMemoryContainer || value === SystemProtectionKind.DialoguePriorityDefault || value === SystemProtectionKind.PolicyImmutable || value === SystemProtectionKind.PolicyDeleteBlocked;
40306
+ return value === SystemProtectionKind.WorldAuthoring || value === SystemProtectionKind.DialogueMemorySchema || value === SystemProtectionKind.DialogueMemoryContainer || value === SystemProtectionKind.DialoguePriorityDefault || value === SystemProtectionKind.PolicyImmutable || value === SystemProtectionKind.PolicyDeleteBlocked || value === SystemProtectionKind.ScriptRuntime;
40150
40307
  }
40151
40308
  function isSystemMetadata(value) {
40152
40309
  const v = value;
@@ -40190,7 +40347,8 @@ var init_core_types = __esm({
40190
40347
  DialogueMemoryContainer: "dialogueMemoryContainer",
40191
40348
  DialoguePriorityDefault: "dialoguePriorityDefault",
40192
40349
  PolicyImmutable: "policyImmutable",
40193
- PolicyDeleteBlocked: "policyDeleteBlocked"
40350
+ PolicyDeleteBlocked: "policyDeleteBlocked",
40351
+ ScriptRuntime: "scriptRuntime"
40194
40352
  };
40195
40353
  }
40196
40354
  });
@@ -52216,7 +52374,9 @@ function emitInterface(context, value) {
52216
52374
  const deferred = member.deferred ? "async " : "";
52217
52375
  return `${emitDocsText(member.docsText)}${id(member.id)}${access}${deferred}${renderReturnType(context, member.returnType)} ${member.name}(${member.arguments.map((argument2) => renderParameter(context, argument2)).join(", ")});`;
52218
52376
  });
52219
- return `${emitDocsText(value.docsText)}${id(value.id)}interface ${value.name}${bases.length ? ` : ${bases.join(", ")}` : ""} {
52377
+ const system = value.system ? `${systemAnnotation2(value.system)}
52378
+ ` : "";
52379
+ return `${emitDocsText(value.docsText)}${id(value.id)}${system}interface ${value.name}${bases.length ? ` : ${bases.join(", ")}` : ""} {
52220
52380
  ${members.map((member) => indentNeoSourceNonEmptyLines(member, 2)).join("\n\n")}
52221
52381
  }
52222
52382
  `;
@@ -52525,7 +52685,7 @@ function renderMemberType(context, member, enclosingClassId, elementSettings = f
52525
52685
  context,
52526
52686
  required(context.members, boundMemberId, "generic binding"),
52527
52687
  enclosingClassId
52528
- ).replace(/\?$/u, "");
52688
+ );
52529
52689
  });
52530
52690
  value = `${schemaClass2.name}${args.length ? `<${args.join(", ")}>` : ""}`;
52531
52691
  }
@@ -65468,13 +65628,11 @@ function classArguments(member, context, seen, genericEnvironment) {
65468
65628
  if (environmentEntry?.kind === "member") {
65469
65629
  const argument3 = analyzerMemberById(context, environmentEntry.memberId);
65470
65630
  return argument3 ? [
65471
- requiredType(
65472
- memberRuntimeType(
65473
- argument3,
65474
- context,
65475
- new Set(seen),
65476
- genericEnvironment
65477
- )
65631
+ memberRuntimeType(
65632
+ argument3,
65633
+ context,
65634
+ new Set(seen),
65635
+ genericEnvironment
65478
65636
  )
65479
65637
  ] : [];
65480
65638
  }
@@ -65489,13 +65647,11 @@ function classArguments(member, context, seen, genericEnvironment) {
65489
65647
  }
65490
65648
  const argument2 = analyzerMemberById(context, binding.memberId);
65491
65649
  return argument2 ? [
65492
- requiredType(
65493
- memberRuntimeType(
65494
- argument2,
65495
- context,
65496
- new Set(seen),
65497
- genericEnvironment
65498
- )
65650
+ memberRuntimeType(
65651
+ argument2,
65652
+ context,
65653
+ new Set(seen),
65654
+ genericEnvironment
65499
65655
  )
65500
65656
  ] : [];
65501
65657
  });
@@ -65682,11 +65838,7 @@ function genericConstraintLanguageType(constraint, context, genericEnvironment,
65682
65838
  ];
65683
65839
  }
65684
65840
  const member = analyzerMemberById(context, binding.memberId);
65685
- return member ? [
65686
- requiredType(
65687
- memberRuntimeType(member, context, /* @__PURE__ */ new Set(), genericEnvironment)
65688
- )
65689
- ] : [];
65841
+ return member ? [memberRuntimeType(member, context, /* @__PURE__ */ new Set(), genericEnvironment)] : [];
65690
65842
  });
65691
65843
  return {
65692
65844
  kind: "named",
@@ -109589,6 +109741,14 @@ var init_system_protection = __esm({
109589
109741
  disallow: [SystemDisallowedOperation.DeleteRecord],
109590
109742
  reasonKey: "systemProtection.reasons.policyDeleteBlocked",
109591
109743
  serverReason: "System default policy may be edited but must keep one default row."
109744
+ },
109745
+ [SystemProtectionKind.ScriptRuntime]: {
109746
+ disallow: [
109747
+ SystemDisallowedOperation.EditRecord,
109748
+ SystemDisallowedOperation.DeleteRecord
109749
+ ],
109750
+ reasonKey: "systemProtection.reasons.scriptRuntime",
109751
+ serverReason: "Lifecycle interface the Neo Compose script runtime calls."
109592
109752
  }
109593
109753
  };
109594
109754
  }
@@ -127740,7 +127900,7 @@ function recordedStoredConstructorId(value, schemaClass2) {
127740
127900
  }
127741
127901
  function storedConstructorArgumentSource(context, type, value, environment, visited, cloneAggregateArguments = false, partial = false, constructorOwnsGraph = false) {
127742
127902
  if (value === null) {
127743
- if (!type.nullable) {
127903
+ if (!type.nullable && (type.kind !== "generic" || !storedGenericArgumentType(context, type, environment).nullable)) {
127744
127904
  throw new Error("Required constructor argument stores null.");
127745
127905
  }
127746
127906
  return "null";
@@ -127830,26 +127990,10 @@ function storedConstructorArgumentSource(context, type, value, environment, visi
127830
127990
  constructorOwnsGraph
127831
127991
  );
127832
127992
  }
127833
- case "generic": {
127834
- const bindingId = environment.get(type.genericParamId);
127835
- const inferredClassId = inferredGenericClassId(bindingId);
127836
- const resolvedType = inferredClassId === null ? bindingId === void 0 ? null : schemaTypeForBindingMember(context, bindingId, environment) : {
127837
- kind: "class",
127838
- nullable: type.nullable,
127839
- classId: inferredClassId,
127840
- classArguments: {}
127841
- };
127842
- if (resolvedType === null) {
127843
- throw new Error(
127844
- `Stored Generic constructor argument cannot resolve parameter ${type.genericParamId}.`
127845
- );
127846
- }
127993
+ case "generic":
127847
127994
  return storedConstructorArgumentSource(
127848
127995
  context,
127849
- withSchemaTypeNullability(
127850
- resolvedType,
127851
- type.nullable || resolvedType.nullable
127852
- ),
127996
+ storedGenericArgumentType(context, type, environment),
127853
127997
  value,
127854
127998
  environment,
127855
127999
  visited,
@@ -127857,7 +128001,6 @@ function storedConstructorArgumentSource(context, type, value, environment, visi
127857
128001
  partial,
127858
128002
  constructorOwnsGraph
127859
128003
  );
127860
- }
127861
128004
  case "interface":
127862
128005
  case "list":
127863
128006
  case "dictionary":
@@ -127908,6 +128051,25 @@ function storedConstructorArgumentSource(context, type, value, environment, visi
127908
128051
  );
127909
128052
  }
127910
128053
  }
128054
+ function storedGenericArgumentType(context, type, environment) {
128055
+ const bindingId = environment.get(type.genericParamId);
128056
+ const inferredClassId = inferredGenericClassId(bindingId);
128057
+ const resolvedType = inferredClassId === null ? bindingId === void 0 ? null : schemaTypeForBindingMember(context, bindingId, environment) : {
128058
+ kind: "class",
128059
+ nullable: type.nullable,
128060
+ classId: inferredClassId,
128061
+ classArguments: {}
128062
+ };
128063
+ if (resolvedType === null) {
128064
+ throw new Error(
128065
+ `Stored Generic constructor argument cannot resolve parameter ${type.genericParamId}.`
128066
+ );
128067
+ }
128068
+ return withSchemaTypeNullability(
128069
+ resolvedType,
128070
+ type.nullable || resolvedType.nullable
128071
+ );
128072
+ }
127911
128073
  function schemaTypeForBindingMember(context, memberId, environment, visited = /* @__PURE__ */ new Set()) {
127912
128074
  if (visited.has(memberId)) {
127913
128075
  throw new Error(
@@ -128073,6 +128235,10 @@ function storedAggregateReplaySource(context, type, valueId, environment, constr
128073
128235
  function isSharedConstructorArgument(context, valueId, constructorOwnsGraph) {
128074
128236
  return !constructorOwnsGraph && context.readMaterializedGraph().independentlyOwnedValueIds.has(valueId);
128075
128237
  }
128238
+ function storedTypeArgumentName(context, type, environment) {
128239
+ const settled = type.kind === "generic" ? storedGenericArgumentType(context, type, environment) : type;
128240
+ return `${storedSchemaTypeName(context, settled, environment)}${settled.nullable ? "?" : ""}`;
128241
+ }
128076
128242
  function storedSchemaTypeName(context, type, environment, valueId) {
128077
128243
  switch (type.kind) {
128078
128244
  case "unknown":
@@ -128085,7 +128251,7 @@ function storedSchemaTypeName(context, type, environment, valueId) {
128085
128251
  const argumentsValue = schemaClass2.genericParameters.flatMap(
128086
128252
  (parameter4) => {
128087
128253
  const argument2 = type.classArguments[parameter4.id];
128088
- return argument2 === void 0 ? [] : [storedSchemaTypeName(context, argument2, environment)];
128254
+ return argument2 === void 0 ? [] : [storedTypeArgumentName(context, argument2, environment)];
128089
128255
  }
128090
128256
  );
128091
128257
  return `${schemaClass2.name}${argumentsValue.length === 0 ? "" : `<${argumentsValue.join(", ")}>`}`;
@@ -128113,10 +128279,10 @@ function storedSchemaTypeName(context, type, environment, valueId) {
128113
128279
  return manifestMemberTypeName(context, member);
128114
128280
  }
128115
128281
  case "list":
128116
- return `List<${storedSchemaTypeName(context, type.entryType, environment)}>`;
128282
+ return `List<${storedTypeArgumentName(context, type.entryType, environment)}>`;
128117
128283
  case "dictionary": {
128118
128284
  const keyType = type.keyEnumId === null ? "string" : stringField3(context.enums.get(type.keyEnumId) ?? {}, "name");
128119
- return `Dictionary<${keyType}, ${storedSchemaTypeName(context, type.entryType, environment)}>`;
128285
+ return `Dictionary<${keyType}, ${storedTypeArgumentName(context, type.entryType, environment)}>`;
128120
128286
  }
128121
128287
  case "bool":
128122
128288
  case "int":
@@ -128333,10 +128499,13 @@ function classValueTypeName(context, classId, member, environment) {
128333
128499
  `Stored Class member ${String(member.name ?? member.id)} has no concrete ${parameter4.name} binding.`
128334
128500
  );
128335
128501
  }
128336
- return manifestMemberTypeName(context, bindingMember);
128502
+ return typeArgumentName(context, bindingMember);
128337
128503
  });
128338
128504
  return `${name}<${argumentsValue.join(", ")}>`;
128339
128505
  }
128506
+ function typeArgumentName(context, member) {
128507
+ return `${manifestMemberTypeName(context, member)}${member.required ? "" : "?"}`;
128508
+ }
128340
128509
  function manifestMemberTypeName(context, member) {
128341
128510
  if (member.kind === "class") {
128342
128511
  const schemaClass2 = context.manifestClasses.get(member.classId);
@@ -128357,7 +128526,7 @@ function manifestMemberTypeName(context, member) {
128357
128526
  `Stored generic binding ${binding.memberId} was not pulled.`
128358
128527
  );
128359
128528
  }
128360
- return manifestMemberTypeName(context, argument2);
128529
+ return typeArgumentName(context, argument2);
128361
128530
  });
128362
128531
  return `${schemaClass2.name}<${argumentsValue.join(", ")}>`;
128363
128532
  }
@@ -142416,6 +142585,260 @@ var init_analyzer = __esm({
142416
142585
  }
142417
142586
  });
142418
142587
 
142588
+ // ../packages/neoscript-language/src/project-source-lifecycle-editor.ts
142589
+ function projectLifecycleHooks(analysis) {
142590
+ let hooks = PROJECT_LIFECYCLE_HOOKS.get(analysis);
142591
+ if (!hooks) {
142592
+ const interfaces = [];
142593
+ for (const document of analysis.documents.values()) {
142594
+ for (const declaration of document.declarations) {
142595
+ if (declaration.kind === "interface") interfaces.push(declaration);
142596
+ }
142597
+ }
142598
+ hooks = lifecycleHooksByMemberName(interfaces);
142599
+ PROJECT_LIFECYCLE_HOOKS.set(analysis, hooks);
142600
+ }
142601
+ return hooks;
142602
+ }
142603
+ function projectLifecycleQuickFixes(analysis, document, diagnostic, findType) {
142604
+ if (diagnostic.code !== "reserved-lifecycle-member") return [];
142605
+ const source = analysis.documents.get(document.uri);
142606
+ if (!source) return [];
142607
+ for (const declaration of source.declarations) {
142608
+ if (declaration.kind !== "class") continue;
142609
+ const member = declaration.members.find(
142610
+ (candidate) => rangesEqual(candidate.nameRange, diagnostic.range)
142611
+ );
142612
+ if (member?.kind !== "function") continue;
142613
+ const hook = projectLifecycleHooks(analysis).get(member.name.toLowerCase());
142614
+ if (hook === void 0) return [];
142615
+ const text = new SourceText(document.text);
142616
+ const tokens = source.syntax.tokens;
142617
+ const edits = [];
142618
+ if (!classLifecycleContext(declaration, findType).implemented.has(
142619
+ hook.interfaceName
142620
+ )) {
142621
+ edits.push(baseListEdit(text, tokens, declaration, hook.interfaceName));
142622
+ }
142623
+ if (effectiveSourceAccessModifier(member.modifiers, "class") !== "public") {
142624
+ edits.push(publicAccessEdit(text, tokens, member));
142625
+ }
142626
+ if (edits.length === 0) return [];
142627
+ return [
142628
+ {
142629
+ title: `Implement '${hook.interfaceName}'`,
142630
+ kind: "quickfix",
142631
+ diagnostics: [diagnostic],
142632
+ edit: { changes: { [document.uri]: edits } },
142633
+ preferred: true
142634
+ }
142635
+ ];
142636
+ }
142637
+ return [];
142638
+ }
142639
+ function lifecycleMemberCompletions(analysis, document, position, findType) {
142640
+ const hooks = projectLifecycleHooks(analysis);
142641
+ if (hooks.size === 0) return [];
142642
+ const source = analysis.documents.get(document.uri);
142643
+ const declaration = source?.declarations.find(
142644
+ (candidate) => candidate.kind === "class" && positionCompare(candidate.range.start, position) < 0 && positionCompare(position, candidate.range.end) < 0
142645
+ );
142646
+ if (!source || source.kind === "flow" || !declaration) return [];
142647
+ const text = new SourceText(document.text);
142648
+ const offset = text.offsetAt(position);
142649
+ const site = memberDeclarationSite(
142650
+ source.syntax.tokens,
142651
+ declaration,
142652
+ text,
142653
+ offset
142654
+ );
142655
+ if (site === null) return [];
142656
+ const own = new Set(
142657
+ declaration.members.map((member) => member.name.toLowerCase())
142658
+ );
142659
+ const context = classLifecycleContext(declaration, findType);
142660
+ const range2 = text.range(site.start, site.end);
142661
+ const typed = document.text.slice(site.start, site.modifiersEnd).trim();
142662
+ const items = [];
142663
+ for (const [key, hook] of hooks) {
142664
+ if (own.has(key)) continue;
142665
+ if (hook.unity && !context.rendersObjects) continue;
142666
+ const inherited = context.inherited.get(key);
142667
+ if (inherited !== void 0 && !isOverridable(inherited)) continue;
142668
+ const name = hook.member.name;
142669
+ const snippet = `public ${inherited === void 0 ? "" : "override "}${hookSignature(hook.member)} {
142670
+ $0
142671
+ }`;
142672
+ items.push({
142673
+ label: name,
142674
+ kind: "method",
142675
+ detail: hook.interfaceName,
142676
+ ...hook.docsText === void 0 ? {} : { documentation: hook.docsText.split("\n")[0] },
142677
+ insertText: snippet,
142678
+ insertTextFormat: "snippet",
142679
+ filterText: typed.length === 0 ? name : `${typed} ${name}`,
142680
+ textEdit: { range: range2, newText: snippet },
142681
+ ...context.implemented.has(hook.interfaceName) ? {} : {
142682
+ additionalTextEdits: [
142683
+ baseListEdit(
142684
+ text,
142685
+ source.syntax.tokens,
142686
+ declaration,
142687
+ hook.interfaceName
142688
+ )
142689
+ ]
142690
+ }
142691
+ });
142692
+ }
142693
+ return items;
142694
+ }
142695
+ function lifecycleHoverMarkdown(analysis, memberName) {
142696
+ const hook = projectLifecycleHooks(analysis).get(memberName.toLowerCase());
142697
+ if (hook === void 0) return "";
142698
+ const docs = hook.docsText === void 0 ? "" : ` ${hook.docsText}`;
142699
+ return `
142700
+
142701
+ Lifecycle hook from \`${hook.interfaceName}\`.${docs}`;
142702
+ }
142703
+ function classLifecycleContext(declaration, findType) {
142704
+ const implemented = /* @__PURE__ */ new Set();
142705
+ const inherited = /* @__PURE__ */ new Map();
142706
+ const pendingInterfaces = [];
142707
+ const visitedClasses = /* @__PURE__ */ new Set();
142708
+ let rendersObjects = false;
142709
+ let current = declaration;
142710
+ while (current !== void 0 && !visitedClasses.has(current.name)) {
142711
+ visitedClasses.add(current.name);
142712
+ if (current.name === NEO_LIFECYCLE_OBJECT_CLASS_NAME) rendersObjects = true;
142713
+ if (current !== declaration) {
142714
+ for (const member of current.members) {
142715
+ const key = member.name.toLowerCase();
142716
+ if (!inherited.has(key)) inherited.set(key, member);
142717
+ }
142718
+ }
142719
+ let next;
142720
+ for (const base of current.baseTypes) {
142721
+ const target = findType(base.name);
142722
+ if (target?.kind === "class") next ??= target;
142723
+ else if (target?.kind === "interface") pendingInterfaces.push(target);
142724
+ }
142725
+ current = next;
142726
+ }
142727
+ while (pendingInterfaces.length > 0) {
142728
+ const target = pendingInterfaces.pop();
142729
+ if (implemented.has(target.name)) continue;
142730
+ implemented.add(target.name);
142731
+ for (const base of target.baseTypes) {
142732
+ const parent = findType(base.name);
142733
+ if (parent?.kind === "interface") pendingInterfaces.push(parent);
142734
+ }
142735
+ }
142736
+ return { implemented, rendersObjects, inherited };
142737
+ }
142738
+ function isOverridable(member) {
142739
+ if (member.modifiers.includes("sealed")) return false;
142740
+ return member.modifiers.includes("virtual") || member.modifiers.includes("abstract") || member.modifiers.includes("override");
142741
+ }
142742
+ function hookSignature(member) {
142743
+ const parameters = member.parameters.map(
142744
+ (parameter4) => `${neoSourceTypeText(parameter4.type)} ${parameter4.name}`
142745
+ ).join(", ");
142746
+ return `${neoSourceTypeText(member.type)} ${member.name}(${parameters})`;
142747
+ }
142748
+ function baseListEdit(text, tokens, declaration, interfaceName) {
142749
+ const last = declaration.baseTypes[declaration.baseTypes.length - 1];
142750
+ if (last !== void 0) {
142751
+ const end = declaration.baseTypes.length === 1 && declaration.baseClause ? declaration.baseClause.range.end : last.range.end;
142752
+ const at4 = text.offsetAt(end);
142753
+ return { range: text.range(at4, at4), newText: `, ${interfaceName}` };
142754
+ }
142755
+ const at3 = headerEnd(tokens, text.offsetAt(declaration.nameRange.end));
142756
+ return { range: text.range(at3, at3), newText: ` : ${interfaceName}` };
142757
+ }
142758
+ function headerEnd(tokens, nameEnd) {
142759
+ let end = nameEnd;
142760
+ let depth = 0;
142761
+ for (const token of tokens) {
142762
+ if (token.start < nameEnd || token.kind === "comment") continue;
142763
+ if (token.text === "(") depth += 1;
142764
+ else if (token.text === ")") depth -= 1;
142765
+ else if (depth === 0 && token.text === "{") break;
142766
+ end = token.end;
142767
+ }
142768
+ return end;
142769
+ }
142770
+ function publicAccessEdit(text, tokens, member) {
142771
+ const start = text.offsetAt(member.range.start);
142772
+ const typeStart = text.offsetAt(member.type.range.start);
142773
+ let firstModifier;
142774
+ let depth = 0;
142775
+ for (const token of tokens) {
142776
+ if (token.start < start || token.kind === "comment") continue;
142777
+ if (token.start >= typeStart) break;
142778
+ if (token.text === "(") depth += 1;
142779
+ else if (token.text === ")") depth -= 1;
142780
+ if (depth !== 0 || token.kind !== "keyword") continue;
142781
+ if (isNeoSourceAccessModifier(token.text)) {
142782
+ return { range: token.range, newText: "public" };
142783
+ }
142784
+ firstModifier ??= token;
142785
+ }
142786
+ const at3 = firstModifier?.start ?? typeStart;
142787
+ return { range: text.range(at3, at3), newText: "public " };
142788
+ }
142789
+ function memberDeclarationSite(tokens, declaration, text, offset) {
142790
+ const headerEndOffset = text.offsetAt(
142791
+ declaration.baseClause?.range.end ?? declaration.nameRange.end
142792
+ );
142793
+ const classStart = text.offsetAt(declaration.range.start);
142794
+ const before = [];
142795
+ let depth = 0;
142796
+ for (const token of tokens) {
142797
+ if (token.start < classStart || token.kind === "comment") continue;
142798
+ if (token.kind === "eof" || token.start >= offset) break;
142799
+ before.push(token);
142800
+ if (token.start < headerEndOffset || token.interpolationHole) continue;
142801
+ if (token.text === "{") depth += 1;
142802
+ else if (token.text === "}") depth -= 1;
142803
+ }
142804
+ if (depth !== 1) return null;
142805
+ let index = before.length - 1;
142806
+ let end = offset;
142807
+ let word = before[index];
142808
+ if (word !== void 0 && (word.kind === "identifier" || word.kind === "type") && word.end === offset) {
142809
+ index -= 1;
142810
+ } else {
142811
+ word = void 0;
142812
+ }
142813
+ let start = word?.start ?? offset;
142814
+ const modifiersEnd = start;
142815
+ while (MEMBER_SITE_MODIFIERS.has(before[index]?.text ?? "")) {
142816
+ start = before[index].start;
142817
+ index -= 1;
142818
+ }
142819
+ const boundary = before[index]?.text;
142820
+ if (boundary !== "{" && boundary !== "}" && boundary !== ";") return null;
142821
+ if (word !== void 0) end = word.end;
142822
+ return { start, modifiersEnd, end };
142823
+ }
142824
+ var PROJECT_LIFECYCLE_HOOKS, MEMBER_SITE_MODIFIERS;
142825
+ var init_project_source_lifecycle_editor = __esm({
142826
+ "../packages/neoscript-language/src/project-source-lifecycle-editor.ts"() {
142827
+ "use strict";
142828
+ init_project_source_ast();
142829
+ init_project_source_lifecycle_hooks();
142830
+ init_project_source_tokens();
142831
+ init_source_text();
142832
+ PROJECT_LIFECYCLE_HOOKS = /* @__PURE__ */ new WeakMap();
142833
+ MEMBER_SITE_MODIFIERS = /* @__PURE__ */ new Set([
142834
+ "public",
142835
+ "protected",
142836
+ "private",
142837
+ "override"
142838
+ ]);
142839
+ }
142840
+ });
142841
+
142419
142842
  // ../packages/neoscript-language/src/quick-fixes.ts
142420
142843
  function quickFixFor(document, diagnostic) {
142421
142844
  if (diagnostic.code === "missing-semicolon") {
@@ -142894,6 +143317,16 @@ function projectFallbackCompletions(analysis, document, position) {
142894
143317
  document,
142895
143318
  position
142896
143319
  );
143320
+ if (!expression && !classBasePosition) {
143321
+ for (const item of lifecycleMemberCompletions(
143322
+ analysis,
143323
+ document,
143324
+ position,
143325
+ (name) => findTypeDeclaration(analysis, name)
143326
+ )) {
143327
+ items.set(`${item.detail}.${item.label}`, item);
143328
+ }
143329
+ }
142897
143330
  const keywords = expression ? afterNew ? [] : ["new"] : typePosition ? [] : [
142898
143331
  "class",
142899
143332
  "interface",
@@ -143337,7 +143770,7 @@ function declaredConstructorNamedArguments(analysis, typeName, used) {
143337
143770
  function parameterArgumentField(parameter4) {
143338
143771
  return {
143339
143772
  name: parameter4.name,
143340
- type: sourceTypeText(parameter4.type),
143773
+ type: neoSourceTypeText(parameter4.type),
143341
143774
  ...parameter4.defaultValue === void 0 ? {} : { defaultText: parameter4.defaultValue.text }
143342
143775
  };
143343
143776
  }
@@ -143357,7 +143790,7 @@ function projectInitializerMemberCompletions(analysis, document, position) {
143357
143790
  const items = unsettled.map((member) => ({
143358
143791
  label: member.name,
143359
143792
  kind: "property",
143360
- detail: `${sourceTypeText(member.member.type)} \u2014 required`,
143793
+ detail: `${neoSourceTypeText(member.member.type)} \u2014 required`,
143361
143794
  insertText: `${member.name} = `,
143362
143795
  // Required members sort ahead of defaulted ones: they are the only entries
143363
143796
  // whose absence is a diagnostic.
@@ -143368,7 +143801,7 @@ function projectInitializerMemberCompletions(analysis, document, position) {
143368
143801
  items.push({
143369
143802
  label: member.name,
143370
143803
  kind: "property",
143371
- detail: sourceTypeText(member.type),
143804
+ detail: neoSourceTypeText(member.type),
143372
143805
  insertText: `${member.name} = `,
143373
143806
  sortText: `1${member.name}`
143374
143807
  });
@@ -143406,10 +143839,6 @@ function functionParameterLabel(parameter4) {
143406
143839
  const suffix = parameter4.defaultValue === void 0 ? "" : ` = ${parameter4.defaultValue.text}`;
143407
143840
  return `${parameter4.type.name} ${parameter4.name}${suffix}`;
143408
143841
  }
143409
- function sourceTypeText(type) {
143410
- const args = type.typeArguments.length > 0 ? `<${type.typeArguments.map(sourceTypeText).join(", ")}>` : "";
143411
- return `${type.name}${args}${type.nullable ? "?" : ""}`;
143412
- }
143413
143842
  function projectConstructionIndex(analysis) {
143414
143843
  const cached = CONSTRUCTION_INDEX_CACHE.get(analysis);
143415
143844
  if (cached) return cached;
@@ -143687,9 +144116,10 @@ ${resolved.symbol.documentation}` : "";
143687
144116
  ID: \`${resolved.symbol.id}\`` : "\n\nPending ID";
143688
144117
  const detail = resolved.symbol.detail ? `: \`${resolved.symbol.detail}\`` : "";
143689
144118
  const contract = resolved.symbol.kind === "class" ? projectConstructionContractMarkdown(analysis, resolved.symbol.name) : "";
144119
+ const lifecycle = resolved.symbol.kind === "member" && resolved.symbol.callable === true && resolved.symbol.static !== true && analysis.documents.get(resolved.symbol.location.uri)?.kind !== "flow" ? lifecycleHoverMarkdown(analysis, resolved.symbol.name) : "";
143690
144120
  return {
143691
144121
  range: resolved.token.range,
143692
- markdown: `**${resolved.symbol.kind}** \`${resolved.symbol.name}\`${detail}${owner}${documentation}${contract}${id2}`
144122
+ markdown: `**${resolved.symbol.kind}** \`${resolved.symbol.name}\`${detail}${owner}${documentation}${lifecycle}${contract}${id2}`
143693
144123
  };
143694
144124
  }
143695
144125
  function projectBuiltinSymbolAt(document, position) {
@@ -143730,7 +144160,7 @@ Construct with \`new(${parameters})\` \u2014 this class declares a required cons
143730
144160
  const lines = [];
143731
144161
  if (required2.length > 0) {
143732
144162
  const members = required2.map(
143733
- (member) => `\`${sourceTypeText(member.member.type)} ${member.name}\``
144163
+ (member) => `\`${neoSourceTypeText(member.member.type)} ${member.name}\``
143734
144164
  ).join(", ");
143735
144165
  lines.push(`Every construction path must settle ${members}.`);
143736
144166
  }
@@ -144894,7 +145324,7 @@ function declarationConstructorParameters(declaration) {
144894
145324
  }
144895
145325
  function sourceParameterProjection(parameter4) {
144896
145326
  return {
144897
- type: sourceTypeText(parameter4.type),
145327
+ type: neoSourceTypeText(parameter4.type),
144898
145328
  name: parameter4.name,
144899
145329
  ...parameter4.defaultValue === void 0 ? {} : { defaultText: parameter4.defaultValue.text }
144900
145330
  };
@@ -145005,6 +145435,8 @@ var init_project_source_language_features = __esm({
145005
145435
  init_language_spec();
145006
145436
  init_project_source_semantics();
145007
145437
  init_project_source_variants();
145438
+ init_project_source_lifecycle_editor();
145439
+ init_project_source_ast();
145008
145440
  init_project();
145009
145441
  init_project_source_semantics();
145010
145442
  init_project_source_settlement();
@@ -145126,7 +145558,7 @@ function localValueRefactorings(analysis, document, range2) {
145126
145558
  start: { line: 0, character: 0 },
145127
145559
  end: { line: 0, character: 0 }
145128
145560
  },
145129
- newText: `${typeText2(type)} ${name} = ${selected};
145561
+ newText: `${neoSourceTypeText(type)} ${name} = ${selected};
145130
145562
  `
145131
145563
  }
145132
145564
  ]
@@ -145135,9 +145567,6 @@ function localValueRefactorings(analysis, document, range2) {
145135
145567
  }
145136
145568
  ];
145137
145569
  }
145138
- function typeText2(type) {
145139
- return type.name + (type.typeArguments.length === 0 ? "" : `<${type.typeArguments.map(typeText2).join(", ")}>`) + (type.nullable ? "?" : "");
145140
- }
145141
145570
  function withoutValueIdentities(text) {
145142
145571
  const tokens = lex(text).roots.filter((token) => token.kind !== "comment");
145143
145572
  const removals = [];
@@ -145214,6 +145643,7 @@ var init_project_local_value_refactorings = __esm({
145214
145643
  init_project_local_values();
145215
145644
  init_project_source_language_features();
145216
145645
  init_source_text();
145646
+ init_project_source_ast();
145217
145647
  }
145218
145648
  });
145219
145649
 
@@ -146142,6 +146572,7 @@ var init_service = __esm({
146142
146572
  init_project_source_identifiers();
146143
146573
  init_project_source_analysis();
146144
146574
  init_project_source_construction_quick_fixes();
146575
+ init_project_source_lifecycle_editor();
146145
146576
  init_project_source_reference_family();
146146
146577
  init_project_source_language_features();
146147
146578
  init_project_source_tokens();
@@ -146459,6 +146890,14 @@ var init_service = __esm({
146459
146890
  actions.push(
146460
146891
  ...projectConstructionQuickFixes(project, state.document, diagnostic)
146461
146892
  );
146893
+ actions.push(
146894
+ ...projectLifecycleQuickFixes(
146895
+ project,
146896
+ state.document,
146897
+ diagnostic,
146898
+ (name) => findTypeDeclaration(project, name)
146899
+ )
146900
+ );
146462
146901
  }
146463
146902
  actions.push(...spellingQuickFixes(state.document, diagnostic));
146464
146903
  const edit = quickFixFor(state.document, diagnostic);
@@ -156205,7 +156644,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
156205
156644
  async function main() {
156206
156645
  const args = parseArgs(process.argv.slice(2));
156207
156646
  if (args.command === "--version") {
156208
- console.log("0.65.0");
156647
+ console.log("0.66.0");
156209
156648
  return;
156210
156649
  }
156211
156650
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
@@ -2124,7 +2124,8 @@ var SystemProtectionKind = {
2124
2124
  DialogueMemoryContainer: "dialogueMemoryContainer",
2125
2125
  DialoguePriorityDefault: "dialoguePriorityDefault",
2126
2126
  PolicyImmutable: "policyImmutable",
2127
- PolicyDeleteBlocked: "policyDeleteBlocked"
2127
+ PolicyDeleteBlocked: "policyDeleteBlocked",
2128
+ ScriptRuntime: "scriptRuntime"
2128
2129
  };
2129
2130
  function isWithId(value) {
2130
2131
  return typeof value?.id === "string";
@@ -2141,7 +2142,7 @@ function isEpochMillis(value) {
2141
2142
  return Number.isFinite(value);
2142
2143
  }
2143
2144
  function isSystemProtectionKind(value) {
2144
- return value === SystemProtectionKind.WorldAuthoring || value === SystemProtectionKind.DialogueMemorySchema || value === SystemProtectionKind.DialogueMemoryContainer || value === SystemProtectionKind.DialoguePriorityDefault || value === SystemProtectionKind.PolicyImmutable || value === SystemProtectionKind.PolicyDeleteBlocked;
2145
+ return value === SystemProtectionKind.WorldAuthoring || value === SystemProtectionKind.DialogueMemorySchema || value === SystemProtectionKind.DialogueMemoryContainer || value === SystemProtectionKind.DialoguePriorityDefault || value === SystemProtectionKind.PolicyImmutable || value === SystemProtectionKind.PolicyDeleteBlocked || value === SystemProtectionKind.ScriptRuntime;
2145
2146
  }
2146
2147
  function isSystemMetadata(value) {
2147
2148
  const v = value;
@@ -40639,13 +40640,11 @@ function classArguments(member, context, seen, genericEnvironment) {
40639
40640
  if (environmentEntry?.kind === "member") {
40640
40641
  const argument3 = analyzerMemberById(context, environmentEntry.memberId);
40641
40642
  return argument3 ? [
40642
- requiredType(
40643
- memberRuntimeType(
40644
- argument3,
40645
- context,
40646
- new Set(seen),
40647
- genericEnvironment
40648
- )
40643
+ memberRuntimeType(
40644
+ argument3,
40645
+ context,
40646
+ new Set(seen),
40647
+ genericEnvironment
40649
40648
  )
40650
40649
  ] : [];
40651
40650
  }
@@ -40660,13 +40659,11 @@ function classArguments(member, context, seen, genericEnvironment) {
40660
40659
  }
40661
40660
  const argument2 = analyzerMemberById(context, binding.memberId);
40662
40661
  return argument2 ? [
40663
- requiredType(
40664
- memberRuntimeType(
40665
- argument2,
40666
- context,
40667
- new Set(seen),
40668
- genericEnvironment
40669
- )
40662
+ memberRuntimeType(
40663
+ argument2,
40664
+ context,
40665
+ new Set(seen),
40666
+ genericEnvironment
40670
40667
  )
40671
40668
  ] : [];
40672
40669
  });
@@ -40853,11 +40850,7 @@ function genericConstraintLanguageType(constraint, context, genericEnvironment,
40853
40850
  ];
40854
40851
  }
40855
40852
  const member = analyzerMemberById(context, binding.memberId);
40856
- return member ? [
40857
- requiredType(
40858
- memberRuntimeType(member, context, /* @__PURE__ */ new Set(), genericEnvironment)
40859
- )
40860
- ] : [];
40853
+ return member ? [memberRuntimeType(member, context, /* @__PURE__ */ new Set(), genericEnvironment)] : [];
40861
40854
  });
40862
40855
  return {
40863
40856
  kind: "named",
@@ -44843,6 +44836,14 @@ var SYSTEM_PROTECTION_RULES = {
44843
44836
  disallow: [SystemDisallowedOperation.DeleteRecord],
44844
44837
  reasonKey: "systemProtection.reasons.policyDeleteBlocked",
44845
44838
  serverReason: "System default policy may be edited but must keep one default row."
44839
+ },
44840
+ [SystemProtectionKind.ScriptRuntime]: {
44841
+ disallow: [
44842
+ SystemDisallowedOperation.EditRecord,
44843
+ SystemDisallowedOperation.DeleteRecord
44844
+ ],
44845
+ reasonKey: "systemProtection.reasons.scriptRuntime",
44846
+ serverReason: "Lifecycle interface the Neo Compose script runtime calls."
44846
44847
  }
44847
44848
  };
44848
44849
 
@@ -46088,7 +46089,8 @@ function isSchemaSystemMetadata(value) {
46088
46089
  "dialogueMemoryContainer",
46089
46090
  "dialoguePriorityDefault",
46090
46091
  "policyImmutable",
46091
- "policyDeleteBlocked"
46092
+ "policyDeleteBlocked",
46093
+ "scriptRuntime"
46092
46094
  ]);
46093
46095
  const worlds = /* @__PURE__ */ new Set([
46094
46096
  "cellPattern",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.65.0",
3
+ "version": "0.66.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.65.0 -->
12
+ <!-- reviewed-through-cli: 0.66.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.65.0 -->
113
+ <!-- reviewed-through-cli: 0.66.0 -->
114
114
  ```
115
115
 
116
116
  The quoted version above is checked too, so this instruction cannot go stale
@@ -197,6 +197,16 @@ itself; both are durable writes deduplicated by target identity, so
197
197
  re-running a subscription cannot double-subscribe. Invoke it as a statement —
198
198
  an action is never a value, never a return type, and never assigned with `=`.
199
199
 
200
+ Implement a system lifecycle interface to run code when the runtime does.
201
+ `INeoOnLoad` and `INeoOnUnload` run when an instance becomes live and when it
202
+ stops being live. The `INeoUnity*` interfaces, such as `INeoUnityUpdate` and
203
+ `INeoUnityOnTriggerEnter2D`, run for a rendered `NeoObjectBase` in play mode.
204
+ Their member names, such as `OnLoad` and `Update`, are reserved: declare one
205
+ only as the public, immediate instance method that implements its interface,
206
+ and never mark it `@effect`. Subscribe Session actions in `OnLoad` and remove
207
+ them in `OnUnload`. React to data changes with `@effect`, and keep `Update`
208
+ for work that really happens every frame.
209
+
200
210
  Keep generic project relations as typed top-level declarations in
201
211
  `Relations.neo`. Let the compiler derive identities for owned descriptors and
202
212
  concrete generic bindings.