@neocompose/cli 0.81.3 → 0.81.5

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,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.81.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 051ab67: Fix generated C# Clone interface implementations for immutable classes that
8
+ inherit generic writable classes. Re-export to regenerate affected clients.
9
+
10
+ ## 0.81.4
11
+
12
+ ### Patch Changes
13
+
14
+ - 7ed5870: Reject values on boolean flags so `push --dry-run=true` cannot silently perform a real commit. Use bare switches such as `--dry-run` and `--help`.
15
+ - 887fb8c: Reject unused branch flags and unsupported dry-run requests before command execution, so branch creation and other mutating commands cannot silently ignore a requested preview.
16
+ - 6631cef: Refuse initialization into a nonempty directory, apart from Git metadata, so init cannot overwrite existing source or managed files without consent.
17
+ - b2a688d: Resolve source conflicts with CRLF or mixed line endings while preserving the selected source bytes and formatting.
18
+ - 743783d: Refuse branch switches with unpushed changes, conflicts, invalid source, or pending push recovery so switching cannot silently discard local work.
19
+ - b0b945f: Report constructor parameter errors at their source location instead of crashing
20
+ with an internal missing-constructor manifest error during status or push.
21
+ - c11209a: Fix lookup initializers losing row identities in projected selection sets.
22
+ Accept Reference(value) for evaluated class values, including collection
23
+ callbacks, while preserving type checking and the original value identity.
24
+
3
25
  ## 0.81.3
4
26
 
5
27
  ### Patch Changes
package/dist/neo.mjs CHANGED
@@ -19229,6 +19229,11 @@ function parseArgs(argv) {
19229
19229
  const equals = arg.indexOf("=");
19230
19230
  const name = arg.slice(2, equals < 0 ? void 0 : equals);
19231
19231
  if (equals >= 0) {
19232
+ if (BOOLEAN_FLAGS.has(name)) {
19233
+ throw new NeoCliUsageError(
19234
+ `--${name} does not accept a value. Pass --${name} to enable it, or omit the flag.`
19235
+ );
19236
+ }
19232
19237
  flags.set(name, arg.slice(equals + 1));
19233
19238
  continue;
19234
19239
  }
@@ -19283,6 +19288,38 @@ function assertAllowedFlags(args, allowed) {
19283
19288
  );
19284
19289
  }
19285
19290
  }
19291
+ function assertCommandFlags(args) {
19292
+ const subcommand = args.positional[0];
19293
+ if (args.command === "branch") {
19294
+ const allowed = /* @__PURE__ */ new Set(["api"]);
19295
+ if (subcommand === "list") {
19296
+ allowed.add("json");
19297
+ allowed.add("all");
19298
+ }
19299
+ if (subcommand === "create") allowed.add("from");
19300
+ if (subcommand === "refresh") allowed.add("dry-run");
19301
+ assertAllowedFlags(args, allowed);
19302
+ }
19303
+ if (!args.flags.has("dry-run")) return;
19304
+ switch (args.command) {
19305
+ case "push":
19306
+ case "merge":
19307
+ case "refresh":
19308
+ return;
19309
+ case "branch":
19310
+ if (subcommand === "refresh") return;
19311
+ break;
19312
+ case "release":
19313
+ if (subcommand === "cut") return;
19314
+ break;
19315
+ case "migrate":
19316
+ if (subcommand === "run") return;
19317
+ break;
19318
+ }
19319
+ throw new NeoCliUsageError(
19320
+ `Flag "--dry-run" is not supported by neo ${args.command}${subcommand === void 0 ? "" : ` ${subcommand}`}. No changes were made.`
19321
+ );
19322
+ }
19286
19323
  var NeoCliUsageError, BOOLEAN_FLAGS, KNOWN_FLAGS, TEST_ONLY_FLAGS;
