@neocompose/cli 0.67.1 → 0.67.3

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,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.67.3] - 2026-10-04
4
+
5
+ - `neo pull --force` and `neo pull --reset` keep the dialogue source names the server stores. Previously both regenerated names, so a force pull renamed bindings like `npc` to `AdoringFan` without rewriting the code that used them, left dozens of unrequested changes in `neo status`, and failed `neo push --dry-run` with "Unknown identifier".
6
+ - `neo pull --regenerate-source-names` only replaces generated defaults such as `Value_1a2b3c4d5e6f`, `NeoFunction_…`, and `Conditions1`. Names you chose are kept. When it renames a binding, it rewrites references to it in dialogue functions, conditions, actions, and text variables. A node that re-links a dialogue value now uses the dialogue's name for it, so a function shared by several nodes reads the same in each.
7
+
8
+ ## [0.67.2] - 2026-10-04
9
+
10
+ - `neo pull --force` reads the workspace layout against the server's records instead of the baseline it is about to replace. Previously a baseline pulled before 0.67.0 failed with "Cannot recover local initializer placement before reset" in any workspace with local values, so the `--force` remedy 0.67.1 suggests could not run.
11
+ - Pull writes `float` setting bounds as the shortest decimal that reads back as the stored float, so `@settings(max: 23.99)` stays `23.99`. Previously it wrote `23.989999771118164`.
12
+
3
13
  ## [0.67.1] - 2026-10-04
4
14
 
5
15
  - Pull adopts records the server only recompiled into `.neo/state.json` before it reads local sources, even when the same pull also merges authored changes. Previously one authored change anywhere in the pull sent every recompiled record through the merge, which read the stale baseline and failed.
package/README.md CHANGED
@@ -479,7 +479,7 @@ authorization boundary.
479
479
  | `logout [--api <url>]` | Delete the stored credential for one API origin and revoke its session. |
480
480
  | `init --project <id> [--version <id>]` | Create a format-4 working copy and perform its first reset pull. |
481
481
  | `doctor` | Validate format/compiler/editor/source/file contracts; no .NET discovery. |
482
- | `pull [--force\|--reset] [--regenerate-source-names]` | Merge remote changes or regenerate canonical source, names, and binaries. |
482
+ | `pull [--force\|--reset] [--regenerate-source-names]` | Merge remote changes or regenerate canonical source and binaries. |
483
483
  | `status [--json]` | Compile source and summarize semantic records and upload intent. |
484
484
  | `diff [--json]` | Show semantic records, spans, digests, and upload intent. |
485
485
  | `push [--dry-run] [--accept-bump] [--no-verify]` | Validate or commit one atomic compare-and-swap transaction. |
@@ -514,9 +514,9 @@ security boundary.
514
514
 
515
515
  ## Regenerating dialogue source names
516
516
 
517
- Normal pull preserves source filenames and symbols by stable record ID. Force
518
- and reset pulls always derive readable dialogue names from authoritative
519
- records. To deliberately refresh those names during a normal merge, run:
517
+ Every pull, including `--force` and `--reset`, keeps the dialogue source names
518
+ the server stores. To replace generated defaults such as `Value_1a2b3c4d5e6f`,
519
+ `NeoFunction_…`, or `Conditions1` with names derived from the editor, run:
520
520
 
521
521
  ```sh
522
522
  neo pull --regenerate-source-names
@@ -524,11 +524,12 @@ neo status
524
524
  neo push --dry-run
525
525
  ```
526
526
 
527
- Custom linked values prefer a non-empty string member named `Name`, using
528
- PascalCase at dialogue scope and camelCase inside nodes.
527
+ Names you chose are kept. When a binding is renamed, code that refers to it is
528
+ rewritten to match. Custom linked values prefer a non-empty string member named
529
+ `Name`, using PascalCase at dialogue scope and camelCase inside nodes.
529
530
 
530
- The reset leaves the regenerated names as local changes. IDs are unchanged,
531
- and nothing is written to the server until `neo push` succeeds.
531
+ The pull leaves the regenerated names as local changes. IDs are unchanged, and
532
+ nothing is written to the server until `neo push` succeeds.
532
533
 
533
534
  ## Packaging
534
535
 
