@neocompose/cli 0.67.0 → 0.67.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.67.2] - 2026-10-04
4
+
5
+ - `neo pull --force` reads the workspace layout against the server's records instead of the baseline it is about to replace. Previously a baseline pulled before 0.67.0 failed with "Cannot recover local initializer placement before reset" in any workspace with local values, so the `--force` remedy 0.67.1 suggests could not run.
6
+ - Pull writes `float` setting bounds as the shortest decimal that reads back as the stored float, so `@settings(max: 23.99)` stays `23.99`. Previously it wrote `23.989999771118164`.
7
+
8
+ ## [0.67.1] - 2026-10-04
9
+
10
+ - Pull adopts records the server only recompiled into `.neo/state.json` before it reads local sources, even when the same pull also merges authored changes. Previously one authored change anywhere in the pull sent every recompiled record through the merge, which read the stale baseline and failed.
11
+ - Status, push, and pull refuse a baseline holding a compiled body this CLI cannot read, name the record, and say to run `neo pull`, or `neo pull --force` after backing up local edits when pull cannot refresh it. Previously they failed with "init.compiled carries compiler revision 16" and suggested `npm run compiler-revision:mint`, which cannot fix a local file.
12
+ - A baseline pulled before 0.67.0 also holds 64-bit floats, which only the server's copy rounded. Plain `neo pull` cannot refresh it. Commit or back up local edits, run `neo pull --force` once, then restore them.
13
+
3
14
  ## [0.67.0] - 2026-10-04
4
15
 
5
16
  - `float` is a 32-bit float everywhere, as in C#. Float literals, defaults, constructor arguments, and stored values round to the nearest 32-bit float, and pull prints the shortest decimal that reads back as that float, so `0.7` stays `0.7`. Previously push stored the 64-bit double, and Unity read a value that its own `float` could not hold.
