@neocompose/cli 0.66.0 → 0.67.1

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.
@@ -1755,7 +1755,7 @@ var TEST_GLOBALS = [
1755
1755
  ];
1756
1756
 
1757
1757
  // ../packages/neoscript-language/src/strict-ir.ts
1758
- var NEOSCRIPT_COMPILER_REVISION = 16;
1758
+ var NEOSCRIPT_COMPILER_REVISION = 17;
1759
1759
 
1760
1760
  // ../src/models/members/member-storage.ts
1761
1761
  function isMemberStorageKind(value) {
@@ -2167,6 +2167,11 @@ function isValidDocsText(value) {
2167
2167
  return textEncoder.encode(value).byteLength <= DOCS_TEXT_MAX_UTF8_BYTES;
2168
2168
  }
2169
2169
 
2170
+ // ../src/models/members/float32.ts
2171
+ function isFloat32(value) {
2172
+ return typeof value === "number" && Number.isFinite(value) && Math.fround(value) === value;
2173
+ }
2174
+
2170
2175
  // ../packages/neoscript-language/src/project-source-identifiers.ts
2171
2176
  var PROJECT_IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
2172
2177
  var PROJECT_RESERVED_NAMES = /* @__PURE__ */ new Set([
@@ -2507,7 +2512,7 @@ function hasValidParameterDefault(value) {
2507
2512
  case 2 /* Int */:
2508
2513
  return typeof payload === "number" && Number.isInteger(payload);
2509
2514
  case 4 /* Float */:
2510
- return typeof payload === "number" && Number.isFinite(payload);
2515
+ return isFloat32(payload);
2511
2516
  case 20 /* Decimal */:
2512
2517
  case 3 /* String */:
2513
2518
  case 8 /* Enum */:
@@ -2793,6 +2798,9 @@ function isNSArithmeticOperationInfo(value) {
2793
2798
  const v = value;
2794
2799
  if (typeof v !== "object" || v === null) return false;
2795
2800
  if (!isNSArithmeticOperationType(v.type)) return false;
2801
+ if (v.numeric !== void 0 && v.numeric !== "int" && v.numeric !== "float") {
2802
+ return false;
2803
+ }
2796
2804
  return isNSPointers(v.pointers);
2797
2805
  }
2798
2806
  function isNSArithmeticOperation(value) {
@@ -3703,6 +3711,9 @@ var STRUCTURED_LEAF_FIELDS = {
3703
3711
  function isStructuredLeafKind(kind) {
3704
3712
  return Object.prototype.hasOwnProperty.call(STRUCTURED_LEAF_FIELDS, kind);
3705
3713
  }
3714
+ function structuredLeafFields(kind) {
3715
+ return STRUCTURED_LEAF_FIELDS[kind];
3716
+ }
3706
3717
  function structuredLeafFieldKeys(kind) {
3707
3718
  return STRUCTURED_LEAF_FIELDS[kind].map((entry) => entry.key);
3708
3719
  }
@@ -3715,10 +3726,10 @@ function isStructuredLeafFieldValue(type, value) {
3715
3726
  return typeof value === "string" && value.length > 0;
3716
3727
  }
3717
3728
  if (typeof value !== "number" || !Number.isFinite(value)) return false;
3718
- if (type === "float") return true;
3729
+ if (type === "float") return isFloat32(value);
3719
3730
  if (type === "int") return Number.isInteger(value);
3720
3731
  if (type === "sliceIndex") return Number.isInteger(value) && value >= 0;
3721
- return value >= 0 && value <= 1;
3732
+ return isFloat32(value) && value >= 0 && value <= 1;
3722
3733
  }
3723
3734
  var STRUCTURED_LEAF_PARTIAL_KEY = "~partial";
3724
3735
  function isStructuredLeafPartialValue(value) {
@@ -3738,16 +3749,36 @@ function structuredLeafPartialViolation(kind, value, legalKeysLabel) {
3738
3749
  return `has no field "${key}"; ${legalKeysLabel} accepts ${structuredLeafFieldKeys(kind).join(", ")}`;
3739
3750
  }
3740
3751
  if (isStructuredLeafFieldValue(field.type, fieldValue)) continue;
3741
- return `field "${key}" ${structuredLeafFieldViolation(field.type)}`;
3752
+ return `field "${key}" ${structuredLeafFieldViolation(field.type, fieldValue)}`;
3742
3753
  }
3743
3754
  return null;
3744
3755
  }
3745
- function structuredLeafFieldViolation(type) {
3756
+ function structuredLeafRecordFieldViolation(kind, value) {
3757
+ if (typeof value !== "object" || value === null) return null;
3758
+ if (Array.isArray(value)) return null;
3759
+ const entries = Object.entries(value);
3760
+ if (entries.length !== structuredLeafFields(kind).length) return null;
3761
+ let found = null;
3762
+ for (const [key, fieldValue] of entries) {
3763
+ const field = structuredLeafField(kind, key);
3764
+ if (field === null) return null;
3765
+ if (typeof fieldValue !== "number") return null;
3766
+ if (found !== null) continue;
3767
+ if (isStructuredLeafFieldValue(field.type, fieldValue)) continue;
3768
+ found = {
3769
+ key,
3770
+ value: fieldValue,
3771
+ violation: structuredLeafFieldViolation(field.type, fieldValue)
3772
+ };
3773
+ }
3774
+ return found;
3775
+ }
3776
+ function structuredLeafFieldViolation(type, value) {
3746
3777
  if (type === "fileId") return "must be a non-empty file id";
3747
- if (type === "float") return "must be a finite number";
3748
3778
  if (type === "int") return "must be a whole number";
3749
3779
  if (type === "sliceIndex") return "must be a whole number 0 or greater";
3750
- return "must be a number between 0 and 1";
3780
+ if (!isFloat32(value)) return "must be a 32-bit float";
3781
+ return "must be between 0 and 1";
3751
3782
  }
3752
3783
 
3753
3784
  // ../src/models/members/member-kinds.ts
@@ -3894,9 +3925,9 @@ function isMemberString(value) {
3894
3925
  function isMemberFloatBase(value) {
3895
3926
  const v = asMemberBaseForKind(value, 4 /* Float */);
3896
3927
  if (v === null) return false;
3897
- if (v.minValue !== void 0 && v.minValue !== null && typeof v.minValue !== "number")
3928
+ if (v.minValue !== void 0 && v.minValue !== null && !isFloat32(v.minValue))
3898
3929
  return false;
3899
- if (v.maxValue !== void 0 && v.maxValue !== null && typeof v.maxValue !== "number")
3930
+ if (v.maxValue !== void 0 && v.maxValue !== null && !isFloat32(v.maxValue))
3900
3931
  return false;
3901
3932
  return v.decimalPoints === void 0 || v.decimalPoints === null || typeof v.decimalPoints === "number";
3902
3933
  }
@@ -3905,12 +3936,8 @@ function hasExactKeys(value, keys) {
3905
3936
  if (actual.length !== keys.length) return false;
3906
3937
  return keys.every((key) => Object.prototype.hasOwnProperty.call(value, key));
3907
3938
  }
3908
- function isFiniteNumberValue(value) {
3909
- return typeof value === "number" && Number.isFinite(value);
3910
- }
3911
3939
  function isVectorComponentValue(value, integer2) {
3912
- if (!isFiniteNumberValue(value)) return false;
3913
- if (!integer2) return true;
3940
+ if (!integer2) return isFloat32(value);
3914
3941
  return Number.isInteger(value);
3915
3942
  }
3916
3943
  function isVector2ValueWithComponents(value, integer2) {
@@ -3944,7 +3971,7 @@ function isVector3IntValue(value) {
3944
3971
  return isVector3ValueWithComponents(value, true);
3945
3972
  }
3946
3973
  function isColorComponentValue(value) {
3947
- if (!isFiniteNumberValue(value)) return false;
3974
+ if (!isFloat32(value)) return false;
3948
3975
  return value >= 0 && value <= 1;
3949
3976
  }
3950
3977
  function isColorValue(value) {
@@ -10038,6 +10065,9 @@ function structuredLeafPartialMemberViolation(member, value, position) {
10038
10065
  }
10039
10066
  return structuredLeafPartialViolation(kind, value, MemberKind[kind]);
10040
10067
  }
10068
+ function storedLeafFieldIssueMessage(issue) {
10069
+ return `${MemberKind[issue.leaf]} field "${issue.key}" ${issue.violation}, not ${issue.value}.`;
10070
+ }
10041
10071
  function* storedValueChildEntries(args) {
10042
10072
  const entryMemberId = args.member.entryMemberId;
10043
10073
  if (args.member.kind === 6 /* List */ && typeof entryMemberId === "string") {
@@ -10071,16 +10101,19 @@ function* storedValueChildEntries(args) {
10071
10101
  }
10072
10102
  function storedLeafShapeIssue(member, value) {
10073
10103
  switch (member.kind) {
10104
+ // Null is an optional Float's absent value.
10105
+ case 4 /* Float */:
10106
+ return value === null || isFloat32(value) ? null : { kind: "float" };
10074
10107
  case 14 /* Vector2 */:
10075
- return isVector2Value(value) ? null : { kind: "vector2" };
10108
+ return isVector2Value(value) ? null : leafIssue(member.kind, "vector2", value);
10076
10109
  case 15 /* Vector2Int */:
10077
- return isVector2IntValue(value) ? null : { kind: "vector2Int" };
10110
+ return isVector2IntValue(value) ? null : leafIssue(member.kind, "vector2Int", value);
10078
10111
  case 16 /* Vector3 */:
10079
- return isVector3Value(value) ? null : { kind: "vector3" };
10112
+ return isVector3Value(value) ? null : leafIssue(member.kind, "vector3", value);
10080
10113
  case 17 /* Vector3Int */:
10081
- return isVector3IntValue(value) ? null : { kind: "vector3Int" };
10114
+ return isVector3IntValue(value) ? null : leafIssue(member.kind, "vector3Int", value);
10082
10115
  case 19 /* Color */:
10083
- return isColorValue(value) ? null : { kind: "color" };
10116
+ return isColorValue(value) ? null : leafIssue(member.kind, "color", value);
10084
10117
  case 20 /* Decimal */: {
10085
10118
  const violation = getDecimalStringViolation(value);
10086
10119
  return violation === null ? null : { kind: "decimal", violation };
@@ -10093,6 +10126,10 @@ function storedLeafShapeIssue(member, value) {
10093
10126
  return null;
10094
10127
  }
10095
10128
  }
10129
+ function leafIssue(leaf, shape, value) {
10130
+ const field = structuredLeafRecordFieldViolation(leaf, value);
10131
+ return field === null ? { kind: shape } : { kind: "field", leaf, ...field };
10132
+ }
10096
10133
 
10097
10134
  // ../src/models/members/build-default-member-value.ts
10098
10135
  var DECLARATION_DEFAULT_POSITION = {
@@ -11174,16 +11211,24 @@ function validateProjectDocumentFileMemberValue(document2, member, body, positio
11174
11211
  return;
11175
11212
  }
11176
11213
  const shapeIssue = storedLeafShapeIssue(member, body.value);
11214
+ if (shapeIssue?.kind === "float") {
11215
+ throw new Error(
11216
+ `Float value for member "${member.name}" must be a 32-bit float, not ${String(body.value)}.`
11217
+ );
11218
+ }
11219
+ if (shapeIssue?.kind === "field") {
11220
+ throw new Error(
11221
+ `Member "${member.name}" ${storedLeafFieldIssueMessage(shapeIssue)}`
11222
+ );
11223
+ }
11177
11224
  if (shapeIssue?.kind === "vector2") {
11178
- throw new Error("Vector2 value must be { x: number, y: number }.");
11225
+ throw new Error("Vector2 value must be { x: float, y: float }.");
11179
11226
  }
11180
11227
  if (shapeIssue?.kind === "vector2Int") {
11181
11228
  throw new Error("Vector2Int value must be { x: integer, y: integer }.");
11182
11229
  }
11183
11230
  if (shapeIssue?.kind === "vector3") {
11184
- throw new Error(
11185
- "Vector3 value must be { x: number, y: number, z: number }."
11186
- );
11231
+ throw new Error("Vector3 value must be { x: float, y: float, z: float }.");
11187
11232
  }
11188
11233
  if (shapeIssue?.kind === "vector3Int") {
11189
11234
  throw new Error(
@@ -11192,7 +11237,7 @@ function validateProjectDocumentFileMemberValue(document2, member, body, positio
11192
11237
  }
11193
11238
  if (shapeIssue?.kind === "color") {
11194
11239
  throw new Error(
11195
- "Color value must be { r, g, b, a } with each component in [0, 1]."
11240
+ "Color value must be { r, g, b, a } with each component a 32-bit float in [0, 1]."
11196
11241
  );
11197
11242
  }
11198
11243
  if (shapeIssue?.kind === "decimal") {
@@ -17454,7 +17499,8 @@ function evalPointer(pointer, scope, ctx, resultUsage = "value") {
17454
17499
  callSiteId: pointer.callSiteId,
17455
17500
  invokeDelegate: (delegate, delegateArgs = []) => invokeDelegateValue(delegate, delegateArgs, ctx, pointer.callSiteId)
17456
17501
  });
17457
- if (intercepted?.handled === true) return intercepted.value;
17502
+ if (intercepted?.handled === true)
17503
+ return hostCallResult(member, intercepted.value, ctx);
17458
17504
  if (member === null) {
17459
17505
  if (pointer.missingMemberFallback === "valueEquality") {
17460
17506
  return jsEqual(innerThis, suppliedArgs[0]);
@@ -17490,7 +17536,8 @@ function evalPointer(pointer, scope, ctx, resultUsage = "value") {
17490
17536
  pointer.callSiteId
17491
17537
  )
17492
17538
  });
17493
- if (hosted?.handled === true) return hosted.value;
17539
+ if (hosted?.handled === true)
17540
+ return hostCallResult(member, hosted.value, ctx);
17494
17541
  const receiverId = trackedRowForValueReference(innerThis, ctx)?.id;
17495
17542
  throw new NativeFunctionDelegateUnavailableError(
17496
17543
  `No native delegate handled Function '${member.name}' (member id '${member.id}'${nativeReceiverDescription(member, innerThis, receiverId)}). Web preview and other non-native evaluator hosts must configure a native handler or call interceptor before evaluation.`,
@@ -17781,7 +17828,8 @@ function invokeDelegateValue(value, args, ctx, callSiteId, ownerReceiver, result
17781
17828
  callSiteId,
17782
17829
  invokeDelegate: (delegate, delegateArgs = []) => invokeDelegateValue(delegate, delegateArgs, ctx, callSiteId)
17783
17830
  });
17784
- if (intercepted?.handled === true) return intercepted.value;
17831
+ if (intercepted?.handled === true)
17832
+ return hostCallResult(member, intercepted.value, ctx);
17785
17833
  state.delegateStack.push(frame);
17786
17834
  try {
17787
17835
  if (member.kind === 13 /* Function */) {
@@ -17796,7 +17844,8 @@ function invokeDelegateValue(value, args, ctx, callSiteId, ownerReceiver, result
17796
17844
  callSiteId,
17797
17845
  invokeDelegate: (delegate, delegateArgs = []) => invokeDelegateValue(delegate, delegateArgs, ctx, callSiteId)
17798
17846
  });
17799
- if (hosted?.handled === true) return hosted.value;
17847
+ if (hosted?.handled === true)
17848
+ return hostCallResult(member, hosted.value, ctx);
17800
17849
  const receiverId = trackedRowForValueReference(receiver, ctx)?.id;
17801
17850
  throw new NativeFunctionDelegateUnavailableError(
17802
17851
  `No native delegate handled Function '${member.name}' (member id '${member.id}'${nativeReceiverDescription(member, receiver, receiverId)}). Web preview and other non-native evaluator hosts must configure a native handler or call interceptor before evaluation.`,
@@ -18665,13 +18714,14 @@ function runtimeCollectionEntryMatchesType(value, typeInfo, ctx, seenCollections
18665
18714
  return resolved !== value && runtimeValueMatchesType(resolved, typeInfo, ctx, seenCollections);
18666
18715
  }
18667
18716
  function evalVectorConstructor(vectorType, componentPointers, scope, ctx) {
18668
- const values = componentPointers.map(
18669
- (pointer) => finiteNumberArg(
18717
+ const integer2 = vectorType === 15 /* Vector2Int */ || vectorType === 17 /* Vector3Int */;
18718
+ const values = componentPointers.map((pointer) => {
18719
+ const value = finiteNumberArg(
18670
18720
  evalPointer(pointer, scope, ctx),
18671
18721
  `${MemberKind[vectorType]} component`
18672
- )
18673
- );
18674
- const integer2 = vectorType === 15 /* Vector2Int */ || vectorType === 17 /* Vector3Int */;
18722
+ );
18723
+ return integer2 ? value : Math.fround(value);
18724
+ });
18675
18725
  if (integer2 && values.some((value) => !Number.isInteger(value))) {
18676
18726
  throw new NSGetterRuntimeError(
18677
18727
  `${MemberKind[vectorType]} components must be integers`
@@ -19562,11 +19612,17 @@ function evalOperation(operation, scope, ctx) {
19562
19612
  if (operation.type === "arithmetic" /* arithmetic */) {
19563
19613
  const op = operation.arithmetic;
19564
19614
  const operands = op.pointers.map((p) => evalPointer(p, scope, ctx));
19565
- return applyArithmetic(op.type, operands, op.decimal === true, ctx);
19615
+ return applyArithmetic(
19616
+ op.type,
19617
+ operands,
19618
+ op.decimal === true,
19619
+ ctx,
19620
+ op.numeric
19621
+ );
19566
19622
  }
19567
19623
  return evalBooleanExpression(operation.expression, scope, ctx);
19568
19624
  }
19569
- function applyArithmetic(op, operands, decimal, ctx) {
19625
+ function applyArithmetic(op, operands, decimal, ctx, numeric) {
19570
19626
  if (operands.length === 0) {
19571
19627
  throw new NSGetterRuntimeError("Arithmetic operation with no operands");
19572
19628
  }
@@ -19599,31 +19655,43 @@ function applyArithmetic(op, operands, decimal, ctx) {
19599
19655
  `Arithmetic operand is not numeric: ${typeof o}`
19600
19656
  );
19601
19657
  });
19658
+ const round = numeric === "float" ? Math.fround : (value) => value;
19602
19659
  switch (op) {
19603
19660
  case "+" /* addition */:
19604
- return numbers.reduce((a, b) => a + b, 0);
19661
+ return numbers.reduce((a, b) => round(a + b), 0);
19605
19662
  case "-" /* subtraction */:
19606
- return numbers.reduce((a, b, i) => i === 0 ? b : a - b);
19663
+ return numbers.reduce((a, b, i) => i === 0 ? b : round(a - b));
19607
19664
  case "*" /* multiplication */:
19608
- return numbers.reduce((a, b) => a * b, 1);
19665
+ return numbers.reduce((a, b) => round(a * b), 1);
19609
19666
  case "/" /* division */:
19610
19667
  return numbers.reduce((a, b, i) => {
19611
19668
  if (i === 0) return b;
19612
19669
  if (b === 0) throw new NSGetterRuntimeError("Division by zero");
19613
- return a / b;
19670
+ return numeric === "int" ? Math.trunc(a / b) : round(a / b);
19614
19671
  });
19615
19672
  case "%" /* remainder */:
19616
19673
  return numbers.reduce((a, b, i) => {
19617
19674
  if (i === 0) return b;
19618
19675
  if (b === 0) throw new NSGetterRuntimeError("Modulo by zero");
19619
- return a % b;
19676
+ return round(a % b);
19620
19677
  });
19621
19678
  }
19622
19679
  }
19680
+ function hostCallResult(member, value, ctx) {
19681
+ if (typeof value !== "number" || member === null) return value;
19682
+ if (member.kind !== 13 /* Function */ && member.kind !== 23 /* NSFunction */) {
19683
+ return value;
19684
+ }
19685
+ const signature = resolveCallableSignature(member.id, member.kind, ctx);
19686
+ return signature?.returnTypeInfo.type === 4 /* Float */ ? Math.fround(value) : value;
19687
+ }
19623
19688
  function coerceAssignmentValue(value, targetTypeInfo, context) {
19624
19689
  if (targetTypeInfo.type === 8 /* Enum */ && typeof value === "string") {
19625
19690
  return [value];
19626
19691
  }
19692
+ if (targetTypeInfo.type === 4 /* Float */ && typeof value === "number") {
19693
+ return Math.fround(value);
19694
+ }
19627
19695
  return targetTypeInfo.type === 20 /* Decimal */ && typeof value === "number" ? coerceDecimalOperand(value, context) : value;
19628
19696
  }
19629
19697
  function coerceDecimalOperand(value, context) {
@@ -19786,7 +19854,7 @@ function evalMathOp(info, scope, ctx) {
19786
19854
  if (value < 0) return -1;
19787
19855
  return 0;
19788
19856
  case "sqrt":
19789
- return Math.sqrt(value);
19857
+ return Math.fround(Math.sqrt(value));
19790
19858
  }
19791
19859
  }
19792
19860
  function evalDecimalMathOp(info, fn, scope, ctx) {
@@ -20427,8 +20495,10 @@ function evalFunction(fn, scope, ctx) {
20427
20495
  );
20428
20496
  }
20429
20497
  case "toFloat":
20430
- return floatFromDecimalString(
20431
- coerceDecimalOperand(receiverRaw, "ToFloat")
20498
+ return Math.fround(
20499
+ floatFromDecimalString(
20500
+ coerceDecimalOperand(receiverRaw, "ToFloat")
20501
+ )
20432
20502
  );
20433
20503
  case "toDecimal": {
20434
20504
  if (typeof receiverRaw !== "number") {
@@ -20937,6 +21007,9 @@ function evaluateConstructorFields(descriptor, scope, ctx) {
20937
21007
  `constructor field '${validated.schemaKey}'`
20938
21008
  );
20939
21009
  }
21010
+ if (typeof value === "number" && validated.typeInfo.type === 4 /* Float */) {
21011
+ value = Math.fround(value);
21012
+ }
20940
21013
  if (value === null || value === void 0) {
20941
21014
  if (validated.requiredWithoutDefault) {
20942
21015
  throw new NSGetterRuntimeError(
@@ -21557,8 +21630,9 @@ function evaluateDeclaredConstructorArguments(info, record2, scope, ctx) {
21557
21630
  );
21558
21631
  }
21559
21632
  const value = byName.get(parameter3.name);
21560
- if (parameter3.type !== 20 /* Decimal */) return value;
21561
21633
  if (typeof value !== "number") return value;
21634
+ if (parameter3.type === 4 /* Float */) return Math.fround(value);
21635
+ if (parameter3.type !== 20 /* Decimal */) return value;
21562
21636
  return coerceDecimalOperand(
21563
21637
  value,
21564
21638
  `constructor argument '${parameter3.name}'`
@@ -32599,7 +32673,10 @@ var StrictNeoScriptResolver = class {
32599
32673
  canonicalDecimal(expression.raw, expression.pos)
32600
32674
  );
32601
32675
  }
32602
- return literal({ kind: "primitive", name: "float" }, expression.value);
32676
+ return literal(
32677
+ { kind: "primitive", name: "float" },
32678
+ Math.fround(expression.value)
32679
+ );
32603
32680
  }
32604
32681
  case "litString":
32605
32682
  return literal({ kind: "primitive", name: "string" }, expression.value);
@@ -37233,7 +37310,9 @@ var StrictNeoScriptResolver = class {
37233
37310
  arithmetic: {
37234
37311
  type: operator,
37235
37312
  pointers: [left.pointer, right.pointer],
37236
- ...isPrimitive(result, "decimal") ? { decimal: true } : {}
37313
+ ...isPrimitive(result, "decimal") ? { decimal: true } : {},
37314
+ ...isPrimitive(result, "int") ? { numeric: "int" } : {},
37315
+ ...isPrimitive(result, "float") ? { numeric: "float" } : {}
37237
37316
  }
37238
37317
  }
37239
37318
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.66.0",
3
+ "version": "0.67.1",
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.66.0 -->
12
+ <!-- reviewed-through-cli: 0.67.1 -->
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.66.0 -->
113
+ <!-- reviewed-through-cli: 0.67.1 -->
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`
@@ -202,6 +202,14 @@ Save, Session, Setter, or otherwise writable. A constructor writes only its
202
202
  `this` instance. A writable structured-leaf field still inherits the receiver's
203
203
  effective storage restrictions.
204
204
 
205
+ ## Numeric types
206
+
207
+ `int` is a 32-bit integer and `float` is a 32-bit float, matching C#. Float
208
+ literals, stored values, and every arithmetic step round to 32 bits, so
209
+ `0.1f + 0.2f` behaves like Unity. `int` division truncates toward zero.
210
+ `decimal` is exact base-10 and is the only type to use for currency or other
211
+ values that must not drift.
212
+
205
213
  ## Numeric builtins
206
214
 
207
215
  Use the `Math` namespace for numeric utilities with C# names and C# semantics: