@neocompose/cli 0.81.3 → 0.81.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,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.81.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 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`.
8
+ - 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.
9
+ - 6631cef: Refuse initialization into a nonempty directory, apart from Git metadata, so init cannot overwrite existing source or managed files without consent.
10
+ - b2a688d: Resolve source conflicts with CRLF or mixed line endings while preserving the selected source bytes and formatting.
11
+ - 743783d: Refuse branch switches with unpushed changes, conflicts, invalid source, or pending push recovery so switching cannot silently discard local work.
12
+ - b0b945f: Report constructor parameter errors at their source location instead of crashing
13
+ with an internal missing-constructor manifest error during status or push.
14
+ - c11209a: Fix lookup initializers losing row identities in projected selection sets.
15
+ Accept Reference(value) for evaluated class values, including collection
16
+ callbacks, while preserving type checking and the original value identity.
17
+
3
18
  ## 0.81.3
4
19
 
5
20
  ### 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.4",
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);
@@ -96947,7 +97034,9 @@ function isNamePath(expression) {
96947
97034
  }
96948
97035
  function isLiteralCall(expression) {
96949
97036
  if (expression.callee.kind === "ident" && expression.callee.name === "Reference") {
96950
- return true;
97037
+ if (expression.argumentNames?.some((name) => name !== null)) return true;
97038
+ const argument2 = expression.args[0];
97039
+ return expression.args.length === 1 && argument2 !== void 0 && (isNamePath(argument2) || argument2.kind === "litString");
96951
97040
  }
96952
97041
  return expression.callee.kind === "member" && expression.callee.name === "Slice";
96953
97042
  }
@@ -97341,21 +97430,15 @@ function lowerClassConstructors(context, declaration, classId, lowerParameterTyp
97341
97430
  const declared = declaration.constructors.map((declared2) => {
97342
97431
  const id2 = declaredConstructorId2(declaration, declared2);
97343
97432
  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
- });
97433
+ const parameters = declared2.parameters.map(
97434
+ (parameter4) => lowerConstructorParameter(
97435
+ context,
97436
+ parameter4,
97437
+ declaration,
97438
+ lowerParameterType,
97439
+ `${declaration.name}.${declared2.name}(${parameter4.name})`
97440
+ )
97441
+ );
97359
97442
  const baseArguments = lowerBaseArguments(
97360
97443
  context,
97361
97444
  declaration,
@@ -97400,26 +97483,41 @@ function lowerRequiredConstructor(context, declaration, classId, lowerParameterT
97400
97483
  selectionSpan: sourceSpan
97401
97484
  },
97402
97485
  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
- }),
97486
+ arguments: required2.parameters.map(
97487
+ (parameter4) => lowerConstructorParameter(
97488
+ context,
97489
+ parameter4,
97490
+ declaration,
97491
+ lowerParameterType,
97492
+ `${declaration.name}(${parameter4.name})`
97493
+ )
97494
+ ),
97418
97495
  code: requiredConstructorBody(required2.body, base?.code),
97419
97496
  ...baseArguments.length === 0 ? {} : { baseArguments },
97420
97497
  ...baseInitializerFields.length === 0 ? {} : { baseInitializerFields }
97421
97498
  };
97422
97499
  }
97500
+ function lowerConstructorParameter(context, parameter4, declaration, lowerParameterType, path) {
97501
+ try {
97502
+ const type = lowerParameterType(parameter4.type, declaration);
97503
+ return {
97504
+ name: parameter4.name,
97505
+ type,
97506
+ default: lowerParameterDefault(context, parameter4, type, path),
97507
+ source: span({ source: parameter4.source }),
97508
+ selectionSpan: span({ source: parameter4.source })
97509
+ };
97510
+ } catch (error) {
97511
+ if (error instanceof SchemaSourceError) throw error;
97512
+ throw new SchemaSourceError(
97513
+ error instanceof Error ? error.message : String(error),
97514
+ parameter4.source.uri,
97515
+ parameter4.source.range.start.line + 1,
97516
+ parameter4.source.range.start.character + 1,
97517
+ "schema-lowering"
97518
+ );
97519
+ }
97520
+ }
97423
97521
  function requiredConstructorRecordId(classId) {
97424
97522
  return isPendingId(classId) ? pendingRequiredConstructorId(classId) : derivedRequiredConstructorId(classId);
97425
97523
  }
@@ -97533,6 +97631,7 @@ var init_lower_constructors = __esm({
97533
97631
  init_pending_id();
97534
97632
  init_declared_constructors();
97535
97633
  init_required_constructor_id();
97634
+ init_source_diagnostics();
97536
97635
  init_declared_constructors2();
97537
97636
  init_lower_parameter_defaults();
97538
97637
  init_init_source();
@@ -148727,6 +148826,11 @@ function computeStatus(workspace, options, conflictedFiles, parseErrors, parseWa
148727
148826
  Object.values(loweringRecords)
148728
148827
  )
148729
148828
  });
148829
+ if (failedDeclarations.size > 0 && validAnalysis.schema.classes.some(
148830
+ (declaration) => failedDeclarations.has(declaration)
148831
+ )) {
148832
+ return refusedStatus(conflictedFiles, parseErrors, parseWarnings);
148833
+ }
148730
148834
  const loweredMemberIds = new Set(
148731
148835
  manifest.members.map((member) => member.id)
148732
148836
  );
@@ -159758,7 +159862,7 @@ function projectSignatureHelp(analysis, document, position) {
159758
159862
  );
159759
159863
  }
159760
159864
  if (callee.text === "Reference") {
159761
- const parameters = ["symbol or id", "withProvenance = false"];
159865
+ const parameters = ["class value, symbol, or id", "withProvenance = false"];
159762
159866
  return signatureResult(
159763
159867
  "Reference",
159764
159868
  parameters,
@@ -189657,7 +189761,13 @@ var init_exports = {};
189657
189761
  __export(init_exports, {
189658
189762
  runInit: () => runInit
189659
189763
  });
189660
- import { existsSync as existsSync19, mkdirSync as mkdirSync14, readFileSync as readFileSync25, writeFileSync as writeFileSync15 } from "node:fs";
189764
+ import {
189765
+ existsSync as existsSync19,
189766
+ mkdirSync as mkdirSync14,
189767
+ readFileSync as readFileSync25,
189768
+ readdirSync as readdirSync7,
189769
+ writeFileSync as writeFileSync15
189770
+ } from "node:fs";
189661
189771
  import { join as join23, resolve as resolve4 } from "node:path";
189662
189772
  async function runInit(options) {
189663
189773
  let projectId = options.projectId;
@@ -189758,6 +189868,16 @@ async function runInit(options) {
189758
189868
  if (existsSync19(join23(root, NEO_CONFIG_FILE))) {
189759
189869
  throw new Error(`"${join23(root, NEO_CONFIG_FILE)}" already exists.`);
189760
189870
  }
189871
+ if (existsSync19(root)) {
189872
+ const existing = readdirSync7(root).find(
189873
+ (name) => name !== ".git" && name !== ".gitignore"
189874
+ );
189875
+ if (existing !== void 0) {
189876
+ throw new Error(
189877
+ `Cannot initialize ${JSON.stringify(root)} because it contains ${JSON.stringify(existing)}. Choose an empty directory, or back up and move existing files first.`
189878
+ );
189879
+ }
189880
+ }
189761
189881
  mkdirSync14(root, { recursive: true });
189762
189882
  await acquireWorkspaceLock(root, "init");
189763
189883
  ensurePrivateStateIgnored(root);
@@ -193079,7 +193199,7 @@ import { createHash as createHash18, randomUUID as randomUUID8 } from "node:cryp
193079
193199
  import {
193080
193200
  existsSync as existsSync20,
193081
193201
  mkdirSync as mkdirSync15,
193082
- readdirSync as readdirSync7,
193202
+ readdirSync as readdirSync8,
193083
193203
  readFileSync as readFileSync27,
193084
193204
  realpathSync as realpathSync2,
193085
193205
  renameSync as renameSync10,
@@ -194373,7 +194493,7 @@ function testBuildFiles(root) {
194373
194493
  if (!existsSync20(root)) return [];
194374
194494
  const files = [];
194375
194495
  const visit = (directory) => {
194376
- for (const entry of readdirSync7(directory, { withFileTypes: true })) {
194496
+ for (const entry of readdirSync8(directory, { withFileTypes: true })) {
194377
194497
  if (entry.isSymbolicLink()) continue;
194378
194498
  const path = join24(directory, entry.name);
194379
194499
  if (entry.isDirectory()) visit(path);
@@ -197660,6 +197780,47 @@ function findVersion(versions, ref) {
197660
197780
  (version) => version.id === ref || version.name === ref || version.semver.label === ref
197661
197781
  );
197662
197782
  }
197783
+ function assertCleanBranchSwitch(workspace) {
197784
+ assertNoPendingPushRecovery(workspace);
197785
+ const records3 = Object.values(workspace.state.records);
197786
+ for (const record7 of records3) {
197787
+ if (record7.conflictServerHash !== void 0) {
197788
+ throw new Error(
197789
+ "Cannot switch branches with unresolved record conflicts. Run neo resolve before switching."
197790
+ );
197791
+ }
197792
+ }
197793
+ if (records3.length === 0) {
197794
+ const inputs = listProjectExportInputsV4(workspace.root);
197795
+ if (inputs.sources.length === 0 && inputs.binaries.length === 0) return;
197796
+ }
197797
+ const status = computeWorkspaceStatus2(workspace);
197798
+ if (status.conflictedFiles.length > 0) {
197799
+ throw new Error(
197800
+ "Cannot switch branches with unresolved conflicts. Resolve them before switching."
197801
+ );
197802
+ }
197803
+ if (status.parseErrors.length > 0) {
197804
+ throw new Error(
197805
+ "Cannot switch branches while local source has errors. Run neo status and fix or back up your edits before switching."
197806
+ );
197807
+ }
197808
+ if (status.changes.length > 0) {
197809
+ throw new Error(
197810
+ "Cannot switch branches with unpushed changes. Push or back up your edits first; use neo pull --force only to deliberately discard them."
197811
+ );
197812
+ }
197813
+ if (status.authoredValueSeeds.size > 0) {
197814
+ throw new Error(
197815
+ "Cannot switch branches with unpushed value seeds. Push or back up your edits first; use neo pull --force only to deliberately discard them."
197816
+ );
197817
+ }
197818
+ if ((status.binaryChanges?.length ?? 0) > 0) {
197819
+ throw new Error(
197820
+ "Cannot switch branches with pending file changes. Run neo status and settle those changes before switching."
197821
+ );
197822
+ }
197823
+ }
197663
197824
  async function runBranch(workspace, subcommand, positional, fromRef) {
197664
197825
  if (subcommand === "list") {
197665
197826
  const { versions } = await loadVersionMetadata(workspace);
@@ -197750,9 +197911,13 @@ async function runBranch(workspace, subcommand, positional, fromRef) {
197750
197911
  console.log(`Already on ${displayLabel(target)}.`);
197751
197912
  return;
197752
197913
  }
197914
+ assertCleanBranchSwitch(workspace);
197753
197915
  workspace.config = { ...workspace.config, versionId: target.id };
197754
197916
  writeWorkspaceConfig(workspace.root, workspace.config);
197755
- workspace.state = { records: {} };
197917
+ workspace.state = {
197918
+ records: workspace.state.records,
197919
+ localValues: workspace.state.localValues
197920
+ };
197756
197921
  writeWorkspaceState(workspace.root, workspace.state);
197757
197922
  success(
197758
197923
  `Switched to ${color.bold(displayLabel(target))} ${color.dim(`(${target.id})`)}. Pulling\u2026`
@@ -198212,6 +198377,8 @@ var init_branch = __esm({
198212
198377
  init_value_comparison();
198213
198378
  init_ui();
198214
198379
  init_pull();
198380
+ init_workspace_status();
198381
+ init_push_recovery();
198215
198382
  init_http();
198216
198383
  init_project_transaction();
198217
198384
  displayLabel = projectVersionDisplayLabel;
@@ -201237,7 +201404,7 @@ var dev_exports = {};
201237
201404
  __export(dev_exports, {
201238
201405
  runDev: () => runDev
201239
201406
  });
201240
- import { readdirSync as readdirSync8, statSync as statSync6, watch } from "node:fs";
201407
+ import { readdirSync as readdirSync9, statSync as statSync6, watch } from "node:fs";
201241
201408
  import { basename as basename5, dirname as dirname15, join as join27, resolve as resolve6 } from "node:path";
201242
201409
  import { emitKeypressEvents } from "node:readline";
201243
201410
  async function runDev(root, loadWorkspace2, options) {
@@ -201370,7 +201537,7 @@ function countsAsExportInput(root, relativePath, event, directories) {
201370
201537
  function rememberDirectories(root, relativePath, directories) {
201371
201538
  let entries;
201372
201539
  try {
201373
- entries = readdirSync8(join27(root, relativePath), { withFileTypes: true });
201540
+ entries = readdirSync9(join27(root, relativePath), { withFileTypes: true });
201374
201541
  } catch (error) {
201375
201542
  if (error instanceof Error && "code" in error) {
201376
201543
  if (error.code === "ENOENT") return;
@@ -201549,18 +201716,19 @@ function resolveMarkers(source, side) {
201549
201716
  const output = [];
201550
201717
  let mode = "outside";
201551
201718
  for (const line of lines) {
201552
- if (line.startsWith("<<<<<<<")) {
201719
+ const marker = line.endsWith("\r") ? line.slice(0, -1) : line;
201720
+ if (marker.startsWith("<<<<<<<")) {
201553
201721
  if (mode !== "outside") {
201554
201722
  throw new Error("Nested conflict markers are not supported.");
201555
201723
  }
201556
201724
  mode = "local";
201557
201725
  continue;
201558
201726
  }
201559
- if (line === "=======" && mode === "local") {
201727
+ if (marker === "=======" && mode === "local") {
201560
201728
  mode = "server";
201561
201729
  continue;
201562
201730
  }
201563
- if (line.startsWith(">>>>>>>")) {
201731
+ if (marker.startsWith(">>>>>>>")) {
201564
201732
  if (mode !== "server") {
201565
201733
  throw new Error("Unbalanced conflict markers.");
201566
201734
  }
@@ -201847,7 +202015,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
201847
202015
  async function main() {
201848
202016
  const args = parseArgs(process.argv.slice(2));
201849
202017
  if (args.command === "--version") {
201850
- console.log("0.81.3");
202018
+ console.log("0.81.4");
201851
202019
  return;
201852
202020
  }
201853
202021
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
@@ -201859,6 +202027,7 @@ async function main() {
201859
202027
  return;
201860
202028
  }
201861
202029
  assertKnownFlags(args);
202030
+ assertCommandFlags(args);
201862
202031
  if (WORKSPACE_LOCK_COMMANDS.has(args.command)) {
201863
202032
  const root = findWorkspaceRoot(process.cwd());
201864
202033
  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.4",
59597
59642
  projectFileUploadBatchSize: 32,
59598
59643
  documentRecords: {
59599
59644
  member: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.81.3",
3
+ "version": "0.81.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.81.3 -->
12
+ <!-- reviewed-through-cli: 0.81.4 -->
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.4 -->
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
 
@@ -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`;