package/dist/neo.mjs CHANGED
@@ -30114,7 +30114,7 @@ var init_registry = __esm({
30114
30114
  "../packages/manifest/schema-contract/registry.mjs"() {
30115
30115
  "use strict";
30116
30116
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
30117
- cliVersion: "0.67.0",
30117
+ cliVersion: "0.67.2",
30118
30118
  projectFileUploadBatchSize: 32,
30119
30119
  documentRecords: {
30120
30120
  member: {
@@ -40491,6 +40491,31 @@ function collectNeoScriptCompiledBodyStamps(record6) {
40491
40491
  visit(record6, "");
40492
40492
  return stamps;
40493
40493
  }
40494
+ function firstRejectedNeoScriptCompiledBody(node, rollout = ACTIVE_NEOSCRIPT_COMPILER_REVISION_ROLLOUT) {
40495
+ if (Array.isArray(node)) {
40496
+ for (let index = 0; index < node.length; index += 1) {
40497
+ const found = firstRejectedNeoScriptCompiledBody(node[index], rollout);
40498
+ if (found !== null) {
40499
+ return { ...found, path: prefixPath(`[${String(index)}]`, found.path) };
40500
+ }
40501
+ }
40502
+ return null;
40503
+ }
40504
+ if (typeof node !== "object" || node === null) return null;
40505
+ const object2 = node;
40506
+ if (isCompiledBodyShape(object2) && !isAcceptedNeoScriptCompilerRevision(object2.compilerRevision, rollout)) {
40507
+ return { path: "", stamp: object2.compilerRevision };
40508
+ }
40509
+ for (const key of Object.keys(object2)) {
40510
+ const found = firstRejectedNeoScriptCompiledBody(object2[key], rollout);
40511
+ if (found === null) continue;
40512
+ return { ...found, path: prefixPath(key, found.path) };
40513
+ }
40514
+ return null;
40515
+ }
40516
+ function prefixPath(head, rest) {
40517
+ return rest === "" || rest.startsWith("[") ? head + rest : `${head}.${rest}`;
40518
+ }
40494
40519
  function isCompiledBodyShape(object2) {
40495
40520
  if (!Array.isArray(object2.parameters)) return false;
40496
40521
  if (!Array.isArray(object2.instructions)) return false;
@@ -52619,14 +52644,17 @@ function renderExtendsGenericBindings(context, schemaClass2, base) {
52619
52644
  });
52620
52645
  return `<${argumentsValue.join(", ")}>`;
52621
52646
  }
52647
+ function settingsBound(value) {
52648
+ return typeof value === "number" ? formatNeoNumber(value) : value;
52649
+ }
52622
52650
  function memberSettings(context, member) {
52623
52651
  const settings = [];
52624
52652
  if (member.kind === "string") {
52625
52653
  if (!member.localizable) settings.push("format: .Plain");
52626
52654
  if (member.searchKey) settings.push("searchBy: .MemberKey");
52627
52655
  } else if (member.kind === "int" || member.kind === "float" || member.kind === "decimal") {
52628
- if (member.min !== null) settings.push(`min: ${member.min}`);
52629
- if (member.max !== null) settings.push(`max: ${member.max}`);
52656
+ if (member.min !== null) settings.push(`min: ${settingsBound(member.min)}`);
52657
+ if (member.max !== null) settings.push(`max: ${settingsBound(member.max)}`);
52630
52658
  if (member.kind !== "int" && member.decimalPoints !== null) {
52631
52659
  settings.push(`decimalPoints: ${member.decimalPoints}`);
52632
52660
  }
@@ -112239,6 +112267,62 @@ var init_schema_source_validation = __esm({
112239
112267
  }
112240
112268
  });
112241
112269
 
112270
+ // src/project-sync/compiler-metadata.ts
112271
+ function compilerOnlyChanges(previous, document, changedKeys) {
112272
+ const keys = changedKeys ?? /* @__PURE__ */ new Set([...Object.keys(previous), ...document.records.keys()]);
112273
+ const refreshed = /* @__PURE__ */ new Set();
112274
+ let complete2 = true;
112275
+ for (const key of keys) {
112276
+ const before = previous[key];
112277
+ const after = document.records.get(key);
112278
+ if (before !== void 0 && after !== void 0 && !after.deleted && before.contentHash === after.contentHash)
112279
+ continue;
112280
+ if (before === void 0 || after === void 0 || after.deleted || before.recordId !== after.recordId || before.recordKind !== after.recordKind || !isProjectRecordKind(after.recordKind) || !isObjectRecord2(before.data) || !isObjectRecord2(after.data) || !canonicallyEqual(
112281
+ authoredCompilerInput(after.recordKind, before.data),
112282
+ authoredCompilerInput(after.recordKind, after.data)
112283
+ )) {
112284
+ complete2 = false;
112285
+ continue;
112286
+ }
112287
+ refreshed.add(key);
112288
+ }
112289
+ return { keys: refreshed, complete: complete2 };
112290
+ }
112291
+ function authoredCompilerInput(kind, data) {
112292
+ const result = stripClientDerivedBodies(kind, data);
112293
+ stampUILogicBodies(kind, result);
112294
+ if (isObjectRecord2(result)) delete result.updatedAt;
112295
+ return result;
112296
+ }
112297
+ function assertCurrentBaseline(records2) {
112298
+ for (const [key, record6] of Object.entries(records2)) {
112299
+ if (!isProjectRecordKind(record6.recordKind)) continue;
112300
+ const rejected = firstRejectedNeoScriptCompiledBody(record6.data);
112301
+ if (rejected === null) continue;
112302
+ try {
112303
+ validateProjectRecordByKind(record6.recordKind, record6.data, {
112304
+ members: "authored-or-compiled"
112305
+ });
112306
+ continue;
112307
+ } catch {
112308
+ }
112309
+ const carried = rejected.stamp === void 0 ? "has no compiler revision stamp" : `carries compiler revision ${JSON.stringify(rejected.stamp)}`;
112310
+ throw new Error(
112311
+ `The pull baseline in .neo/state.json predates this CLI: ${key} ${rejected.path} ${carried}. Run \`neo pull\` to refresh it. If pull reports this again, commit or back up your local edits, run \`neo pull --force\`, then restore them.`
112312
+ );
112313
+ }
112314
+ }
112315
+ var init_compiler_metadata = __esm({
112316
+ "src/project-sync/compiler-metadata.ts"() {
112317
+ "use strict";
112318
+ init_project2();
112319
+ init_neo_script_derived_bodies();
112320
+ init_neoscript_compiler_revision();
112321
+ init_project_record_validation();
112322
+ init_value_comparison();
112323
+ }
112324
+ });
112325
+
112242
112326
  // src/project-sync/value-record-comparison.ts
112243
112327
  function valueRecordComparisonBody(value) {
112244
112328
  if (!isObjectRecord2(value)) return value;
@@ -118422,6 +118506,7 @@ function materializeWorkspaceStatusInitializers(workspace, status, forceRecompil
118422
118506
  });
118423
118507
  }
118424
118508
  function computeWorkspaceStatus(workspace, options) {
118509
+ assertCurrentBaseline(workspace.state.records);
118425
118510
  let phaseStarted = performance.now();
118426
118511
  options.onPhase?.("Reading project schema\u2026");
118427
118512
  const reportPhase = (phase, nextLabel) => {
@@ -120396,6 +120481,7 @@ var init_workspace_status_core = __esm({
120396
120481
  init_key_reference_spelling();
120397
120482
  init_workspace();
120398
120483
  init_source_diagnostics();
120484
+ init_compiler_metadata();
120399
120485
  init_migration_files();
120400
120486
  init_value_comparison();
120401
120487
  init_projection();
@@ -131285,9 +131371,25 @@ function workspaceBinaryPathsV4(workspace, local) {
131285
131371
  for (const file of local ?? []) paths.set(file.fileId, file.path);
131286
131372
  return paths;
131287
131373
  }
131288
- function readWorkspaceLayoutStatusV4(workspace) {
131374
+ function readWorkspaceLayoutStatusV4(workspace, document) {
131375
+ const records2 = {};
131376
+ for (const [key, server] of document.records) {
131377
+ if (server.deleted) continue;
131378
+ const previous = workspace.state.records[key];
131379
+ records2[key] = {
131380
+ recordKind: server.recordKind,
131381
+ recordId: server.recordId,
131382
+ contentHash: server.contentHash,
131383
+ data: server.data,
131384
+ ...previous?.file === void 0 ? {} : { file: previous.file },
131385
+ ...previous?.projectBinary === void 0 ? {} : { projectBinary: previous.projectBinary }
131386
+ };
131387
+ }
131289
131388
  try {
131290
- const status = computeWorkspaceStatus2(workspace);
131389
+ const status = computeWorkspaceStatus2({
131390
+ ...workspace,
131391
+ state: { ...workspace.state, records: records2 }
131392
+ });
131291
131393
  const failure = status.parseErrors.find(isBlockingSchemaSourceError);
131292
131394
  if (failure !== void 0) throw failure;
131293
131395
  return status;
@@ -131299,8 +131401,8 @@ function readWorkspaceLayoutStatusV4(workspace) {
131299
131401
  for (const path of listProjectSourceFilesV4(workspace.root)) {
131300
131402
  const content = readFileSync9(path, "utf8");
131301
131403
  if (neoProjectSourceKind(path, content) !== "definition") continue;
131302
- const document = parseNeoProjectSource(content, "definition", path);
131303
- if (document.declarations.some(
131404
+ const document2 = parseNeoProjectSource(content, "definition", path);
131405
+ if (document2.declarations.some(
131304
131406
  (declaration) => declaration.kind === "global" && isLocalValueGlobal(declaration)
131305
131407
  )) {
131306
131408
  throw new Error(
@@ -137688,7 +137790,7 @@ import { dirname as dirname8, join as join13, relative as relative7, sep as sep7
137688
137790
  function prepareWorkspaceResetV4(workspace, document, options = {}) {
137689
137791
  const emissionRecords = options.regenerateSourceNames ? regenerateDialogueSourceNamesV4(document.records) : document.records;
137690
137792
  const layoutWorkspace = workspaceForSourceLayoutV4(workspace, document);
137691
- const status = listProjectSourceFilesV4(workspace.root).length === 0 ? null : readWorkspaceLayoutStatusV4(layoutWorkspace);
137793
+ const status = listProjectSourceFilesV4(workspace.root).length === 0 ? null : readWorkspaceLayoutStatusV4(layoutWorkspace, document);
137692
137794
  const localValues = status?.localValues ?? workspace.state.localValues;
137693
137795
  const emitted = emitProjectDocumentFilesV4(emissionRecords, {
137694
137796
  localValues,
@@ -137892,40 +137994,6 @@ var init_static_value_seed_records = __esm({
137892
137994
  }
137893
137995
  });
137894
137996
 
137895
- // src/project-sync/compiler-metadata.ts
137896
- function compilerMetadataChangedKeys(previous, document, changedKeys) {
137897
- const keys = changedKeys ?? /* @__PURE__ */ new Set([...Object.keys(previous), ...document.records.keys()]);
137898
- const refreshed = /* @__PURE__ */ new Set();
137899
- for (const key of keys) {
137900
- const before = previous[key];
137901
- const after = document.records.get(key);
137902
- if (before === void 0 || after === void 0 || after.deleted || before.recordId !== after.recordId || before.recordKind !== after.recordKind || !isProjectRecordKind(after.recordKind))
137903
- return null;
137904
- if (before.contentHash === after.contentHash) continue;
137905
- if (!isObjectRecord2(before.data) || !isObjectRecord2(after.data) || !canonicallyEqual(
137906
- authoredCompilerInput(after.recordKind, before.data),
137907
- authoredCompilerInput(after.recordKind, after.data)
137908
- ))
137909
- return null;
137910
- refreshed.add(key);
137911
- }
137912
- return refreshed.size === 0 ? null : refreshed;
137913
- }
137914
- function authoredCompilerInput(kind, data) {
137915
- const result = stripClientDerivedBodies(kind, data);
137916
- stampUILogicBodies(kind, result);
137917
- if (isObjectRecord2(result)) delete result.updatedAt;
137918
- return result;
137919
- }
137920
- var init_compiler_metadata = __esm({
137921
- "src/project-sync/compiler-metadata.ts"() {
137922
- "use strict";
137923
- init_project2();
137924
- init_neo_script_derived_bodies();
137925
- init_value_comparison();
137926
- }
137927
- });
137928
-
137929
137997
  // src/commands/pull.ts
137930
137998
  var pull_exports = {};
137931
137999
  __export(pull_exports, {
@@ -138014,20 +138082,23 @@ async function runNormalPull(workspace, options, progress) {
138014
138082
  document = await fetchProjectDocument(workspace);
138015
138083
  nextCursor = document.revision === void 0 ? null : cursorFromRevision(document.revision);
138016
138084
  }
138017
- const compilerRefreshKeys = !destructive && hasBaseline && !hadConflicts && !options.regenerateSourceNames ? compilerMetadataChangedKeys(
138018
- workspace.state.records,
138019
- document,
138020
- changedRecordKeys
138021
- ) : null;
138022
- if (compilerRefreshKeys !== null) {
138023
- finishRecordOnlyDeltaPull({
138024
- workspace,
138085
+ if (!destructive && hasBaseline && !hadConflicts) {
138086
+ const compilerOnly = compilerOnlyChanges(
138087
+ workspace.state.records,
138025
138088
  document,
138026
- changedRecordKeys: compilerRefreshKeys,
138027
- nextCursor,
138028
- progress
138029
- });
138030
- return;
138089
+ changedRecordKeys
138090
+ );
138091
+ if (compilerOnly.complete && compilerOnly.keys.size > 0 && !options.regenerateSourceNames) {
138092
+ finishRecordOnlyDeltaPull({
138093
+ workspace,
138094
+ document,
138095
+ changedRecordKeys: compilerOnly.keys,
138096
+ nextCursor,
138097
+ progress
138098
+ });
138099
+ return;
138100
+ }
138101
+ adoptServerRecords(workspace, document, compilerOnly.keys);
138031
138102
  }
138032
138103
  let localByKey = /* @__PURE__ */ new Map();
138033
138104
  let localStatus = null;
@@ -138291,7 +138362,7 @@ async function finishFormat4Pull(args) {
138291
138362
  for (const [key, record6] of localStatus?.reconstructed ?? []) {
138292
138363
  if (record6.file != null) recordFiles.set(key, record6.file);
138293
138364
  }
138294
- const layoutStatus = localStatus ?? (destructive ? readWorkspaceLayoutStatusV4(layoutWorkspace) : null);
138365
+ const layoutStatus = localStatus ?? (destructive ? readWorkspaceLayoutStatusV4(layoutWorkspace, document) : null);
138295
138366
  const localValues = layoutStatus?.localValues ?? workspace.state.localValues;
138296
138367
  const binaryPaths = workspaceBinaryPathsV4(
138297
138368
  workspace,
@@ -138616,9 +138687,25 @@ function deltaRequiresSourceProjectionV4(args) {
138616
138687
  return false;
138617
138688
  }
138618
138689
  function finishRecordOnlyDeltaPull(args) {
138619
- const records2 = { ...args.workspace.state.records };
138620
- for (const key of args.changedRecordKeys) {
138621
- const server = args.document.records.get(key);
138690
+ adoptServerRecords(args.workspace, args.document, args.changedRecordKeys);
138691
+ if (args.nextCursor !== null) {
138692
+ args.workspace.state.documentRevisionCursor = mutableCursor(
138693
+ args.nextCursor
138694
+ );
138695
+ }
138696
+ args.workspace.state.headTransactionHash = pulledHeadTransactionHash(
138697
+ args.document
138698
+ );
138699
+ writeWorkspaceState(args.workspace.root, args.workspace.state);
138700
+ args.progress.succeed(
138701
+ `Pulled ${args.changedRecordKeys.size} changed record(s) \u2014 0 file(s) updated \u2014 0 removed.`
138702
+ );
138703
+ }
138704
+ function adoptServerRecords(workspace, document, keys) {
138705
+ if (keys.size === 0) return;
138706
+ const records2 = { ...workspace.state.records };
138707
+ for (const key of keys) {
138708
+ const server = document.records.get(key);
138622
138709
  if (server === void 0) {
138623
138710
  delete records2[key];
138624
138711
  continue;
@@ -138633,19 +138720,7 @@ function finishRecordOnlyDeltaPull(args) {
138633
138720
  ...previous?.projectBinary === void 0 ? {} : { projectBinary: previous.projectBinary }
138634
138721
  };
138635
138722
  }
138636
- args.workspace.state.records = records2;
138637
- if (args.nextCursor !== null) {
138638
- args.workspace.state.documentRevisionCursor = mutableCursor(
138639
- args.nextCursor
138640
- );
138641
- }
138642
- args.workspace.state.headTransactionHash = pulledHeadTransactionHash(
138643
- args.document
138644
- );
138645
- writeWorkspaceState(args.workspace.root, args.workspace.state);
138646
- args.progress.succeed(
138647
- `Pulled ${args.changedRecordKeys.size} changed record(s) \u2014 0 file(s) updated \u2014 0 removed.`
138648
- );
138723
+ workspace.state.records = records2;
138649
138724
  }
138650
138725
  function pulledHeadTransactionHash(document) {
138651
138726
  return document.revision?.headTransactionHash ?? null;
@@ -156777,7 +156852,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
156777
156852
  async function main() {
156778
156853
  const args = parseArgs(process.argv.slice(2));
156779
156854
  if (args.command === "--version") {
156780
- console.log("0.67.0");
156855
+ console.log("0.67.2");
156781
156856
  return;
156782
156857
  }
156783
156858
  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.67.0",
3
+ "version": "0.67.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.67.0 -->
12
+ <!-- reviewed-through-cli: 0.67.2 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -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.67.0 -->
113
+ <!-- reviewed-through-cli: 0.67.2 -->
114
114
  ```
115
115
 
116
116
  The quoted version above is checked too, so this instruction cannot go stale
@@ -40,12 +40,14 @@ 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
- 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.
43
+ After a compiler upgrade, run `neo pull` with `.neo/state.json` intact. Pull
44
+ refreshes every cached record whose remote change touches only compiler-owned
45
+ bodies or timestamps before local compilation, even when the same pull merges
46
+ other changes. When nothing else changed, it leaves authored files and managed
47
+ binaries untouched. Added or deleted records, authored changes, and unresolved
48
+ conflicts take the normal merge path. Status, push, and pull refuse a baseline
49
+ holding a compiled body this CLI rejects and name the record. If `neo pull`
50
+ cannot refresh it, back up local edits and run `neo pull --force`.
49
51
 
50
52
  If a push commits but source rewriting fails, `neo pull` resumes its saved
51
53
  result before checking the remote head. The recovery journal in `.neo/state.json`