@neocompose/cli 0.24.2 → 0.24.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,32 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.24.4] - 2026-08-07
4
+
5
+ ### Fixed
6
+
7
+ - Preserve every operand and its grouping when compiling chained NeoScript
8
+ `&&` and `||` expressions. The previous lowering overwrote the left
9
+ expression's connective, so `a || b || c` evaluated as `a || c`.
10
+ - Short-circuit logical expressions in the local evaluator instead of eagerly
11
+ evaluating an unused right-hand operand.
12
+
13
+ ## [0.24.3] - 2026-08-07
14
+
15
+ ### Fixed
16
+
17
+ - Close generic classes constructed by normal member-default evaluation from
18
+ the concrete member slot, so local candidate preparation no longer rejects
19
+ values such as `NeoAnimationSegmentFrame<int>` as open generics before
20
+ `neo test` can discover or execute tests.
21
+ - Key compiled spec artifacts by the prepared candidate document as well as
22
+ source, preventing stale pending class and enum IDs from leaking across
23
+ repeated `neo test` runs.
24
+
25
+ ### Changed
26
+
27
+ - Require full-project `neo test` validation in the bundled CLI development
28
+ guidance when evaluator or candidate-materialization behavior changes.
29
+
3
30
  ## [0.24.2] - 2026-08-07
4
31
 
5
32
  ### Fixed
package/dist/neo.mjs CHANGED
@@ -7323,7 +7323,7 @@ var NEOSCRIPT_COMPILER_REVISION;
7323
7323
  var init_strict_ir = __esm({
7324
7324
  "../packages/neoscript-language/src/strict-ir.ts"() {
7325
7325
  "use strict";
7326
- NEOSCRIPT_COMPILER_REVISION = 7;
7326
+ NEOSCRIPT_COMPILER_REVISION = 8;
7327
7327
  }
7328
7328
  });
7329
7329
 