19287
19324
  var init_args = __esm({
19288
19325
  "src/args.ts"() {
@@ -33735,7 +33772,48 @@ var init_strict_resolver = __esm({
33735
33772
  );
33736
33773
  case "call": {
33737
33774
  const receiverValue = this.callReceiver(expression.callee, scope);
33738
- if (expression.callee.kind === "ident" && expression.callee.name === "Reference" && Array.isArray(expression.typeArguments)) {
33775
+ if (expression.callee.kind === "ident" && expression.callee.name === "Reference") {
33776
+ if (expression.args.length === 1 && expression.argumentNames?.[0] == null) {
33777
+ if ((expression.typeArguments?.length ?? 0) > 1) {
33778
+ throw new CompileError(
33779
+ "Reference accepts at most one generic target type.",
33780
+ expression.pos
33781
+ );
33782
+ }
33783
+ const explicit = expression.typeArguments?.[0];
33784
+ const targetType = explicit === void 0 ? void 0 : this.resolveType(explicit);
33785
+ const target = this.resolveExpression(
33786
+ expression.args[0],
33787
+ scope,
33788
+ targetType
33789
+ );
33790
+ const type = targetType ?? target.type;
33791
+ if (type.kind !== "typeParameter" && !(type.kind === "named" && this.project.typeById.get(type.typeId)?.kind === "class")) {
33792
+ throw new CompileError(
33793
+ "Reference(value) requires a class value.",
33794
+ expression.pos
33795
+ );
33796
+ }
33797
+ if (!isNeoScriptTypeAssignable(target.type, type, this.project)) {
33798
+ throw new CompileError(
33799
+ `Reference target ${this.describe(target.type)} is not assignable to ${this.describe(type)}.`,
33800
+ expression.pos
33801
+ );
33802
+ }
33803
+ if (expected !== void 0 && !isNeoScriptTypeAssignable(type, expected, this.project)) {
33804
+ throw new CompileError(
33805
+ `Reference target ${this.describe(type)} is not assignable to ${this.describe(expected)}.`,
33806
+ expression.pos
33807
+ );
33808
+ }
33809
+ return { ...target, type };
33810
+ }
33811
+ if (expression.typeArguments === void 0) {
33812
+ throw new CompileError(
33813
+ 'Reference requires one class value, or Reference<T>(id: "...").',
33814
+ expression.pos
33815
+ );
33816
+ }
33739
33817
  const [referenceTypeArgument] = expression.typeArguments;
33740
33818
  if (expression.typeArguments.length !== 1 || referenceTypeArgument === void 0) {
33741
33819
  throw new CompileError(
@@ -45719,6 +45797,7 @@ function validateExpression(expression, expected, scope, environment, uri, range
45719
45797
  validateReferenceExpression(
45720
45798
  expression,
45721
45799
  expected,
45800
+ scope,
45722
45801
  environment,
45723
45802
  uri,
45724
45803
  range2,
@@ -46496,7 +46575,10 @@ function inferExpressionType(expression, scope, environment) {
46496
46575
  const explicit = expression.typeArguments?.[0];
46497
46576
  if (explicit) return semanticAstType(explicit);
46498
46577
  const target = expression.args[0] ? referenceTargetPath(expression.args[0]) : null;
46499
- return target ? environment.referenceTargets.get(target)?.type : void 0;
46578
+ const symbolType = target ? environment.referenceTargets.get(target)?.type : void 0;
46579
+ if (symbolType !== void 0) return symbolType;
46580
+ const argument2 = expression.args[0];
46581
+ return argument2 === void 0 ? void 0 : inferExpressionType(argument2, scope, environment);
46500
46582
  }
46501
46583
  return environment.functions.get(expression.callee.name)?.[0]?.returnType;
46502
46584
  }
@@ -46544,7 +46626,7 @@ function subscriptKeyType(receiver) {
46544
46626
  if (receiver?.name === "List") return primitiveType("int");
46545
46627
  return void 0;
46546
46628
  }
46547
- function validateReferenceExpression(expression, declared, environment, uri, range2, diagnostics) {
46629
+ function validateReferenceExpression(expression, declared, scope, environment, uri, range2, diagnostics) {
46548
46630
  const expected = declared?.name === "NeoDialogueReference" ? { ...declared, name: "NeoDialogue" } : declared;
46549
46631
  const typeArguments = expression.typeArguments ?? [];
46550
46632
  const names = expression.argumentNames ?? [];
@@ -46632,6 +46714,7 @@ function validateReferenceExpression(expression, declared, environment, uri, ran
46632
46714
  if (path !== null && path.startsWith("root.")) return;
46633
46715
  const target = path ? environment.referenceTargets.get(path) : void 0;
46634
46716
  if (!target) {
46717
+ if (path === null || scope.has(path.split(".")[0])) return;
46635
46718
  malformed(
46636
46719
  "Reference target must be a local value, stored static value, or dialogue class symbol."
46637
46720
  );
@@ -54564,7 +54647,7 @@ var init_registry = __esm({
54564
54647
  "../packages/manifest/schema-contract/registry.mjs"() {
54565
54648
  "use strict";
54566
54649
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
54567
- cliVersion: "0.81.3",
54650
+ cliVersion: "0.81.5",
54568
54651
  projectFileUploadBatchSize: 32,
54569
54652
  documentRecords: {
54570
54653
  member: {
@@ -93948,6 +94031,7 @@ function evaluateMemberInitializer(args) {
93948
94031
  __constructedFieldSchemaKeysByValue: /* @__PURE__ */ new WeakMap(),
93949
94032
  __createdStorageKeyDeclarations: args.storageKeyDeclarations ?? /* @__PURE__ */ new Map()
93950
94033
  };
94034
+ let evaluatedContext = ctx;
93951
94035
  const result = (() => {
93952
94036
  try {
93953
94037
  return evaluateNSGetterWithEffects(
@@ -93956,6 +94040,9 @@ function evaluateMemberInitializer(args) {
93956
94040
  args.argumentValues ?? [],
93957
94041
  {
93958
94042
  exportInitializerValues: true,
94043
+ inspectResult: (_value, runtimeContext) => {
94044
+ evaluatedContext = runtimeContext;
94045
+ },
93959
94046
  // Seeded rows already hold their placement.
93960
94047
  ...args.sessionValues === void 0 ? {
93961
94048
  initializerResult: {
@@ -93981,7 +94068,7 @@ function evaluateMemberInitializer(args) {
93981
94068
  args.member,
93982
94069
  result.value,
93983
94070
  created,
93984
- ctx
94071
+ evaluatedContext
93985
94072
  );
93986
94073
  }
93987
94074
  const rootRow = initializerRootRow(result.value, created);
@@ -94957,6 +95044,40 @@ var init_effective_value_reader = __esm({
94957
95044
  }
94958
95045
  }
94959
95046
  classDefaultRows = /* @__PURE__ */ new Map();
95047
+ /** Prefer the stored field; expand a sparse construction only if that field is absent. */
95048
+ classFieldRow(valueId, schemaKey, placement) {
95049
+ if (this.deletedIds.has(valueId)) return void 0;
95050
+ this.placements.set(valueId, placement);
95051
+ this.context.readTracking?.valueIds.add(valueId);
95052
+ const mode = this.mode(valueId);
95053
+ const row = mode === "partial" ? this.rawRow(valueId) : this.effectiveRows.get(valueId) ?? this.rawRow(valueId);
95054
+ if (row === void 0 || !isStringRecord6(row.value)) return void 0;
95055
+ const classId = row.classId ?? (isMemberClassBase(placement) ? placement.classId : void 0);
95056
+ if (classId === void 0) return void 0;
95057
+ const entry = this.schema(classId).find(
95058
+ (entry2) => entry2.schemaKey === schemaKey
95059
+ );
95060
+ if (entry?.memberId == null) return void 0;
95061
+ const rawMember = this.memberForId(entry.memberId);
95062
+ if (rawMember === null) return void 0;
95063
+ const env = this.graph.genericEnvironmentForRow(row, placement);
95064
+ const member = substituteMember(rawMember, env, this.document.members);
95065
+ const slot = readClassMember({
95066
+ member,
95067
+ schemaKey,
95068
+ record: row.value,
95069
+ expandedRecord: () => {
95070
+ const expanded = this.row(valueId, void 0, placement)?.value;
95071
+ return isStringRecord6(expanded) ? expanded : null;
95072
+ },
95073
+ mode
95074
+ });
95075
+ if (slot.kind === "declaration") return this.declarationRow(member, env);
95076
+ if (slot.kind !== "value" || typeof slot.value !== "string")
95077
+ return void 0;
95078
+ this.context.readTracking?.valueIds.add(slot.value);
95079
+ return this.row(slot.value, void 0, member);
95080
+ }
94960
95081
  /** A row projection for editors that follow child ids through sparse class records. */
94961
95082
  rowWithClassDefaults(valueId) {
94962
95083
  const cached = this.classDefaultRows.get(valueId);
@@ -96947,7 +97068,9 @@ function isNamePath(expression) {
96947
97068
  }
96948
97069
  function isLiteralCall(expression) {
96949
97070
  if (expression.callee.kind === "ident" && expression.callee.name === "Reference") {
96950
- return true;
97071
+ if (expression.argumentNames?.some((name) => name !== null)) return true;
97072
+ const argument2 = expression.args[0];
97073
+ return expression.args.length === 1 && argument2 !== void 0 && (isNamePath(argument2) || argument2.kind === "litString");
96951
97074
  }
96952
97075
  return expression.callee.kind === "member" && expression.callee.name === "Slice";
96953
97076
  }
@@ -97341,21 +97464,15 @@ function lowerClassConstructors(context, declaration, classId, lowerParameterTyp
97341
97464
  const declared = declaration.constructors.map((declared2) => {
97342
97465
  const id2 = declaredConstructorId2(declaration, declared2);
97343
97466
  const base = context.baseConstructors.get(id2);
97344
- const parameters = declared2.parameters.map((parameter4) => {
97345
- const parameterType = lowerParameterType(parameter4.type, declaration);
97346
- return {
97347
- name: parameter4.name,
97348
- type: parameterType,
97349
- default: lowerParameterDefault(
97350
- context,
97351
- parameter4,
97352
- parameterType,
97353
- `${declaration.name}.${declared2.name}(${parameter4.name})`
97354
- ),
97355
- source: span({ source: parameter4.source }),
97356
- selectionSpan: span({ source: parameter4.source })
97357
- };
97358
- });
97467
+ const parameters = declared2.parameters.map(
97468
+ (parameter4) => lowerConstructorParameter(
97469
+ context,
97470
+ parameter4,
97471
+ declaration,
97472
+ lowerParameterType,
97473
+ `${declaration.name}.${declared2.name}(${parameter4.name})`
97474
+ )
97475
+ );
97359
97476
  const baseArguments = lowerBaseArguments(
97360
97477
  context,
97361
97478
  declaration,
@@ -97400,26 +97517,41 @@ function lowerRequiredConstructor(context, declaration, classId, lowerParameterT
97400
97517
  selectionSpan: sourceSpan
97401
97518
  },
97402
97519
  classId,
97403
- arguments: required2.parameters.map((parameter4) => {
97404
- const parameterType = lowerParameterType(parameter4.type, declaration);
97405
- return {
97406
- name: parameter4.name,
97407
- type: parameterType,
97408
- default: lowerParameterDefault(
97409
- context,
97410
- parameter4,
97411
- parameterType,
97412
- `${declaration.name}(${parameter4.name})`
97413
- ),
97414
- source: span({ source: parameter4.source }),
97415
- selectionSpan: span({ source: parameter4.source })
97416
- };
97417
- }),
97520
+ arguments: required2.parameters.map(
97521
+ (parameter4) => lowerConstructorParameter(
97522
+ context,
97523
+ parameter4,
97524
+ declaration,
97525
+ lowerParameterType,
97526
+ `${declaration.name}(${parameter4.name})`
97527
+ )
97528
+ ),
97418
97529
  code: requiredConstructorBody(required2.body, base?.code),
97419
97530
  ...baseArguments.length === 0 ? {} : { baseArguments },
97420
97531
  ...baseInitializerFields.length === 0 ? {} : { baseInitializerFields }
97421
97532
  };
97422
97533
  }
97534
+ function lowerConstructorParameter(context, parameter4, declaration, lowerParameterType, path) {
97535
+ try {
97536
+ const type = lowerParameterType(parameter4.type, declaration);
97537
+ return {
97538
+ name: parameter4.name,
97539
+ type,
97540
+ default: lowerParameterDefault(context, parameter4, type, path),
97541
+ source: span({ source: parameter4.source }),
97542
+ selectionSpan: span({ source: parameter4.source })
97543
+ };
97544
+ } catch (error) {
97545
+ if (error instanceof SchemaSourceError) throw error;
97546
+ throw new SchemaSourceError(
97547
+ error instanceof Error ? error.message : String(error),
97548
+ parameter4.source.uri,
97549
+ parameter4.source.range.start.line + 1,
97550
+ parameter4.source.range.start.character + 1,
97551
+ "schema-lowering"
97552
+ );
97553
+ }
97554
+ }
97423
97555
  function requiredConstructorRecordId(classId) {
97424
97556
  return isPendingId(classId) ? pendingRequiredConstructorId(classId) : derivedRequiredConstructorId(classId);
97425
97557
  }
@@ -97533,6 +97665,7 @@ var init_lower_constructors = __esm({
97533
97665
  init_pending_id();
97534
97666
  init_declared_constructors();
97535
97667
  init_required_constructor_id();
97668
+ init_source_diagnostics();
97536
97669
  init_declared_constructors2();
97537
97670
  init_lower_parameter_defaults();
97538
97671
  init_init_source();
@@ -148727,6 +148860,11 @@ function computeStatus(workspace, options, conflictedFiles, parseErrors, parseWa
148727
148860
  Object.values(loweringRecords)
148728
148861
  )
148729
148862
  });
148863
+ if (failedDeclarations.size > 0 && validAnalysis.schema.classes.some(
148864
+ (declaration) => failedDeclarations.has(declaration)
148865
+ )) {
148866
+ return refusedStatus(conflictedFiles, parseErrors, parseWarnings);
148867
+ }
148730
148868
  const loweredMemberIds = new Set(
148731
148869
  manifest.members.map((member) => member.id)
148732
148870
  );
@@ -159758,7 +159896,7 @@ function projectSignatureHelp(analysis, document, position) {
159758
159896
  );
159759
159897
  }
159760
159898
  if (callee.text === "Reference") {
159761
- const parameters = ["symbol or id", "withProvenance = false"];
159899
+ const parameters = ["class value, symbol, or id", "withProvenance = false"];
159762
159900
  return signatureResult(
159763
159901
  "Reference",
159764
159902
  parameters,
@@ -172926,7 +173064,11 @@ function generateNeoSchemaClassClass(ctx, classId) {
172926
173064
  classDef,
172927
173065
  memberEnv
172928
173066
  );
172929
- const projectedAncestorCloneAdapters = hasWritableFamily ? generateProjectedAncestorCloneAdapters(ctx, classDef, genericEnv) : [];
173067
+ const projectedAncestorCloneAdapters = generateProjectedAncestorCloneAdapters(
173068
+ ctx,
173069
+ classDef,
173070
+ genericEnv
173071
+ );
172930
173072
  const familyAdapters = [
172931
173073
  ...projectedAncestorCloneAdapters,
172932
173074
  ...projectedAncestorCloneAdapters.length > 0 && (explicitFunctionAdapters.length > 0 || inheritedPropertyAdapters.length > 0) ? [""] : [],
@@ -182019,7 +182161,7 @@ var UNITY_CODEGEN_REVISION;
182019
182161
  var init_unity_codegen_revision = __esm({
182020
182162
  "../src/models/exports/unity-codegen-revision.ts"() {
182021
182163
  "use strict";
182022
- UNITY_CODEGEN_REVISION = "d4026a3848848c8507d45dd54b6d31c1a0588b17b64c35e3fe9b25c89eb2fc05";
182164
+ UNITY_CODEGEN_REVISION = "c3d56ed8b41f473d0023ec967df2a4f75507b760551edf58026f5163ee546006";
182023
182165
  }
182024
182166
  });
182025
182167
 
@@ -189657,7 +189799,13 @@ var init_exports = {};
189657
189799
  __export(init_exports, {
189658
189800
  runInit: () => runInit
189659
189801
  });
189660
- import { existsSync as existsSync19, mkdirSync as mkdirSync14, readFileSync as readFileSync25, writeFileSync as writeFileSync15 } from "node:fs";
189802
+ import {
189803
+ existsSync as existsSync19,
189804
+ mkdirSync as mkdirSync14,
189805
+ readFileSync as readFileSync25,
189806
+ readdirSync as readdirSync7,
189807
+ writeFileSync as writeFileSync15
189808
+ } from "node:fs";
189661
189809
  import { join as join23, resolve as resolve4 } from "node:path";
189662
189810
  async function runInit(options) {
189663
189811
  let projectId = options.projectId;
@@ -189758,6 +189906,16 @@ async function runInit(options) {
189758
189906
  if (existsSync19(join23(root, NEO_CONFIG_FILE))) {
189759
189907
  throw new Error(`"${join23(root, NEO_CONFIG_FILE)}" already exists.`);
189760
189908
  }
189909
+ if (existsSync19(root)) {
189910
+ const existing = readdirSync7(root).find(
189911
+ (name) => name !== ".git" && name !== ".gitignore"
189912
+ );
189913
+ if (existing !== void 0) {
189914
+ throw new Error(
189915
+ `Cannot initialize ${JSON.stringify(root)} because it contains ${JSON.stringify(existing)}. Choose an empty directory, or back up and move existing files first.`
189916
+ );
189917
+ }
189918
+ }
189761
189919
  mkdirSync14(root, { recursive: true });
189762
189920
  await acquireWorkspaceLock(root, "init");
189763
189921
  ensurePrivateStateIgnored(root);
@@ -193079,7 +193237,7 @@ import { createHash as createHash18, randomUUID as randomUUID8 } from "node:cryp
193079
193237
  import {
193080
193238
  existsSync as existsSync20,
193081
193239
  mkdirSync as mkdirSync15,
193082
- readdirSync as readdirSync7,
193240
+ readdirSync as readdirSync8,
193083
193241
  readFileSync as readFileSync27,
193084
193242
  realpathSync as realpathSync2,
193085
193243
  renameSync as renameSync10,
@@ -194373,7 +194531,7 @@ function testBuildFiles(root) {
194373
194531
  if (!existsSync20(root)) return [];
194374
194532
  const files = [];
194375
194533
  const visit = (directory) => {
194376
- for (const entry of readdirSync7(directory, { withFileTypes: true })) {
194534
+ for (const entry of readdirSync8(directory, { withFileTypes: true })) {
194377
194535
  if (entry.isSymbolicLink()) continue;
194378
194536
  const path = join24(directory, entry.name);
194379
194537
  if (entry.isDirectory()) visit(path);
@@ -197660,6 +197818,47 @@ function findVersion(versions, ref) {
197660
197818
  (version) => version.id === ref || version.name === ref || version.semver.label === ref
197661
197819
  );
197662
197820
  }
197821
+ function assertCleanBranchSwitch(workspace) {
197822
+ assertNoPendingPushRecovery(workspace);
197823
+ const records3 = Object.values(workspace.state.records);
197824
+ for (const record7 of records3) {
197825
+ if (record7.conflictServerHash !== void 0) {
197826
+ throw new Error(
197827
+ "Cannot switch branches with unresolved record conflicts. Run neo resolve before switching."
197828
+ );
197829
+ }
197830
+ }
197831
+ if (records3.length === 0) {
197832
+ const inputs = listProjectExportInputsV4(workspace.root);
197833
+ if (inputs.sources.length === 0 && inputs.binaries.length === 0) return;
197834
+ }
197835
+ const status = computeWorkspaceStatus2(workspace);
197836
+ if (status.conflictedFiles.length > 0) {
197837
+ throw new Error(
197838
+ "Cannot switch branches with unresolved conflicts. Resolve them before switching."
197839
+ );
197840
+ }
197841
+ if (status.parseErrors.length > 0) {
197842
+ throw new Error(
197843
+ "Cannot switch branches while local source has errors. Run neo status and fix or back up your edits before switching."
197844
+ );
197845
+ }
197846
+ if (status.changes.length > 0) {
197847
+ throw new Error(
197848
+ "Cannot switch branches with unpushed changes. Push or back up your edits first; use neo pull --force only to deliberately discard them."
197849
+ );
197850
+ }
197851
+ if (status.authoredValueSeeds.size > 0) {
197852
+ throw new Error(
197853
+ "Cannot switch branches with unpushed value seeds. Push or back up your edits first; use neo pull --force only to deliberately discard them."
197854
+ );
197855
+ }
197856
+ if ((status.binaryChanges?.length ?? 0) > 0) {
197857
+ throw new Error(
197858
+ "Cannot switch branches with pending file changes. Run neo status and settle those changes before switching."
197859
+ );
197860
+ }
197861
+ }
197663
197862
  async function runBranch(workspace, subcommand, positional, fromRef) {
197664
197863
  if (subcommand === "list") {
197665
197864
  const { versions } = await loadVersionMetadata(workspace);
@@ -197750,9 +197949,13 @@ async function runBranch(workspace, subcommand, positional, fromRef) {
197750
197949
  console.log(`Already on ${displayLabel(target)}.`);
197751
197950
  return;
197752
197951
  }
197952
+ assertCleanBranchSwitch(workspace);
197753
197953
  workspace.config = { ...workspace.config, versionId: target.id };
197754
197954
  writeWorkspaceConfig(workspace.root, workspace.config);
197755
- workspace.state = { records: {} };
197955
+ workspace.state = {
197956
+ records: workspace.state.records,
197957
+ localValues: workspace.state.localValues
197958
+ };
197756
197959
  writeWorkspaceState(workspace.root, workspace.state);
197757
197960
  success(
197758
197961
  `Switched to ${color.bold(displayLabel(target))} ${color.dim(`(${target.id})`)}. Pulling\u2026`
@@ -198212,6 +198415,8 @@ var init_branch = __esm({
198212
198415
  init_value_comparison();
198213
198416
  init_ui();
198214
198417
  init_pull();
198418
+ init_workspace_status();
198419
+ init_push_recovery();
198215
198420
  init_http();
198216
198421
  init_project_transaction();
198217
198422
  displayLabel = projectVersionDisplayLabel;
@@ -201237,7 +201442,7 @@ var dev_exports = {};
201237
201442
  __export(dev_exports, {
201238
201443
  runDev: () => runDev
201239
201444
  });
201240
- import { readdirSync as readdirSync8, statSync as statSync6, watch } from "node:fs";
201445
+ import { readdirSync as readdirSync9, statSync as statSync6, watch } from "node:fs";
201241
201446
  import { basename as basename5, dirname as dirname15, join as join27, resolve as resolve6 } from "node:path";
201242
201447
  import { emitKeypressEvents } from "node:readline";
201243
201448
  async function runDev(root, loadWorkspace2, options) {
@@ -201370,7 +201575,7 @@ function countsAsExportInput(root, relativePath, event, directories) {
201370
201575
  function rememberDirectories(root, relativePath, directories) {
201371
201576
  let entries;
201372
201577
  try {
201373
- entries = readdirSync8(join27(root, relativePath), { withFileTypes: true });
201578
+ entries = readdirSync9(join27(root, relativePath), { withFileTypes: true });
201374
201579
  } catch (error) {
201375
201580
  if (error instanceof Error && "code" in error) {
201376
201581
  if (error.code === "ENOENT") return;
@@ -201549,18 +201754,19 @@ function resolveMarkers(source, side) {
201549
201754
  const output = [];
201550
201755
  let mode = "outside";
201551
201756
  for (const line of lines) {
201552
- if (line.startsWith("<<<<<<<")) {
201757
+ const marker = line.endsWith("\r") ? line.slice(0, -1) : line;
201758
+ if (marker.startsWith("<<<<<<<")) {
201553
201759
  if (mode !== "outside") {
201554
201760
  throw new Error("Nested conflict markers are not supported.");
201555
201761
  }
201556
201762
  mode = "local";
201557
201763
  continue;
201558
201764
  }
201559
- if (line === "=======" && mode === "local") {
201765
+ if (marker === "=======" && mode === "local") {
201560
201766
  mode = "server";
201561
201767
  continue;
201562
201768
  }
201563
- if (line.startsWith(">>>>>>>")) {
201769
+ if (marker.startsWith(">>>>>>>")) {
201564
201770
  if (mode !== "server") {
201565
201771
  throw new Error("Unbalanced conflict markers.");
201566
201772
  }
@@ -201847,7 +202053,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
201847
202053
  async function main() {
201848
202054
  const args = parseArgs(process.argv.slice(2));
201849
202055
  if (args.command === "--version") {
201850
- console.log("0.81.3");
202056
+ console.log("0.81.5");
201851
202057
  return;
201852
202058
  }
201853
202059
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
@@ -201859,6 +202065,7 @@ async function main() {
201859
202065
  return;
201860
202066
  }
201861
202067
  assertKnownFlags(args);
202068
+ assertCommandFlags(args);
201862
202069
  if (WORKSPACE_LOCK_COMMANDS.has(args.command)) {
201863
202070
  const root = findWorkspaceRoot(process.cwd());
201864
202071
  if (root !== null) {
@@ -35697,6 +35697,7 @@ function evaluateMemberInitializer(args) {
35697
35697
  __constructedFieldSchemaKeysByValue: /* @__PURE__ */ new WeakMap(),
35698
35698
  __createdStorageKeyDeclarations: args.storageKeyDeclarations ?? /* @__PURE__ */ new Map()
35699
35699
  };
35700
+ let evaluatedContext = ctx;
35700
35701
  const result = (() => {
35701
35702
  try {
35702
35703
  return evaluateNSGetterWithEffects(
@@ -35705,6 +35706,9 @@ function evaluateMemberInitializer(args) {
35705
35706
  args.argumentValues ?? [],
35706
35707
  {
35707
35708
  exportInitializerValues: true,
35709
+ inspectResult: (_value, runtimeContext) => {
35710
+ evaluatedContext = runtimeContext;
35711
+ },
35708
35712
  // Seeded rows already hold their placement.
35709
35713
  ...args.sessionValues === void 0 ? {
35710
35714
  initializerResult: {
@@ -35730,7 +35734,7 @@ function evaluateMemberInitializer(args) {
35730
35734
  args.member,
35731
35735
  result.value,
35732
35736
  created,
35733
- ctx
35737
+ evaluatedContext
35734
35738
  );
35735
35739
  }
35736
35740
  const rootRow = initializerRootRow(result.value, created);
@@ -40073,7 +40077,48 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
40073
40077
  );
40074
40078
  case "call": {
40075
40079
  const receiverValue = this.callReceiver(expression.callee, scope);
40076
- if (expression.callee.kind === "ident" && expression.callee.name === "Reference" && Array.isArray(expression.typeArguments)) {
40080
+ if (expression.callee.kind === "ident" && expression.callee.name === "Reference") {
40081
+ if (expression.args.length === 1 && expression.argumentNames?.[0] == null) {
40082
+ if ((expression.typeArguments?.length ?? 0) > 1) {
40083
+ throw new CompileError(
40084
+ "Reference accepts at most one generic target type.",
40085
+ expression.pos
40086
+ );
40087
+ }
40088
+ const explicit = expression.typeArguments?.[0];
40089
+ const targetType = explicit === void 0 ? void 0 : this.resolveType(explicit);
40090
+ const target = this.resolveExpression(
40091
+ expression.args[0],
40092
+ scope,
40093
+ targetType
40094
+ );
40095
+ const type = targetType ?? target.type;
40096
+ if (type.kind !== "typeParameter" && !(type.kind === "named" && this.project.typeById.get(type.typeId)?.kind === "class")) {
40097
+ throw new CompileError(
40098
+ "Reference(value) requires a class value.",
40099
+ expression.pos
40100
+ );
40101
+ }
40102
+ if (!isNeoScriptTypeAssignable(target.type, type, this.project)) {
40103
+ throw new CompileError(
40104
+ `Reference target ${this.describe(target.type)} is not assignable to ${this.describe(type)}.`,
40105
+ expression.pos
40106
+ );
40107
+ }
40108
+ if (expected !== void 0 && !isNeoScriptTypeAssignable(type, expected, this.project)) {
40109
+ throw new CompileError(
40110
+ `Reference target ${this.describe(type)} is not assignable to ${this.describe(expected)}.`,
40111
+ expression.pos
40112
+ );
40113
+ }
40114
+ return { ...target, type };
40115
+ }
40116
+ if (expression.typeArguments === void 0) {
40117
+ throw new CompileError(
40118
+ 'Reference requires one class value, or Reference<T>(id: "...").',
40119
+ expression.pos
40120
+ );
40121
+ }
40077
40122
  const [referenceTypeArgument] = expression.typeArguments;
40078
40123
  if (expression.typeArguments.length !== 1 || referenceTypeArgument === void 0) {
40079
40124
  throw new CompileError(
@@ -59593,7 +59638,7 @@ var INITIALIZER_KEY_PLACEHOLDER = new RegExp(
59593
59638
 
59594
59639
  // ../packages/manifest/schema-contract/registry.mjs
59595
59640
  var PROJECT_SCHEMA_CONTRACT = Object.freeze({
59596
- cliVersion: "0.81.3",
59641
+ cliVersion: "0.81.5",
59597
59642
  projectFileUploadBatchSize: 32,
59598
59643
  documentRecords: {
59599
59644
  member: {
@@ -61434,7 +61479,7 @@ var SCRIPT_COLLECTION_READERS = {
61434
61479
  };
61435
61480
 
61436
61481
  // ../src/models/exports/unity-codegen-revision.ts
61437
- var UNITY_CODEGEN_REVISION = "d4026a3848848c8507d45dd54b6d31c1a0588b17b64c35e3fe9b25c89eb2fc05";
61482
+ var UNITY_CODEGEN_REVISION = "c3d56ed8b41f473d0023ec967df2a4f75507b760551edf58026f5163ee546006";
61438
61483
 
61439
61484
  // src/export-cache.ts
61440
61485
  var CACHE_BUILD = JSON.stringify([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.81.3",
3
+ "version": "0.81.5",
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.81.3 -->
12
+ <!-- reviewed-through-cli: 0.81.5 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -88,6 +88,10 @@ neo doctor
88
88
  Use `node cli/bin/neo.mjs` while developing the repository. Pass explicit flags
89
89
  and IDs in automation. Non-TTY/CI commands never wait for pickers or confirms.
90
90
 
91
+ `neo init` requires a new or empty destination, apart from `.git` and
92
+ `.gitignore`. It refuses other existing entries before writing anything; choose
93
+ a separate directory when recovering a checkout whose config is missing.
94
+
91
95
  Require `formatVersion: 4` in `neo.json`. There is no format-3 reader or
92
96
  upgrader. Reconstruct an authoritative format-4 checkout with `neo init`.
93
97
 
@@ -129,7 +129,7 @@ wrappers.
129
129
  The marker near the top of `SKILL.md` must exactly match the package version:
130
130
 
131
131
  ```html
132
- <!-- reviewed-through-cli: 0.81.3 -->
132
+ <!-- reviewed-through-cli: 0.81.5 -->
133
133
  ```
134
134
 
135
135
  The quoted version above is checked too, so this instruction cannot go stale
@@ -29,6 +29,11 @@ neo push
29
29
  neo push --dry-run --force-recompile
30
30
  ```
31
31
 
32
+ `neo status` reports invalid constructor parameters at their `.neo` file, line,
33
+ and column. Fix the reported parameter type before pushing. For example,
34
+ constructor parameters use `Item`; `Reference<Item>` declares member storage.
35
+ A failed class or constructor declaration blocks candidate preparation.
36
+
32
37
  Use `neo pull --regenerate-source-names` to replace generated default dialogue
33
38
  names with readable ones. It keeps chosen names and rewrites references. Use
34
39
  `--reset` to regenerate canonical source and managed binaries. Reset retains
@@ -151,6 +156,9 @@ become real only after acceptance.
151
156
 
152
157
  ## Conflicts and files
153
158
 
159
+ Conflict resolution accepts LF, CRLF, and mixed line endings. It retains the
160
+ selected source lines and their existing line endings.
161
+
154
162
  Resolve source conflicts by editing the final intended source. Use
155
163
  `neo resolve --mine` or `neo resolve --theirs` only for a deliberate whole-side
156
164
  choice. Pull, inspect, dry-run, and retry; there is no force-CAS path.
@@ -228,6 +236,11 @@ neo history status --run <run-id>
228
236
  neo history resume --run <run-id>
229
237
  ```
230
238
 
239
+ Branch switching requires a clean working copy. Unpushed source or binary
240
+ changes, unresolved conflicts, source errors, and pending push recovery stop the
241
+ switch before it changes the selected version or baseline. Push or back up edits
242
+ first; use `neo pull --force` only when discarding them is intended.
243
+
231
244
  Treat branches as copy-on-write forks and releases as immutable snapshots. The
232
245
  server derives the minimum compatibility bump; a caller may only raise it.
233
246
  Require the release login profile for release operations.
@@ -294,6 +307,12 @@ so concurrent environments sharing one OS user cannot read, overwrite, or
294
307
  delete each other's tokens. Login, logout, and authenticated commands all
295
308
  honor both variables.
296
309
 
310
+ Boolean flags are switches: pass `--dry-run` or `--help` without a value.
311
+ Values such as `--dry-run=true` are rejected before a command runs.
312
+ `--dry-run` is supported by push, merge, refresh, branch refresh, release cut,
313
+ and migrate run. Other commands reject it before taking action. Branch options
314
+ are scoped to their subcommands; branch create does not support dry-run.
315
+
297
316
  Pass explicit project/version IDs and flags in automation. Prefer `--json` for
298
317
  machine-readable output and do not depend on interactive pickers or confirms.
299
318
 
@@ -308,7 +327,8 @@ changed, skips codegen when its schema inputs are unchanged, and writes
308
327
  `Library/NeoCompose/export.json` last for Unity to ingest. Repeated exports
309
328
  verify current inputs and output bytes before reusing the last successful
310
329
  export; changed or missing outputs are regenerated. Edited content is validated
311
- even when C# generation is skipped.
330
+ even when C# generation is skipped. After a CLI code-generation fix, run
331
+ `neo export` again; generator revision changes invalidate cached C# output.
312
332
 
313
333
  - A clean working copy exports the pulled head. Diagnostic location differences
314
334
  alone, including line shifts after push inserts ids, do not make it local.
@@ -180,6 +180,22 @@ is one such row — its identity comes from the schema key its parameter settles
180
180
 
181
181
  ## Same-push references
182
182
 
183
+ `Reference<T>` and `Set<Reference<T>>` declare lookup storage. Their runtime
184
+ values are `T` and `Set<T>`, so a projection can assign existing class values
185
+ directly:
186
+
187
+ ```neo
188
+ EligibleIngredients = root.Assets.Crafting.FurnaceRecipes
189
+ .Select((MachineRecipe recipe) => recipe.Ingredient)
190
+ .ToSet()
191
+ ```
192
+
193
+ Writing `Reference(recipe.Ingredient)` inside the callback is also valid.
194
+ `Reference(value)` preserves an evaluated class value and its identity; it does
195
+ not clone it or turn it into a separate wrapper object. An optional generic
196
+ argument checks the target type. Lookup assignment still requires an existing
197
+ backing row and enforces its declared target and collection constraints.
198
+
183
199
  Use the `Reference` intrinsic. Prefer the most readable form the target
184
200
  supports:
185
201
 
@@ -197,9 +213,8 @@ Reference<NeoDialogue>(id: "capitol-dialogue-id")
197
213
  - Use `key:` only for a collection with a unique index. Omit `index:` when the
198
214
  collection has exactly one usable unique index; supply the bare member name
199
215
  when it has more than one.
200
- - Use the generic `id:` form when the ID is the only target information. A
201
- `Reference` call whose argument is neither `id:` nor `key:` is rejected by
202
- name rather than read as an ID.
216
+ - Use the generic `id:` form when the ID is the only target information.
217
+ Unrecognized named arguments are rejected rather than read as IDs.
203
218
  - Inside executable NeoScript, add `withProvenance: true` when the ID names an
204
219
  authored row and the reference must select its nearest `sourceValueId` clone
205
220
  in the lexical receiver's ownership graph. The parameter defaults to `false`;