@neocompose/cli 0.75.1 → 0.75.2

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,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.75.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 45f828c: Preserve generic constructor arguments and nested reference types when exporting member defaults, so Neowyn animation schemas compile without changing record identities.
8
+ - 60935a5: Report invalid object-initializer semicolons consistently in the editor and CLI, with the authored file position and a comma-separator hint.
9
+ - 869b95b: Ignore formatting-only changes in stored initializer recipes, computed member
10
+ defaults, and inline constructor delegates. Preserve their existing compiled
11
+ bodies while continuing to detect code and string-literal content edits.
12
+ - 267ccd2: Keep valid root value graphs available when another root construction fails, and show required-member declaration locations in CLI diagnostics.
13
+
3
14
  ## 0.75.1
4
15
 
5
16
  ### Patch Changes
package/dist/neo.mjs CHANGED
@@ -18773,6 +18773,12 @@ var init_executable_parser = __esm({
18773
18773
  value: this.parseExpr(),
18774
18774
  pos: at(name)
18775
18775
  });
18776
+ if (this.peek().text === ";") {
18777
+ throw new CompileError(
18778
+ "Object initializer assignments use commas, not semicolons. Replace ';' with ',' or omit it after the last assignment; the enclosing declaration still ends with ';'.",
18779
+ at(this.peek())
18780
+ );
18781
+ }
18776
18782
  if (!this.eat("punctuation", ",")) break;
18777
18783
  if (this.peek().kind === "punctuation" && this.peek().text === "}") break;
18778
18784
  }
@@ -22534,8 +22540,13 @@ var init_source_diagnostics = __esm({
22534
22540
  "neoflow-incomplete-draft"
22535
22541
  ]);
22536
22542
  SchemaSourceError = class extends Error {
22537
- constructor(detail, file, line, column, code, severity = "error", range2) {
22538
- super(`${file}:${line}:${column} \u2014 ${detail}`);
22543
+ constructor(detail, file, line, column, code, severity = "error", range2, relatedInformation) {
22544
+ super(
22545
+ `${file}:${line}:${column} \u2014 ${detail}${relatedInformation?.map(
22546
+ ({ location: location3, message }) => `
22547
+ ${location3.uri}:${location3.range.start.line + 1}:${location3.range.start.character + 1} \u2014 ${message}`
22548
+ ).join("") ?? ""}`
22549
+ );
22539
22550
  this.detail = detail;
22540
22551
  this.file = file;
22541
22552
  this.line = line;
@@ -22543,6 +22554,7 @@ var init_source_diagnostics = __esm({
22543
22554
  this.code = code;
22544
22555
  this.severity = severity;
22545
22556
  this.range = range2;
22557
+ this.relatedInformation = relatedInformation;
22546
22558
  this.name = "SchemaSourceError";
22547
22559
  }
22548
22560
  detail;
@@ -22552,6 +22564,7 @@ var init_source_diagnostics = __esm({
22552
22564
  code;
22553
22565
  severity;
22554
22566
  range;
22567
+ relatedInformation;
22555
22568
  };
22556
22569
  }
22557
22570
  });
@@ -43398,7 +43411,11 @@ function validateDeclaration(uri, declaration, documentKind, environment, diagno
43398
43411
  environment,
43399
43412
  declaration.initializer.range,
43400
43413
  diagnostics,
43401
- anchorOf(declaration.initializer)
43414
+ anchorOf(declaration.initializer),
43415
+ DECLARATION_BODY_GLOBAL_TYPES.has(declaration.type.name) || isNeoVariantFamilyTypeName(declaration.type.name) ? void 0 : {
43416
+ code: "invalid-project-global-initializer",
43417
+ label: `Global '${declaration.name}' initializer`
43418
+ }
43402
43419
  );
43403
43420
  return;
43404
43421
  }
@@ -43753,6 +43770,10 @@ function validateProjectRootGlobal(uri, declaration, environment, diagnostics) {
43753
43770
  {
43754
43771
  text: actual.initializer,
43755
43772
  start: slotPosition(declaration.initializer.range.start, actual.start)
43773
+ },
43774
+ {
43775
+ code: "invalid-project-global-initializer",
43776
+ label: `Root '${expected.name}' initializer`
43756
43777
  }
43757
43778
  );
43758
43779
  }
