@neocompose/cli 0.68.2 → 0.68.4

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,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.68.4] - 2026-10-05
4
+
5
+ - An empty header base clause such as `class Child(string name) : Base() { Name = name } {}` uses the base's member initializers when its declared constructors require arguments. The base block must settle remaining required members. Zero-argument and fully defaulted base constructors still run; required base constructors and declared `: base()` calls still require valid arguments. Fixes [#1186](https://github.com/ryanbliss/neo-compose/issues/1186). Use SDK 0.57.4 for matching Unity runtime behavior.
6
+
7
+ ## [0.68.3] - 2026-10-05
8
+
9
+ - `neo push --dry-run` preserves valid authored UUID-v4 IDs on new nested member and dictionary values, including values named by local declarations. Previously, verification dropped the ID and failed with an internal "has no pending id assignment" error. Status and push report malformed new member-value IDs at the annotation, name the affected `Class.Member`, and suggest removing `@id` or providing an unused UUID v4 ([#1189](https://github.com/ryanbliss/neo-compose/issues/1189)).
10
+
11
+ - A stored nested class value can keep its UUID-v4 when moved between compatible empty fields on existing parents. Its old owning declaration must be removed in the same push. Unsafe ownership changes are refused with an explanation.
12
+
13
+ - Trusted verification resolves complete pending IDs inside initializer code, so `Reference<T>(key:)` can target a row created in the same push without confusing its ID with the containing value's ID.
14
+
15
+ - An override of a generic class's function passes the push-time animation check, including an override of an override, as in `class ClampedIntWatcher : MinIntWatcher` where `MinIntWatcher : ValueWatcher<int>`. Before, status warned and test and push stopped with "Prospective animation validation received an invalid member record … (\"Apply\")" in any project with animation classes.
16
+
3
17
  ## [0.68.2] - 2026-10-05
4
18
 
5
19
  - Local initializer IDs may appear above the variable declaration. Formatting and pull move inline IDs there while preserving the stored row identity. Nested IDs remain on their expressions.
package/dist/neo.mjs CHANGED
@@ -22862,6 +22862,11 @@ var init_project_source_variants = __esm({
22862
22862
  });
22863
22863
 
22864
22864
  // ../packages/neoscript-language/src/project-source-settlement.ts
22865
+ function requiredConstructorParameters(declaration) {
22866
+ if (declaration.headerParameters !== void 0)
22867
+ return declaration.headerParameters;
22868
+ return declaration.initBlocks.length > 0 ? [] : void 0;
22869
+ }
22865
22870
  function buildNeoConstructionIndex(documents) {
22866
22871
  const classes = /* @__PURE__ */ new Map();
22867
22872
  const interfaceNames = /* @__PURE__ */ new Set();
@@ -22933,8 +22938,9 @@ function unsettledRequiredMembers(index, className, settledNames, typeArguments
22933
22938
  function classConstructionContract(index, className) {
22934
22939
  const declaration = index.classes.get(className)?.declaration;
22935
22940
  if (!declaration) return null;
22936
- if (declaration.headerParameters !== void 0) {
22937
- return { kind: "required", parameters: declaration.headerParameters };
22941
+ const parameters = requiredConstructorParameters(declaration);
22942
+ if (parameters !== void 0) {
22943
+ return { kind: "required", parameters };
22938
22944
  }
22939
22945
  if (declaration.constructors.length > 0) {
22940
22946
  return { kind: "declared", constructors: declaration.constructors };
@@ -22998,7 +23004,7 @@ function validateBaseConstruction(index, entry, diagnostics) {
22998
23004
  }
22999
23005
  for (const diagnostic of validateNeoConstructionSite(index, {
23000
23006
  className: baseClause.type.name,
23001
- site: "base",
23007
+ site: "headerBase",
23002
23008
  typeArguments: baseClause.type.typeArguments.map(typeShape),
23003
23009
  argumentNames: (baseClause.arguments ?? []).map(
23004
23010
  (argument2) => argument2.name ?? null
@@ -23024,11 +23030,10 @@ function validateBaseConstruction(index, entry, diagnostics) {
23024
23030
  }
23025
23031
  }
23026
23032
  function validateUnconstructedBase(entry, base, range2, diagnostics) {
23027
- const parameters = base.declaration.headerParameters;
23033
+ const parameters = requiredConstructorParameters(base.declaration);
23028
23034
  if (parameters === void 0) {
23029
- if (entry.declaration.headerParameters === void 0 && entry.declaration.initBlocks.length === 0)
23030
- return;
23031
- pushBaseCall(entry, base, [], () => range2, diagnostics);
23035
+ if (requiredConstructorParameters(entry.declaration) === void 0) return;
23036
+ pushBaseCall(entry, base, [], () => range2, diagnostics, "headerBase");
23032
23037
  return;
23033
23038
  }
23034
23039
  if (parameterListIsFullyDefaulted(parameters)) return;
@@ -23047,7 +23052,7 @@ function validateUnconstructedBase(entry, base, range2, diagnostics) {
23047
23052
  function validateDeclaredBaseCalls(entry, base, diagnostics) {
23048
23053
  for (const constructor2 of entry.declaration.constructors) {
23049
23054
  const baseArguments = constructor2.baseArguments ?? [];
23050
- if (constructor2.baseArguments === void 0 && base.declaration.headerParameters !== void 0) {
23055
+ if (constructor2.baseArguments === void 0 && requiredConstructorParameters(base.declaration) !== void 0) {
23051
23056
  continue;
23052
23057
  }
23053
23058
  pushBaseCall(
@@ -23062,13 +23067,13 @@ function validateDeclaredBaseCalls(entry, base, diagnostics) {
23062
23067
  );
23063
23068
  }
23064
23069
  }
23065
- function pushBaseCall(entry, base, argumentNames, rangeOf, diagnostics) {
23070
+ function pushBaseCall(entry, base, argumentNames, rangeOf, diagnostics, site = "base") {
23066
23071
  const siteDiagnostics = [];
23067
23072
  resolveInvokedConstructor(
23068
23073
  base.declaration,
23069
23074
  {
23070
23075
  className: base.declaration.name,
23071
- site: "base",
23076
+ site,
23072
23077
  argumentNames,
23073
23078
  initializerNames: []
23074
23079
  },
@@ -23127,7 +23132,7 @@ function validateDeclaredConstructionPaths(index, entry, diagnostics) {
23127
23132
  []
23128
23133
  ).filter((member) => owned || member.declaringClassName === declaration.name);
23129
23134
  if (outstanding.length === 0) return;
23130
- if (declaration.headerParameters !== void 0) {
23135
+ if (requiredConstructorParameters(declaration) !== void 0) {
23131
23136
  const initBlock = declaration.initBlocks[0];
23132
23137
  const assigned = assignedOnEveryPath(initBlock?.body.text ?? "{}");
23133
23138
  const unsettled = filterUnassigned(outstanding, assigned);
@@ -23168,13 +23173,30 @@ function filterUnassigned(members, assigned) {
23168
23173
  }
23169
23174
  function declarationSettledMembers(index, entry) {
23170
23175
  const settled = /* @__PURE__ */ new Set();
23171
- const baseClause = entry.declaration.baseClause;
23172
- if (!baseClause) return settled;
23173
- for (const field of baseClause.initializerFields ?? []) {
23176
+ const declaration = entry.declaration;
23177
+ const baseClause = declaration.baseClause;
23178
+ for (const field of baseClause?.initializerFields ?? []) {
23174
23179
  settled.add(field.name.toLowerCase());
23175
23180
  }
23176
- if ((baseClause.arguments ?? []).length === 0) return settled;
23177
- for (const member of requiredMembersForClass(index, baseClause.type.name)) {
23181
+ if (baseClause === void 0 && requiredConstructorParameters(declaration) === void 0)
23182
+ return settled;
23183
+ const baseName = baseClause?.type.name ?? declaration.baseTypes[0]?.name;
23184
+ const base = baseName === void 0 ? void 0 : index.classes.get(baseName);
23185
+ if (base === void 0) return settled;
23186
+ if (!resolveInvokedConstructor(
23187
+ base.declaration,
23188
+ {
23189
+ className: base.declaration.name,
23190
+ site: "headerBase",
23191
+ argumentNames: (baseClause?.arguments ?? []).map(
23192
+ (argument2) => argument2.name ?? null
23193
+ ),
23194
+ initializerNames: []
23195
+ },
23196
+ []
23197
+ ))
23198
+ return settled;
23199
+ for (const member of requiredMembersForClass(index, base.declaration.name)) {
23178
23200
  settled.add(member.name.toLowerCase());
23179
23201
  }
23180
23202
  return settled;
@@ -23192,7 +23214,7 @@ function baseClauseRange(declaration, anchor) {
23192
23214
  return baseClause.range;
23193
23215
  }
23194
23216
  function resolveInvokedConstructor(declaration, call, diagnostics) {
23195
- const headerParameters = declaration.headerParameters;
23217
+ const headerParameters = requiredConstructorParameters(declaration);
23196
23218
  if (headerParameters !== void 0) {
23197
23219
  if (call.argumentNames.length > 0) {
23198
23220
  validateArgumentsAgainstParameters(
@@ -23228,7 +23250,7 @@ function resolveInvokedConstructor(declaration, call, diagnostics) {
23228
23250
  const parameterless = declaration.constructors.some(
23229
23251
  (constructor2) => parameterListIsFullyDefaulted(constructor2.parameters)
23230
23252
  );
23231
- if (parameterless || call.site === "new") return parameterless;
23253
+ if (parameterless || call.site !== "base") return parameterless;
23232
23254
  diagnostics.push({
23233
23255
  code: "base-requires-construction",
23234
23256
  message: `The base call must pass arguments: '${declaration.name}' declares no parameterless constructor \u2014 candidates: ${describeConstructors(declaration)}.`,
@@ -23245,7 +23267,7 @@ function resolveInvokedConstructor(declaration, call, diagnostics) {
23245
23267
  );
23246
23268
  return true;
23247
23269
  }
23248
- if (call.site === "base") {
23270
+ if (call.site !== "new") {
23249
23271
  let positional = false;
23250
23272
  call.argumentNames.forEach((name, index) => {
23251
23273
  if (name !== null) return;
@@ -31690,7 +31712,7 @@ var init_registry = __esm({
31690
31712
  "../packages/manifest/schema-contract/registry.mjs"() {
31691
31713
  "use strict";
31692
31714
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
31693
- cliVersion: "0.68.2",
31715
+ cliVersion: "0.68.4",
31694
31716
  projectFileUploadBatchSize: 32,
31695
31717
  documentRecords: {
31696
31718
  member: {
@@ -79315,6 +79337,8 @@ function resolveBaseConstructorRecord(record6, ctx) {
79315
79337
  );
79316
79338
  if (resolution.kind === "resolved") return resolution.overload;
79317
79339
  if (resolution.kind === "noMatch" && baseArguments.length === 0) {
79340
+ if (owningClass.requiredConstructorId === record6.id && evaluatorSchemaIndex(ctx).class(baseClassId)?.requiredConstructorId == null)
79341
+ return null;
79318
79342
  throw new NSGetterRuntimeError(
79319
79343
  `Constructor '${record6.id}' on '${owningClass.name}' must call ': base(...)': its base class declares constructors but none parameterless.`
79320
79344
  );
@@ -88861,6 +88885,8 @@ function resolveBaseConstructorOverload(args, owner) {
88861
88885
  );
88862
88886
  if (resolution.kind === "resolved") return resolution.overload;
88863
88887
  if (resolution.kind === "noMatch" && baseArguments.length === 0) {
88888
+ if (owner.requiredConstructorId === args.constructor.id && baseClass.requiredConstructorId == null)
88889
+ return null;
88864
88890
  throw new Error(
88865
88891
  `Constructor "${args.constructor.id}" must declare a base call: base class "${baseClass.name}" declares constructors but none parameterless \u2014 candidates: ${describeConstructorOverloads(candidates)}.`
88866
88892
  );
@@ -116847,6 +116873,59 @@ var init_client_commit_preparation = __esm({
116847
116873
  }
116848
116874
  });
116849
116875
 
116876
+ // src/project-source/identity-rewriter.ts
116877
+ function createIdentityRewriter(assignments, onReplace) {
116878
+ if (assignments.size === 0) return (value) => value;
116879
+ const root = { children: /* @__PURE__ */ new Map() };
116880
+ for (const [source, replacement] of assignments) {
116881
+ let prefix = root;
116882
+ for (let index = 0; index < source.length; index += 1) {
116883
+ const character = source[index];
116884
+ let child2 = prefix.children.get(character);
116885
+ if (child2 === void 0) {
116886
+ child2 = { children: /* @__PURE__ */ new Map() };
116887
+ prefix.children.set(character, child2);
116888
+ }
116889
+ prefix = child2;
116890
+ }
116891
+ prefix.replacement = replacement;
116892
+ }
116893
+ return (value) => {
116894
+ const exact = assignments.get(value);
116895
+ if (exact !== void 0) {
116896
+ onReplace?.(value);
116897
+ return exact;
116898
+ }
116899
+ const parts = [];
116900
+ let copiedThrough = 0;
116901
+ for (let start = 0; start < value.length; start += 1) {
116902
+ let prefix = root.children.get(value[start]);
116903
+ let replacement;
116904
+ let end = start;
116905
+ for (let index = start; prefix !== void 0; index += 1) {
116906
+ if (prefix.replacement !== void 0) {
116907
+ replacement = prefix.replacement;
116908
+ end = index + 1;
116909
+ }
116910
+ prefix = prefix.children.get(value[index + 1]);
116911
+ }
116912
+ if (replacement === void 0) continue;
116913
+ onReplace?.(value.slice(start, end));
116914
+ parts.push(value.slice(copiedThrough, start), replacement);
116915
+ copiedThrough = end;
116916
+ start = end - 1;
116917
+ }
116918
+ if (copiedThrough === 0) return value;
116919
+ parts.push(value.slice(copiedThrough));
116920
+ return parts.join("");
116921
+ };
116922
+ }
116923
+ var init_identity_rewriter = __esm({
116924
+ "src/project-source/identity-rewriter.ts"() {
116925
+ "use strict";
116926
+ }
116927
+ });
116928
+
116850
116929
  // src/project-source/schema-source-validation.ts
116851
116930
  function sourceDiagnosticMapper(analysis) {
116852
116931
  const locations = /* @__PURE__ */ new Map();
@@ -124875,7 +124954,8 @@ function computeWorkspaceStatus(workspace, options) {
124875
124954
  claim.message,
124876
124955
  position.file,
124877
124956
  position.line,
124878
- position.column
124957
+ position.column,
124958
+ claim.code
124879
124959
  )
124880
124960
  );
124881
124961
  }
@@ -125305,7 +125385,7 @@ function prospectiveAnimationDocumentV4(records2) {
125305
125385
  let project;
125306
125386
  const constructors = [];
125307
125387
  const classes = [];
125308
- const members = [];
125388
+ const memberData = [];
125309
125389
  const values = [];
125310
125390
  for (const record6 of candidates) {
125311
125391
  if (record6.recordKind === "project") {
@@ -125325,16 +125405,7 @@ function prospectiveAnimationDocumentV4(records2) {
125325
125405
  }
125326
125406
  classes.push(record6.data);
125327
125407
  } else if (record6.recordKind === "member") {
125328
- const member = hydrateProspectiveMemberOverride(
125329
- record6.data,
125330
- memberRecords
125331
- );
125332
- if (!isMember(member)) {
125333
- throw new Error(
125334
- `Prospective animation validation received an invalid member record ${recordLabel(record6.data)}.`
125335
- );
125336
- }
125337
- members.push(member);
125408
+ memberData.push(record6.data);
125338
125409
  } else if (record6.recordKind === "value") {
125339
125410
  if (!isMemberValue(record6.data)) {
125340
125411
  throw new Error(
@@ -125345,6 +125416,22 @@ function prospectiveAnimationDocumentV4(records2) {
125345
125416
  }
125346
125417
  }
125347
125418
  if (project === void 0) return null;
125419
+ const members = [];
125420
+ const inheritedCallables = [];
125421
+ for (const data of memberData) {
125422
+ const member = hydrateProspectiveMemberOverride(data, memberRecords);
125423
+ if (typeof data.extendsMemberId === "string" && member.returnTypeInfo !== void 0) {
125424
+ inheritedCallables.push(member);
125425
+ } else {
125426
+ members.push(validProspectiveMember(member));
125427
+ }
125428
+ }
125429
+ const closedCallables = inheritedCallables.map(
125430
+ (member) => validProspectiveMember(
125431
+ closeInheritedCallableSignature(member, classes, members)
125432
+ )
125433
+ );
125434
+ members.push(...closedCallables);
125348
125435
  const classesById = new Map(
125349
125436
  classes.map((schemaClass2) => [schemaClass2.id, schemaClass2])
125350
125437
  );
@@ -125768,6 +125855,36 @@ function hydrateProspectiveMemberOverride(data, members) {
125768
125855
  }
125769
125856
  return hydrated;
125770
125857
  }
125858
+ function validProspectiveMember(member) {
125859
+ if (!isMember(member)) {
125860
+ throw new Error(
125861
+ `Prospective animation validation received an invalid member record ${recordLabel(member)}.`
125862
+ );
125863
+ }
125864
+ return member;
125865
+ }
125866
+ function closeInheritedCallableSignature(member, classes, members) {
125867
+ if (typeof member.id !== "string") return member;
125868
+ const env = resolveDeclaringClassGenericEnv(member.id, classes);
125869
+ if (env.size === 0) return member;
125870
+ const close = (typeInfo) => substituteInheritedCallableTypeInfo(typeInfo, env, classes, members);
125871
+ const closed = { ...member };
125872
+ if (isNSFunctionReturnTypeInfo(member.returnTypeInfo)) {
125873
+ closed.returnTypeInfo = close(member.returnTypeInfo);
125874
+ }
125875
+ if (Array.isArray(member.argumentTypes)) {
125876
+ closed.argumentTypes = member.argumentTypes.map((argument2) => {
125877
+ if (!isNSFunctionArgumentTypeInfo(argument2)) return argument2;
125878
+ const { name, defaultValue } = argument2;
125879
+ return {
125880
+ ...close(argument2),
125881
+ name,
125882
+ ...defaultValue === void 0 ? {} : { defaultValue }
125883
+ };
125884
+ });
125885
+ }
125886
+ return closed;
125887
+ }
125771
125888
  function resolveProspectiveMemberField(start, field, members) {
125772
125889
  let current = start;
125773
125890
  const visited = /* @__PURE__ */ new Set();
@@ -126149,6 +126266,8 @@ var init_workspace_status_core = __esm({
126149
126266
  init_project_documents();
126150
126267
  init_animation_clips();
126151
126268
  init_classes();
126269
+ init_generics();
126270
+ init_neoscript_guards();
126152
126271
  init_members();
126153
126272
  init_value_row_owner_members();
126154
126273
  init_constructors2();
@@ -126255,7 +126374,16 @@ ${blockingErrors.map((error) => error.message).join("\n")}` : `Trusted source lo
126255
126374
  );
126256
126375
  const usedAssignments = /* @__PURE__ */ new Set();
126257
126376
  const positionless = positionIndependentAssignments(assignments);
126258
- const rewrite = (value) => rewritePending(value, assignments, usedAssignments, positionless);
126377
+ const rewriteEmbedded = createIdentityRewriter(assignments, (id2) => {
126378
+ usedAssignments.add(id2);
126379
+ });
126380
+ const rewrite = (value) => rewritePending(
126381
+ value,
126382
+ assignments,
126383
+ usedAssignments,
126384
+ positionless,
126385
+ rewriteEmbedded
126386
+ );
126259
126387
  const expectedChanges = status.changes.map((change) => {
126260
126388
  const rewrittenData = change.nextData === void 0 ? void 0 : rewrite(change.nextData);
126261
126389
  return {
@@ -126495,7 +126623,7 @@ function relocatedAssignment(pendingId2, assignments, byPosition, used) {
126495
126623
  const relocatedVirtual = pendingVirtualValueId(relocatedRoot, sourceIdentity3);
126496
126624
  return assignments.has(relocatedVirtual) ? relocatedVirtual : null;
126497
126625
  }
126498
- function rewritePending(value, assignments, used, positionless) {
126626
+ function rewritePending(value, assignments, used, positionless, rewriteEmbedded) {
126499
126627
  if (typeof value === "string") {
126500
126628
  const exact = assignments.get(value);
126501
126629
  if (exact !== void 0) {
@@ -126518,28 +126646,23 @@ function rewritePending(value, assignments, used, positionless) {
126518
126646
  );
126519
126647
  }
126520
126648
  if (!value.includes("__pending__:")) return value;
126521
- let rewritten = value;
126522
- for (const [pendingId2, assignedId] of assignments) {
126523
- if (!rewritten.includes(pendingId2)) continue;
126524
- rewritten = rewritten.replaceAll(pendingId2, assignedId);
126525
- used.add(pendingId2);
126526
- }
126527
- return rewritten;
126649
+ return rewriteEmbedded(value);
126528
126650
  }
126529
126651
  if (Array.isArray(value)) {
126530
126652
  return value.map(
126531
- (entry) => rewritePending(entry, assignments, used, positionless)
126653
+ (entry) => rewritePending(entry, assignments, used, positionless, rewriteEmbedded)
126532
126654
  );
126533
126655
  }
126534
126656
  if (isObjectRecord2(value)) {
126535
126657
  const result = {};
126536
126658
  for (const [key, entry] of Object.entries(value)) {
126537
- const rewrittenKey = key.startsWith("__pending__:") ? rewritePending(key, assignments, used, positionless) : key;
126659
+ const rewrittenKey = key.startsWith("__pending__:") ? rewritePending(key, assignments, used, positionless, rewriteEmbedded) : key;
126538
126660
  result[rewrittenKey] = rewritePending(
126539
126661
  entry,
126540
126662
  assignments,
126541
126663
  used,
126542
- positionless
126664
+ positionless,
126665
+ rewriteEmbedded
126543
126666
  );
126544
126667
  }
126545
126668
  return result;
@@ -126775,6 +126898,7 @@ var ProjectSourceCommitVerificationError, ABSENT_DIFFERENCE_VALUE;
126775
126898
  var init_trusted_commit_verification = __esm({
126776
126899
  "src/project-source/trusted-commit-verification.ts"() {
126777
126900
  "use strict";
126901
+ init_identity_rewriter();
126778
126902
  init_schema_source_validation();
126779
126903
  init_projectRecordBuckets();
126780
126904
  init_workspace_status_core();
@@ -128342,6 +128466,8 @@ function createValueLowerRegistryV4(options = {}) {
128342
128466
  initAuthoredRowIds: /* @__PURE__ */ new Map(),
128343
128467
  loweringFailures: [],
128344
128468
  storedRowClaims: /* @__PURE__ */ new Map(),
128469
+ movedValueSites: /* @__PURE__ */ new Map(),
128470
+ storedMoveRoots: /* @__PURE__ */ new Map(),
128345
128471
  placedStoredRowIds: /* @__PURE__ */ new Set(),
128346
128472
  pendingValueIdentitySites: /* @__PURE__ */ new Map(),
128347
128473
  initializerSites: /* @__PURE__ */ new Map(),
@@ -128468,6 +128594,7 @@ function buildValueLowerContext(state, manifest, options = {}) {
128468
128594
  localizationConversionValueIds: registry.localizationConversionValueIds,
128469
128595
  loweredMemberIdByValueId: /* @__PURE__ */ new Map(),
128470
128596
  storedClassSchemaMemberIds: /* @__PURE__ */ new Map(),
128597
+ ordinaryMoveFields: /* @__PURE__ */ new Map(),
128471
128598
  pendingBindingMembersByClassId: registry.pendingBindingMembersByClassId,
128472
128599
  declaredConstructors: declaredConstructorIndex,
128473
128600
  initAuthoredRowIds: seedInitAuthoredRowIds(
@@ -128481,6 +128608,8 @@ function buildValueLowerContext(state, manifest, options = {}) {
128481
128608
  referenceObligations: registry.referenceObligations,
128482
128609
  loweringFailures: registry.loweringFailures,
128483
128610
  storedRowClaims: registry.storedRowClaims,
128611
+ movedValueSites: registry.movedValueSites,
128612
+ storedMoveRoots: registry.storedMoveRoots,
128484
128613
  placedStoredRowIds: registry.placedStoredRowIds,
128485
128614
  pendingValueIdentitySites: registry.pendingValueIdentitySites,
128486
128615
  initializerSites: registry.initializerSites,
@@ -129131,6 +129260,21 @@ function drainValueReferenceObligationsV4(state, manifest, options) {
129131
129260
  })
129132
129261
  });
129133
129262
  const failures = [];
129263
+ const movedRoots = /* @__PURE__ */ new Map();
129264
+ for (const [valueId, move] of options.registry.movedValueSites) {
129265
+ const placements = context.valuePlacements.placementsByValueId.get(valueId) ?? [];
129266
+ let message;
129267
+ if (placements.length !== 1) {
129268
+ message = `Value @id(${JSON.stringify(valueId)}) must have exactly one owning slot after a move; found ${placements.length}. Remove its declaration from the old parent before keeping the same ID on the new owner.`;
129269
+ } else if (singleSlotBindingRoot(context, valueId, movedRoots) !== move.rootValueId) {
129270
+ message = `Value @id(${JSON.stringify(valueId)}) cannot move: moves require one acyclic path under the same root or static binding, through non-overridden class fields with inherited storage and automatic distribution.`;
129271
+ } else continue;
129272
+ failures.push({
129273
+ code: "invalid-value-move",
129274
+ message,
129275
+ ...composeReferenceSitePosition(move.site, options.sourceTextByUri)
129276
+ });
129277
+ }
129134
129278
  for (const recorded of options.registry.loweringFailures) {
129135
129279
  failures.push({
129136
129280
  message: recorded.message,
@@ -129730,27 +129874,17 @@ function lowerRowBackedDefault(context, member, backed, baseData3, binding) {
129730
129874
  const childValueMember = recursivePartialMember(member, childMember);
129731
129875
  const childSlice = assignmentSlices.get(assignment.name);
129732
129876
  const existingId = isObjectRecord2(baseBody) ? baseBody[assignment.name] : void 0;
129733
- const annotatedId = authoredRowId(context, assignment.value, binding);
129734
- const storedId = typeof existingId === "string" ? existingId : annotatedId;
129735
- if (storedId !== null && storedId !== void 0) {
129736
- if (typeof existingId === "string" || context.state[`value:${storedId}`] !== void 0) {
129737
- body[assignment.name] = lowerValueRow(
129738
- context,
129739
- childValueMember,
129740
- assignment.value,
129741
- storedId,
129742
- binding,
129743
- environment,
129744
- childSlice
129745
- );
129746
- continue;
129747
- }
129748
- if (!isPendingId(storedId) && !isUuidV4Id(storedId)) {
129749
- context.loweringFailures.push({
129750
- message: `Default value ${binding.label}.${assignment.name} is annotated @id("${storedId}"), which names no existing row, and a new row's durable identity must be a UUID v4. Fix the id if it meant an existing row, or drop the @id to mint a fresh identity.`,
129751
- site: referenceSite(binding, assignment.value)
129752
- });
129753
- }
129877
+ if (typeof existingId === "string") {
129878
+ body[assignment.name] = lowerValueRow(
129879
+ context,
129880
+ childValueMember,
129881
+ assignment.value,
129882
+ existingId,
129883
+ binding,
129884
+ environment,
129885
+ childSlice
129886
+ );
129887
+ continue;
129754
129888
  }
129755
129889
  const placedId = sourceValueSymbol(context, assignment.value);
129756
129890
  if (placedId !== null) {
@@ -129782,6 +129916,7 @@ function lowerRowBackedDefault(context, member, backed, baseData3, binding) {
129782
129916
  }
129783
129917
  const entryMember = requiredMember(context, member.entryMemberId);
129784
129918
  const existingIds = new Set(backed.body);
129919
+ const placedIds = /* @__PURE__ */ new Set();
129785
129920
  const entrySlices = topLevelEntrySlices(binding.initializer, "[", "]");
129786
129921
  const ids = expression.elements.map((element, index) => {
129787
129922
  const symbolId = sourceValueSymbol(context, element);
@@ -129812,6 +129947,21 @@ function lowerRowBackedDefault(context, member, backed, baseData3, binding) {
129812
129947
  entrySlices[index]
129813
129948
  );
129814
129949
  }
129950
+ if (!existingIds.has(itemId)) {
129951
+ context.storedRowClaims.set(itemId, {
129952
+ code: "invalid-value-move",
129953
+ message: `Value @id(${JSON.stringify(itemId)}) cannot move into another default list. Remove @id to create a new value.`,
129954
+ site: referenceSite(binding, element)
129955
+ });
129956
+ return itemId;
129957
+ }
129958
+ recordRepeatedRow(
129959
+ context,
129960
+ itemId,
129961
+ `Default list ${binding.label}`,
129962
+ referenceSite(binding, element),
129963
+ placedIds
129964
+ );
129815
129965
  return lowerValueRow(
129816
129966
  context,
129817
129967
  entryMember,
@@ -129832,6 +129982,8 @@ function lowerRowBackedDefault(context, member, backed, baseData3, binding) {
129832
129982
  const keyEnum = dictionaryKeyEnum(context, member);
129833
129983
  const baseBody = backed.body;
129834
129984
  const body = {};
129985
+ const existingIds = new Set(Object.values(baseBody));
129986
+ const placedIds = /* @__PURE__ */ new Set();
129835
129987
  const entrySlices = topLevelEntrySlices(binding.initializer, "{", "}");
129836
129988
  let entryIndex = 0;
129837
129989
  for (const entry of expression.entries) {
@@ -129843,6 +129995,23 @@ function lowerRowBackedDefault(context, member, backed, baseData3, binding) {
129843
129995
  `Default dictionary ${binding.label}[${JSON.stringify(key)}] has no existing owned value row. Creating new default rows from source is not implemented by this slice.`
129844
129996
  );
129845
129997
  }
129998
+ if (context.state[`value:${entryId}`] !== void 0 && !existingIds.has(entryId)) {
129999
+ context.storedRowClaims.set(entryId, {
130000
+ code: "invalid-value-move",
130001
+ message: `Value @id(${JSON.stringify(entryId)}) cannot move into another default dictionary. Remove @id to create a new value.`,
130002
+ site: referenceSite(binding, entry.value)
130003
+ });
130004
+ body[key] = entryId;
130005
+ entryIndex += 1;
130006
+ continue;
130007
+ }
130008
+ recordRepeatedRow(
130009
+ context,
130010
+ entryId,
130011
+ `Default dictionary ${binding.label}`,
130012
+ referenceSite(binding, entry.value),
130013
+ placedIds
130014
+ );
129846
130015
  body[key] = lowerValueRow(
129847
130016
  context,
129848
130017
  entryMember,
@@ -130909,8 +131078,64 @@ function structuralMemberByParameterName(context, effectiveClass) {
130909
131078
  function lowerSeedChild(context, member, expression, source, path, rows, localizedTexts, containerId, environment, authoredSlice) {
130910
131079
  const symbolId = sourceValueSymbol(context, expression);
130911
131080
  if (symbolId !== null) return symbolId;
130912
- const annotatedId = authoredRowId(context, expression, source);
131081
+ const local = context.localValues.resolve(expression, source);
131082
+ const annotatedId = annotatedValue(local.expression).id;
131083
+ if (annotatedId !== null && !isPendingId(annotatedId) && context.state[`value:${annotatedId}`] === void 0 && member.owner.kind === "classMember" && !isUuidV4Id(annotatedId)) {
131084
+ const owner = context.classes.get(member.owner.classId)?.name;
131085
+ const name = owner === void 0 ? member.name : `${owner}.${member.name}`;
131086
+ context.loweringFailures.push({
131087
+ code: "invalid-new-value-id",
131088
+ message: `Value ${name} has @id(${JSON.stringify(annotatedId)}), which names no existing row. A new nested value requires a UUID v4. Remove @id to let Neo assign the value's identity, or provide a valid, unused UUID v4.`,
131089
+ site: referenceSiteAt(
131090
+ local.source,
131091
+ local.expression.kind === "annotated" ? local.expression.annotations.find((entry) => entry.name === "id")?.pos ?? local.expression.pos : local.expression.pos
131092
+ )
131093
+ });
131094
+ }
130913
131095
  const valueId = annotatedId ?? context.structuralStoredIds.get(path) ?? pendingNestedValueId(source, path);
131096
+ if (!context.structuralConstruction && annotatedId !== null && context.state[`value:${annotatedId}`] !== void 0) {
131097
+ const failure = storedClassMoveFailure(
131098
+ context,
131099
+ member,
131100
+ annotatedId,
131101
+ path,
131102
+ containerId
131103
+ );
131104
+ if (failure !== null) {
131105
+ context.storedRowClaims.set(annotatedId, {
131106
+ code: "invalid-value-move",
131107
+ message: failure,
131108
+ site: referenceSite(local.source, local.expression)
131109
+ });
131110
+ return annotatedId;
131111
+ }
131112
+ const rootValueId = singleSlotBindingRoot(
131113
+ context,
131114
+ annotatedId,
131115
+ context.storedMoveRoots
131116
+ );
131117
+ if (rootValueId === null) {
131118
+ context.storedRowClaims.set(annotatedId, {
131119
+ code: "invalid-value-move",
131120
+ message: `Value @id(${JSON.stringify(annotatedId)}) cannot move: moves require an existing path to a root or static binding through non-overridden class fields with inherited storage and automatic distribution.`,
131121
+ site: referenceSite(local.source, local.expression)
131122
+ });
131123
+ return annotatedId;
131124
+ }
131125
+ context.movedValueSites.set(annotatedId, {
131126
+ site: referenceSite(local.source, local.expression),
131127
+ rootValueId
131128
+ });
131129
+ return lowerValueRow(
131130
+ context,
131131
+ member,
131132
+ expression,
131133
+ annotatedId,
131134
+ source,
131135
+ environment,
131136
+ authoredSlice
131137
+ );
131138
+ }
130914
131139
  lowerSeedRow(
130915
131140
  context,
130916
131141
  member,
@@ -130926,6 +131151,99 @@ function lowerSeedChild(context, member, expression, source, path, rows, localiz
130926
131151
  );
130927
131152
  return valueId;
130928
131153
  }
131154
+ function singleSlotBindingRoot(context, start, roots) {
131155
+ const placements = context.valuePlacements;
131156
+ const path = /* @__PURE__ */ new Set();
131157
+ let cursor = start;
131158
+ let root = null;
131159
+ while (!path.has(cursor)) {
131160
+ const cached = roots.get(cursor);
131161
+ if (cached !== void 0) {
131162
+ root = cached;
131163
+ break;
131164
+ }
131165
+ if (placements.memberIdsByBoundValueId.has(cursor)) {
131166
+ root = cursor;
131167
+ break;
131168
+ }
131169
+ path.add(cursor);
131170
+ const parents = placements.placementsByValueId.get(cursor) ?? [];
131171
+ const parent = parents[0];
131172
+ if (parents.length !== 1 || parent === void 0) break;
131173
+ if (parent.containerClassId === null) break;
131174
+ const key = `${parent.containerClassId}:${parent.schemaKey}`;
131175
+ let ordinary = context.ordinaryMoveFields.get(key);
131176
+ if (ordinary === void 0) {
131177
+ const member = classMemberByName(
131178
+ context,
131179
+ parent.containerClassId,
131180
+ parent.schemaKey
131181
+ );
131182
+ ordinary = member.overrideOf === null && member.storage === null && member.storageKey === null && member.distribution === null;
131183
+ context.ordinaryMoveFields.set(key, ordinary);
131184
+ }
131185
+ if (!ordinary) break;
131186
+ cursor = parent.containerValueId;
131187
+ }
131188
+ for (const id2 of path) roots.set(id2, root);
131189
+ return root;
131190
+ }
131191
+ function storedClassMoveFailure(context, member, valueId, path, containerId) {
131192
+ const refuse = (reason) => `Value @id(${JSON.stringify(valueId)}) cannot move: ${reason}. Remove @id to create a new value.`;
131193
+ if (!isUuidV4Id(valueId))
131194
+ return refuse("its identity is derived from its original placement");
131195
+ if (containerId !== void 0)
131196
+ return refuse("unordered-list membership is immutable");
131197
+ if (member.owner.kind !== "classMember")
131198
+ return refuse("the new owner is not a class member");
131199
+ if (member.kind !== "class")
131200
+ return refuse("only nested class values support moves");
131201
+ const base = valueBase(context, valueId);
131202
+ if (typeof base.containerId === "string")
131203
+ return refuse("unordered-list membership is immutable");
131204
+ if ((context.valuePlacements.memberIdsByBoundValueId.get(valueId)?.length ?? 0) > 0)
131205
+ return refuse("a root or static binding owns it");
131206
+ const placement = context.readStoredValuePlacements().placementByChildValueId.get(valueId);
131207
+ if (placement === void 0)
131208
+ return refuse("its old owning class slot could not be resolved");
131209
+ if (placement.source !== "body")
131210
+ return refuse("its old owner supplies it through a constructor");
131211
+ const parent = valueBase(context, placement.parentValueId);
131212
+ const targetParentId = path.slice(0, -(member.name.length + 1));
131213
+ if (context.state[`value:${targetParentId}`] === void 0)
131214
+ return refuse("the new parent is not an existing stored class value");
131215
+ const targetParent = valueBase(context, targetParentId);
131216
+ for (const row of [base, parent, targetParent]) {
131217
+ if (typeof row.sourceValueId === "string")
131218
+ return refuse("a projected value depends on its original owner");
131219
+ if (typeof row.mapKey === "string")
131220
+ return refuse("partitioned values cannot change ownership");
131221
+ if (isObjectRecord2(row.genericBindings) && Object.keys(row.genericBindings).length > 0)
131222
+ return refuse("generic value ownership cannot be changed");
131223
+ }
131224
+ if (typeof targetParent.instanceConstructorId === "string" || isObjectRecord2(targetParent.constructorArgs) && Object.keys(targetParent.constructorArgs).length > 0)
131225
+ return refuse("the new owner has constructor creation data");
131226
+ if (typeof parent.instanceConstructorId === "string" || isObjectRecord2(parent.constructorArgs) && Object.keys(parent.constructorArgs).length > 0)
131227
+ return refuse("its old owner has constructor creation data");
131228
+ const oldMember = placement.memberId === null ? void 0 : context.members.get(placement.memberId);
131229
+ if (oldMember?.kind !== "class")
131230
+ return refuse(
131231
+ "its old owner is not a class member containing a class value"
131232
+ );
131233
+ if (oldMember.storageKey !== member.storageKey)
131234
+ return refuse("the new member declares a different storage key");
131235
+ if (oldMember.distribution !== member.distribution)
131236
+ return refuse("the new member declares a different distribution");
131237
+ if (oldMember.storage !== member.storage)
131238
+ return refuse("the new member uses different storage");
131239
+ if (oldMember.classId !== member.classId)
131240
+ return refuse("the new member declares a different class type");
131241
+ if (Object.keys(oldMember.classArguments).length > 0)
131242
+ return refuse("generic member ownership cannot be changed");
131243
+ if (Object.keys(member.classArguments).length > 0)
131244
+ return refuse("the new member declares generic arguments");
131245
+ return null;
131246
+ }
130929
131247
  function unorderedRowSubtreePath(valueId, path, containerId) {
130930
131248
  if (containerId === void 0 || containerId === null) return path;
130931
131249
  return valueId;
@@ -131174,7 +131492,8 @@ function lowerValueBody(context, member, expression, base, source, environment,
131174
131492
  expression,
131175
131493
  base,
131176
131494
  source,
131177
- environment
131495
+ environment,
131496
+ authoredSlice
131178
131497
  );
131179
131498
  default:
131180
131499
  throw new Error(
@@ -132133,9 +132452,9 @@ function lowerListValue(context, member, expression, base, source, inheritedEnvi
132133
132452
  value: member.listKind === "unordered" ? [] : ids
132134
132453
  };
132135
132454
  }
132136
- function recordRepeatedRow(context, rowId, collection, site) {
132137
- if (!context.placedStoredRowIds.has(rowId)) {
132138
- context.placedStoredRowIds.add(rowId);
132455
+ function recordRepeatedRow(context, rowId, collection, site, placedIds = context.placedStoredRowIds) {
132456
+ if (!placedIds.has(rowId)) {
132457
+ placedIds.add(rowId);
132139
132458
  return;
132140
132459
  }
132141
132460
  context.loweringFailures.push({
@@ -132177,18 +132496,20 @@ function existingListItemIds(context, member, base) {
132177
132496
  Array.isArray(base.value) ? base.value.filter((entry) => typeof entry === "string") : []
132178
132497
  );
132179
132498
  }
132180
- function lowerDictionaryValue(context, member, expression, base, source, inheritedEnvironment) {
132499
+ function lowerDictionaryValue(context, member, expression, base, source, inheritedEnvironment, authoredSlice) {
132181
132500
  if (expression.kind === "litNull")
132182
132501
  return { ...valueFileFields(base), value: null };
132183
132502
  if (expression.kind !== "litDict") {
132184
132503
  throw new Error("Dictionary values require {...}.");
132185
132504
  }
132186
132505
  const baseBody = isObjectRecord2(base.value) ? base.value : {};
132506
+ const existingIds = new Set(Object.values(baseBody));
132187
132507
  const keyEnum = dictionaryKeyEnum(context, member);
132188
132508
  const entryMember = requiredMember(context, member.entryMemberId);
132189
132509
  const environment = valueGenericEnvironment(base, inheritedEnvironment);
132190
132510
  const body = {};
132191
- for (const entry of expression.entries) {
132511
+ const entrySlices = authoredSlice === void 0 ? [] : topLevelEntrySlices(authoredSlice, "{", "}");
132512
+ for (const [entryIndex, entry] of expression.entries.entries()) {
132192
132513
  const key = lowerDictionaryKey(entry.key, keyEnum, member.name);
132193
132514
  const existingId = baseBody[key];
132194
132515
  const symbolId = sourceValueSymbol(context, entry.value);
@@ -132208,6 +132529,15 @@ function lowerDictionaryValue(context, member, expression, base, source, inherit
132208
132529
  `Dictionary ${member.name}[${JSON.stringify(key)}] has no existing row. New dictionary entries are not implemented by this slice.`
132209
132530
  );
132210
132531
  }
132532
+ if (context.state[`value:${valueId}`] !== void 0 && !existingIds.has(valueId)) {
132533
+ context.storedRowClaims.set(valueId, {
132534
+ code: "invalid-value-move",
132535
+ message: `Value @id(${JSON.stringify(valueId)}) cannot move into another dictionary. Remove @id to create a new value.`,
132536
+ site: referenceSite(source, entry.value)
132537
+ });
132538
+ body[key] = valueId;
132539
+ continue;
132540
+ }
132211
132541
  recordRepeatedRow(
132212
132542
  context,
132213
132543
  valueId,
@@ -132220,7 +132550,8 @@ function lowerDictionaryValue(context, member, expression, base, source, inherit
132220
132550
  entry.value,
132221
132551
  valueId,
132222
132552
  source,
132223
- environment
132553
+ environment,
132554
+ dictionaryValueSlice(entrySlices[entryIndex])
132224
132555
  );
132225
132556
  }
132226
132557
  return { ...valueFileFields(base), value: body };
@@ -133724,6 +134055,15 @@ function emitValue(context, member, valueId, options) {
133724
134055
  );
133725
134056
  return prefixFor(expression);
133726
134057
  }
134058
+ function needsProspectiveValueIdentity(context, valueId, parentId, key) {
134059
+ if (pendingVirtualValueIdentity(valueId) !== null) return true;
134060
+ if (context.storedValueBase === void 0 || isPendingId(valueId))
134061
+ return false;
134062
+ if (typeof parentId !== "string")
134063
+ return !context.storedValueBase.has(valueId);
134064
+ const storedBody = context.storedValueBase.get(parentId)?.value;
134065
+ return !isObjectRecord2(storedBody) || storedBody[key] !== valueId;
134066
+ }
133727
134067
  function emitValueBody(context, member, value, visited, targetTyped, environment) {
133728
134068
  const kind = numberField(member, "kind");
133729
134069
  const body = value.value;
@@ -133993,9 +134333,13 @@ function classValueBody(context, member, value, visited, targetTyped, outerEnvir
133993
134333
  `${key} = ${emitValue(context, emittedChildMember, childId, {
133994
134334
  environment,
133995
134335
  definitionSite: !context.symbolsByValueId.has(childId),
133996
- // Existing slots recover identity from storage. A newly pinned virtual
133997
- // slot must keep its derived pending identity through repeat lowering.
133998
- writeIdentity: pendingVirtualValueIdentity(childId) !== null,
134336
+ // New and moved identities must survive repeat lowering against the old placement.
134337
+ writeIdentity: needsProspectiveValueIdentity(
134338
+ context,
134339
+ childId,
134340
+ value.id,
134341
+ key
134342
+ ),
133999
134343
  visited
134000
134344
  })}`
134001
134345
  );
@@ -135273,9 +135617,14 @@ ${entries.flatMap(
135273
135617
  {
135274
135618
  environment,
135275
135619
  definitionSite: !context.symbolsByValueId.has(id2),
135276
- // Dictionary keys, like class schema keys, recover entry
135277
- // identity from the owning row. Only list entries need an id.
135278
- writeIdentity: false,
135620
+ // Existing keys recover identity from their owning row;
135621
+ // new authored identities must survive repeat lowering.
135622
+ writeIdentity: needsProspectiveValueIdentity(
135623
+ context,
135624
+ id2,
135625
+ value.id,
135626
+ key
135627
+ ),
135279
135628
  visited
135280
135629
  }
135281
135630
  )}`,
@@ -137495,55 +137844,6 @@ var init_status_output = __esm({
137495
137844
  }
137496
137845
  });
137497
137846
 
137498
- // src/project-source/identity-rewriter.ts
137499
- function createIdentityRewriter(assignments) {
137500
- if (assignments.size === 0) return (value) => value;
137501
- const root = { children: /* @__PURE__ */ new Map() };
137502
- for (const [source, replacement] of assignments) {
137503
- let prefix = root;
137504
- for (let index = 0; index < source.length; index += 1) {
137505
- const character = source[index];
137506
- let child2 = prefix.children.get(character);
137507
- if (child2 === void 0) {
137508
- child2 = { children: /* @__PURE__ */ new Map() };
137509
- prefix.children.set(character, child2);
137510
- }
137511
- prefix = child2;
137512
- }
137513
- prefix.replacement = replacement;
137514
- }
137515
- return (value) => {
137516
- const exact = assignments.get(value);
137517
- if (exact !== void 0) return exact;
137518
- const parts = [];
137519
- let copiedThrough = 0;
137520
- for (let start = 0; start < value.length; start += 1) {
137521
- let prefix = root.children.get(value[start]);
137522
- let replacement;
137523
- let end = start;
137524
- for (let index = start; prefix !== void 0; index += 1) {
137525
- if (prefix.replacement !== void 0) {
137526
- replacement = prefix.replacement;
137527
- end = index + 1;
137528
- }
137529
- prefix = prefix.children.get(value[index + 1]);
137530
- }
137531
- if (replacement === void 0) continue;
137532
- parts.push(value.slice(copiedThrough, start), replacement);
137533
- copiedThrough = end;
137534
- start = end - 1;
137535
- }
137536
- if (copiedThrough === 0) return value;
137537
- parts.push(value.slice(copiedThrough));
137538
- return parts.join("");
137539
- };
137540
- }
137541
- var init_identity_rewriter = __esm({
137542
- "src/project-source/identity-rewriter.ts"() {
137543
- "use strict";
137544
- }
137545
- });
137546
-
137547
137847
  // src/project-source/source-layout.ts
137548
137848
  import { readFileSync as readFileSync9 } from "node:fs";
137549
137849
  import { relative as relative4, sep as sep4 } from "node:path";
@@ -164838,7 +165138,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
164838
165138
  async function main() {
164839
165139
  const args = parseArgs(process.argv.slice(2));
164840
165140
  if (args.command === "--version") {
164841
- console.log("0.68.2");
165141
+ console.log("0.68.4");
164842
165142
  return;
164843
165143
  }
164844
165144
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
@@ -22401,6 +22401,8 @@ function resolveBaseConstructorRecord(record3, ctx) {
22401
22401
  );
22402
22402
  if (resolution.kind === "resolved") return resolution.overload;
22403
22403
  if (resolution.kind === "noMatch" && baseArguments.length === 0) {
22404
+ if (owningClass.requiredConstructorId === record3.id && evaluatorSchemaIndex(ctx).class(baseClassId)?.requiredConstructorId == null)
22405
+ return null;
22404
22406
  throw new NSGetterRuntimeError(
22405
22407
  `Constructor '${record3.id}' on '${owningClass.name}' must call ': base(...)': its base class declares constructors but none parameterless.`
22406
22408
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.68.2",
3
+ "version": "0.68.4",
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.68.2 -->
12
+ <!-- reviewed-through-cli: 0.68.4 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -63,8 +63,9 @@ opaque JSON or guessed syntax.
63
63
  values, files, or other records. `@system` is reserved for platform-owned
64
64
  system records; authoring it can make the record uneditable.
65
65
  - Preserve existing `@id` annotations through rename, reorder, and file moves.
66
- A new row cannot reuse a stored row's `@id`, including inside a new parent:
67
- author the new row without it.
66
+ A copy must get a new ID. A stored nested class value may keep its UUID-v4
67
+ when moved between compatible empty fields on existing parents, provided
68
+ its old parent no longer declares it. See the identity reference for limits.
68
69
  - Omit `@id` on new ordinary declarations and nested rows unless a same-push
69
70
  structural reference genuinely requires an explicit UUID. A successful push
70
71
  assigns the durable IDs and rewrites source.
@@ -110,7 +110,7 @@ wrappers.
110
110
  The marker near the top of `SKILL.md` must exactly match the package version:
111
111
 
112
112
  ```html
113
- <!-- reviewed-through-cli: 0.68.2 -->
113
+ <!-- reviewed-through-cli: 0.68.4 -->
114
114
  ```
115
115
 
116
116
  The quoted version above is checked too, so this instruction cannot go stale
@@ -385,6 +385,14 @@ class Child(string name) : Base {
385
385
  }
386
386
  ```
387
387
 
388
+ A header base clause with no arguments, such as `: Base()` or `: Base`,
389
+ runs a zero-argument or fully defaulted base constructor when available.
390
+ Otherwise, if the base has only declared constructors, it uses the member
391
+ initializers and base block without running a base constructor. The base block
392
+ must settle any remaining required members. A required base constructor cannot
393
+ be skipped. A declared constructor's `: base()` must call a base constructor
394
+ when the base declares one.
395
+
388
396
  Bind generic base arguments explicitly when needed. Reject a subclass that
389
397
  omits required base arguments or leaves any inherited required member
390
398
  unsettled.
@@ -57,9 +57,19 @@ keys as quoted IDs when their option has been deleted.
57
57
 
58
58
  ## Identity rules
59
59
 
60
- Preserve every existing ID through rename, reorder, and file moves. A new row
61
- cannot reuse a stored row's `@id`, including inside a new parent, and source
62
- cannot place one stored row in two collection positions. For a new ordinary record, omit
60
+ Preserve every existing ID through rename, reorder, and file moves. A copy
61
+ must get a new ID. A stored nested class value may keep its UUID-v4 when moved
62
+ to an empty, compatible class field on another existing parent. Remove its
63
+ old owning declaration in the same push. Both fields must declare the same
64
+ non-generic class and storage rules. The move must leave exactly one owning
65
+ path to the same root or static binding through ordinary class fields.
66
+ Both ownership paths must use inherited storage and automatic distribution;
67
+ member overrides are not supported for moves. Moving
68
+ projected values, constructor-owned slots, partitioned values, generic
69
+ instances, or unordered-list entries is refused. Moves into newly constructed
70
+ parents are also refused; omit the ID to create a new value there.
71
+
72
+ For a new ordinary record, omit
63
73
  `@id` and let a successful real push assign and insert it. Dry-run and failed pushes
64
74
  leave tracked source unchanged.
65
75
 
@@ -91,6 +101,11 @@ form, an author may assign a previously unseen UUID-v4 with `@id` to the new
91
101
  row and reference it in the same edit. Reusing an existing ID means update, not
92
102
  create. The server validates authored new IDs for UUID-v4 shape and uniqueness.
93
103
  Prefer generated IDs unless the structural reference needs an ID immediately.
104
+ Dry-run preserves a valid authored UUID-v4 ID on a new nested member or dictionary
105
+ value, including one declared as a local initializer. An unknown malformed member
106
+ value ID is an error at its annotation; remove `@id` to let push assign the ID,
107
+ or supply an unused UUID v4. This does not change the derived identity of a
108
+ root/static binding or an existing projected value.
94
109
 
95
110
  Do not invent IDs for structural rows whose identities derive from an owner
96
111
  role. Do not add a separate root/static initializer ID. A constructor argument