package/dist/neo.mjs CHANGED
@@ -30114,7 +30114,7 @@ var init_registry = __esm({
30114
30114
  "../packages/manifest/schema-contract/registry.mjs"() {
30115
30115
  "use strict";
30116
30116
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
30117
- cliVersion: "0.67.1",
30117
+ cliVersion: "0.67.3",
30118
30118
  projectFileUploadBatchSize: 32,
30119
30119
  documentRecords: {
30120
30120
  member: {
@@ -52644,14 +52644,17 @@ function renderExtendsGenericBindings(context, schemaClass2, base) {
52644
52644
  });
52645
52645
  return `<${argumentsValue.join(", ")}>`;
52646
52646
  }
52647
+ function settingsBound(value) {
52648
+ return typeof value === "number" ? formatNeoNumber(value) : value;
52649
+ }
52647
52650
  function memberSettings(context, member) {
52648
52651
  const settings = [];
52649
52652
  if (member.kind === "string") {
52650
52653
  if (!member.localizable) settings.push("format: .Plain");
52651
52654
  if (member.searchKey) settings.push("searchBy: .MemberKey");
52652
52655
  } else if (member.kind === "int" || member.kind === "float" || member.kind === "decimal") {
52653
- if (member.min !== null) settings.push(`min: ${member.min}`);
52654
- if (member.max !== null) settings.push(`max: ${member.max}`);
52656
+ if (member.min !== null) settings.push(`min: ${settingsBound(member.min)}`);
52657
+ if (member.max !== null) settings.push(`max: ${settingsBound(member.max)}`);
52655
52658
  if (member.kind !== "int" && member.decimalPoints !== null) {
52656
52659
  settings.push(`decimalPoints: ${member.decimalPoints}`);
52657
52660
  }
@@ -59688,6 +59691,23 @@ function rewriteNeoScriptThisReceiver(source, alias) {
59688
59691
  }
59689
59692
  return result + source.slice(cursor);
59690
59693
  }
59694
+ function renameNeoScriptIdentifiers(source, renames) {
59695
+ if (renames.size === 0) return source;
59696
+ let result = "";
59697
+ let cursor = 0;
59698
+ let previous;
59699
+ for (const token of lex(source).tokens) {
59700
+ if (token.kind === "comment") continue;
59701
+ const isMemberName = previous?.text === "." || previous?.text === "?.";
59702
+ previous = token;
59703
+ if (token.kind !== "identifier" || isMemberName) continue;
59704
+ const renamed = renames.get(token.text);
59705
+ if (renamed === void 0) continue;
59706
+ result += source.slice(cursor, token.start) + renamed;
59707
+ cursor = token.end;
59708
+ }
59709
+ return result + source.slice(cursor);
59710
+ }
59691
59711
  var init_neoscript_source_rewrite = __esm({
59692
59712
  "src/project-source/neoscript-source-rewrite.ts"() {
59693
59713
  "use strict";
@@ -59715,58 +59735,116 @@ function regenerateDialogueSourceNamesV4(records2) {
59715
59735
  live.filter((record6) => record6.recordKind === "dialogue-node").map((record6) => ({ record: record6, data: recordData2(record6) })),
59716
59736
  ({ data }) => stringField(data, "dialogueId")
59717
59737
  );
59738
+ const keptClassNames = claimKeptNames(
59739
+ typeNames,
59740
+ dialogues.map((record6) => [record6.recordId, recordData2(record6).sourceName])
59741
+ );
59718
59742
  dialogues.forEach((dialogueRecord, dialogueIndex) => {
59719
59743
  const dialogue = recordData2(dialogueRecord);
59720
59744
  const dialogueId = dialogueRecord.recordId;
59721
- const className = typeNames.claim(
59745
+ const className = keptClassNames.get(dialogueId) ?? typeNames.claim(
59722
59746
  dialogueSourceNameFromDisplayName(
59723
59747
  optionalString2(dialogue.name),
59724
59748
  `Dialogue${dialogueIndex + 1}`
59725
59749
  )
59726
59750
  );
59727
- const nodeEntries = nodesByDialogue.get(dialogueId) ?? [];
59751
+ const nodeEntries = (nodesByDialogue.get(dialogueId) ?? []).map(
59752
+ ({ record: record6, data }) => ({
59753
+ record: record6,
59754
+ data,
59755
+ links: linkedValueRecords(data.linkedValues, "Dialogue-node")
59756
+ })
59757
+ );
59728
59758
  const nodes = nodeEntries.map(({ data }) => data);
59729
- const triggerId = optionalString2(dialogue.triggerNodeId);
59759
+ const bodyNodes = orderedBodyNodes(
59760
+ nodes,
59761
+ optionalString2(dialogue.triggerNodeId)
59762
+ );
59763
+ const links = linkedValueRecords(dialogue.linkedValues, "Dialogue");
59764
+ const functions = sourceFunctionUses(nodes);
59730
59765
  const classMembers = new DialogueSourceNameAllocator(["Trigger"]);
59731
- const nodeNames = regeneratedNodeNames(nodes, triggerId, classMembers);
59732
- const dialogueLinks = regenerateBindingSourceNames(
59733
- dialogue.linkedValues,
59766
+ const keptMembers = claimKeptNames(classMembers, [
59767
+ ...bodyNodes.map(
59768
+ (node) => [`node:${stringField(node, "id")}`, node.sourceName]
59769
+ ),
59770
+ ...links.map(
59771
+ (link) => [`value:${stringField(link, "valueId")}`, link.sourceName]
59772
+ ),
59773
+ ...[...functions].map(
59774
+ ([id2, fn]) => [`function:${id2}`, fn.existingName]
59775
+ )
59776
+ ]);
59777
+ const valueIdsByOldName = /* @__PURE__ */ new Map();
59778
+ const ambiguousOldNames = /* @__PURE__ */ new Set();
59779
+ for (const { links: nodeLinks } of nodeEntries) {
59780
+ for (const link of nodeLinks) {
59781
+ const kept = keptSourceName(link.sourceName);
59782
+ if (kept !== null) classMembers.reserve(kept);
59783
+ const oldName = optionalString2(link.sourceName);
59784
+ if (oldName === null) continue;
59785
+ const valueId = stringField(link, "valueId");
59786
+ const seen = valueIdsByOldName.get(oldName);
59787
+ if (seen === void 0) valueIdsByOldName.set(oldName, valueId);
59788
+ else if (seen !== valueId) ambiguousOldNames.add(oldName);
59789
+ }
59790
+ }
59791
+ const nodeNames = regeneratedNodeNames(
59792
+ bodyNodes,
59734
59793
  classMembers,
59735
- placements,
59736
- "pascal"
59794
+ keptMembers
59737
59795
  );
59738
- const dialogueBindingNames = new Map(
59739
- dialogueLinks.map((link) => [
59740
- stringField(link, "valueId"),
59741
- stringField(link, "sourceName")
59742
- ])
59796
+ const dialogueLinks = regenerateBindingSourceNames(
59797
+ links,
59798
+ classMembers,
59799
+ keptMembers,
59800
+ placements
59743
59801
  );
59744
- const functionNames = regeneratedSourceFunctionNames(nodes, classMembers);
59745
- const nextDialogue = rewriteFunctionSourceNames(
59746
- { ...dialogue, sourceName: className, linkedValues: dialogueLinks },
59747
- functionNames
59802
+ const functionNames = allocateSourceFunctionNames(
59803
+ functions,
59804
+ classMembers,
59805
+ keptMembers
59748
59806
  );
59749
- result.set(recordKey(dialogueRecord), {
59750
- ...dialogueRecord,
59751
- data: nextDialogue
59807
+ const namesByOldName = /* @__PURE__ */ new Map();
59808
+ const dialogueNamesByValueId = /* @__PURE__ */ new Map();
59809
+ links.forEach((link, index) => {
59810
+ const name = stringField(dialogueLinks[index], "sourceName");
59811
+ dialogueNamesByValueId.set(stringField(link, "valueId"), name);
59812
+ const oldName = optionalString2(link.sourceName);
59813
+ if (oldName !== null) namesByOldName.set(oldName, name);
59752
59814
  });
59753
- for (const { record: record6, data } of nodeEntries) {
59754
- const nodeId = stringField(data, "id");
59755
- const nodeName = nodeNames.get(nodeId) ?? "Trigger";
59756
- const nodeLinks = regenerateNodeBindingSourceNames(
59757
- data.linkedValues,
59758
- placements,
59759
- dialogueBindingNames
59760
- );
59815
+ const nextNodes = nodeEntries.map(({ record: record6, data, links: nodeLinks }) => {
59761
59816
  let nextNode = {
59762
59817
  ...data,
59763
- sourceName: nodeName,
59764
- linkedValues: nodeLinks
59818
+ sourceName: nodeNames.get(stringField(data, "id")) ?? "Trigger",
59819
+ linkedValues: regenerateNodeBindingSourceNames(
59820
+ nodeLinks,
59821
+ classMembers,
59822
+ placements,
59823
+ dialogueNamesByValueId,
59824
+ ambiguousOldNames,
59825
+ namesByOldName
59826
+ )
59765
59827
  };
59766
59828
  if (data.type === 1) nextNode = regenerateTextChildren(nextNode);
59767
59829
  if (data.type === 3) nextNode = regenerateConditionOutcomes(nextNode);
59768
- nextNode = rewriteFunctionSourceNames(nextNode, functionNames);
59769
- result.set(recordKey(record6), { ...record6, data: nextNode });
59830
+ return { record: record6, data: nextNode };
59831
+ });
59832
+ const renames = new Map(
59833
+ [...namesByOldName].filter(([oldName, name]) => oldName !== name)
59834
+ );
59835
+ result.set(recordKey(dialogueRecord), {
59836
+ ...dialogueRecord,
59837
+ data: rewriteSourceReferences(
59838
+ { ...dialogue, sourceName: className, linkedValues: dialogueLinks },
59839
+ functionNames,
59840
+ renames
59841
+ )
59842
+ });
59843
+ for (const { record: record6, data } of nextNodes) {
59844
+ result.set(recordKey(record6), {
59845
+ ...record6,
59846
+ data: rewriteSourceReferences(data, functionNames, renames)
59847
+ });
59770
59848
  }
59771
59849
  });
59772
59850
  for (const record6 of live.filter(
@@ -59777,23 +59855,54 @@ function regenerateDialogueSourceNamesV4(records2) {
59777
59855
  if (functionNames.size === 0) continue;
59778
59856
  result.set(recordKey(record6), {
59779
59857
  ...record6,
59780
- data: rewriteFunctionSourceNames(data, functionNames)
59858
+ data: rewriteSourceReferences(data, functionNames, /* @__PURE__ */ new Map())
59781
59859
  });
59782
59860
  }
59783
59861
  return result;
59784
59862
  }
59785
- function regeneratedNodeNames(nodes, triggerId, allocator) {
59863
+ function claimKeptNames(allocator, entries) {
59864
+ const result = /* @__PURE__ */ new Map();
59865
+ for (const [key, name] of entries) {
59866
+ if (result.has(key)) continue;
59867
+ const kept = keptSourceName(name);
59868
+ if (kept !== null) result.set(key, allocator.claim(kept));
59869
+ }
59870
+ return result;
59871
+ }
59872
+ function keptSourceName(value) {
59873
+ if (!isDialogueSourceIdentifier(value)) return null;
59874
+ return isDefaultDialogueSourceName(value) ? null : value;
59875
+ }
59876
+ function isDefaultDialogueSourceName(value) {
59877
+ return /^(?:Dialogue|Node|Option|Outcome|Value)_[0-9a-f_]{8,}$/iu.test(value) || /^NeoFunction_[0-9a-f]{16,}$/iu.test(value) || /^(?:Actions?|Conditions?|Dialogue|Node|Option|Outcome|Text|TextValue|Value)[0-9]+(?:_[0-9]+)?$/iu.test(
59878
+ value
59879
+ );
59880
+ }
59881
+ function linkedValueRecords(rawLinks, owner) {
59882
+ if (!Array.isArray(rawLinks)) return [];
59883
+ return rawLinks.map((rawLink, index) => {
59884
+ if (!isObjectRecord2(rawLink)) {
59885
+ throw new Error(`${owner} linked value ${index} is not an object.`);
59886
+ }
59887
+ if (typeof rawLink.valueId !== "string") {
59888
+ throw new Error(`${owner} linked value ${index} has no valueId.`);
59889
+ }
59890
+ return rawLink;
59891
+ });
59892
+ }
59893
+ function regeneratedNodeNames(bodyNodes, allocator, kept) {
59786
59894
  const result = /* @__PURE__ */ new Map();
59787
- if (triggerId !== null) result.set(triggerId, "Trigger");
59788
- const bodyNodes = orderedBodyNodes(nodes, triggerId);
59789
59895
  const descriptors = bodyNodes.map((node) => ({
59790
- node,
59791
59896
  id: stringField(node, "id"),
59792
59897
  displayName: optionalMeaningfulString(node.name),
59793
59898
  kind: dialogueNodeKindName(node.type)
59794
59899
  }));
59795
59900
  for (const descriptor of descriptors) {
59796
- if (descriptor.displayName === null) continue;
59901
+ const keptName = kept.get(`node:${descriptor.id}`);
59902
+ if (keptName !== void 0) result.set(descriptor.id, keptName);
59903
+ }
59904
+ for (const descriptor of descriptors) {
59905
+ if (result.has(descriptor.id) || descriptor.displayName === null) continue;
59797
59906
  result.set(
59798
59907
  descriptor.id,
59799
59908
  allocator.claim(
@@ -59813,111 +59922,79 @@ function regeneratedNodeNames(nodes, triggerId, allocator) {
59813
59922
  }
59814
59923
  return result;
59815
59924
  }
59816
- function regenerateBindingSourceNames(rawLinks, allocator, placements, casing) {
59817
- if (!Array.isArray(rawLinks)) return [];
59818
- return rawLinks.map((rawLink, index) => {
59819
- if (!isObjectRecord2(rawLink) || typeof rawLink.valueId !== "string") {
59820
- throw new Error(`Dialogue linked value ${index} is malformed.`);
59821
- }
59822
- const preferred = placements.preferredBindingName(
59823
- rawLink.valueId,
59824
- optionalString2(rawLink.declaredTypeName) ?? `value${index + 1}`,
59825
- casing
59925
+ function regenerateBindingSourceNames(links, allocator, kept, placements) {
59926
+ return links.map((link, index) => {
59927
+ const valueId = stringField(link, "valueId");
59928
+ const sourceName = kept.get(`value:${valueId}`) ?? allocator.claim(
59929
+ placements.preferredBindingName(
59930
+ valueId,
59931
+ optionalString2(link.declaredTypeName) ?? `value${index + 1}`,
59932
+ "pascal"
59933
+ )
59826
59934
  );
59827
- return { ...rawLink, sourceName: allocator.claim(preferred) };
59935
+ return { ...link, sourceName };
59828
59936
  });
59829
59937
  }
59830
- function regenerateNodeBindingSourceNames(rawLinks, placements, dialogueNamesByValueId) {
59831
- if (!Array.isArray(rawLinks)) return [];
59832
- const links = rawLinks.map((rawLink, index) => {
59833
- if (!isObjectRecord2(rawLink) || typeof rawLink.valueId !== "string") {
59834
- throw new Error(`Dialogue-node linked value ${index} is malformed.`);
59835
- }
59836
- return rawLink;
59837
- });
59838
- const inheritedValueIds = new Set(
59839
- links.flatMap((link) => {
59840
- const valueId = stringField(link, "valueId");
59841
- return dialogueNamesByValueId.has(valueId) ? [valueId] : [];
59842
- })
59843
- );
59844
- const allocator = new DialogueSourceNameAllocator(
59845
- [...dialogueNamesByValueId].filter(([valueId]) => !inheritedValueIds.has(valueId)).map(([, name]) => name)
59846
- );
59847
- const namesByValueId = /* @__PURE__ */ new Map();
59848
- const allocate = (link, index) => {
59938
+ function regenerateNodeBindingSourceNames(links, allocator, placements, dialogueNamesByValueId, ambiguousOldNames, namesByOldName) {
59939
+ return links.map((link, index) => {
59940
+ if (keptSourceName(link.sourceName) !== null) return link;
59849
59941
  const valueId = stringField(link, "valueId");
59850
- namesByValueId.set(
59851
- valueId,
59852
- allocator.claim(
59853
- placements.preferredBindingName(
59854
- valueId,
59855
- optionalString2(link.declaredTypeName) ?? `value${index + 1}`,
59856
- "camel"
59857
- )
59942
+ const oldName = optionalString2(link.sourceName);
59943
+ const mapped = oldName === null ? void 0 : namesByOldName.get(oldName);
59944
+ const inherited = oldName !== null && ambiguousOldNames.has(oldName) ? void 0 : dialogueNamesByValueId.get(valueId);
59945
+ const sourceName = mapped ?? inherited ?? allocator.claim(
59946
+ placements.preferredBindingName(
59947
+ valueId,
59948
+ optionalString2(link.declaredTypeName) ?? `value${index + 1}`,
59949
+ "camel"
59858
59950
  )
59859
59951
  );
59860
- };
59861
- links.forEach((link, index) => {
59862
- if (dialogueNamesByValueId.has(stringField(link, "valueId"))) {
59863
- allocate(link, index);
59864
- }
59865
- });
59866
- links.forEach((link, index) => {
59867
- if (!dialogueNamesByValueId.has(stringField(link, "valueId"))) {
59868
- allocate(link, index);
59869
- }
59952
+ if (oldName !== null) namesByOldName.set(oldName, sourceName);
59953
+ return { ...link, sourceName };
59870
59954
  });
59871
- return links.map((link) => ({
59872
- ...link,
59873
- sourceName: requiredMap(
59874
- namesByValueId,
59875
- stringField(link, "valueId"),
59876
- "dialogue-node linked value"
59877
- )
59878
- }));
59879
59955
  }
59880
59956
  function regenerateTextChildren(node) {
59881
59957
  if (!isObjectRecord2(node.optionSettings)) return node;
59882
59958
  if (!Array.isArray(node.optionSettings.options)) return node;
59883
- const allocator = new DialogueSourceNameAllocator();
59884
- const options = node.optionSettings.options.map((rawOption, index) => {
59885
- if (!isObjectRecord2(rawOption)) return rawOption;
59886
- return {
59887
- ...rawOption,
59888
- sourceName: allocator.claim(
59889
- dialogueSourceNameFromDisplayName(
59890
- optionalMeaningfulString(rawOption.name),
59891
- `Option${index + 1}`
59892
- )
59893
- )
59894
- };
59895
- });
59896
59959
  return {
59897
59960
  ...node,
59898
- optionSettings: { ...node.optionSettings, options }
59961
+ optionSettings: {
59962
+ ...node.optionSettings,
59963
+ options: regenerateEntrySourceNames(
59964
+ node.optionSettings.options,
59965
+ "Option"
59966
+ )
59967
+ }
59899
59968
  };
59900
59969
  }
59901
59970
  function regenerateConditionOutcomes(node) {
59902
59971
  if (!Array.isArray(node.outcomes)) return node;
59903
- const allocator = new DialogueSourceNameAllocator();
59904
59972
  return {
59905
59973
  ...node,
59906
- outcomes: node.outcomes.map((rawOutcome, index) => {
59907
- if (!isObjectRecord2(rawOutcome)) return rawOutcome;
59908
- return {
59909
- ...rawOutcome,
59910
- sourceName: allocator.claim(
59911
- dialogueSourceNameFromDisplayName(
59912
- optionalMeaningfulString(rawOutcome.name),
59913
- `Outcome${index + 1}`
59914
- )
59915
- )
59916
- };
59917
- })
59974
+ outcomes: regenerateEntrySourceNames(node.outcomes, "Outcome")
59918
59975
  };
59919
59976
  }
59920
- function regeneratedSourceFunctionNames(nodes, allocator) {
59977
+ function regenerateEntrySourceNames(entries, fallback) {
59978
+ const allocator = new DialogueSourceNameAllocator();
59979
+ const kept = claimKeptNames(
59980
+ allocator,
59981
+ entries.map((entry, index) => [
59982
+ String(index),
59983
+ isObjectRecord2(entry) ? entry.sourceName : null
59984
+ ])
59985
+ );
59986
+ return entries.map((entry, index) => {
59987
+ if (!isObjectRecord2(entry)) return entry;
59988
+ const sourceName = kept.get(String(index)) ?? allocator.claim(
59989
+ dialogueSourceNameFromDisplayName(
59990
+ optionalMeaningfulString(entry.name),
59991
+ `${fallback}${index + 1}`
59992
+ )
59993
+ );
59994
+ return { ...entry, sourceName };
59995
+ });
59996
+ }
59997
+ function sourceFunctionUses(nodes) {
59921
59998
  const functions = /* @__PURE__ */ new Map();
59922
59999
  const register = (raw, role) => {
59923
60000
  if (!isObjectRecord2(raw)) return;
@@ -59959,7 +60036,7 @@ function regeneratedSourceFunctionNames(nodes, allocator) {
59959
60036
  registerConditions(option.settings.selectableConditions);
59960
60037
  }
59961
60038
  }
59962
- return allocateSourceFunctionNames(functions, allocator);
60039
+ return functions;
59963
60040
  }
59964
60041
  function regeneratedGroupFunctionNames(rawConditions) {
59965
60042
  const functions = /* @__PURE__ */ new Map();
@@ -59974,40 +60051,50 @@ function regeneratedGroupFunctionNames(rawConditions) {
59974
60051
  });
59975
60052
  }
59976
60053
  }
59977
- return allocateSourceFunctionNames(
59978
- functions,
59979
- new DialogueSourceNameAllocator()
60054
+ const allocator = new DialogueSourceNameAllocator();
60055
+ const kept = claimKeptNames(
60056
+ allocator,
60057
+ [...functions].map(([id2, fn]) => [`function:${id2}`, fn.existingName])
59980
60058
  );
60059
+ return allocateSourceFunctionNames(functions, allocator, kept);
59981
60060
  }
59982
- function allocateSourceFunctionNames(functions, allocator) {
60061
+ function allocateSourceFunctionNames(functions, allocator, kept) {
59983
60062
  const result = /* @__PURE__ */ new Map();
59984
60063
  const roleCounts = /* @__PURE__ */ new Map();
59985
60064
  for (const [id2, fn] of functions) {
59986
- const keepExisting = fn.existingName !== null && isDialogueSourceIdentifier(fn.existingName) && !isMachineGeneratedDialogueSourceName(fn.existingName);
59987
60065
  const ordinal = (roleCounts.get(fn.role) ?? 0) + 1;
59988
60066
  roleCounts.set(fn.role, ordinal);
59989
- const preferred = keepExisting ? fn.existingName : `${fn.role}${ordinal}`;
59990
- result.set(id2, allocator.claim(preferred));
60067
+ result.set(
60068
+ id2,
60069
+ kept.get(`function:${id2}`) ?? allocator.claim(`${fn.role}${ordinal}`)
60070
+ );
59991
60071
  }
59992
60072
  return result;
59993
60073
  }
59994
- function rewriteFunctionSourceNames(value, names) {
60074
+ function rewriteSourceReferences(value, functionNames, bindingRenames) {
59995
60075
  if (Array.isArray(value)) {
59996
- return value.map((entry) => rewriteFunctionSourceNames(entry, names));
60076
+ return value.map(
60077
+ (entry) => rewriteSourceReferences(entry, functionNames, bindingRenames)
60078
+ );
59997
60079
  }
59998
60080
  if (!isObjectRecord2(value)) return value;
59999
60081
  const result = Object.fromEntries(
60000
60082
  Object.entries(value).map(([key, entry]) => [
60001
60083
  key,
60002
- rewriteFunctionSourceNames(entry, names)
60084
+ renamedBindingReference(key, entry, bindingRenames) ?? rewriteSourceReferences(entry, functionNames, bindingRenames)
60003
60085
  ])
60004
60086
  );
60005
60087
  const id2 = optionalString2(value.sourceFunctionId);
60006
- const name = id2 === null ? null : names.get(id2);
60088
+ const name = id2 === null ? null : functionNames.get(id2);
60007
60089
  return name === void 0 || name === null ? result : { ...result, sourceFunctionName: name };
60008
60090
  }
60009
- function isMachineGeneratedDialogueSourceName(value) {
60010
- return /^(?:Dialogue|Node|Option|Outcome|Value)_[0-9a-f_]{8,}$/iu.test(value) || /^NeoFunction_[0-9a-f]{16,}$/iu.test(value) || /^(?:Action|Condition|TextValue)[0-9]+$/u.test(value);
60091
+ function renamedBindingReference(key, value, renames) {
60092
+ if (typeof value !== "string") return null;
60093
+ if (key === "code") return renameNeoScriptIdentifiers(value, renames);
60094
+ if (key !== "sourcePath" || PERSISTED_VARIABLE_PATH.test(value)) return null;
60095
+ const head = value.split(".", 1)[0];
60096
+ const renamed = renames.get(head);
60097
+ return renamed === void 0 ? value : renamed + value.slice(head.length);
60011
60098
  }
60012
60099
  function dialogueNodeKindName(value) {
60013
60100
  if (value === 1) return "Text";
@@ -60565,7 +60652,7 @@ function recoverableProse(context, textId, rawVariables, owner, nodeBindings, pr
60565
60652
  }
60566
60653
  }
60567
60654
  function authoredVariablePath(sourcePath, nodeBindings, dialogueBindings, memberNames) {
60568
- const persisted = /^(?:ref|linked):([^.]+)(\..+)?$/u.exec(sourcePath);
60655
+ const persisted = PERSISTED_VARIABLE_PATH.exec(sourcePath);
60569
60656
  const lexicalPath = persisted ? (() => {
60570
60657
  const valueId = persisted[1];
60571
60658
  const binding = nodeBindings.get(valueId) ?? dialogueBindings.get(valueId);
@@ -60947,7 +61034,7 @@ function requiredMap(map, id2, label) {
60947
61034
  function quote3(value) {
60948
61035
  return quoteNeoString(value);
60949
61036
  }
60950
- var DIALOGUE_SYSTEM_TYPE_NAMES, ValuePlacementIndex, UniqueNames;
61037
+ var PERSISTED_VARIABLE_PATH, DIALOGUE_SYSTEM_TYPE_NAMES, ValuePlacementIndex, UniqueNames;
60951
61038
  var init_dialogue_sources = __esm({
60952
61039
  "src/project-source/dialogue-sources.ts"() {
60953
61040
  "use strict";
@@ -60960,6 +61047,7 @@ var init_dialogue_sources = __esm({
60960
61047
  init_neoscript_source_rewrite();
60961
61048
  init_source_format();
60962
61049
  init_ui_action_source();
61050
+ PERSISTED_VARIABLE_PATH = /^(?:ref|linked):([^.]+)(\..+)?$/u;
60963
61051
  DIALOGUE_SYSTEM_TYPE_NAMES = [
60964
61052
  "Dialogue",
60965
61053
  "Trigger",
@@ -131368,9 +131456,25 @@ function workspaceBinaryPathsV4(workspace, local) {
131368
131456
  for (const file of local ?? []) paths.set(file.fileId, file.path);
131369
131457
  return paths;
131370
131458
  }
131371
- function readWorkspaceLayoutStatusV4(workspace) {
131459
+ function readWorkspaceLayoutStatusV4(workspace, document) {
131460
+ const records2 = {};
131461
+ for (const [key, server] of document.records) {
131462
+ if (server.deleted) continue;
131463
+ const previous = workspace.state.records[key];
131464
+ records2[key] = {
131465
+ recordKind: server.recordKind,
131466
+ recordId: server.recordId,
131467
+ contentHash: server.contentHash,
131468
+ data: server.data,
131469
+ ...previous?.file === void 0 ? {} : { file: previous.file },
131470
+ ...previous?.projectBinary === void 0 ? {} : { projectBinary: previous.projectBinary }
131471
+ };
131472
+ }
131372
131473
  try {
131373
- const status = computeWorkspaceStatus2(workspace);
131474
+ const status = computeWorkspaceStatus2({
131475
+ ...workspace,
131476
+ state: { ...workspace.state, records: records2 }
131477
+ });
131374
131478
  const failure = status.parseErrors.find(isBlockingSchemaSourceError);
131375
131479
  if (failure !== void 0) throw failure;
131376
131480
  return status;
@@ -131382,8 +131486,8 @@ function readWorkspaceLayoutStatusV4(workspace) {
131382
131486
  for (const path of listProjectSourceFilesV4(workspace.root)) {
131383
131487
  const content = readFileSync9(path, "utf8");
131384
131488
  if (neoProjectSourceKind(path, content) !== "definition") continue;
131385
- const document = parseNeoProjectSource(content, "definition", path);
131386
- if (document.declarations.some(
131489
+ const document2 = parseNeoProjectSource(content, "definition", path);
131490
+ if (document2.declarations.some(
131387
131491
  (declaration) => declaration.kind === "global" && isLocalValueGlobal(declaration)
131388
131492
  )) {
131389
131493
  throw new Error(
@@ -137771,7 +137875,7 @@ import { dirname as dirname8, join as join13, relative as relative7, sep as sep7
137771
137875
  function prepareWorkspaceResetV4(workspace, document, options = {}) {
137772
137876
  const emissionRecords = options.regenerateSourceNames ? regenerateDialogueSourceNamesV4(document.records) : document.records;
137773
137877
  const layoutWorkspace = workspaceForSourceLayoutV4(workspace, document);
137774
- const status = listProjectSourceFilesV4(workspace.root).length === 0 ? null : readWorkspaceLayoutStatusV4(layoutWorkspace);
137878
+ const status = listProjectSourceFilesV4(workspace.root).length === 0 ? null : readWorkspaceLayoutStatusV4(layoutWorkspace, document);
137775
137879
  const localValues = status?.localValues ?? workspace.state.localValues;
137776
137880
  const emitted = emitProjectDocumentFilesV4(emissionRecords, {
137777
137881
  localValues,
@@ -137991,7 +138095,7 @@ import {
137991
138095
  import { dirname as dirname9 } from "node:path";
137992
138096
  async function runPull(workspace, options, progress = spinner("Checking local workspace\u2026")) {
137993
138097
  if (options.reset) {
137994
- await runResetPull(workspace, progress);
138098
+ await runResetPull(workspace, options.regenerateSourceNames, progress);
137995
138099
  return;
137996
138100
  }
137997
138101
  try {
@@ -138233,18 +138337,18 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
138233
138337
  conflictKeys,
138234
138338
  localStatus,
138235
138339
  destructive,
138236
- regenerateSourceNames: destructive || options.regenerateSourceNames,
138340
+ regenerateSourceNames: options.regenerateSourceNames,
138237
138341
  changedRecordKeys,
138238
138342
  nextCursor,
138239
138343
  progress
138240
138344
  });
138241
138345
  }
138242
- async function runResetPull(workspace, progress) {
138346
+ async function runResetPull(workspace, regenerateSourceNames, progress) {
138243
138347
  progress.update("Pulling project snapshot\u2026");
138244
138348
  try {
138245
138349
  const document = await fetchProjectDocument(workspace);
138246
138350
  const preparation = prepareWorkspaceResetV4(workspace, document, {
138247
- regenerateSourceNames: true
138351
+ regenerateSourceNames
138248
138352
  });
138249
138353
  progress.update("Downloading project files\u2026");
138250
138354
  const previousRecords = workspace.state.records;
@@ -138258,7 +138362,7 @@ async function runResetPull(workspace, progress) {
138258
138362
  });
138259
138363
  progress.update("Regenerating Neo sources\u2026");
138260
138364
  const reset = resetWorkspaceToProjectSourcesV4(workspace, document, {
138261
- regenerateSourceNames: true,
138365
+ regenerateSourceNames,
138262
138366
  preparation
138263
138367
  });
138264
138368
  for (const recovery of reset.staticMemberOwnershipRecoveries ?? []) {
@@ -138343,7 +138447,7 @@ async function finishFormat4Pull(args) {
138343
138447
  for (const [key, record6] of localStatus?.reconstructed ?? []) {
138344
138448
  if (record6.file != null) recordFiles.set(key, record6.file);
138345
138449
  }
138346
- const layoutStatus = localStatus ?? (destructive ? readWorkspaceLayoutStatusV4(layoutWorkspace) : null);
138450
+ const layoutStatus = localStatus ?? (destructive ? readWorkspaceLayoutStatusV4(layoutWorkspace, document) : null);
138347
138451
  const localValues = layoutStatus?.localValues ?? workspace.state.localValues;
138348
138452
  const binaryPaths = workspaceBinaryPathsV4(
138349
138453
  workspace,
@@ -156706,9 +156810,9 @@ ${h("Usage")}
156706
156810
  neo pull ${d("[--force|--reset] [--regenerate-source-names]")}
156707
156811
 
156708
156812
  ${h("Flags")}
156709
- --force ${d("Discard local edits, take the current server schema, and regenerate readable dialogue symbols.")}
156710
- --reset ${d("Destructively regenerate the entire working copy, binaries, and readable dialogue symbols, even when the sync head is unchanged.")}
156711
- --regenerate-source-names ${d("Regenerate readable dialogue symbols during an otherwise normal pull.")}
156813
+ --force ${d("Discard local edits and take the current server schema.")}
156814
+ --reset ${d("Destructively regenerate the entire working copy and binaries, even when the sync head is unchanged.")}
156815
+ --regenerate-source-names ${d("Replace generated default dialogue symbols with readable names.")}
156712
156816
 
156713
156817
  Use ${h("--reset")} to reconstruct every managed ${h(".neo")}/${h(".neoflow")} source file
156714
156818
  and tracked project binary from the authoritative server records.
@@ -156833,7 +156937,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
156833
156937
  async function main() {
156834
156938
  const args = parseArgs(process.argv.slice(2));
156835
156939
  if (args.command === "--version") {
156836
- console.log("0.67.1");
156940
+ console.log("0.67.3");
156837
156941
  return;
156838
156942
  }
156839
156943
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.67.1",
3
+ "version": "0.67.3",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,7 @@ description: >-
9
9
  `@neocompose/cli` or `node cli/bin/neo.mjs` in the neo-compose repository.
10
10
  ---
11
11
 
12
- <!-- reviewed-through-cli: 0.67.1 -->
12
+ <!-- reviewed-through-cli: 0.67.3 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -91,10 +91,10 @@ upgrader. Reconstruct an authoritative format-4 checkout with `neo init`.
91
91
 
92
92
  For an existing checkout:
93
93
 
94
- - `neo pull --reset` regenerates canonical source, names, and managed binaries.
94
+ - `neo pull --reset` regenerates canonical source and managed binaries.
95
95
  - `neo pull --force` discards local edits in favor of the server.
96
- - `neo pull --regenerate-source-names` refreshes readable source names while
97
- retaining normal merge behavior.
96
+ - `neo pull --regenerate-source-names` replaces generated default dialogue
97
+ names with readable ones, keeps chosen names, and rewrites references.
98
98
 
99
99
  Use reset and force only when their destructive effect is intended.
100
100
 
@@ -110,7 +110,7 @@ wrappers.
110
110
  The marker near the top of `SKILL.md` must exactly match the package version:
111
111
 
112
112
  ```html
113
- <!-- reviewed-through-cli: 0.67.1 -->
113
+ <!-- reviewed-through-cli: 0.67.3 -->
114
114
  ```
115
115
 
116
116
  The quoted version above is checked too, so this instruction cannot go stale
@@ -29,11 +29,11 @@ neo push
29
29
  neo push --dry-run --force-recompile
30
30
  ```
31
31
 
32
- Use `neo pull --regenerate-source-names` to refresh readable naming while
33
- retaining normal merge behavior. Use `--reset` to regenerate canonical source
34
- and managed binaries. Reset retains declaration file grouping already recorded
35
- by the workspace, while declarations with no prior placement use the canonical
36
- per-declaration layout. Use `--force` only to discard local edits for the server
32
+ Use `neo pull --regenerate-source-names` to replace generated default dialogue
33
+ names with readable ones. It keeps chosen names and rewrites references. Use
34
+ `--reset` to regenerate canonical source and managed binaries. Reset retains
35
+ declaration file grouping already recorded by the workspace, while
36
+ declarations with no prior placement use the canonical per-declaration layout. Use `--force` only to discard local edits for the server
37
37
  version. Normal pulls persist a transaction cursor and fetch only changed
38
38
  records. If the remote head is unchanged, pull preserves the local source
39
39
  without compiling it; use `neo status` to inspect local edits and diagnostics.