@neocompose/cli 0.28.0 → 0.30.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 +34 -0
- package/dist/neo.mjs +164 -18
- package/package.json +1 -1
- package/skills/neocompose-cli/SKILL.md +1 -1
- package/skills/neocompose-cli/references/animation-and-world-authoring.md +13 -11
- package/skills/neocompose-cli/references/cli-development.md +1 -1
- package/skills/neocompose-cli/references/declarations-and-construction.md +11 -2
- package/skills/neocompose-cli/references/values-identities-and-references.md +9 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.30.0] - 2026-08-13
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- P66 sealed classes: author a leaf type as `sealed class` in project source.
|
|
8
|
+
The modifier round-trips through pull and push, and the CLI rejects both
|
|
9
|
+
`sealed abstract` declarations and attempts to extend a sealed class.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Sealed classes are omitted from extends-position language completions, and
|
|
14
|
+
the project contract carries optional `isSealed` metadata. Absence and
|
|
15
|
+
explicit `false` are equivalent, so existing projects need no migration.
|
|
16
|
+
- Unity export schema version is 20. Matching SDKs preserve the sealed
|
|
17
|
+
inheritance contract in generated C#.
|
|
18
|
+
|
|
19
|
+
## [0.29.0] - 2026-08-12
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- `Reference<T>(id: "...", withProvenance: true)` for executable NeoScript.
|
|
24
|
+
The opt-in flag resolves an authored row id to the nearest clone carrying it
|
|
25
|
+
as `sourceValueId` in the lexical receiver's ownership graph. It defaults to
|
|
26
|
+
`false`, preserving exact-ID behavior for every existing reference.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Animation child selectors created by the world UI and the legacy
|
|
31
|
+
`Child`-to-`Selector` reconciler now opt into provenance so clips authored
|
|
32
|
+
against a class default select each placement's cloned child.
|
|
33
|
+
- Unity export schema version is 19 and NeoScript compiler revision is 9.
|
|
34
|
+
Older SDKs reject the export rather than silently ignoring provenance and
|
|
35
|
+
applying an override to the shared authored row.
|
|
36
|
+
|
|
3
37
|
## [0.28.0] - 2026-08-12
|
|
4
38
|
|
|
5
39
|
### Added
|
package/dist/neo.mjs
CHANGED
|
@@ -7891,7 +7891,7 @@ var NEOSCRIPT_COMPILER_REVISION;
|
|
|
7891
7891
|
var init_strict_ir = __esm({
|
|
7892
7892
|
"../packages/neoscript-language/src/strict-ir.ts"() {
|
|
7893
7893
|
"use strict";
|
|
7894
|
-
NEOSCRIPT_COMPILER_REVISION =
|
|
7894
|
+
NEOSCRIPT_COMPILER_REVISION = 9;
|
|
7895
7895
|
}
|
|
7896
7896
|
});
|
|
7897
7897
|
|
|
@@ -10597,9 +10597,34 @@ var init_strict_resolver = __esm({
|
|
|
10597
10597
|
expression.pos
|
|
10598
10598
|
);
|
|
10599
10599
|
}
|
|
10600
|
-
|
|
10600
|
+
const hasProvenanceArgument = expression.args.length === 2;
|
|
10601
|
+
if (expression.args.length < 1 || expression.args.length > 2) {
|
|
10601
10602
|
throw new CompileError(
|
|
10602
|
-
"Reference<T> requires
|
|
10603
|
+
"Reference<T> requires id: string and optionally withProvenance: bool.",
|
|
10604
|
+
expression.pos
|
|
10605
|
+
);
|
|
10606
|
+
}
|
|
10607
|
+
if (expression.argumentNames?.[0] !== "id") {
|
|
10608
|
+
throw new CompileError(
|
|
10609
|
+
"Reference<T> requires id: as its first named argument.",
|
|
10610
|
+
expression.pos
|
|
10611
|
+
);
|
|
10612
|
+
}
|
|
10613
|
+
if (expression.args[0]?.kind !== "litString" || expression.args[0].value.length === 0) {
|
|
10614
|
+
throw new CompileError(
|
|
10615
|
+
"Reference<T> id must be a non-empty string literal.",
|
|
10616
|
+
expression.pos
|
|
10617
|
+
);
|
|
10618
|
+
}
|
|
10619
|
+
if (hasProvenanceArgument && expression.argumentNames?.[1] !== "withProvenance") {
|
|
10620
|
+
throw new CompileError(
|
|
10621
|
+
"Reference<T>'s second named argument must be withProvenance: bool.",
|
|
10622
|
+
expression.pos
|
|
10623
|
+
);
|
|
10624
|
+
}
|
|
10625
|
+
if (hasProvenanceArgument && expression.args[1]?.kind !== "litBool") {
|
|
10626
|
+
throw new CompileError(
|
|
10627
|
+
"Reference<T> withProvenance must be a boolean literal.",
|
|
10603
10628
|
expression.pos
|
|
10604
10629
|
);
|
|
10605
10630
|
}
|
|
@@ -10613,7 +10638,8 @@ var init_strict_resolver = __esm({
|
|
|
10613
10638
|
return {
|
|
10614
10639
|
pointer: {
|
|
10615
10640
|
type: "reference" /* Reference */,
|
|
10616
|
-
valueId: expression.args[0].value
|
|
10641
|
+
valueId: expression.args[0].value,
|
|
10642
|
+
...expression.args[1]?.kind === "litBool" && expression.args[1].value ? { withProvenance: true } : {}
|
|
10617
10643
|
},
|
|
10618
10644
|
type: referencedType
|
|
10619
10645
|
};
|
|
@@ -17350,7 +17376,7 @@ var init_project_schema_contract_generated = __esm({
|
|
|
17350
17376
|
"../packages/neoscript-language/src/project-schema-contract.generated.ts"() {
|
|
17351
17377
|
"use strict";
|
|
17352
17378
|
PROJECT_SCHEMA_MANIFEST_FORMAT_VERSION = 3;
|
|
17353
|
-
PROJECT_SCHEMA_MANIFEST_CONTRACT_VERSION = "3.
|
|
17379
|
+
PROJECT_SCHEMA_MANIFEST_CONTRACT_VERSION = "3.11";
|
|
17354
17380
|
PROJECT_FILE_UPLOAD_BATCH_SIZE = 32;
|
|
17355
17381
|
NEO_PROJECT_SOURCE_RECORD_CONTRACT = {
|
|
17356
17382
|
"recordFields": {
|
|
@@ -17411,6 +17437,7 @@ var init_project_schema_contract_generated = __esm({
|
|
|
17411
17437
|
"extendsClassId",
|
|
17412
17438
|
"implementsInterfaceIds",
|
|
17413
17439
|
"hiddenInMemberSelector",
|
|
17440
|
+
"isSealed",
|
|
17414
17441
|
"isAbstract",
|
|
17415
17442
|
"system",
|
|
17416
17443
|
"allowedStorage",
|
|
@@ -21834,7 +21861,9 @@ function validateReferenceExpression(expression, expected, environment, uri, ran
|
|
|
21834
21861
|
const typeArguments = expression.typeArguments ?? [];
|
|
21835
21862
|
const names = expression.argumentNames ?? [];
|
|
21836
21863
|
const idIndex = names.findIndex((name) => name === "id");
|
|
21864
|
+
const provenanceIndex = names.findIndex((name) => name === "withProvenance");
|
|
21837
21865
|
const hasId = idIndex >= 0;
|
|
21866
|
+
const hasProvenance = provenanceIndex >= 0;
|
|
21838
21867
|
const argument2 = expression.args[hasId ? idIndex : 0];
|
|
21839
21868
|
const malformed = (message) => pushDiagnostic(
|
|
21840
21869
|
diagnostics,
|
|
@@ -21878,9 +21907,11 @@ function validateReferenceExpression(expression, expected, environment, uri, ran
|
|
|
21878
21907
|
}
|
|
21879
21908
|
return;
|
|
21880
21909
|
}
|
|
21881
|
-
|
|
21910
|
+
const validIdShape = hasId && idIndex === 0 && expression.args.length === (hasProvenance ? 2 : 1) && (!hasProvenance || provenanceIndex === 1) && names.every((name) => name === "id" || name === "withProvenance");
|
|
21911
|
+
const validSymbolShape = !hasId && !hasProvenance && expression.args.length === 1 && names.every((name) => name === null);
|
|
21912
|
+
if (!validIdShape && !validSymbolShape) {
|
|
21882
21913
|
malformed(
|
|
21883
|
-
"Reference requires
|
|
21914
|
+
"Reference requires one symbol argument, or id: string followed by optional withProvenance: bool."
|
|
21884
21915
|
);
|
|
21885
21916
|
return;
|
|
21886
21917
|
}
|
|
@@ -21890,6 +21921,10 @@ function validateReferenceExpression(expression, expected, environment, uri, ran
|
|
|
21890
21921
|
malformed("Reference id must be one non-empty string literal.");
|
|
21891
21922
|
return;
|
|
21892
21923
|
}
|
|
21924
|
+
if (hasProvenance && expression.args[provenanceIndex]?.kind !== "litBool") {
|
|
21925
|
+
malformed("Reference withProvenance must be a boolean literal.");
|
|
21926
|
+
return;
|
|
21927
|
+
}
|
|
21893
21928
|
if (!explicit && expected?.name !== "string") {
|
|
21894
21929
|
malformed("Reference(id: ...) requires an explicit generic target type.");
|
|
21895
21930
|
return;
|
|
@@ -25986,6 +26021,7 @@ function projectFallbackCompletions(analysis, document, position) {
|
|
|
25986
26021
|
const expectedTypeName = construction?.expectedTypeName ?? recoveredInitializerType;
|
|
25987
26022
|
const afterNew = expression && projectAfterNewAt(document, position);
|
|
25988
26023
|
const typePosition = projectTypePositionAt(analysis, document, position);
|
|
26024
|
+
const classBasePosition = projectClassBasePositionAt(document, position);
|
|
25989
26025
|
const keywords = expression ? afterNew ? [] : ["new"] : typePosition ? [] : [
|
|
25990
26026
|
"class",
|
|
25991
26027
|
"interface",
|
|
@@ -26024,6 +26060,11 @@ function projectFallbackCompletions(analysis, document, position) {
|
|
|
26024
26060
|
continue;
|
|
26025
26061
|
}
|
|
26026
26062
|
const isType = symbol.kind === "class" || symbol.kind === "interface" || symbol.kind === "enum" || symbol.kind === "genericParameter";
|
|
26063
|
+
if (classBasePosition && symbol.kind === "class") {
|
|
26064
|
+
const declaration = findTypeDeclaration(analysis, symbol.name);
|
|
26065
|
+
if (declaration?.kind === "class" && declaration.modifiers.includes("sealed"))
|
|
26066
|
+
continue;
|
|
26067
|
+
}
|
|
26027
26068
|
if (typePosition && !isType) continue;
|
|
26028
26069
|
if (expression) {
|
|
26029
26070
|
if (afterNew) {
|
|
@@ -26165,6 +26206,49 @@ function projectTypePositionAt(analysis, document, position) {
|
|
|
26165
26206
|
line
|
|
26166
26207
|
);
|
|
26167
26208
|
}
|
|
26209
|
+
function projectClassBasePositionAt(document, position) {
|
|
26210
|
+
const tokens = tokensBeforePosition(document.text, position);
|
|
26211
|
+
let classIndex = -1;
|
|
26212
|
+
for (let index = tokens.length - 1; index >= 0; index--) {
|
|
26213
|
+
const text = tokens[index]?.text;
|
|
26214
|
+
if (text === "class") {
|
|
26215
|
+
classIndex = index;
|
|
26216
|
+
break;
|
|
26217
|
+
}
|
|
26218
|
+
if (text === "{" || text === "}" || text === ";") return false;
|
|
26219
|
+
}
|
|
26220
|
+
if (classIndex < 0) return false;
|
|
26221
|
+
let angleDepth = 0;
|
|
26222
|
+
let parenthesisDepth = 0;
|
|
26223
|
+
let bracketDepth = 0;
|
|
26224
|
+
let colonIndex = -1;
|
|
26225
|
+
for (let index = classIndex + 1; index < tokens.length; index++) {
|
|
26226
|
+
const text = tokens[index]?.text;
|
|
26227
|
+
if (text === "<") angleDepth++;
|
|
26228
|
+
else if (text === ">") angleDepth = Math.max(0, angleDepth - 1);
|
|
26229
|
+
else if (text === "(") parenthesisDepth++;
|
|
26230
|
+
else if (text === ")") parenthesisDepth = Math.max(0, parenthesisDepth - 1);
|
|
26231
|
+
else if (text === "[") bracketDepth++;
|
|
26232
|
+
else if (text === "]") bracketDepth = Math.max(0, bracketDepth - 1);
|
|
26233
|
+
else if (text === ":" && angleDepth === 0 && parenthesisDepth === 0 && bracketDepth === 0) {
|
|
26234
|
+
colonIndex = index;
|
|
26235
|
+
break;
|
|
26236
|
+
} else if (text === "{" || text === "}" || text === ";") {
|
|
26237
|
+
return false;
|
|
26238
|
+
}
|
|
26239
|
+
}
|
|
26240
|
+
if (colonIndex < 0) return false;
|
|
26241
|
+
angleDepth = 0;
|
|
26242
|
+
for (let index = colonIndex + 1; index < tokens.length; index++) {
|
|
26243
|
+
const text = tokens[index]?.text;
|
|
26244
|
+
if (text === "<") angleDepth++;
|
|
26245
|
+
else if (text === ">") angleDepth = Math.max(0, angleDepth - 1);
|
|
26246
|
+
else if (angleDepth === 0 && (text === "(" || text === "{" || text === "}" || text === ";")) {
|
|
26247
|
+
return false;
|
|
26248
|
+
}
|
|
26249
|
+
}
|
|
26250
|
+
return true;
|
|
26251
|
+
}
|
|
26168
26252
|
function sourceTypeAt(document, position) {
|
|
26169
26253
|
const contains2 = (type) => {
|
|
26170
26254
|
for (const argument2 of type.typeArguments) {
|
|
@@ -26596,7 +26680,11 @@ function projectSignatureHelp(analysis, document, position) {
|
|
|
26596
26680
|
return signatureResult(`@${callee.text}`, parameters, activeParameter);
|
|
26597
26681
|
}
|
|
26598
26682
|
if (callee.text === "Reference") {
|
|
26599
|
-
return signatureResult(
|
|
26683
|
+
return signatureResult(
|
|
26684
|
+
"Reference",
|
|
26685
|
+
["symbol or id", "withProvenance = false"],
|
|
26686
|
+
activeParameter
|
|
26687
|
+
);
|
|
26600
26688
|
}
|
|
26601
26689
|
if (callee.text === "Pause") {
|
|
26602
26690
|
return signatureResult(
|
|
@@ -31482,6 +31570,7 @@ var init_document_contracts = __esm({
|
|
|
31482
31570
|
"extendsClassId",
|
|
31483
31571
|
"implementsInterfaceIds",
|
|
31484
31572
|
"hiddenInMemberSelector",
|
|
31573
|
+
"isSealed",
|
|
31485
31574
|
"isAbstract",
|
|
31486
31575
|
"system",
|
|
31487
31576
|
"allowedStorage",
|
|
@@ -31496,6 +31585,7 @@ var init_document_contracts = __esm({
|
|
|
31496
31585
|
derived: [],
|
|
31497
31586
|
volatile: ["projectId", "createdAt", "updatedAt"],
|
|
31498
31587
|
normalization: {
|
|
31588
|
+
isSealed: "falseIsAbsent",
|
|
31499
31589
|
targetMemberId: "nullIsAbsent",
|
|
31500
31590
|
schemaKeyOrder: "nullIsAbsent",
|
|
31501
31591
|
extendsClassId: "nullIsAbsent",
|
|
@@ -31764,6 +31854,10 @@ function normalizeDocumentFields(recordKind, value) {
|
|
|
31764
31854
|
}
|
|
31765
31855
|
continue;
|
|
31766
31856
|
}
|
|
31857
|
+
if (rule === "falseIsAbsent") {
|
|
31858
|
+
if (current === false) delete normalized[field];
|
|
31859
|
+
continue;
|
|
31860
|
+
}
|
|
31767
31861
|
const isFullMember = recordKind !== "member" || typeof normalized.extendsMemberId !== "string";
|
|
31768
31862
|
if (recordKind === "member" && (current === void 0 || current === null) && rule.startsWith("missingIs") && !memberDefaultApplies(field, normalized.kind)) {
|
|
31769
31863
|
if (current === null) delete normalized[field];
|
|
@@ -34361,6 +34455,7 @@ function schemaClassFromDocument(record3, context) {
|
|
|
34361
34455
|
name: requiredString(data, "name", record3),
|
|
34362
34456
|
...optionalDocsTextProperty(data.docsText, record3),
|
|
34363
34457
|
declarationModifier: data.isAbstract === true ? "abstract" : "concrete",
|
|
34458
|
+
...data.isSealed === void 0 ? {} : { isSealed: booleanValue(data.isSealed, record3, "isSealed") },
|
|
34364
34459
|
schema: requiredRecord(data, "schema", record3),
|
|
34365
34460
|
schemaKeyOrder: optionalStringArrayOrNull(data.schemaKeyOrder),
|
|
34366
34461
|
extendsClassId: optionalStringOrNull(data.extendsClassId),
|
|
@@ -34396,6 +34491,7 @@ function schemaClassToDocument(schemaClass2) {
|
|
|
34396
34491
|
implementsInterfaceIds: schemaClass2.implementsInterfaceIds,
|
|
34397
34492
|
hiddenInMemberSelector: schemaClass2.hiddenInMemberSelector,
|
|
34398
34493
|
isAbstract: schemaClass2.declarationModifier === "abstract",
|
|
34494
|
+
isSealed: schemaClass2.isSealed,
|
|
34399
34495
|
system: systemToDocument(schemaClass2.system),
|
|
34400
34496
|
allowedStorage: schemaClass2.allowedStorage,
|
|
34401
34497
|
allowedStorageKeys: schemaClass2.allowedStorageKeys,
|
|
@@ -35670,6 +35766,7 @@ function assertNeoSchemaClass(value, path) {
|
|
|
35670
35766
|
],
|
|
35671
35767
|
[
|
|
35672
35768
|
"docsText",
|
|
35769
|
+
"isSealed",
|
|
35673
35770
|
"constructorProjections",
|
|
35674
35771
|
"constructorIds",
|
|
35675
35772
|
"requiredConstructorId"
|
|
@@ -35685,6 +35782,9 @@ function assertNeoSchemaClass(value, path) {
|
|
|
35685
35782
|
);
|
|
35686
35783
|
stringRecord(type.schema, `${path}.schema`);
|
|
35687
35784
|
nullableStringArray(type.schemaKeyOrder, `${path}.schemaKeyOrder`);
|
|
35785
|
+
if (type.isSealed !== void 0) {
|
|
35786
|
+
booleanAt(type.isSealed, `${path}.isSealed`);
|
|
35787
|
+
}
|
|
35688
35788
|
nullableNonEmptyString(type.extendsClassId, `${path}.extendsClassId`);
|
|
35689
35789
|
stringArray(type.implementsInterfaceIds, `${path}.implementsInterfaceIds`);
|
|
35690
35790
|
booleanAt(type.hiddenInMemberSelector, `${path}.hiddenInMemberSelector`);
|
|
@@ -38455,7 +38555,9 @@ function isNSKeyOf(value) {
|
|
|
38455
38555
|
}
|
|
38456
38556
|
function isNSPointerReference(value) {
|
|
38457
38557
|
const v = value;
|
|
38458
|
-
|
|
38558
|
+
if (v?.type !== "reference" /* reference */) return false;
|
|
38559
|
+
if (typeof v.valueId !== "string") return false;
|
|
38560
|
+
return v.withProvenance === void 0 || typeof v.withProvenance === "boolean";
|
|
38459
38561
|
}
|
|
38460
38562
|
function isNSPointerVariable(value) {
|
|
38461
38563
|
const v = value;
|
|
@@ -39442,7 +39544,7 @@ function isNeoSchemaClassBase(value) {
|
|
|
39442
39544
|
const v = value;
|
|
39443
39545
|
return typeof v?.name === "string" && isValidDocsText(v.docsText) && isClassSchema(v?.schema) && isOptionalSchemaKeyOrder(v?.schemaKeyOrder) && (v.implementsInterfaceIds === void 0 || v.implementsInterfaceIds === null || Array.isArray(v.implementsInterfaceIds) && v.implementsInterfaceIds.every(
|
|
39444
39546
|
(interfaceId) => typeof interfaceId === "string" && interfaceId.length > 0
|
|
39445
|
-
)) && typeof v?.hiddenInMemberSelector === "boolean" && typeof v?.isAbstract === "boolean" && (v.allowedStorage === void 0 || v.allowedStorage === null || isEffectiveMemberStorage(v.allowedStorage)) && (v.allowedStorageKeys === void 0 || v.allowedStorageKeys === null || Array.isArray(v.allowedStorageKeys) && v.allowedStorageKeys.every((key) => typeof key === "string")) && (v.genericParams === void 0 || v.genericParams === null || Array.isArray(v.genericParams) && v.genericParams.every(isGenericParamDeclaration)) && (v.extendsGenericBindings === void 0 || v.extendsGenericBindings === null || isGenericBindingsRecord(v.extendsGenericBindings)) && (v.constructorProjections === void 0 || v.constructorProjections === null || Array.isArray(v.constructorProjections) && v.constructorProjections.every(isNeoClassConstructorProjection) && v.system?.kind === SystemProtectionKind.WorldAuthoring) && (v.constructorIds === void 0 || v.constructorIds === null || Array.isArray(v.constructorIds) && v.constructorIds.every(
|
|
39547
|
+
)) && typeof v?.hiddenInMemberSelector === "boolean" && typeof v?.isAbstract === "boolean" && (v.isSealed === void 0 || typeof v.isSealed === "boolean") && (v.allowedStorage === void 0 || v.allowedStorage === null || isEffectiveMemberStorage(v.allowedStorage)) && (v.allowedStorageKeys === void 0 || v.allowedStorageKeys === null || Array.isArray(v.allowedStorageKeys) && v.allowedStorageKeys.every((key) => typeof key === "string")) && (v.genericParams === void 0 || v.genericParams === null || Array.isArray(v.genericParams) && v.genericParams.every(isGenericParamDeclaration)) && (v.extendsGenericBindings === void 0 || v.extendsGenericBindings === null || isGenericBindingsRecord(v.extendsGenericBindings)) && (v.constructorProjections === void 0 || v.constructorProjections === null || Array.isArray(v.constructorProjections) && v.constructorProjections.every(isNeoClassConstructorProjection) && v.system?.kind === SystemProtectionKind.WorldAuthoring) && (v.constructorIds === void 0 || v.constructorIds === null || Array.isArray(v.constructorIds) && v.constructorIds.every(
|
|
39446
39548
|
(constructorId) => typeof constructorId === "string" && constructorId.length > 0
|
|
39447
39549
|
) && new Set(v.constructorIds).size === v.constructorIds.length) && (v.requiredConstructorId === void 0 || v.requiredConstructorId === null || typeof v.requiredConstructorId === "string" && v.requiredConstructorId.length > 0 && (v.constructorIds ?? []).length === 0) && (v.targetMemberId === void 0 || v.targetMemberId === null || typeof v.targetMemberId === "string" && v.targetMemberId.length > 0) && (v.system === void 0 || v.system === null || isSystemMetadata(v.system));
|
|
39448
39550
|
}
|
|
@@ -46642,7 +46744,7 @@ function emitClass(context, schemaClass2, memberIds) {
|
|
|
46642
46744
|
...schemaClass2.system ? [systemAnnotation2(schemaClass2.system)] : [],
|
|
46643
46745
|
...relationsAnnotations(context, schemaClass2)
|
|
46644
46746
|
];
|
|
46645
|
-
const modifier = schemaClass2.declarationModifier === "abstract" ? "abstract " : "";
|
|
46747
|
+
const modifier = schemaClass2.isSealed ? "sealed " : schemaClass2.declarationModifier === "abstract" ? "abstract " : "";
|
|
46646
46748
|
const generics = schemaClass2.genericParameters.length ? `<
|
|
46647
46749
|
${schemaClass2.genericParameters.map((parameter4) => {
|
|
46648
46750
|
const constraint = parameter4.constraint ? ` extends ${parameter4.constraint.kind === "class" ? required(
|
|
@@ -51301,9 +51403,14 @@ function collectFunctions(args) {
|
|
|
51301
51403
|
for (const action of node.actions) {
|
|
51302
51404
|
if (!isObjectRecord2(action) || action.type !== 0 || !isObjectRecord2(action.logic))
|
|
51303
51405
|
continue;
|
|
51304
|
-
if (action.logic.sourceInline === true || action.logic.type === 0 && isCompleteUIAction(action.logic.action))
|
|
51305
|
-
continue;
|
|
51306
51406
|
const useId = stringField(action, "id");
|
|
51407
|
+
if (action.logic.sourceInline === true) continue;
|
|
51408
|
+
if (action.logic.type === 0) {
|
|
51409
|
+
if (isCompleteUIAction(action.logic.action)) continue;
|
|
51410
|
+
throw new Error(
|
|
51411
|
+
`Dialogue action "${useId}" on actions node "${nodeId}" is incomplete. Complete or remove the action in Neo Compose before pulling.`
|
|
51412
|
+
);
|
|
51413
|
+
}
|
|
51307
51414
|
add(action.logic, `action:${useId}`, "void");
|
|
51308
51415
|
}
|
|
51309
51416
|
}
|
|
@@ -52989,6 +53096,7 @@ function lowerClass(context, declaration) {
|
|
|
52989
53096
|
name: declaration.name,
|
|
52990
53097
|
...declaration.docsText === void 0 ? {} : { docsText: declaration.docsText },
|
|
52991
53098
|
declarationModifier: declaration.modifiers.includes("abstract") ? "abstract" : "concrete",
|
|
53099
|
+
...declaration.modifiers.includes("sealed") ? { isSealed: true } : {},
|
|
52992
53100
|
schema,
|
|
52993
53101
|
schemaKeyOrder: persistedSchemaOrder,
|
|
52994
53102
|
extendsClassId,
|
|
@@ -54332,6 +54440,16 @@ function lowerExpressionValue(context, expression, declaredExpected, ownerClass,
|
|
|
54332
54440
|
if (expression.callee.kind === "ident" && expression.callee.name === "Reference") {
|
|
54333
54441
|
const index = expression.argumentNames?.findIndex((name) => name === "id") ?? -1;
|
|
54334
54442
|
if (index >= 0) {
|
|
54443
|
+
if (expression.argumentNames?.includes("withProvenance") === true) {
|
|
54444
|
+
throw new Error(
|
|
54445
|
+
`${path} writes Reference(id: ..., withProvenance: ...) as a persisted member value. Provenance-aware references are executable NeoScript values and must be used inside a function or delegate closure.`
|
|
54446
|
+
);
|
|
54447
|
+
}
|
|
54448
|
+
if (expression.args.length !== 1 || expression.argumentNames?.some((name) => name !== "id")) {
|
|
54449
|
+
throw new Error(
|
|
54450
|
+
`${path} writes Reference(id: ...) with unsupported arguments.`
|
|
54451
|
+
);
|
|
54452
|
+
}
|
|
54335
54453
|
const idArgument = expression.args[index];
|
|
54336
54454
|
if (idArgument?.kind === "litString") return idArgument.value;
|
|
54337
54455
|
throw new Error(
|
|
@@ -59911,13 +60029,14 @@ function evaluatorOwnershipDistances(rowId, indexes) {
|
|
|
59911
60029
|
indexes.ownershipDistancesByRowId.set(rowId, distances);
|
|
59912
60030
|
return distances;
|
|
59913
60031
|
}
|
|
59914
|
-
function resolveRuntimeReferenceRow(sourceValueId, ctx) {
|
|
60032
|
+
function resolveRuntimeReferenceRow(sourceValueId, ctx, withProvenance) {
|
|
59915
60033
|
const direct = evalValueById(
|
|
59916
60034
|
ctx,
|
|
59917
60035
|
sourceValueId,
|
|
59918
60036
|
ctx.__runtimeSessionValues,
|
|
59919
60037
|
ctx.__valueOverlay
|
|
59920
60038
|
);
|
|
60039
|
+
if (!withProvenance) return direct;
|
|
59921
60040
|
const receiver = trackedRowForValueReference(ctx.thisValue, ctx);
|
|
59922
60041
|
if (receiver === null) return direct;
|
|
59923
60042
|
const indexes = evaluatorIndexes(ctx);
|
|
@@ -62128,7 +62247,11 @@ function evalPointer(pointer, scope, ctx) {
|
|
|
62128
62247
|
return scope.get(pointer.variableId);
|
|
62129
62248
|
}
|
|
62130
62249
|
case "reference" /* reference */: {
|
|
62131
|
-
const row = resolveRuntimeReferenceRow(
|
|
62250
|
+
const row = resolveRuntimeReferenceRow(
|
|
62251
|
+
pointer.valueId,
|
|
62252
|
+
ctx,
|
|
62253
|
+
pointer.withProvenance === true
|
|
62254
|
+
);
|
|
62132
62255
|
if (!row) {
|
|
62133
62256
|
throw new NSGetterRuntimeError(
|
|
62134
62257
|
`Missing value reference: ${pointer.valueId}`
|
|
@@ -76955,6 +77078,19 @@ function readClass(value) {
|
|
|
76955
77078
|
throw new Error(`Class "${value.id}" schema value is invalid.`);
|
|
76956
77079
|
}
|
|
76957
77080
|
}
|
|
77081
|
+
if (value.isSealed !== void 0 && typeof value.isSealed !== "boolean") {
|
|
77082
|
+
throw new Error(`Class "${value.name}" (${value.id}) isSealed is invalid.`);
|
|
77083
|
+
}
|
|
77084
|
+
if (value.isAbstract !== void 0 && typeof value.isAbstract !== "boolean") {
|
|
77085
|
+
throw new Error(
|
|
77086
|
+
`Class "${value.name}" (${value.id}) isAbstract is invalid.`
|
|
77087
|
+
);
|
|
77088
|
+
}
|
|
77089
|
+
if (value.isAbstract === true && value.isSealed === true) {
|
|
77090
|
+
throw new Error(
|
|
77091
|
+
`Class "${value.name}" (${value.id}) cannot be both abstract and sealed.`
|
|
77092
|
+
);
|
|
77093
|
+
}
|
|
76958
77094
|
if (value.implementsInterfaceIds != null && (!Array.isArray(value.implementsInterfaceIds) || !value.implementsInterfaceIds.every((id2) => typeof id2 === "string"))) {
|
|
76959
77095
|
throw new Error(`Class "${value.id}" implementsInterfaceIds is invalid.`);
|
|
76960
77096
|
}
|
|
@@ -77227,6 +77363,14 @@ function assertProjectInterfaceDocumentValid(view, options = {}) {
|
|
|
77227
77363
|
const classesById = new Map(
|
|
77228
77364
|
classes.map((schemaClass2) => [schemaClass2.id, schemaClass2])
|
|
77229
77365
|
);
|
|
77366
|
+
for (const schemaClass2 of classes) {
|
|
77367
|
+
if (schemaClass2.extendsClassId === void 0) continue;
|
|
77368
|
+
const baseClass = classesById.get(schemaClass2.extendsClassId);
|
|
77369
|
+
if (baseClass?.isSealed !== true) continue;
|
|
77370
|
+
throw new Error(
|
|
77371
|
+
`Class "${schemaClass2.name}" (${schemaClass2.id}) cannot extend sealed class "${baseClass.name}" (${baseClass.id}).`
|
|
77372
|
+
);
|
|
77373
|
+
}
|
|
77230
77374
|
const genericParamConstraintsById = new Map(
|
|
77231
77375
|
classes.flatMap(
|
|
77232
77376
|
(schemaClass2) => (schemaClass2.genericParams ?? []).map(
|
|
@@ -104469,8 +104613,8 @@ var init_registry2 = __esm({
|
|
|
104469
104613
|
"use strict";
|
|
104470
104614
|
PROJECT_SCHEMA_CONTRACT = Object.freeze({
|
|
104471
104615
|
formatVersion: 3,
|
|
104472
|
-
contractVersion: "3.
|
|
104473
|
-
cliVersion: "0.
|
|
104616
|
+
contractVersion: "3.11",
|
|
104617
|
+
cliVersion: "0.30.0",
|
|
104474
104618
|
projectFileUploadBatchSize: 32,
|
|
104475
104619
|
documentRecords: {
|
|
104476
104620
|
member: {
|
|
@@ -104565,6 +104709,7 @@ var init_registry2 = __esm({
|
|
|
104565
104709
|
"extendsClassId",
|
|
104566
104710
|
"implementsInterfaceIds",
|
|
104567
104711
|
"hiddenInMemberSelector",
|
|
104712
|
+
"isSealed",
|
|
104568
104713
|
"isAbstract",
|
|
104569
104714
|
"system",
|
|
104570
104715
|
"allowedStorage",
|
|
@@ -104579,6 +104724,7 @@ var init_registry2 = __esm({
|
|
|
104579
104724
|
derived: [],
|
|
104580
104725
|
volatile: ["projectId", "createdAt", "updatedAt"],
|
|
104581
104726
|
normalization: {
|
|
104727
|
+
isSealed: "falseIsAbsent",
|
|
104582
104728
|
targetMemberId: "nullIsAbsent",
|
|
104583
104729
|
schemaKeyOrder: "nullIsAbsent",
|
|
104584
104730
|
extendsClassId: "nullIsAbsent",
|
|
@@ -111036,7 +111182,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
|
|
|
111036
111182
|
async function main() {
|
|
111037
111183
|
const args = parseArgs(process.argv.slice(2));
|
|
111038
111184
|
if (args.command === "--version") {
|
|
111039
|
-
console.log("0.
|
|
111185
|
+
console.log("0.30.0");
|
|
111040
111186
|
return;
|
|
111041
111187
|
}
|
|
111042
111188
|
if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
|
package/package.json
CHANGED
|
@@ -109,12 +109,14 @@ override replaces the whole leaf.
|
|
|
109
109
|
Frame overrides may change `Enabled`, `FlipX`, and `SortingOrder`; they may
|
|
110
110
|
not add, remove, or reorder `Children`.
|
|
111
111
|
|
|
112
|
-
Address direct children through selector delegates.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
112
|
+
Address direct children through selector delegates. Prefer the project's own
|
|
113
|
+
stable identity contract, such as an immutable `Name`, slug, or semantic
|
|
114
|
+
position. When a selector means one specific authored child slot whose
|
|
115
|
+
placement clones carry `sourceValueId`, return
|
|
116
|
+
`Reference<T>(id: "<authored-child-id>", withProvenance: true)`. The flag is
|
|
117
|
+
opt-in and defaults to `false`; without it the ID is matched exactly. Missing
|
|
118
|
+
optional child slots are skipped with diagnostics when the authored slot is
|
|
119
|
+
absent, while stale pre-provenance placements may still fail closed.
|
|
118
120
|
|
|
119
121
|
A placement row carries its `assetClassId` binding and optional `assetValueId`
|
|
120
122
|
override beside its schema keys. They are row provenance, not declared schema:
|
|
@@ -176,11 +178,11 @@ class LegPart : NeoObject {
|
|
|
176
178
|
```
|
|
177
179
|
|
|
178
180
|
Selectors may also be compatible inline closures. Prefer a named function when
|
|
179
|
-
the identity rule is reused or deserves an explicit failure message.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
pulled rows.
|
|
181
|
+
the identity rule is reused or deserves an explicit failure message. An exact
|
|
182
|
+
`Reference<T>(id: "...")` recreates the row-ID coupling selectors were
|
|
183
|
+
introduced to remove; use `withProvenance: true` only for a deliberately
|
|
184
|
+
authored slot identity. For new track rows, omit `@id` and let the successful
|
|
185
|
+
push assign it. Preserve IDs already present on pulled rows.
|
|
184
186
|
|
|
185
187
|
A selector argument such as `this.SelectPants` is evaluated in the lexical
|
|
186
188
|
scope of the class that declares the clip. Here `this` is the `LegPart`, not
|
|
@@ -83,7 +83,7 @@ wrappers.
|
|
|
83
83
|
The marker near the top of `SKILL.md` must exactly match the package version:
|
|
84
84
|
|
|
85
85
|
```html
|
|
86
|
-
<!-- reviewed-through-cli: 0.
|
|
86
|
+
<!-- reviewed-through-cli: 0.30.0 -->
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
The quoted version above is checked too, so this instruction cannot go stale
|
|
@@ -47,8 +47,17 @@ abstract class Item<TContext extends SomeClass> : INamed {
|
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
Use ordinary `public`, `protected`, `private`, `static`, `virtual`, `abstract`,
|
|
50
|
-
and `override` semantics.
|
|
51
|
-
|
|
50
|
+
`sealed`, and `override` semantics. A sealed class is an instantiable leaf:
|
|
51
|
+
it may extend another class but cannot be extended, cannot also be abstract,
|
|
52
|
+
and can only be sealed while it has no subclasses. Interfaces are non-generic.
|
|
53
|
+
Do not put `@id` on inferred list/dictionary type entries or on derived
|
|
54
|
+
structural descriptors.
|
|
55
|
+
|
|
56
|
+
```neo
|
|
57
|
+
sealed class DirtTile : PlaceableTile {
|
|
58
|
+
public bool IsWet = false;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
52
61
|
|
|
53
62
|
A bodyless function must be `abstract`, `native`, or an interface contract.
|
|
54
63
|
`async` alone does not make it valid.
|
|
@@ -83,6 +83,7 @@ supports:
|
|
|
83
83
|
Reference(Assets.Capitol)
|
|
84
84
|
Reference(root.Assets.Cosmetics.Pants)
|
|
85
85
|
Reference<Outpost>(id: "capitol-value-id")
|
|
86
|
+
Reference<NeoSpriteObject>(id: "authored-child-id", withProvenance: true)
|
|
86
87
|
Reference<PantsAsset>(key: "pants.long")
|
|
87
88
|
Reference<PantsAsset>(key: "pants.long", index: Slug)
|
|
88
89
|
Reference<Dialogue>(id: "capitol-dialogue-id")
|
|
@@ -95,14 +96,20 @@ Reference<Dialogue>(id: "capitol-dialogue-id")
|
|
|
95
96
|
- Use the generic `id:` form when the ID is the only target information. A
|
|
96
97
|
`Reference` call whose argument is neither `id:` nor `key:` is rejected by
|
|
97
98
|
name rather than read as an ID.
|
|
99
|
+
- Inside executable NeoScript, add `withProvenance: true` when the ID names an
|
|
100
|
+
authored row and the reference must select its nearest `sourceValueId` clone
|
|
101
|
+
in the lexical receiver's ownership graph. The parameter defaults to `false`;
|
|
102
|
+
absent or explicit `false` keeps exact-ID behavior. This option is not a
|
|
103
|
+
persisted member-default form.
|
|
98
104
|
- Both forms are legal in a member's declaration default, not only inside a
|
|
99
105
|
value graph. A key written there resolves after every pass has run, so it may
|
|
100
106
|
name a row the same push creates.
|
|
101
107
|
- Keep genuine structural references ID-based when their collection has no
|
|
102
108
|
stable symbol, path, or key surface. Animation track and frame-override
|
|
103
109
|
targets are selectors, not structural references: encode a stable
|
|
104
|
-
project-owned identity such as `Name` or slug
|
|
105
|
-
|
|
110
|
+
project-owned identity such as `Name` or slug. The exception is an authored
|
|
111
|
+
child slot whose placement clones retain `sourceValueId`: select that slot
|
|
112
|
+
with `Reference<T>(id: "...", withProvenance: true)`.
|
|
106
113
|
|
|
107
114
|
Declaration and file order do not affect resolution within one push. Cyclic
|
|
108
115
|
identity references are legal because they resolve IDs rather than evaluate a
|