@neocompose/cli 0.51.0 → 0.51.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,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.51.2] - 2026-09-16
4
+
5
+ - Preserve aggregate arguments owned by declared constructors when materializing nested values, including `NeoCellPattern` values in stored bindings.
6
+ - Preserve local initializer identities when canonical source emission reorders unordered lists.
7
+ - Preserve texture and audio templates in script documents so tests and script evaluation can construct classes with template-backed sprite defaults.
8
+ - Identify a pending server derived-edge repair explicitly instead of recommending another pull to rebuild the index.
9
+ - Keep local tests and dialogue dry-runs available while the server's derived-edge index awaits repair. Push dry-runs still check the live server watermark.
10
+
11
+ ## [0.51.1] - 2026-09-16
12
+
13
+ - Refresh compiler-only changes with ordinary `neo pull` before compiling the local workspace. Preserve authored source, local edits, custom paths, and managed binaries while adopting the current server compilation.
14
+ - Refuse a normal pull when the baseline is missing but authored source files remain, preventing duplicate declarations and overwritten local edits. An explicit forced refresh recovers custom file paths from the existing declarations.
15
+
3
16
  ## [0.51.0] - 2026-09-15
4
17
 
5
18
  Compiler revision 14 requires Unity SDK 0.37.0. The server migration recompiles stored scripts and retained history.
package/dist/neo.mjs CHANGED
@@ -71079,15 +71079,14 @@ function ownedConstructorArgumentValues(row, ctx, readRow) {
71079
71079
  }
71080
71080
  const classId = row.classId;
71081
71081
  if (typeof classId !== "string") return [];
71082
- const schemaClass2 = evaluatorSchemaIndex(ctx).class(classId);
71083
- const requiredConstructorId2 = schemaClass2?.requiredConstructorId;
71084
- if (requiredConstructorId2 === void 0 || requiredConstructorId2 === null) {
71085
- return [];
71086
- }
71087
- const constructor2 = evaluatorSchemaIndex(ctx).constructorRecord(
71088
- requiredConstructorId2
71089
- );
71082
+ const constructorId = row.instanceConstructorId;
71083
+ if (constructorId === void 0 || constructorId === null) return [];
71084
+ const constructor2 = evaluatorSchemaIndex(ctx).constructorRecord(constructorId);
71090
71085
  if (constructor2 === void 0 || constructor2.classId !== classId) return [];
71086
+ if (!constructor2.argumentTypes.some(
71087
+ (typeInfo) => typeInfo.type === 7 /* Class */ || typeInfo.type === 22 /* Interface */ || typeInfo.type === 6 /* List */ || typeInfo.type === 5 /* Dictionary */ || typeInfo.type === 21 /* Generic */
71088
+ ))
71089
+ return [];
71091
71090
  let genericEnv = /* @__PURE__ */ new Map();