@@ -46105,7 +46126,7 @@ function classDerivesFromName(name, ancestorName, environment, seen = /* @__PURE
46105
46126
  return classDerivesFromName(base.name, ancestorName, environment, seen);
46106
46127
  });
46107
46128
  }
46108
- var NEO_VARIANT_FOLDER_SETTING, primitiveType, LIST_COLUMN_INHERITANCE_KEY, SEMANTIC_PRIMITIVE_TYPE_NAMES;
46129
+ var NEO_VARIANT_FOLDER_SETTING, primitiveType, LIST_COLUMN_INHERITANCE_KEY, DECLARATION_BODY_GLOBAL_TYPES, SEMANTIC_PRIMITIVE_TYPE_NAMES;
46109
46130
  var init_project_source_semantics = __esm({
46110
46131
  "../packages/neoscript-language/src/project-source-semantics.ts"() {
46111
46132
  "use strict";
@@ -46129,6 +46150,12 @@ var init_project_source_semantics = __esm({
46129
46150
  NEO_VARIANT_FOLDER_SETTING = "folder";
46130
46151
  primitiveType = (name) => ({ name });
46131
46152
  LIST_COLUMN_INHERITANCE_KEY = "__other__";
46153
+ DECLARATION_BODY_GLOBAL_TYPES = /* @__PURE__ */ new Set([
46154
+ NEOSCRIPT_IMAGE_REGISTRY_TYPE,
46155
+ NEOSCRIPT_AUDIO_CLIP_REGISTRY_TYPE,
46156
+ "NeoPriorityGroup",
46157
+ "NeoDialogueGroup"
46158
+ ]);
46132
46159
  SEMANTIC_PRIMITIVE_TYPE_NAMES = /* @__PURE__ */ new Set([
46133
46160
  "bool",
46134
46161
  "int",
@@ -51821,7 +51848,7 @@ var init_registry = __esm({
51821
51848
  "../packages/manifest/schema-contract/registry.mjs"() {
51822
51849
  "use strict";
51823
51850
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
51824
- cliVersion: "0.75.1",
51851
+ cliVersion: "0.75.2",
51825
51852
  projectFileUploadBatchSize: 32,
51826
51853
  documentRecords: {
51827
51854
  member: {
@@ -63657,6 +63684,7 @@ var init_variant_selection_source = __esm({
63657
63684
  function emitProjectSourcesV4(manifest, options = {}) {
63658
63685
  const context = {
63659
63686
  manifest,
63687
+ classBindings: /* @__PURE__ */ new Map(),
63660
63688
  classes: new Map(manifest.classes.map((entry) => [entry.id, entry])),
63661
63689
  interfaces: new Map(manifest.interfaces.map((entry) => [entry.id, entry])),
63662
63690
  enums: new Map(manifest.enums.map((entry) => [entry.id, entry])),
@@ -64373,7 +64401,6 @@ function renderEntryType(context, entryMemberId, label, enclosingClassId, elemen
64373
64401
  }
64374
64402
  function renderMemberType(context, member, enclosingClassId, elementSettings = false) {
64375
64403
  const nullable2 = !member.required;
64376
- const closedGenerics = enclosingClassId === void 0 ? void 0 : classGenericMemberBindings(context.classes, enclosingClassId);
64377
64404
  let value;
64378
64405
  switch (member.kind) {
64379
64406
  case "null":
@@ -64441,11 +64468,11 @@ function renderMemberType(context, member, enclosingClassId, elementSettings = f
64441
64468
  if (binding.kind === "member") {
64442
64469
  boundMemberId = binding.memberId;
64443
64470
  } else {
64444
- const closed = closedGenerics?.get(binding.genericParamId);
64445
- if (closed === void 0) {
64446
- return genericName(context, binding.genericParamId);
64447
- }
64448
- boundMemberId = closed;
64471
+ return renderGenericType(
64472
+ context,
64473
+ binding.genericParamId,
64474
+ enclosingClassId
64475
+ );
64449
64476
  }
64450
64477
  return renderMemberType(
64451
64478
  context,
@@ -64478,7 +64505,11 @@ function renderMemberType(context, member, enclosingClassId, elementSettings = f
64478
64505
  value = member.multiselect ? "Set<NeoDialogueReference>" : "NeoDialogueReference";
64479
64506
  break;
64480
64507
  case "generic":
64481
- value = genericName(context, member.genericParamId);
64508
+ value = renderGenericType(
64509
+ context,
64510
+ member.genericParamId,
64511
+ enclosingClassId
64512
+ );
64482
64513
  break;
64483
64514
  case "computed":
64484
64515
  value = renderType(context, member.returnType);
@@ -64705,7 +64736,71 @@ function dictionaryOptions(context, enumId) {
64705
64736
  context.dictionaryKeyOptions.set(enumId, result);
64706
64737
  return result;
64707
64738
  }
64739
+ function cachedClassBindings(context, classId) {
64740
+ let bindings = context.classBindings.get(classId);
64741
+ if (bindings === void 0) {
64742
+ bindings = classGenericBindings(context.classes, classId);
64743
+ context.classBindings.set(classId, bindings);
64744
+ }
64745
+ return bindings;
64746
+ }
64747
+ function renderGenericType(context, parameterId, enclosingClassId) {
64748
+ const bindings = context.genericBindings ?? (enclosingClassId === void 0 ? void 0 : cachedClassBindings(context, enclosingClassId));
64749
+ const binding = resolveGenericBinding(parameterId, bindings);
64750
+ return binding.kind === "member" ? renderMemberType(
64751
+ context,
64752
+ required(context.members, binding.memberId, "generic binding"),
64753
+ enclosingClassId
64754
+ ) : genericName(context, binding.genericParamId);
64755
+ }
64756
+ function resolveGenericBinding(parameterId, bindings) {
64757
+ const visited = /* @__PURE__ */ new Set();
64758
+ let cursor = parameterId;
64759
+ while (!visited.has(cursor)) {
64760
+ visited.add(cursor);
64761
+ const binding = bindings?.get(cursor);
64762
+ if (binding === void 0) break;
64763
+ if (binding.kind === "member") return binding;
64764
+ cursor = binding.genericParamId;
64765
+ }
64766
+ return { kind: "generic", genericParamId: cursor };
64767
+ }
64768
+ function constructionContext(context, member, enclosingClassId) {
64769
+ const outer = context.genericBindings ?? (enclosingClassId === void 0 ? void 0 : cachedClassBindings(context, enclosingClassId));
64770
+ const inherited = cachedClassBindings(context, member.classId);
64771
+ const arguments_ = Object.entries(member.classArguments);
64772
+ if (inherited.size === 0 && arguments_.length === 0) return context;
64773
+ const bindings = new Map(outer);
64774
+ for (const [parameter4, binding] of inherited)
64775
+ bindings.set(parameter4, binding);
64776
+ for (const [parameter4, binding] of arguments_) {
64777
+ bindings.set(
64778
+ parameter4,
64779
+ binding.kind === "generic" ? resolveGenericBinding(binding.genericParamId, outer) : binding
64780
+ );
64781
+ }
64782
+ return { ...context, genericBindings: bindings };
64783
+ }
64708
64784
  function renderDefaultValue(context, member, wrapper, enclosingClassId) {
64785
+ if (member.kind === "generic" && context.genericBindings) {
64786
+ const bindingId = terminalGenericBindingMemberId(
64787
+ member.genericParamId,
64788
+ context.genericBindings
64789
+ );
64790
+ if (bindingId !== void 0) {
64791
+ const binding = required(
64792
+ context.members,
64793
+ bindingId,
64794
+ "generic default binding"
64795
+ );
64796
+ const positioned = structuredLeafPartialChild(
64797
+ member.partial === true,
64798
+ binding
64799
+ );
64800
+ const resolved = positioned.kind === "class" && member.partial === true ? { ...positioned, partial: true } : positioned;
64801
+ return renderDefaultValue(context, resolved, wrapper, enclosingClassId);
64802
+ }
64803
+ }
64709
64804
  const value = wrapper.value;
64710
64805
  if (value === null) return "null";
64711
64806
  if (member.kind === "decimal" && typeof value === "string") return value;
@@ -64741,16 +64836,21 @@ function renderDefaultValue(context, member, wrapper, enclosingClassId) {
64741
64836
  }
64742
64837
  if (member.kind === "class" && isObject3(value)) {
64743
64838
  const target = required(context.classes, member.classId, "class");
64839
+ const nestedContext = constructionContext(
64840
+ context,
64841
+ member,
64842
+ enclosingClassId
64843
+ );
64744
64844
  const assignments = Object.entries(value).map(([key, item]) => {
64745
64845
  const childId = target.schema[key];
64746
64846
  const child2 = childId ? context.members.get(childId) : void 0;
64747
64847
  const positioned = child2 ? structuredLeafPartialChild(member.partial === true, child2) : void 0;
64748
- return `${key} = ${positioned ? renderNestedValue(context, positioned, item, enclosingClassId) : jsonValue2(item)}`;
64848
+ return `${key} = ${positioned ? renderNestedValue(nestedContext, positioned, item, enclosingClassId) : jsonValue2(item)}`;
64749
64849
  });
64750
64850
  if (member.partial === true) {
64751
64851
  return `new { ${assignments.join(", ")} }`;
64752
64852
  }
64753
- return `new ${target.name}${assignments.length ? ` { ${assignments.join(", ")} }` : "()"}`;
64853
+ return `new ${renderMemberType(context, member, enclosingClassId).replace(/\?$/u, "")}${assignments.length ? ` { ${assignments.join(", ")} }` : "()"}`;
64754
64854
  }
64755
64855
  if (member.kind === "action") {
64756
64856
  return renderActionListeners(context, member, value);
@@ -66138,45 +66238,30 @@ function commentEndIndex(source, index) {
66138
66238
  return null;
66139
66239
  }
66140
66240
  function stripInitializerComments(source) {
66141
- const lines = [{ parts: [], removedComment: false }];
66142
- let quote6 = null;
66143
- let segmentStart = 0;
66144
- const specialCharacters = /["'\\/\n]/g;
66145
- for (let token = specialCharacters.exec(source); token !== null; token = specialCharacters.exec(source)) {
66146
- const index = token.index;
66147
- const character = token[0];
66148
- if (quote6 !== null) {
66149
- if (character === "\\") specialCharacters.lastIndex = index + 2;
66150
- else if (character === quote6) quote6 = null;
66151
- continue;
66152
- }
66153
- const afterComment = commentEndIndex(source, index);
66154
- if (afterComment !== null) {
66155
- const line = lines[lines.length - 1];
66156
- if (segmentStart < index) {
66157
- line.parts.push(source.slice(segmentStart, index));
66158
- }
66159
- line.removedComment = true;
66160
- specialCharacters.lastIndex = afterComment;
66161
- segmentStart = afterComment;
66162
- continue;
66163
- }
66164
- if (character === "\n") {
66165
- if (segmentStart < index) {
66166
- lines[lines.length - 1].parts.push(source.slice(segmentStart, index));
66167
- }
66168
- lines.push({ parts: [], removedComment: false });
66169
- segmentStart = index + 1;
66170
- continue;
66241
+ const lexed = lex(source);
66242
+ const commentLines = /* @__PURE__ */ new Set();
66243
+ const parts = [];
66244
+ let cursor = 0;
66245
+ for (const token of lexed.tokens) {
66246
+ if (token.kind !== "comment") continue;
66247
+ parts.push(source.slice(cursor, token.start));
66248
+ parts.push(token.raw.replace(/[^\r\n]/g, ""));
66249
+ cursor = token.end;
66250
+ for (let line = token.range.start.line; line <= token.range.end.line; line++) {
66251
+ commentLines.add(line);
66171
66252
  }
66172
- if (character === '"' || character === "'") quote6 = character;
66173
66253
  }
66174
- if (segmentStart < source.length) {
66175
- lines[lines.length - 1].parts.push(source.slice(segmentStart));
66254
+ parts.push(source.slice(cursor));
66255
+ const literalNewlines = /* @__PURE__ */ new Set();
66256
+ for (const token of lexed.roots) {
66257
+ if (token.kind !== "string") continue;
66258
+ for (let line = token.range.start.line; line < token.range.end.line; line++) {
66259
+ literalNewlines.add(line);
66260
+ }
66176
66261
  }
66177
- return lines.map((line) => ({
66178
- text: line.parts.join("").trimEnd(),
66179
- removedComment: line.removedComment
66262
+ return parts.join("").split("\n").map((line, index) => ({
66263
+ text: literalNewlines.has(index) ? line : line.trimEnd(),
66264
+ removedComment: commentLines.has(index)
66180
66265
  })).filter((line) => !line.removedComment || line.text.trim().length > 0).map((line) => line.text).join("\n").trim();
66181
66266
  }
66182
66267
  function normalizeBodySource(source) {
@@ -66222,7 +66307,8 @@ function normalizeInitializerSourceUncached(source) {
66222
66307
  const stripped = stripInitializerComments(source);
66223
66308
  const lines = stripped.split("\n");
66224
66309
  if (lines.length < 2) return stripped;
66225
- const continuations = lines.slice(1).filter((line) => line.length > 0);
66310
+ const literalLines = neoScriptLiteralContinuationLines(stripped);
66311
+ const continuations = lines.slice(1).filter((line, index) => !literalLines.has(index + 1) && line.length > 0);
66226
66312
  if (continuations.length === 0) return stripped;
66227
66313
  const common = Math.min(
66228
66314
  ...continuations.map((line) => line.length - line.trimStart().length)
@@ -66230,7 +66316,9 @@ function normalizeInitializerSourceUncached(source) {
66230
66316
  if (common === 0) return stripped;
66231
66317
  return [
66232
66318
  lines[0],
66233
- ...lines.slice(1).map((line) => line.length === 0 ? line : line.slice(common))
66319
+ ...lines.slice(1).map(
66320
+ (line, index) => literalLines.has(index + 1) || line.length === 0 ? line : line.slice(common)
66321
+ )
66234
66322
  ].join("\n");
66235
66323
  }
66236
66324
  function constructorExpressionSlice(source) {
@@ -66362,7 +66450,10 @@ function matchesStoredInitializer(defaultValue, source) {
66362
66450
  if (!isObjectRecord2(defaultValue) || !isObjectRecord2(defaultValue.init))
66363
66451
  return false;
66364
66452
  const code = defaultValue.init.code;
66365
- return typeof code === "string" && normalizeInitializerSource(code) === normalizeInitializerSource(source);
66453
+ return typeof code === "string" && sameNeoScriptTokens(
66454
+ normalizeInitializerSource(code),
66455
+ normalizeInitializerSource(source)
66456
+ );
66366
66457
  }
66367
66458
  var normalizedInitializers, normalizedInitializerCharacters, MAX_NORMALIZED_INITIALIZER_CHARACTERS;
66368
66459
  var init_init_source = __esm({
@@ -126720,7 +126811,8 @@ function mergedOverStoredRow(base, lowered) {
126720
126811
  if ("action" in argument2) continue;
126721
126812
  const stored = storedArgs[key];
126722
126813
  if (!isObjectRecord2(stored)) continue;
126723
- if (stored.code !== argument2.code) continue;
126814
+ if (typeof stored.code !== "string") continue;
126815
+ if (!sameNeoScriptTokens(stored.code, argument2.code)) continue;
126724
126816
  if (!("action" in stored)) continue;
126725
126817
  preserved ??= { ...loweredArgs };
126726
126818
  preserved[key] = stored;
@@ -127065,6 +127157,27 @@ function drainValueReferenceObligationsV4(state, manifest, options) {
127065
127157
  ...collectionValueObligations(manifest),
127066
127158
  ...options.registry.referenceObligations
127067
127159
  ]) {
127160
+ if (obligation.kind === "reference" && options.unavailableReferenceMemberIds?.has(obligation.memberId))
127161
+ continue;
127162
+ if (options.unavailableValueIds?.size) {
127163
+ let targetId;
127164
+ switch (obligation.kind) {
127165
+ case "collectionValue":
127166
+ targetId = obligation.collectionValueId;
127167
+ break;
127168
+ case "constructorProjection":
127169
+ targetId = obligation.targetId;
127170
+ break;
127171
+ case "reference":
127172
+ if (obligation.target.spelling === "id")
127173
+ targetId = obligation.target.id;
127174
+ else if (obligation.target.spelling === "symbol")
127175
+ targetId = obligation.target.resolved.id;
127176
+ break;
127177
+ }
127178
+ if (targetId !== void 0 && options.unavailableValueIds.has(targetId))
127179
+ continue;
127180
+ }
127068
127181
  const failure = settleReferenceObligation(
127069
127182
  context,
127070
127183
  obligation,
@@ -131707,6 +131820,10 @@ function annotationId2(annotations) {
131707
131820
  }
131708
131821
  function addReconstructed(context, recordKind, recordId, fileFields, source, options = {}) {
131709
131822
  const key = `${recordKind}:${recordId}`;
131823
+ const base = context.state[key]?.data;
131824
+ if (recordKind === "value" && isObjectRecord2(base) && isObjectRecord2(fileFields.init) && typeof fileFields.init.code === "string" && matchesStoredInitializer(base, fileFields.init.code)) {
131825
+ fileFields = { ...fileFields, init: base.init };
131826
+ }
131710
131827
  const existing = context.reconstructed.get(key);
131711
131828
  const line = source.range.start.line + 1;
131712
131829
  if (existing !== void 0) {
@@ -134006,6 +134123,7 @@ var INFERRED_GENERIC_CLASS_PREFIX, MEMBER_KIND_DICTIONARY2, MEMBER_KIND_LIST2, M
134006
134123
  var init_value_sources = __esm({
134007
134124
  "src/project-source/value-sources.ts"() {
134008
134125
  "use strict";
134126
+ init_lexer();
134009
134127
  init_codecs();
134010
134128
  init_reference_target_types();
134011
134129
  init_listener_template_graph();
@@ -136920,26 +137038,94 @@ ${declarations.join("\n\n")}
136920
137038
  }
136921
137039
  function lowerProjectRootSourceV4(state, analysis, manifest, options = {}) {
136922
137040
  const registry = options.registry ?? createValueLowerRegistryV4();
137041
+ const unavailableSlots = /* @__PURE__ */ new Set();
137042
+ const unavailableValueIds = /* @__PURE__ */ new Set();
136923
137043
  const validated = validateProjectRootEnvelopeV4(
136924
137044
  state,
136925
137045
  analysis,
136926
137046
  registry.parsedInitializers
136927
137047
  );
136928
137048
  if (validated === null) {
136929
- return { records: [], memberValueIds: /* @__PURE__ */ new Map(), seeds: /* @__PURE__ */ new Map() };
137049
+ return {
137050
+ records: [],
137051
+ memberValueIds: /* @__PURE__ */ new Map(),
137052
+ seeds: /* @__PURE__ */ new Map(),
137053
+ unavailableSlots,
137054
+ unavailableValueIds
137055
+ };
136930
137056
  }
136931
137057
  const { expected, global, slots } = validated;
136932
- const bindings = expected.map((root, index) => ({
136933
- memberId: root.memberId,
136934
- purpose: "stored",
136935
- initializer: slots[index].initializer,
136936
- source: global.source,
136937
- label: `root.${root.slot.name}`
136938
- }));
136939
- return lowerStoredValueBindingsV4(state, manifest, bindings, {
136940
- analysis,
136941
- registry
137058
+ const initializerStart = global.initializerSource?.range.start;
137059
+ const errors = analysis.diagnostics.filter(
137060
+ (diagnostic) => diagnostic.severity === "error" && diagnostic.uri === global.source.uri
137061
+ );
137062
+ const bindings = expected.flatMap((root, index) => {
137063
+ const slot = slots[index];
137064
+ if (errors.length > 0 && initializerStart !== void 0) {
137065
+ const line = initializerStart.line + slot.start.line - 1;
137066
+ const character = slot.start.column - 1 + (slot.start.line === 1 ? initializerStart.character : 0);
137067
+ const lines = slot.initializer.split("\n");
137068
+ const endLine = line + lines.length - 1;
137069
+ const endCharacter = lines.length === 1 ? character + slot.initializer.length : lines[lines.length - 1].length;
137070
+ if (errors.some(
137071
+ ({ range: { start } }) => (start.line > line || start.line === line && start.character >= character) && (start.line < endLine || start.line === endLine && start.character < endCharacter)
137072
+ )) {
137073
+ unavailableSlots.add(slot.name);
137074
+ collectAuthoredValueIds(
137075
+ parseCachedInitializer(registry.parsedInitializers, slot.initializer),
137076
+ unavailableValueIds
137077
+ );
137078
+ return [];
137079
+ }
137080
+ } else if (errors.length > 0) {
137081
+ unavailableSlots.add(slot.name);
137082
+ collectAuthoredValueIds(
137083
+ parseCachedInitializer(registry.parsedInitializers, slot.initializer),
137084
+ unavailableValueIds
137085
+ );
137086
+ return [];
137087
+ }
137088
+ return [
137089
+ {
137090
+ memberId: root.memberId,
137091
+ purpose: "stored",
137092
+ initializer: slots[index].initializer,
137093
+ source: global.source,
137094
+ label: `root.${root.slot.name}`
137095
+ }
137096
+ ];
136942
137097
  });
137098
+ return {
137099
+ ...lowerStoredValueBindingsV4(state, manifest, bindings, {
137100
+ analysis,
137101
+ registry
137102
+ }),
137103
+ unavailableSlots,
137104
+ unavailableValueIds
137105
+ };
137106
+ }
137107
+ function collectAuthoredValueIds(expression, ids) {
137108
+ const pending = [expression];
137109
+ while (pending.length > 0) {
137110
+ const node = pending.pop();
137111
+ if (Array.isArray(node)) {
137112
+ for (const entry of node) pending.push(entry);
137113
+ continue;
137114
+ }
137115
+ if (!isObjectRecord2(node)) continue;
137116
+ if (node.kind === "annotated" && Array.isArray(node.annotations)) {
137117
+ for (const annotation2 of node.annotations) {
137118
+ if (!isObjectRecord2(annotation2) || annotation2.name !== "id" || !Array.isArray(annotation2.args))
137119
+ continue;
137120
+ const argument2 = annotation2.args[0];
137121
+ if (isObjectRecord2(argument2) && argument2.kind === "litString" && typeof argument2.value === "string")
137122
+ ids.add(argument2.value);
137123
+ }
137124
+ }
137125
+ for (const [key, value] of Object.entries(node)) {
137126
+ if (key !== "pos" && key !== "range") pending.push(value);
137127
+ }
137128
+ }
136943
137129
  }
136944
137130
  function validateProjectRootEnvelopeV4(state, analysis, parsedInitializers) {
136945
137131
  const project = Object.values(state).find(
@@ -137048,7 +137234,8 @@ function resolveRootPathCollectionValuesV4(args) {
137048
137234
  placements,
137049
137235
  rootValues,
137050
137236
  fail2,
137051
- args.resolveVirtualPath
137237
+ args.resolveVirtualPath,
137238
+ args.unavailableSlots
137052
137239
  );
137053
137240
  if (target !== null) resolved.set(member.id, target);
137054
137241
  }
@@ -137087,7 +137274,8 @@ function resolveRootPathVariantFolderCollectionValuesV4(args) {
137087
137274
  placements,
137088
137275
  rootValues,
137089
137276
  fail2,
137090
- args.resolveVirtualPath
137277
+ args.resolveVirtualPath,
137278
+ args.unavailableSlots
137091
137279
  );
137092
137280
  if (target !== null) resolved.set(folder.id, target);
137093
137281
  }
@@ -137125,7 +137313,8 @@ function resolveRootPathDialogueGroupCollectionValuesV4(args) {
137125
137313
  label: String(record7.fileFields.name)
137126
137314
  }
137127
137315
  }),
137128
- args.resolveVirtualPath
137316
+ args.resolveVirtualPath,
137317
+ args.unavailableSlots
137129
137318
  );
137130
137319
  if (target !== null) record7.fileFields.collectionValueId = target;
137131
137320
  }
@@ -137143,7 +137332,7 @@ function rootSlotValueIds(state) {
137143
137332
  }
137144
137333
  return values;
137145
137334
  }
137146
- function walkRootValuePath(path, state, registry, placements, rootValues, fail2, resolveVirtualPath) {
137335
+ function walkRootValuePath(path, state, registry, placements, rootValues, fail2, resolveVirtualPath, unavailableSlots) {
137147
137336
  const segments = path.split(".");
137148
137337
  const slotName = segments[1];
137149
137338
  if (segments[0] !== "root" || slotName === void 0) {
@@ -137152,6 +137341,7 @@ function walkRootValuePath(path, state, registry, placements, rootValues, fail2,
137152
137341
  );
137153
137342
  return null;
137154
137343
  }
137344
+ if (unavailableSlots?.has(slotName)) return null;
137155
137345
  const slotIndex = PROJECT_ROOT_SOURCE_SLOTS.findIndex(
137156
137346
  (slot) => slot.name === slotName
137157
137347
  );
@@ -140890,24 +141080,25 @@ function computeStatus(workspace, options, conflictedFiles, parseErrors, parseWa
140890
141080
  diagnostic2.range.start.character + 1,
140891
141081
  diagnostic2.code,
140892
141082
  diagnostic2.severity,
140893
- diagnostic2.range
141083
+ diagnostic2.range,
141084
+ diagnostic2.relatedInformation
140894
141085
  );
140895
141086
  if (positioned.severity === "error") parseErrors.push(positioned);
140896
141087
  else parseWarnings.push(positioned);
140897
141088
  }
140898
141089
  if (analysis.diagnostics.some(
140899
- (diagnostic2) => diagnostic2.code === "duplicate-project-id" && diagnostic2.severity === "error"
141090
+ (diagnostic2) => diagnostic2.severity === "error" && (diagnostic2.code === "duplicate-project-id" || diagnostic2.code === "invalid-project-global-initializer")
140900
141091
  )) {
140901
141092
  return refusedStatus(conflictedFiles, parseErrors, parseWarnings);
140902
141093
  }
140903
141094
  const rootDeclaration = analysis.configuration.globals.find(
140904
141095
  (global) => global.type.name === "Root"
140905
141096
  );
140906
- const rootHasCompilerError = rootDeclaration !== void 0 && analysis.diagnostics.some(
140907
- (error) => error.severity === "error" && error.uri === rootDeclaration.source.uri && sourceRangeContains(rootDeclaration.source.range, error.range.start)
141097
+ const rootHasEnvelopeError = rootDeclaration !== void 0 && analysis.diagnostics.some(
141098
+ (error) => error.severity === "error" && (error.code === "invalid-root-envelope" || error.code === "protected-root-envelope") && error.uri === rootDeclaration.source.uri && sourceRangeContains(rootDeclaration.source.range, error.range.start)
140908
141099
  );
140909
141100
  try {
140910
- if (!rootHasCompilerError) {
141101
+ if (!rootHasEnvelopeError) {
140911
141102
  validateProjectRootEnvelopeV4(
140912
141103
  loweringRecords,
140913
141104
  analysis,
@@ -140940,7 +141131,7 @@ function computeStatus(workspace, options, conflictedFiles, parseErrors, parseWa
140940
141131
  ...analysis,
140941
141132
  configuration: {
140942
141133
  globals: analysis.configuration.globals.filter(
140943
- (global) => global.type.name !== "Root" || rootEnvelopeValid && !rootHasCompilerError
141134
+ (global) => global.type.name !== "Root" || rootEnvelopeValid && !rootHasEnvelopeError
140944
141135
  )
140945
141136
  },
140946
141137
  schema: {
@@ -141143,8 +141334,28 @@ function computeStatus(workspace, options, conflictedFiles, parseErrors, parseWa
141143
141334
  }) : {
141144
141335
  records: [],
141145
141336
  memberValueIds: /* @__PURE__ */ new Map(),
141146
- seeds: /* @__PURE__ */ new Map()
141147
- };
141337
+ seeds: /* @__PURE__ */ new Map(),
141338
+ unavailableSlots: new Set(
141339
+ PROJECT_ROOT_SOURCE_SLOTS.map((slot) => slot.name)
141340
+ ),
141341
+ unavailableValueIds: /* @__PURE__ */ new Set()
141342
+ };
141343
+ const unavailableReferenceMemberIds = /* @__PURE__ */ new Set();
141344
+ if (rootValues.unavailableSlots.size > 0) {
141345
+ for (const member of manifest.members) {
141346
+ if (member.kind !== "lookup") continue;
141347
+ const spelled = member.collectionValueId;
141348
+ if (typeof spelled !== "string" || !spelled.startsWith("__pending__:root-path:") && !rootValues.unavailableValueIds.has(spelled))
141349
+ continue;
141350
+ if (rootValues.unavailableValueIds.has(spelled)) {
141351
+ unavailableReferenceMemberIds.add(member.id);
141352
+ continue;
141353
+ }
141354
+ const slot = spelled.slice("__pending__:root-path:".length).split(".")[1];
141355
+ if (slot !== void 0 && rootValues.unavailableSlots.has(slot))
141356
+ unavailableReferenceMemberIds.add(member.id);
141357
+ }
141358
+ }
141148
141359
  reportPhase("documents-static-root", "Reading variants and dialogues\u2026");
141149
141360
  authoredValueSeeds = new Map([...authoredValueSeeds, ...rootValues.seeds]);
141150
141361
  const rootPathResolutionState = overlayProspectiveSourceRecords(
@@ -141182,6 +141393,7 @@ function computeStatus(workspace, options, conflictedFiles, parseErrors, parseWa
141182
141393
  manifest,
141183
141394
  state: rootPathResolutionState,
141184
141395
  resolveVirtualPath,
141396
+ unavailableSlots: rootValues.unavailableSlots,
141185
141397
  registry: valueLowerRegistry,
141186
141398
  siteForMember: (memberId) => {
141187
141399
  const span2 = sourceByKey.get(recordStateKey("member", memberId))?.span;
@@ -141211,6 +141423,7 @@ function computeStatus(workspace, options, conflictedFiles, parseErrors, parseWa
141211
141423
  manifest,
141212
141424
  state: rootPathResolutionState,
141213
141425
  resolveVirtualPath,
141426
+ unavailableSlots: rootValues.unavailableSlots,
141214
141427
  registry: valueLowerRegistry,
141215
141428
  siteForFolder: (folderId) => {
141216
141429
  const span2 = sourceByKey.get(
@@ -141324,6 +141537,7 @@ function computeStatus(workspace, options, conflictedFiles, parseErrors, parseWa
141324
141537
  manifest,
141325
141538
  state: rootPathResolutionState,
141326
141539
  resolveVirtualPath,
141540
+ unavailableSlots: rootValues.unavailableSlots,
141327
141541
  registry: valueLowerRegistry
141328
141542
  });
141329
141543
  if (collectionBindings !== void 0) {
@@ -141527,6 +141741,8 @@ function computeStatus(workspace, options, conflictedFiles, parseErrors, parseWa
141527
141741
  registry: valueLowerRegistry,
141528
141742
  analysis: projectAnalysisV4,
141529
141743
  deletedValueIds,
141744
+ unavailableReferenceMemberIds,
141745
+ unavailableValueIds: rootValues.unavailableValueIds,
141530
141746
  sourceTextByUri
141531
141747
  }
141532
141748
  );
@@ -142991,6 +143207,7 @@ var ANIMATION_DOCUMENT_RECORD_KINDS, ANIMATION_REPLAY_RECORD_KINDS, PROSPECTIVE_
142991
143207
  var init_workspace_status_core = __esm({
142992
143208
  "src/project-source/workspace-status-core.ts"() {
142993
143209
  "use strict";
143210
+ init_project_root();
142994
143211
  init_source_text();
142995
143212
  init_runtime_diagnostic();
142996
143213
  init_NSGetterRuntimeError();
@@ -145862,7 +146079,8 @@ function projectStatusJsonV4(status, options) {
145862
146079
  code: error.code ?? null,
145863
146080
  severity: error.severity,
145864
146081
  blocking: isBlockingSchemaSourceError(error),
145865
- message: error.message
146082
+ message: error.message,
146083
+ ...error.relatedInformation?.length ? { relatedInformation: error.relatedInformation } : {}
145866
146084
  })
145867
146085
  ),
145868
146086
  records: status.changes.map(
@@ -192010,7 +192228,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
192010
192228
  async function main() {
192011
192229
  const args = parseArgs(process.argv.slice(2));
192012
192230
  if (args.command === "--version") {
192013
- console.log("0.75.1");
192231
+ console.log("0.75.2");
192014
192232
  return;
192015
192233
  }
192016
192234
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
@@ -29803,6 +29803,12 @@ var Parser = class _Parser {
29803
29803
  value: this.parseExpr(),
29804
29804
  pos: at(name)
29805
29805
  });
29806
+ if (this.peek().text === ";") {
29807
+ throw new CompileError(
29808
+ "Object initializer assignments use commas, not semicolons. Replace ';' with ',' or omit it after the last assignment; the enclosing declaration still ends with ';'.",
29809
+ at(this.peek())
29810
+ );
29811
+ }
29806
29812
  if (!this.eat("punctuation", ",")) break;
29807
29813
  if (this.peek().kind === "punctuation" && this.peek().text === "}") break;
29808
29814
  }
@@ -55714,7 +55720,7 @@ var SPECIALIZED_RELATION_KINDS = /* @__PURE__ */ new Set([
55714
55720
 
55715
55721
  // ../packages/manifest/schema-contract/registry.mjs
55716
55722
  var PROJECT_SCHEMA_CONTRACT = Object.freeze({
55717
- cliVersion: "0.75.1",
55723
+ cliVersion: "0.75.2",
55718
55724
  projectFileUploadBatchSize: 32,
55719
55725
  documentRecords: {
55720
55726
  member: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.75.1",
3
+ "version": "0.75.2",
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.75.1 -->
12
+ <!-- reviewed-through-cli: 0.75.2 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -308,5 +308,6 @@ and `insert_final_newline` there to keep regenerated source consistent with your
308
308
  editor. Defaults are two spaces, LF, and a final newline. Push preserves authored
309
309
  text when regeneration has the same tokens; pull preserves semantically unchanged
310
310
  files. Formatting-only body or source-location changes do not create a subsequent
311
- push; real code and string-content edits still do. Formatting preferences belong
311
+ push, including stored initializer recipes, computed member defaults, and inline
312
+ constructor delegates; real code and string-content edits still do. Formatting preferences belong
312
313
  to the checkout, not stored script bodies.
@@ -105,7 +105,10 @@ project-file bytes, and web editor commits use the shared client preparation pro
105
105
  Unrelated source files are not uploaded or recompiled on every push.
106
106
  Optional source maps missing from valid stored bodies do not request a backfill.
107
107
  Formatting-only source location changes do not create pushes, including when
108
- other records change. Evaluation maps the current local layout; authored edits,
108
+ other records change. Stored initializer recipes, computed member defaults, and
109
+ inline constructor delegates preserve their pulled code and compiled bodies when
110
+ source tokens are unchanged. String-literal content remains significant.
111
+ Evaluation maps the current local layout; authored edits,
109
112
  dependency changes, and explicit forced recompilation retain their normal compile scope. A CLI lowering mismatch on a normal push is exposed by the next
110
113
  canonical pull; `neo push --dry-run` remains the complete source-to-record
111
114
  rehearsal.
@@ -126,7 +129,7 @@ wrappers.
126
129
  The marker near the top of `SKILL.md` must exactly match the package version:
127
130
 
128
131
  ```html
129
- <!-- reviewed-through-cli: 0.75.1 -->
132
+ <!-- reviewed-through-cli: 0.75.2 -->
130
133
  ```
131
134
 
132
135
  The quoted version above is checked too, so this instruction cannot go stale
@@ -446,3 +446,28 @@ A lookup's declared class must be assignable to the entry class of its
446
446
  selection. Constructor arguments and member initializers must select compatible
447
447
  targets too. An `NPC` lookup cannot select from `List<Quest>`, even when its
448
448
  initializer is typed `NPC`; status reports the conflicting collection annotation.
449
+
450
+ Schema export retains a constructed default's generic arguments, including
451
+ nested collection references. For example, a `NeoAnimationClip<ArmPart>`
452
+ default constructs `new NeoAnimationClip<ArmPart>` and its frames reference
453
+ `List<NeoAnimationFrame<ArmPart>>`.
454
+
455
+ When a construction leaves a required member unsettled, CLI diagnostics point to
456
+ `new` and include the member declaration as a related location. Fix that
457
+ construction before pushing. Valid root initializers still participate in
458
+ reference checking, so an unrelated root error does not imply their values are
459
+ missing or deleted. Independent reference errors remain visible.
460
+
461
+ Object initializer assignments use commas. The last comma is optional; the
462
+ semicolon terminating the enclosing declaration is valid:
463
+
464
+ ```neo
465
+ AudioSettings AUDIO_SETTINGS = new() {
466
+ Main = new AudioMixerGroupSettingsFolder("Main", "Master", "MasterVolume", []),
467
+ };
468
+ ```
469
+
470
+ A semicolon after `Main = ...` inside the initializer is invalid. Editor and CLI
471
+ diagnostics highlight that separator in the authored file, including global,
472
+ static member, and protected root initializers. Statement semicolons inside
473
+ function or delegate bodies remain valid.