@neocompose/cli 0.6.6 → 0.6.7

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,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased]
4
+
5
+ ### Changed
6
+
7
+ - Document the final relation-only world layer-link contract and keep the
8
+ format-4 Hello World corpus free of the retired value-side binding field.
9
+
10
+ ## [0.6.7] - 2026-07-21
11
+
12
+ ### Fixed
13
+
14
+ - Treat class-level layer-link relations, including inherited targets, as the
15
+ authoritative binding during pull, status, diff, and push.
16
+ - Reject concrete system layer-link bases, targetless concrete descendants,
17
+ ambiguous or invalid targets, abstract construction, and authored
18
+ `layerClassId` sidecars before a source transaction reaches the server.
19
+
3
20
  ## [0.6.6] - 2026-07-21
4
21
 
5
22
  ### Fixed
package/dist/neo.mjs CHANGED
@@ -13981,6 +13981,7 @@ function validateProjectSourceSemantics(documents) {
13981
13981
  );
13982
13982
  }
13983
13983
  }
13984
+ validateWorldLayerLinkSourceContracts(documents, environment, diagnostics);
13984
13985
  return diagnostics;
13985
13986
  }
13986
13987
  function contextualAnnotationFields(document, position) {
@@ -14500,6 +14501,188 @@ function relationFields(context, environment) {
14500
14501
  }
14501
14502
  return [];
14502
14503
  }