71092
71091
  try {
71093
71092
  genericEnv = runtimeGenericEnvForValueRow(row, ctx, /* @__PURE__ */ new Set(), readRow);
@@ -87329,6 +87328,44 @@ function stampUILogicBody(logic, bodyKey) {
87329
87328
  body.compilerRevision = NEOSCRIPT_COMPILER_REVISION;
87330
87329
  return true;
87331
87330
  }
87331
+ function stampUILogicBodies(recordKind, data) {
87332
+ const record5 = asRecord2(data);
87333
+ if (record5 === null) return false;
87334
+ if (recordKind === "dialogue-group") {
87335
+ return stampConditionList(record5.conditions);
87336
+ }
87337
+ if (recordKind !== "dialogue-node") return false;
87338
+ let changed = stampConditionList(record5.conditions);
87339
+ if (Array.isArray(record5.actions)) {
87340
+ for (const actionValue2 of record5.actions) {
87341
+ const logic = asRecord2(asRecord2(actionValue2)?.logic);
87342
+ changed = stampUILogicBody(logic, "action") || changed;
87343
+ }
87344
+ }
87345
+ if (Array.isArray(record5.outcomes)) {
87346
+ for (const outcomeValue of record5.outcomes) {
87347
+ changed = stampConditionList(asRecord2(outcomeValue)?.conditions) || changed;
87348
+ }
87349
+ }
87350
+ const options = asRecord2(record5.optionSettings)?.options;
87351
+ if (Array.isArray(options)) {
87352
+ for (const optionValue of options) {
87353
+ const settings = asRecord2(asRecord2(optionValue)?.settings);
87354
+ if (settings === null) continue;
87355
+ changed = stampConditionList(settings.conditions) || changed;
87356
+ changed = stampConditionList(settings.selectableConditions) || changed;
87357
+ }
87358
+ }
87359
+ return changed;
87360
+ }
87361
+ function stampConditionList(value) {
87362
+ if (!Array.isArray(value)) return false;
87363
+ let changed = false;
87364
+ for (const conditionValue of value) {
87365
+ changed = stampUILogicBody(asRecord2(conditionValue), "getter") || changed;
87366
+ }
87367
+ return changed;
87368
+ }
87332
87369
  function asRecord2(value) {
87333
87370
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
87334
87371
  return null;
@@ -88759,6 +88796,15 @@ var init_localizable_member_value_writes = __esm({
88759
88796
  }
88760
88797
  });
88761
88798
 
88799
+ // ../src/database/project-snapshot-derived-edge-version.ts
88800
+ var PROJECT_SNAPSHOT_DERIVED_EDGE_EXTRACTOR_VERSION;
88801
+ var init_project_snapshot_derived_edge_version = __esm({
88802
+ "../src/database/project-snapshot-derived-edge-version.ts"() {
88803
+ "use strict";
88804
+ PROJECT_SNAPSHOT_DERIVED_EDGE_EXTRACTOR_VERSION = 4;
88805
+ }
88806
+ });
88807
+
88762
88808
  // ../src/database/project-snapshot-derived-edges.ts
88763
88809
  function extractProjectSnapshotDerivedEdges(source) {
88764
88810
  const edges = /* @__PURE__ */ new Map();
@@ -88917,7 +88963,7 @@ function collectDirectBodyChildIds(value) {
88917
88963
  function asRecord3(value) {
88918
88964
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
88919
88965
  }
88920
- var PROJECT_SNAPSHOT_DERIVED_EDGE_EXTRACTOR_VERSION, STORED_PROJECT_RECORD_ID_PATTERN;
88966
+ var STORED_PROJECT_RECORD_ID_PATTERN;
88921
88967
  var init_project_snapshot_derived_edges = __esm({
88922
88968
  "../src/database/project-snapshot-derived-edges.ts"() {
88923
88969
  "use strict";
@@ -88925,7 +88971,7 @@ var init_project_snapshot_derived_edges = __esm({
88925
88971
  init_neo_script_recompile_scope();
88926
88972
  init_member_kinds();
88927
88973
  init_packed_value_encoding();
88928
- PROJECT_SNAPSHOT_DERIVED_EDGE_EXTRACTOR_VERSION = 4;
88974
+ init_project_snapshot_derived_edge_version();
88929
88975
  STORED_PROJECT_RECORD_ID_PATTERN = /^(?:system_)?[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
88930
88976
  }
88931
88977
  });
@@ -115890,11 +115936,13 @@ function verifyProjectSourceCommitAgainstStateV4(args) {
115890
115936
  assignments
115891
115937
  );
115892
115938
  const usedAssignments = /* @__PURE__ */ new Set();
115939
+ const positionless = positionIndependentAssignments(assignments);
115940
+ const rewrite = (value) => rewritePending(value, assignments, usedAssignments, positionless);
115893
115941
  const expectedChanges = status.changes.map((change) => {
115894
- const rewrittenData = change.nextData === void 0 ? void 0 : rewritePending(change.nextData, assignments, usedAssignments);
115942
+ const rewrittenData = change.nextData === void 0 ? void 0 : rewrite(change.nextData);
115895
115943
  return {
115896
115944
  recordKind: change.recordKind,
115897
- recordId: rewritePending(change.recordId, assignments, usedAssignments),
115945
+ recordId: rewrite(change.recordId),
115898
115946
  operation: change.kind,
115899
115947
  // Source syntax does not author project ownership. The CLI stamps the
115900
115948
  // authenticated project id on creates immediately before transport;
@@ -115906,14 +115954,12 @@ function verifyProjectSourceCommitAgainstStateV4(args) {
115906
115954
  });
115907
115955
  const expectedSeeds = [...status.authoredValueSeeds].map(
115908
115956
  ([memberId, seed]) => ({
115909
- memberId: rewritePending(memberId, assignments, usedAssignments),
115910
- ...rewritePending(seed, assignments, usedAssignments)
115957
+ memberId: rewrite(memberId),
115958
+ ...rewrite(seed)
115911
115959
  })
115912
115960
  );
115913
- const expectedInitializerMaterialization = rewritePending(
115914
- status.localInitializerMaterialization ?? { roots: [] },
115915
- assignments,
115916
- usedAssignments
115961
+ const expectedInitializerMaterialization = rewrite(
115962
+ status.localInitializerMaterialization ?? { roots: [] }
115917
115963
  );
115918
115964
  for (const pendingId2 of assignments.keys()) {
115919
115965
  if (!usedAssignments.has(pendingId2)) {
@@ -116078,8 +116124,27 @@ function positionIndependentAssignments(assignments) {
116078
116124
  }
116079
116125
  return resolved;
116080
116126
  }
116081
- function rewritePending(value, assignments, used, byPosition) {
116082
- const positionless = byPosition ?? positionIndependentAssignments(assignments);
116127
+ function relocatedAssignment(pendingId2, assignments, byPosition) {
116128
+ const key = positionIndependentPendingKey(pendingId2);
116129
+ const relocated = key === null ? void 0 : byPosition.get(key);
116130
+ if (relocated !== void 0) return relocated;
116131
+ const virtual = pendingVirtualValueIdentity(pendingId2);
116132
+ if (virtual === null) return null;
116133
+ let relocatedRoot = virtual.instanceRootId;
116134
+ if (!assignments.has(relocatedRoot)) {
116135
+ const rootKey = positionIndependentPendingKey(relocatedRoot);
116136
+ if (rootKey === null) return null;
116137
+ const canonicalRoot = byPosition.get(rootKey);
116138
+ if (canonicalRoot === void 0) return null;
116139
+ relocatedRoot = canonicalRoot;
116140
+ }
116141
+ const relocatedVirtual = pendingVirtualValueId(
116142
+ relocatedRoot,
116143
+ virtual.sourceIdentity
116144
+ );
116145
+ return assignments.has(relocatedVirtual) ? relocatedVirtual : null;
116146
+ }
116147
+ function rewritePending(value, assignments, used, positionless) {
116083
116148
  if (typeof value === "string") {
116084
116149
  const exact = assignments.get(value);
116085
116150
  if (exact !== void 0) {
@@ -116087,9 +116152,8 @@ function rewritePending(value, assignments, used, byPosition) {
116087
116152
  return exact;
116088
116153
  }
116089
116154
  if (value.startsWith("__pending__:")) {
116090
- const key = positionIndependentPendingKey(value);
116091
- const relocated = key === null ? void 0 : positionless.get(key);
116092
- if (relocated !== void 0) {
116155
+ const relocated = relocatedAssignment(value, assignments, positionless);
116156
+ if (relocated !== null) {
116093
116157
  used.add(relocated);
116094
116158
  return assignments.get(relocated);
116095
116159
  }
@@ -116097,6 +116161,7 @@ function rewritePending(value, assignments, used, byPosition) {
116097
116161
  `Verified source identity ${value} has no pending id assignment.`
116098
116162
  );
116099
116163
  }
116164
+ if (!value.includes("__pending__:")) return value;
116100
116165
  let rewritten = value;
116101
116166
  for (const [pendingId2, assignedId] of assignments) {
116102
116167
  if (!rewritten.includes(pendingId2)) continue;
@@ -116414,7 +116479,6 @@ function assertServerPreparationSucceeds(args) {
116414
116479
  });
116415
116480
  if (refusal !== null) throw new ServerPreparationPreflightError(refusal);
116416
116481
  }
116417
- assertDerivedEdgeCompatibility(args.workspace);
116418
116482
  try {
116419
116483
  return prepareServerPreparationChanges({
116420
116484
  ...args,
@@ -116443,6 +116507,14 @@ function assertDerivedEdgeCompatibility(workspace) {
116443
116507
  `Project "${workspace.config.projectId}" has no derived-edge watermark. Run \`neo pull\` and retry. If this persists, repair the project's derived edge index.`
116444
116508
  );
116445
116509
  }
116510
+ if (serverVersion === 0) {
116511
+ throw new DerivedEdgeCompatibilityError(
116512
+ "server-stale",
116513
+ localVersion,
116514
+ serverVersion,
116515
+ `Project "${workspace.config.projectId}" has a pending server derived-edge repair (watermark 0). Wait for repair to complete and retry. If it remains pending, inspect the server repair status and logs; \`neo pull\` cannot rebuild this index.`
116516
+ );
116517
+ }
116446
116518
  if (serverVersion > localVersion) {
116447
116519
  throw new DerivedEdgeCompatibilityError(
116448
116520
  "cli-stale",
@@ -120468,7 +120540,9 @@ function structuralMemberByParameterName(context, effectiveClass) {
120468
120540
  function lowerSeedChild(context, member, expression, source, path, rows, localizedTexts, containerId, environment, authoredSlice) {
120469
120541
  const symbolId = sourceValueSymbol(context, expression);
120470
120542
  if (symbolId !== null) return symbolId;
120471
- const annotatedId = annotatedValue(expression).id;
120543
+ const annotatedId = annotatedValue(
120544
+ context.localValues.resolve(expression, source).expression
120545
+ ).id;
120472
120546
  const valueId = annotatedId ?? context.structuralStoredIds.get(path) ?? pendingNestedValueId(source, path);
120473
120547
  lowerSeedRow(
120474
120548
  context,
@@ -126661,6 +126735,14 @@ var init_convex = __esm({
126661
126735
  // src/project-source/source-layout.ts
126662
126736
  import { readFileSync as readFileSync8 } from "node:fs";
126663
126737
  import { relative as relative4, sep as sep4 } from "node:path";
126738
+ function workspaceForSourceLayoutV4(workspace, document) {
126739
+ if (Object.keys(workspace.state.records).length > 0) return workspace;
126740
+ const records2 = {};
126741
+ for (const [key, record5] of document.records) {
126742
+ if (!record5.deleted) records2[key] = record5;
126743
+ }
126744
+ return { ...workspace, state: { ...workspace.state, records: records2 } };
126745
+ }
126664
126746
  function readWorkspaceRecordFilesV4(workspace) {
126665
126747
  const paths = /* @__PURE__ */ new Map();
126666
126748
  const keysById = /* @__PURE__ */ new Map();
@@ -127446,17 +127528,7 @@ import {
127446
127528
  import { dirname as dirname7, join as join8, relative as relative5, sep as sep5 } from "node:path";
127447
127529
  function prepareWorkspaceResetV4(workspace, document, options = {}) {
127448
127530
  const emissionRecords = options.regenerateSourceNames ? regenerateDialogueSourceNamesV4(document.records) : document.records;
127449
- let layoutWorkspace = workspace;
127450
- if (Object.keys(workspace.state.records).length === 0) {
127451
- const records2 = {};
127452
- for (const [key, record5] of document.records) {
127453
- if (!record5.deleted) records2[key] = record5;
127454
- }
127455
- layoutWorkspace = {
127456
- ...workspace,
127457
- state: { ...workspace.state, records: records2 }
127458
- };
127459
- }
127531
+ const layoutWorkspace = workspaceForSourceLayoutV4(workspace, document);
127460
127532
  const status = listProjectSourceFilesV4(workspace.root).length === 0 ? null : readWorkspaceLayoutStatusV4(layoutWorkspace);
127461
127533
  const localValues = status?.localValues ?? workspace.state.localValues;
127462
127534
  const emitted = emitProjectDocumentFilesV4(emissionRecords, {
@@ -127874,6 +127946,40 @@ var init_static_value_seed_records = __esm({
127874
127946
  }
127875
127947
  });
127876
127948
 
127949
+ // src/project-sync/compiler-metadata.ts
127950
+ function compilerMetadataChangedKeys(previous, document, changedKeys) {
127951
+ const keys = changedKeys ?? /* @__PURE__ */ new Set([...Object.keys(previous), ...document.records.keys()]);
127952
+ const refreshed = /* @__PURE__ */ new Set();
127953
+ for (const key of keys) {
127954
+ const before = previous[key];
127955
+ const after = document.records.get(key);
127956
+ if (before === void 0 || after === void 0 || after.deleted || before.recordId !== after.recordId || before.recordKind !== after.recordKind || !isProjectRecordKind(after.recordKind))
127957
+ return null;
127958
+ if (before.contentHash === after.contentHash) continue;
127959
+ if (!isObjectRecord2(before.data) || !isObjectRecord2(after.data) || !canonicallyEqual(
127960
+ authoredCompilerInput(after.recordKind, before.data),
127961
+ authoredCompilerInput(after.recordKind, after.data)
127962
+ ))
127963
+ return null;
127964
+ refreshed.add(key);
127965
+ }
127966
+ return refreshed.size === 0 ? null : refreshed;
127967
+ }
127968
+ function authoredCompilerInput(kind, data) {
127969
+ const result = stripClientDerivedBodies(kind, data);
127970
+ stampUILogicBodies(kind, result);
127971
+ if (isObjectRecord2(result)) delete result.updatedAt;
127972
+ return result;
127973
+ }
127974
+ var init_compiler_metadata = __esm({
127975
+ "src/project-sync/compiler-metadata.ts"() {
127976
+ "use strict";
127977
+ init_project2();
127978
+ init_neo_script_derived_bodies();
127979
+ init_projection();
127980
+ }
127981
+ });
127982
+
127877
127983
  // src/commands/pull.ts
127878
127984
  var pull_exports = {};
127879
127985
  __export(pull_exports, {
@@ -127903,7 +128009,15 @@ async function runPull(workspace, options, progress = spinner("Checking local wo
127903
128009
  async function runNormalPull(workspace, options, progress) {
127904
128010
  const destructive = options.force;
127905
128011
  const hasBaseline = Object.keys(workspace.state.records).length > 0;
128012
+ if (!destructive && !hasBaseline && listProjectSourceFilesV4(workspace.root).length > 0) {
128013
+ throw new Error(
128014
+ "Cannot merge existing Neo sources without a pull baseline in .neo/state.json. Restore that file to preserve local edits, or back up your sources and run `neo pull --force` to replace them with the server version while preserving their paths."
128015
+ );
128016
+ }
127906
128017
  const mainLocale = workspaceMainLocale(workspace.state.records);
128018
+ const hadConflicts = Object.values(workspace.state.records).some(
128019
+ (record5) => record5.conflictServerHash !== void 0 || record5.conflictFiles !== void 0 || record5.projectBinary?.conflict !== void 0
128020
+ );
127907
128021
  if (acceptSourceEquivalentConflictBases(workspace, mainLocale) > 0) {
127908
128022
  writeWorkspaceState(workspace.root, workspace.state);
127909
128023
  }
@@ -127961,6 +128075,28 @@ async function runNormalPull(workspace, options, progress) {
127961
128075
  document = await fetchProjectDocument(workspace);
127962
128076
  nextCursor = document.revision === void 0 ? null : cursorFromRevision(document.revision);
127963
128077
  }
128078
+ const compilerRefreshKeys = !destructive && hasBaseline && !hadConflicts && !options.regenerateSourceNames ? compilerMetadataChangedKeys(
128079
+ workspace.state.records,
128080
+ document,
128081
+ changedRecordKeys
128082
+ ) : null;
128083
+ if (compilerRefreshKeys !== null) {
128084
+ if (changedRecordKeys === null) {
128085
+ await updateFullPullSyncMetadata(workspace, document);
128086
+ observedHeadTransactionHash = workspace.state.headTransactionHash;
128087
+ observedDerivedEdgeVersion = workspace.state.derivedEdgeVersion;
128088
+ }
128089
+ finishRecordOnlyDeltaPull({
128090
+ workspace,
128091
+ document,
128092
+ changedRecordKeys: compilerRefreshKeys,
128093
+ nextCursor,
128094
+ observedHeadTransactionHash,
128095
+ observedDerivedEdgeVersion,
128096
+ progress
128097
+ });
128098
+ return;
128099
+ }
127964
128100
  let localByKey = /* @__PURE__ */ new Map();
127965
128101
  let localStatus = null;
127966
128102
  if (hasBaseline && !destructive) {
@@ -128151,18 +128287,7 @@ async function runResetPull(workspace, progress) {
128151
128287
  if (record5) record5.projectBinary = state;
128152
128288
  }
128153
128289
  progress.update("Finalizing pull state\u2026");
128154
- try {
128155
- const convex = await createConvexClient(workspace);
128156
- const signal = await convex.query(api2.projectExportData.schemaSignal, {
128157
- projectId: workspace.config.projectId,
128158
- versionId: workspace.config.versionId
128159
- });
128160
- workspace.state.headTransactionHash = document.revision !== void 0 && signal?.transactionId === document.revision.latestTransactionId ? signal.transactionHash : null;
128161
- workspace.state.derivedEdgeVersion = signal?.derivedEdgeVersion ?? null;
128162
- } catch {
128163
- workspace.state.headTransactionHash = null;
128164
- delete workspace.state.derivedEdgeVersion;
128165
- }
128290
+ await updateFullPullSyncMetadata(workspace, document);
128166
128291
  if (document.revision !== void 0) {
128167
128292
  workspace.state.documentRevisionCursor = mutableCursor(
128168
128293
  cursorFromRevision(document.revision)
@@ -128234,11 +128359,12 @@ async function finishFormat4Pull(args) {
128234
128359
  ([key, record5]) => localRecords.get(key) === record5 ? [] : [key]
128235
128360
  )
128236
128361
  );
128237
- const recordFiles = readWorkspaceRecordFilesV4(workspace);
128362
+ const layoutWorkspace = workspaceForSourceLayoutV4(workspace, document);
128363
+ const recordFiles = readWorkspaceRecordFilesV4(layoutWorkspace);
128238
128364
  for (const [key, record5] of localStatus?.reconstructed ?? []) {
128239
128365
  if (record5.file != null) recordFiles.set(key, record5.file);
128240
128366
  }
128241
- const layoutStatus = localStatus ?? (destructive ? readWorkspaceLayoutStatusV4(workspace) : null);
128367
+ const layoutStatus = localStatus ?? (destructive ? readWorkspaceLayoutStatusV4(layoutWorkspace) : null);
128242
128368
  const localValues = layoutStatus?.localValues ?? workspace.state.localValues;
128243
128369
  const binaryPaths = workspaceBinaryPathsV4(
128244
128370
  workspace,
@@ -128256,6 +128382,16 @@ async function finishFormat4Pull(args) {
128256
128382
  buildEmitRecordSet(document, plans, "server"),
128257
128383
  { recordFiles, localValues, binaryPaths }
128258
128384
  );
128385
+ const resolveSourcePath = createWorkspaceSourcePathResolver(workspace.root);
128386
+ if (!destructive && Object.keys(workspace.state.records).length === 0) {
128387
+ for (const file of localResult.files) {
128388
+ if (existsSync8(resolveSourcePath(file.path))) {
128389
+ throw new Error(
128390
+ `Cannot replace existing source ${JSON.stringify(file.path)} without a pull baseline in .neo/state.json. Restore that file to preserve local edits, or back up your sources and run \`neo pull --force\` to replace them with the server version.`
128391
+ );
128392
+ }
128393
+ }
128394
+ }
128259
128395
  const binaries = await pullProjectBinariesV4({
128260
128396
  workspace,
128261
128397
  document,
@@ -128309,7 +128445,6 @@ async function finishFormat4Pull(args) {
128309
128445
  for (const key of regeneratedRecordKeys) {
128310
128446
  for (const path of localRecordFiles.get(key) ?? []) rewritePaths.add(path);
128311
128447
  }
128312
- const resolveSourcePath = createWorkspaceSourcePathResolver(workspace.root);
128313
128448
  const markedPaths = /* @__PURE__ */ new Set();
128314
128449
  let written = 0;
128315
128450
  for (const path of emittedPaths) {
@@ -128415,18 +128550,7 @@ async function finishFormat4Pull(args) {
128415
128550
  if (observedHeadTransactionHash !== void 0) {
128416
128551
  workspace.state.headTransactionHash = observedHeadTransactionHash;
128417
128552
  } else {
128418
- try {
128419
- const convex = await createConvexClient(workspace);
128420
- const signal = await convex.query(api2.projectExportData.schemaSignal, {
128421
- projectId: workspace.config.projectId,
128422
- versionId: workspace.config.versionId
128423
- });
128424
- workspace.state.headTransactionHash = document.revision !== void 0 && signal?.transactionId === document.revision.latestTransactionId ? signal.transactionHash : null;
128425
- workspace.state.derivedEdgeVersion = signal?.derivedEdgeVersion ?? null;
128426
- } catch {
128427
- workspace.state.headTransactionHash = null;
128428
- delete workspace.state.derivedEdgeVersion;
128429
- }
128553
+ await updateFullPullSyncMetadata(workspace, document);
128430
128554
  }
128431
128555
  if (observedDerivedEdgeVersion !== void 0) {
128432
128556
  workspace.state.derivedEdgeVersion = observedDerivedEdgeVersion;
@@ -128598,6 +128722,20 @@ function finishRecordOnlyDeltaPull(args) {
128598
128722
  `Pulled ${args.changedRecordKeys.size} changed record(s) \u2014 0 file(s) updated \u2014 0 removed.`
128599
128723
  );
128600
128724
  }
128725
+ async function updateFullPullSyncMetadata(workspace, document) {
128726
+ try {
128727
+ const convex = await createConvexClient(workspace);
128728
+ const signal = await convex.query(api2.projectExportData.schemaSignal, {
128729
+ projectId: workspace.config.projectId,
128730
+ versionId: workspace.config.versionId
128731
+ });
128732
+ workspace.state.headTransactionHash = document.revision !== void 0 && signal?.transactionId === document.revision.latestTransactionId ? signal.transactionHash : null;
128733
+ workspace.state.derivedEdgeVersion = signal?.derivedEdgeVersion ?? null;
128734
+ } catch {
128735
+ workspace.state.headTransactionHash = null;
128736
+ delete workspace.state.derivedEdgeVersion;
128737
+ }
128738
+ }
128601
128739
  function cursorFromRevision(revision) {
128602
128740
  return {
128603
128741
  readBase: revision.readBase,
@@ -128755,6 +128893,7 @@ var init_pull = __esm({
128755
128893
  init_static_value_seed_records();
128756
128894
  init_static_member_ownership_recovery();
128757
128895
  init_value_base_desync();
128896
+ init_compiler_metadata();
128758
128897
  }
128759
128898
  });
128760
128899
 
@@ -130473,6 +130612,8 @@ function readDocumentArrays(raw) {
130473
130612
  return {
130474
130613
  project: raw.project,
130475
130614
  projectFiles: arrayOf2("projectFiles"),
130615
+ textureTemplates: arrayOf2("textureTemplates"),
130616
+ audioClipTemplates: arrayOf2("audioClipTemplates"),
130476
130617
  members: arrayOf2("members"),
130477
130618
  classes: arrayOf2("classes"),
130478
130619
  constructors: arrayOf2("constructors"),
@@ -131293,6 +131434,7 @@ async function runScript(workspace, command, options, dependencies = {}) {
131293
131434
  variants: document.variants,
131294
131435
  variantFolders: document.variantFolders,
131295
131436
  projectFiles: document.projectFiles,
131437
+ textureTemplates: document.textureTemplates,
131296
131438
  localizationConfig: document.localizationConfig,
131297
131439
  localizedTexts: document.localizedTexts,
131298
131440
  ...runtime
@@ -134564,6 +134706,7 @@ async function prepareLocalCandidateV4(workspace, options = {}) {
134564
134706
  versionId: workspace.config.versionId
134565
134707
  });
134566
134708
  workspace.state.derivedEdgeVersion = signal?.derivedEdgeVersion ?? null;
134709
+ assertDerivedEdgeCompatibility(workspace);
134567
134710
  }
134568
134711
  preparedLocal = await prepareLocalPushArtifactsV4(
134569
134712
  workspace,
@@ -134903,7 +135046,8 @@ function createPendingProjectSourceIdentityV4(workspace, status, authoredValueSe
134903
135046
  recordFiles: projectRecordFilesV4(
134904
135047
  Object.values(workspace.state.records),
134905
135048
  status.reconstructed.values()
134906
- )
135049
+ ),
135050
+ localValues: status.localValues
134907
135051
  });
134908
135052
  const files = emission.files.map((file) => {
134909
135053
  const kind = neoProjectSourceKind(file.path, file.content);
@@ -136399,7 +136543,7 @@ var init_registry2 = __esm({
136399
136543
  "schema-contract/registry.mjs"() {
136400
136544
  "use strict";
136401
136545
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
136402
- cliVersion: "0.51.0",
136546
+ cliVersion: "0.51.2",
136403
136547
  projectFileUploadBatchSize: 32,
136404
136548
  documentRecords: {
136405
136549
  member: {
@@ -137996,6 +138140,7 @@ function sharedEvaluatorBase(rawDocument) {
137996
138140
  variants: document.variants,
137997
138141
  variantFolders: document.variantFolders,
137998
138142
  projectFiles: document.projectFiles,
138143
+ textureTemplates: document.textureTemplates,
137999
138144
  localizationConfig: document.localizationConfig,
138000
138145
  localizedTexts: document.localizedTexts,
138001
138146
  ...runtime
@@ -143628,7 +143773,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
143628
143773
  async function main() {
143629
143774
  const args = parseArgs(process.argv.slice(2));
143630
143775
  if (args.command === "--version") {
143631
- console.log("0.51.0");
143776
+ console.log("0.51.2");
143632
143777
  return;
143633
143778
  }
143634
143779
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.51.0",
3
+ "version": "0.51.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.51.0 -->
12
+ <!-- reviewed-through-cli: 0.51.2 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -90,7 +90,7 @@ wrappers.
90
90
  The marker near the top of `SKILL.md` must exactly match the package version:
91
91
 
92
92
  ```html
93
- <!-- reviewed-through-cli: 0.51.0 -->
93
+ <!-- reviewed-through-cli: 0.51.2 -->
94
94
  ```
95
95
 
96
96
  The quoted version above is checked too, so this instruction cannot go stale
@@ -40,9 +40,17 @@ without compiling it; use `neo status` to inspect local edits and diagnostics.
40
40
  An undo/reset metadata change invalidates the cursor and causes a safe
41
41
  full-snapshot fallback.
42
42
 
43
- If a compiler upgrade rejects the cached snapshot, save local edits outside the
44
- checkout, run `neo pull --force`, then reapply the edits. Normal pull cannot
45
- merge against a snapshot whose compiler revision is no longer supported.
43
+ After a compiler upgrade, run `neo pull` with `.neo/state.json` intact. When
44
+ remote changes affect only compiler-owned bodies or timestamps, pull refreshes
45
+ the cached records before local compilation and leaves authored files and
46
+ managed binaries untouched. It handles both changed-record pulls and full
47
+ snapshot refreshes. Added or deleted records, authored changes, and unresolved
48
+ conflicts still require the normal merge path.
49
+
50
+ Keep `.neo/state.json`: it holds the merge baseline and source placements.
51
+ Normal pull refuses existing production sources without that baseline. To
52
+ deliberately replace a checkout whose state is gone, back it up first and use
53
+ `neo pull --force`; the CLI recovers custom paths from the remaining declarations.
46
54
  The force pull replaces local source with the server version.
47
55
 
48
56
  Pull pins every page and snapshot batch to one published generation and logical