@@ -8157,6 +8157,9 @@ function pointerToBooleanExpression(pointer) {
8157
8157
  if (pointer.type === "operation" /* Operation */ && pointer.operation.type === "boolean" /* Boolean */) {
8158
8158
  return pointer.operation.expression;
8159
8159
  }
8160
+ return pointerToBooleanOperandExpression(pointer);
8161
+ }
8162
+ function pointerToBooleanOperandExpression(pointer) {
8160
8163
  return {
8161
8164
  condition: {
8162
8165
  type: "equalTo" /* EqualTo */,
@@ -12701,7 +12704,7 @@ var init_strict_resolver = __esm({
12701
12704
  operation: {
12702
12705
  type: "boolean" /* Boolean */,
12703
12706
  expression: {
12704
- ...pointerToBooleanExpression(l.pointer),
12707
+ ...pointerToBooleanOperandExpression(l.pointer),
12705
12708
  connective: {
12706
12709
  type: expression.op === "&&" ? "&&" /* And */ : "||" /* Or */,
12707
12710
  to: pointerToBooleanExpression(r.pointer)
@@ -60276,12 +60279,11 @@ function stringifyForInterp(v) {
60276
60279
  function evalBooleanExpression(expression, scope, ctx) {
60277
60280
  const head = evalCondition(expression.condition, scope, ctx);
60278
60281
  if (!expression.connective) return head;
60279
- const tail = evalBooleanExpression(expression.connective.to, scope, ctx);
60280
60282
  switch (expression.connective.type) {
60281
60283
  case "&&" /* and */:
60282
- return head && tail;
60284
+ return head ? evalBooleanExpression(expression.connective.to, scope, ctx) : false;
60283
60285
  case "||" /* or */:
60284
- return head || tail;
60286
+ return head ? true : evalBooleanExpression(expression.connective.to, scope, ctx);
60285
60287
  }
60286
60288
  }
60287
60289
  function evalCondition(condition, scope, ctx) {
@@ -60764,8 +60766,8 @@ function validateClassConstructorDescriptor(info, ctx, requireEveryRequiredField
60764
60766
  `Cannot construct abstract Class '${schemaClass2.name}'.`
60765
60767
  );
60766
60768
  }
60767
- const replaySlot = ctx.__storedConstructionReplayNestedSlots?.toReversed().find((slot) => slot.classId === classId) ?? ctx.__storedConstructionReplaySlot;
60768
- const classArguments2 = replaySlot?.classId === classId ? replaySlot.classArguments : void 0;
60769
+ const genericSlot = ctx.__constructionGenericSlots?.toReversed().find((slot) => slot.classId === classId) ?? ctx.__storedConstructionReplaySlot;
60770
+ const classArguments2 = genericSlot?.classId === classId ? genericSlot.classArguments : void 0;
60769
60771
  const instanceEnv = resolveInstanceEnv(
60770
60772
  classId,
60771
60773
  classArguments2,
@@ -61621,7 +61623,7 @@ function constructionInitEvaluator(ctx, createdValues, argumentScopes, construct
61621
61623
  };
61622
61624
  return (member, init, sourceValueId) => {
61623
61625
  const evaluate = (argumentValues = []) => {
61624
- if (ctx.storedConstructionReplay !== true || !isMemberClassBase(member) || Object.keys(member.classArguments ?? {}).length === 0) {
61626
+ if (!isMemberClassBase(member) || Object.keys(member.classArguments ?? {}).length === 0) {
61625
61627
  return evaluateInitializerInContext(
61626
61628
  init,
61627
61629
  member,
@@ -61631,8 +61633,8 @@ function constructionInitEvaluator(ctx, createdValues, argumentScopes, construct
61631
61633
  sourceValueId ?? null
61632
61634
  );
61633
61635
  }
61634
- const previousSlots = ctx.__storedConstructionReplayNestedSlots;
61635
- ctx.__storedConstructionReplayNestedSlots = [
61636
+ const previousSlots = ctx.__constructionGenericSlots;
61637
+ ctx.__constructionGenericSlots = [
61636
61638
  ...previousSlots ?? [],
61637
61639
  {
61638
61640
  classId: member.classId,
@@ -61649,7 +61651,7 @@ function constructionInitEvaluator(ctx, createdValues, argumentScopes, construct
61649
61651
  sourceValueId ?? null
61650
61652
  );
61651
61653
  } finally {
61652
- ctx.__storedConstructionReplayNestedSlots = previousSlots;
61654
+ ctx.__constructionGenericSlots = previousSlots;
61653
61655
  }
61654
61656
  };
61655
61657
  if (init.compiled === void 0) {
@@ -101546,7 +101548,7 @@ var init_registry2 = __esm({
101546
101548
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
101547
101549
  formatVersion: 3,
101548
101550
  contractVersion: "3.9",
101549
- cliVersion: "0.24.2",
101551
+ cliVersion: "0.24.4",
101550
101552
  projectFileUploadBatchSize: 32,
101551
101553
  documentRecords: {
101552
101554
  member: {
@@ -103077,7 +103079,7 @@ function preparedHookCandidate(workspace) {
103077
103079
  );
103078
103080
  }
103079
103081
  }
103080
- function compileSpec(workspace, document, absolutePath, projectSourceHash) {
103082
+ function compileSpec(workspace, document, absolutePath, projectCompilationHash2) {
103081
103083
  const scriptDocument = readDocumentArrays(document);
103082
103084
  const path = relative6(workspace.root, absolutePath).split(sep6).join("/");
103083
103085
  const source = readFileSync17(absolutePath, "utf8");
@@ -103087,12 +103089,12 @@ function compileSpec(workspace, document, absolutePath, projectSourceHash) {
103087
103089
  ".neo",
103088
103090
  "test-build",
103089
103091
  "v1",
103090
- projectSourceHash,
103092
+ projectCompilationHash2,
103091
103093
  `${sourceHash}.json`
103092
103094
  );
103093
103095
  try {
103094
103096
  const cached = JSON.parse(readFileSync17(artifactPath, "utf8"));
103095
- if (isRecord10(cached) && cached.version === 1 && cached.compilerRevision === NEOSCRIPT_COMPILER_REVISION && cached.projectSourceHash === projectSourceHash && cached.sourceHash === sourceHash && isRecord10(cached.action) && typeof cached.artifactSha256 === "string" && createHash10("sha256").update(JSON.stringify(cached.action)).digest("hex") === cached.artifactSha256 && cached.action.compilerRevision === NEOSCRIPT_COMPILER_REVISION && Array.isArray(cached.action.parameters) && Array.isArray(cached.action.instructions) && isRecord10(cached.action.typeInfo)) {
103097
+ if (isRecord10(cached) && cached.version === 1 && cached.compilerRevision === NEOSCRIPT_COMPILER_REVISION && cached.projectCompilationHash === projectCompilationHash2 && cached.sourceHash === sourceHash && isRecord10(cached.action) && typeof cached.artifactSha256 === "string" && createHash10("sha256").update(JSON.stringify(cached.action)).digest("hex") === cached.artifactSha256 && cached.action.compilerRevision === NEOSCRIPT_COMPILER_REVISION && Array.isArray(cached.action.parameters) && Array.isArray(cached.action.instructions) && isRecord10(cached.action.typeInfo)) {
103096
103098
  return {
103097
103099
  path,
103098
103100
  source,
@@ -103122,7 +103124,7 @@ function compileSpec(workspace, document, absolutePath, projectSourceHash) {
103122
103124
  `${JSON.stringify({
103123
103125
  version: 1,
103124
103126
  compilerRevision: NEOSCRIPT_COMPILER_REVISION,
103125
- projectSourceHash,
103127
+ projectCompilationHash: projectCompilationHash2,
103126
103128
  sourceHash,
103127
103129
  artifactSha256: createHash10("sha256").update(JSON.stringify(compiled.action)).digest("hex"),
103128
103130
  action: compiled.action
@@ -103131,6 +103133,9 @@ function compileSpec(workspace, document, absolutePath, projectSourceHash) {
103131
103133
  );
103132
103134
  return compiled;
103133
103135
  }
103136
+ function projectCompilationHash(projectSourceHash, document) {
103137
+ return createHash10("sha256").update(projectSourceHash).update("\0").update(JSON.stringify(document)).digest("hex");
103138
+ }
103134
103139
  function selectedSpecPaths(workspace, selectors) {
103135
103140
  const all = listProjectTestFilesV1(workspace.root);
103136
103141
  const relativePaths = new Map(
@@ -104006,6 +104011,10 @@ async function runTest(workspace, options, dependencies = {}) {
104006
104011
  }
104007
104012
  projectFingerprint = `sha256:${candidate.sourceHash}`;
104008
104013
  const rawDocument = documentRecord(candidate.document);
104014
+ const compilationHash = projectCompilationHash(
104015
+ candidate.sourceHash,
104016
+ rawDocument
104017
+ );
104009
104018
  phase = "select";
104010
104019
  let selectedPaths;
104011
104020
  try {
@@ -104023,7 +104032,7 @@ async function runTest(workspace, options, dependencies = {}) {
104023
104032
  }
104024
104033
  phase = "compile";
104025
104034
  specs = selectedPaths.map(
104026
- (path) => compileSpec(workspace, rawDocument, path, candidate.sourceHash)
104035
+ (path) => compileSpec(workspace, rawDocument, path, compilationHash)
104027
104036
  );
104028
104037
  let pattern = null;
104029
104038
  if (options.testNamePattern !== null) {
@@ -104216,10 +104225,14 @@ async function inspectNeoTestCompilation(workspace, dependencies = {}) {
104216
104225
  };
104217
104226
  }
104218
104227
  const document = documentRecord(candidate.document);
104228
+ const compilationHash = projectCompilationHash(
104229
+ candidate.sourceHash,
104230
+ document
104231
+ );
104219
104232
  const errors = [];
104220
104233
  for (const path of selectedPaths) {
104221
104234
  try {
104222
- compileSpec(workspace, document, path, candidate.sourceHash);
104235
+ compileSpec(workspace, document, path, compilationHash);
104223
104236
  } catch (error) {
104224
104237
  errors.push({
104225
104238
  file: relative6(workspace.root, path).split(sep6).join("/"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.24.2",
3
+ "version": "0.24.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.24.2 -->
12
+ <!-- reviewed-through-cli: 0.24.4 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -82,7 +82,7 @@ wrappers.
82
82
  The marker near the top of `SKILL.md` must exactly match the package version:
83
83
 
84
84
  ```html
85
- <!-- reviewed-through-cli: 0.24.2 -->
85
+ <!-- reviewed-through-cli: 0.24.4 -->
86
86
  ```
87
87
 
88
88
  The quoted version above is checked too, so this instruction cannot go stale
@@ -115,5 +115,7 @@ npm run doctor
115
115
 
116
116
  Also run contract/corpus checks, Monaco/browser parity, VSIX packaging, and
117
117
  interactive sample no-op verification when the corresponding implementation
118
- surface changes. Run `npm run doctor` last; do not rerun successful suites only
119
- because doctor formatted files.
118
+ surface changes. Evaluator or candidate-materialization changes also require an
119
+ actual `neo test` run against the affected full-project candidate; focused
120
+ Vitest coverage does not replace that end-to-end command. Run `npm run doctor`
121
+ last; do not rerun successful suites only because doctor formatted files.
@@ -7,6 +7,7 @@ runtime ownership, evaluation, and migrations.
7
7
 
8
8
  - Inline bodies and snippets
9
9
  - Nullability and ownership
10
+ - Logical operators
10
11
  - Loops and transfer
11
12
  - Switch
12
13
  - Try/catch
@@ -53,6 +54,13 @@ Save, Session, Setter, or otherwise writable. A constructor writes only its
53
54
  `this` instance. A writable structured-leaf field still inherits the receiver's
54
55
  effective storage restrictions.
55
56
 
57
+ ## Logical operators
58
+
59
+ Use `&&` and `||` with C# precedence: `&&` binds tighter than `||`, and
60
+ parentheses preserve explicit grouping. Both operators short-circuit, so the
61
+ right operand is evaluated only when it can change the result. Chained
62
+ expressions retain every operand; `a || b || c` never skips `b`.
63
+
56
64
  ## Loops and transfer
57
65
 
58
66
  Use C#-shaped loops: