@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 +17 -0
- package/dist/neo.mjs +234 -4
- package/package.json +1 -1
- package/skills/neocompose-cli/SKILL.md +8 -0
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 =
|
|
14851
|
-
|
|
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
|
@@ -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:
|