14504
+ function validateWorldLayerLinkSourceContracts(documents, environment, diagnostics) {
14505
+ for (const [uri, document] of documents) {
14506
+ for (const declaration of document.declarations) {
14507
+ if (declaration.kind !== "class") continue;
14508
+ const descriptor = worldLayerLinkSourceDescriptor(
14509
+ declaration,
14510
+ environment
14511
+ );
14512
+ if (descriptor === null) continue;
14513
+ const directTargets = directLayerLinkTargets(declaration, uri);
14514
+ const systemWorldKind = contextualAnnotationEnum(
14515
+ declaration.annotations,
14516
+ "system",
14517
+ "worldKind"
14518
+ );
14519
+ const isSystemBase = systemWorldKind === descriptor.linkWorldKind;
14520
+ if (isSystemBase) {
14521
+ if (!declaration.modifiers.includes("abstract")) {
14522
+ pushDiagnostic(
14523
+ diagnostics,
14524
+ uri,
14525
+ declaration.nameRange,
14526
+ "concrete-system-layer-link-base",
14527
+ `System layer-link base '${declaration.name}' must be abstract.`
14528
+ );
14529
+ }
14530
+ if (directTargets.length > 0) {
14531
+ pushDiagnostic(
14532
+ diagnostics,
14533
+ uri,
14534
+ directTargets[0].range,
14535
+ "system-layer-link-target-relation",
14536
+ `System layer-link base '${declaration.name}' cannot declare targetLayer. Declare the relation on a project-authored descendant instead.`
14537
+ );
14538
+ }
14539
+ }
14540
+ for (const target of directTargets) {
14541
+ validateLayerLinkTargetSource(
14542
+ declaration,
14543
+ descriptor,
14544
+ target,
14545
+ environment,
14546
+ diagnostics
14547
+ );
14548
+ }
14549
+ if (isSystemBase || declaration.modifiers.includes("abstract")) continue;
14550
+ const effectiveTargets = effectiveLayerLinkTargets(
14551
+ declaration,
14552
+ uri,
14553
+ environment
14554
+ );
14555
+ if (effectiveTargets.length === 0) {
14556
+ pushDiagnostic(
14557
+ diagnostics,
14558
+ uri,
14559
+ declaration.nameRange,
14560
+ "concrete-layer-link-missing-target",
14561
+ `Concrete layer-link class '${declaration.name}' must resolve exactly one inherited or directly declared targetLayer relation.`
14562
+ );
14563
+ } else if (effectiveTargets.length > 1) {
14564
+ pushDiagnostic(
14565
+ diagnostics,
14566
+ uri,
14567
+ declaration.nameRange,
14568
+ "concrete-layer-link-multiple-targets",
14569
+ `Concrete layer-link class '${declaration.name}' resolves ${effectiveTargets.length} targetLayer relations at the nearest declaration depth; exactly one is required.`
14570
+ );
14571
+ }
14572
+ }
14573
+ }
14574
+ }
14575
+ function worldLayerLinkSourceDescriptor(declaration, environment) {
14576
+ const worldKind = effectiveWorldKind(declaration, environment);
14577
+ if (worldKind === "TileLayerLink") {
14578
+ return {
14579
+ linkWorldKind: "TileLayerLink",
14580
+ targetWorldKind: "TileLayer"
14581
+ };
14582
+ }
14583
+ if (worldKind === "ObjectLayerLink") {
14584
+ return {
14585
+ linkWorldKind: "ObjectLayerLink",
14586
+ targetWorldKind: "ObjectLayer"
14587
+ };
14588
+ }
14589
+ return null;
14590
+ }
14591
+ function directLayerLinkTargets(declaration, uri) {
14592
+ const result = [];
14593
+ for (const annotation2 of declaration.annotations) {
14594
+ if (annotation2.name !== "relations") continue;
14595
+ for (const argument2 of annotation2.arguments) {
14596
+ if (argument2.name !== "targetLayer") continue;
14597
+ let expression;
14598
+ try {
14599
+ expression = parseExpression(argument2.text);
14600
+ } catch (error) {
14601
+ if (error instanceof CompileError) continue;
14602
+ throw error;
14603
+ }
14604
+ const entries = expression.kind === "litList" ? expression.elements : [expression];
14605
+ for (const entry of entries) {
14606
+ result.push({
14607
+ expression: unwrapAnnotatedExpression(entry),
14608
+ range: argument2.range,
14609
+ uri
14610
+ });
14611
+ }
14612
+ }
14613
+ }
14614
+ return result;
14615
+ }
14616
+ function effectiveLayerLinkTargets(declaration, uri, environment) {
14617
+ const visited = /* @__PURE__ */ new Set();
14618
+ let current = declaration;
14619
+ let currentUri = uri;
14620
+ while (current !== void 0 && !visited.has(current.name)) {
14621
+ visited.add(current.name);
14622
+ const systemWorldKind = contextualAnnotationEnum(
14623
+ current.annotations,
14624
+ "system",
14625
+ "worldKind"
14626
+ );
14627
+ if (systemWorldKind === "TileLayerLink" || systemWorldKind === "ObjectLayerLink") {
14628
+ return [];
14629
+ }
14630
+ const direct = directLayerLinkTargets(current, currentUri);
14631
+ if (direct.length > 0) return direct;
14632
+ const parent = current.baseTypes.map((base) => environment.types.get(base.name)).find((candidate) => candidate !== void 0);
14633
+ current = parent;
14634
+ currentUri = uri;
14635
+ }
14636
+ return [];
14637
+ }
14638
+ function validateLayerLinkTargetSource(declaration, descriptor, targetSource, environment, diagnostics) {
14639
+ const expression = targetSource.expression;
14640
+ if (isReferenceCall(expression)) return;
14641
+ if (expression.kind !== "ident") {
14642
+ pushDiagnostic(
14643
+ diagnostics,
14644
+ targetSource.uri,
14645
+ targetSource.range,
14646
+ "invalid-layer-link-target",
14647
+ `Layer-link class '${declaration.name}' targetLayer must be a layer class symbol or typed Reference.`
14648
+ );
14649
+ return;
14650
+ }
14651
+ const target = environment.types.get(expression.name);
14652
+ if (target === void 0) {
14653
+ pushDiagnostic(
14654
+ diagnostics,
14655
+ targetSource.uri,
14656
+ targetSource.range,
14657
+ "unknown-layer-link-target",
14658
+ `Layer-link class '${declaration.name}' targets unknown class '${expression.name}'.`
14659
+ );
14660
+ return;
14661
+ }
14662
+ const targetWorldKind = effectiveWorldKind(target, environment);
14663
+ if (targetWorldKind !== descriptor.targetWorldKind) {
14664
+ pushDiagnostic(
14665
+ diagnostics,
14666
+ targetSource.uri,
14667
+ targetSource.range,
14668
+ "wrong-layer-link-target-kind",
14669
+ `Layer-link class '${declaration.name}' targetLayer '${target.name}' must extend world class kind '${descriptor.targetWorldKind}'.`
14670
+ );
14671
+ return;
14672
+ }
14673
+ if (target.modifiers.includes("abstract")) {
14674
+ pushDiagnostic(
14675
+ diagnostics,
14676
+ targetSource.uri,
14677
+ targetSource.range,
14678
+ "abstract-layer-link-target",
14679
+ `Layer-link class '${declaration.name}' targetLayer '${target.name}' must be concrete.`
14680
+ );
14681
+ }
14682
+ }
14683
+ function unwrapAnnotatedExpression(expression) {
14684
+ return expression.kind === "annotated" ? unwrapAnnotatedExpression(expression.expression) : expression;
14685
+ }
14503
14686
  function effectiveWorldKind(declaration, environment) {
14504
14687
  const queue = [declaration];
14505
14688
  const seen = /* @__PURE__ */ new Set();
@@ -14829,7 +15012,26 @@ function validateExpression(expression, expected, scope, environment, uri, range
14829
15012
  if (expression.kind === "new") {
14830
15013
  const typeName = expression.className ?? expected?.name;
14831
15014
  const declaration = typeName ? environment.types.get(typeName) : void 0;
15015
+ for (const entry of expression.initializer ?? []) {
15016
+ if (entry.name !== "layerClassId") continue;
15017
+ pushDiagnostic(
15018
+ diagnostics,
15019
+ uri,
15020
+ range2,
15021
+ "reserved-layer-class-id-source",
15022
+ "layerClassId is an unsupported legacy field. Declare @relations(targetLayer: ...) on the concrete layer-link class instead."
15023
+ );
15024
+ }
14832
15025
  if (!declaration) return;
15026
+ if (declaration.modifiers.includes("abstract")) {
15027
+ pushDiagnostic(
15028
+ diagnostics,
15029
+ uri,
15030
+ range2,
15031
+ "abstract-class-instantiation",
15032
+ `Cannot instantiate abstract class '${declaration.name}'. Use a concrete descendant instead.`
15033
+ );
15034
+ }
14833
15035
  const fields = declaration?.members.filter(
14834
15036
  (member) => member.kind === "field" && !member.modifiers.includes("static")
14835
15037
  ) ?? [];
@@ -14847,8 +15049,10 @@ function validateExpression(expression, expected, scope, environment, uri, range
14847
15049
  );
14848
15050
  });
14849
15051
  for (const entry of expression.initializer ?? []) {
14850
- const field = declaration?.members.find(
14851
- (candidate) => candidate.name === entry.name
15052
+ const field = sourceClassFieldByName(
15053
+ declaration,
15054
+ entry.name,
15055
+ environment
14852
15056
  );
14853
15057
  validateExpression(
14854
15058
  entry.value,
@@ -14950,6 +15154,19 @@ function validateExpression(expression, expected, scope, environment, uri, range
14950
15154
  );
14951
15155
  }
14952
15156
  }
15157
+ function sourceClassFieldByName(declaration, name, environment) {
15158
+ const visited = /* @__PURE__ */ new Set();
15159
+ let current = declaration;
15160
+ while (current !== void 0 && !visited.has(current.name)) {
15161
+ visited.add(current.name);
15162
+ const member = current.members.find(
15163
+ (candidate) => candidate.kind === "field" && candidate.name === name
15164
+ );
15165
+ if (member !== void 0) return member;
15166
+ current = current.baseTypes.map((base) => environment.types.get(base.name)).find((candidate) => candidate !== void 0);
15167
+ }
15168
+ return void 0;
15169
+ }
14953
15170
  function semanticTypesAssignable(actual, expected) {
14954
15171
  if (actual.name === expected.name) return true;
14955
15172
  if (actual.name === "int" && (expected.name === "float" || expected.name === "decimal")) {
@@ -40331,6 +40548,11 @@ function lowerSeedRow(context, member, sourceExpression, source, valueId, path,
40331
40548
  if (effectiveClass === void 0) {
40332
40549
  throw new Error(`Unknown value class ${member.classId}.`);
40333
40550
  }
40551
+ if (effectiveClass.declarationModifier === "abstract") {
40552
+ throw new Error(
40553
+ `Cannot instantiate abstract class ${effectiveClass.name} for ${path}. Use a concrete descendant instead.`
40554
+ );
40555
+ }
40334
40556
  classId = effectiveClass.id;
40335
40557
  const body = {};
40336
40558
  for (const assignment of expression.initializer ?? []) {
@@ -40528,6 +40750,11 @@ function lowerClassValue(context, member, expression, base, source) {
40528
40750
  const schemaClass2 = context.classes.get(classId);
40529
40751
  if (schemaClass2 === void 0)
40530
40752
  throw new Error(`Unknown value class ${classId}.`);
40753
+ if (schemaClass2.declarationModifier === "abstract") {
40754
+ throw new Error(
40755
+ `Cannot instantiate abstract class ${schemaClass2.name} for value ${String(base.id)}. Use a concrete descendant instead.`
40756
+ );
40757
+ }
40531
40758
  const baseBody = isObjectRecord2(base.value) ? base.value : {};
40532
40759
  const body = { ...baseBody };
40533
40760
  for (const assignment of expression.initializer ?? []) {
@@ -41368,7 +41595,7 @@ function classValue(context, member, value, visited, targetTyped) {
41368
41595
  return targetTyped ? "new()" : `new ${name}()`;
41369
41596
  const schema = isObjectRecord2(schemaClass2.schema) ? schemaClass2.schema : {};
41370
41597
  const order = Array.isArray(schemaClass2.schemaKeyOrder) ? schemaClass2.schemaKeyOrder.filter(
41371
- (entry) => typeof entry === "string"
41598
+ (entry) => typeof entry === "string" && typeof schema[entry] === "string"
41372
41599
  ) : Object.keys(schema);
41373
41600
  const fields = [];
41374
41601
  for (const key of [
@@ -44914,7 +45141,7 @@ var init_project_migration_types = __esm({
44914
45141
  });
44915
45142
 
44916
45143
  // ../src/models/project/internal-record-relations.ts
44917
- function worldClassContract(relationKind, sourceWorldKind, targetWorldKind, targetClassPolicy, merge, allowAbstractTarget) {
45144
+ function worldClassContract(relationKind, sourceWorldKind, targetWorldKind, targetClassPolicy, merge, allowAbstractTarget, allowDirectSystemSource = true) {
44918
45145
  return {
44919
45146
  relationKind,
44920
45147
  endpointPairs: [{ sourceRecordKind: "class", targetRecordKind: "class" }],
@@ -44924,6 +45151,7 @@ function worldClassContract(relationKind, sourceWorldKind, targetWorldKind, targ
44924
45151
  allowCycles: false,
44925
45152
  sourceWorldKind,
44926
45153
  targetWorldKind,
45154
+ allowDirectSystemSource,
44927
45155
  allowAbstractTarget
44928
45156
  };
44929
45157
  }
@@ -45018,6 +45246,7 @@ var init_internal_record_relations = __esm({
45018
45246
  NeoWorldSystemClassKind.TileLayer,
45019
45247
  "exact",
45020
45248
  "nearest-single",
45249
+ false,
45021
45250
  false
45022
45251
  ),
45023
45252
  worldClassContract(
@@ -45026,6 +45255,7 @@ var init_internal_record_relations = __esm({
45026
45255
  NeoWorldSystemClassKind.ObjectLayer,
45027
45256
  "exact",
45028
45257
  "nearest-single",
45258
+ false,
45029
45259
  false
45030
45260
  ),
45031
45261
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.6.6",
3
+ "version": "0.6.7",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -205,6 +205,14 @@ declarations. Structurally owned descriptors and concrete generic bindings
205
205
  derive their identities from stable owner roles; do not invent `@id`
206
206
  annotations for them.
207
207
 
208
+ For world layer links, `NeoTileLayerLink` and `NeoObjectLayerLink` are abstract,
209
+ relation-free system bases. Instantiate a concrete project-authored descendant
210
+ that resolves exactly one `targetLayer` relation, declared directly or inherited
211
+ from a project-authored ancestor. The class relation is the complete binding:
212
+ native source emits no value-level target metadata, and link values carry no
213
+ second persistence representation. Painting and other world-content writes
214
+ never create or repair link targets.
215
+
208
216
  ## Root and authored values
209
217
 
210
218
  `Root.neo` contains a compiler-owned envelope of this form: