@neocompose/cli 0.67.2 → 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,10 @@
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
+
3
8
  ## [0.67.2] - 2026-10-04
4
9
 
5
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.
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.2",
30117
+ cliVersion: "0.67.3",
30118
30118
  projectFileUploadBatchSize: 32,
30119
30119
  documentRecords: {
30120
30120
  member: {
@@ -59691,6 +59691,23 @@ function rewriteNeoScriptThisReceiver(source, alias) {
59691
59691
  }
59692
59692
  return result + source.slice(cursor);
59693
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
+ }
59694
59711
  var init_neoscript_source_rewrite = __esm({
59695
59712
  "src/project-source/neoscript-source-rewrite.ts"() {
59696
59713
  "use strict";
@@ -59718,58 +59735,116 @@ function regenerateDialogueSourceNamesV4(records2) {
59718
59735
  live.filter((record6) => record6.recordKind === "dialogue-node").map((record6) => ({ record: record6, data: recordData2(record6) })),
59719
59736
  ({ data }) => stringField(data, "dialogueId")
59720
59737
  );
59738
+ const keptClassNames = claimKeptNames(
59739
+ typeNames,
59740
+ dialogues.map((record6) => [record6.recordId, recordData2(record6).sourceName])
59741
+ );
59721
59742
  dialogues.forEach((dialogueRecord, dialogueIndex) => {
59722
59743
  const dialogue = recordData2(dialogueRecord);
59723
59744
  const dialogueId = dialogueRecord.recordId;
59724
- const className = typeNames.claim(
59745
+ const className = keptClassNames.get(dialogueId) ?? typeNames.claim(
59725
59746
  dialogueSourceNameFromDisplayName(
59726
59747
  optionalString2(dialogue.name),
59727
59748
  `Dialogue${dialogueIndex + 1}`
59728
59749
  )
59729
59750
  );
59730
- 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
+ );
59731
59758
  const nodes = nodeEntries.map(({ data }) => data);
59732
- 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);
59733
59765
  const classMembers = new DialogueSourceNameAllocator(["Trigger"]);
59734
- const nodeNames = regeneratedNodeNames(nodes, triggerId, classMembers);
59735
- const dialogueLinks = regenerateBindingSourceNames(
59736
- 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,
59737
59793
  classMembers,
59738
- placements,
59739
- "pascal"
59794
+ keptMembers
59740
59795
  );
59741
- const dialogueBindingNames = new Map(
59742
- dialogueLinks.map((link) => [
59743
- stringField(link, "valueId"),
59744
- stringField(link, "sourceName")
59745
- ])
59796
+ const dialogueLinks = regenerateBindingSourceNames(
59797
+ links,
59798
+ classMembers,
59799
+ keptMembers,
59800
+ placements
59746
59801
  );
59747
- const functionNames = regeneratedSourceFunctionNames(nodes, classMembers);
59748
- const nextDialogue = rewriteFunctionSourceNames(
59749
- { ...dialogue, sourceName: className, linkedValues: dialogueLinks },
59750
- functionNames
59802
+ const functionNames = allocateSourceFunctionNames(
59803
+ functions,
59804
+ classMembers,
59805
+ keptMembers
59751
59806
  );
59752
- result.set(recordKey(dialogueRecord), {
59753
- ...dialogueRecord,
59754
- 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);
59755
59814
  });
59756
- for (const { record: record6, data } of nodeEntries) {
59757
- const nodeId = stringField(data, "id");
59758
- const nodeName = nodeNames.get(nodeId) ?? "Trigger";
59759
- const nodeLinks = regenerateNodeBindingSourceNames(
59760
- data.linkedValues,
59761
- placements,
59762
- dialogueBindingNames
59763
- );
59815
+ const nextNodes = nodeEntries.map(({ record: record6, data, links: nodeLinks }) => {
59764
59816
  let nextNode = {
59765
59817
  ...data,
59766
- sourceName: nodeName,
59767
- 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
+ )
59768
59827
  };
59769
59828
  if (data.type === 1) nextNode = regenerateTextChildren(nextNode);
59770
59829
  if (data.type === 3) nextNode = regenerateConditionOutcomes(nextNode);
59771
- nextNode = rewriteFunctionSourceNames(nextNode, functionNames);
59772
- 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
+ });
59773
59848
  }
59774
59849
  });
59775
59850
  for (const record6 of live.filter(
@@ -59780,23 +59855,54 @@ function regenerateDialogueSourceNamesV4(records2) {
59780
59855
  if (functionNames.size === 0) continue;
59781
59856
  result.set(recordKey(record6), {
59782
59857
  ...record6,
59783
- data: rewriteFunctionSourceNames(data, functionNames)
59858
+ data: rewriteSourceReferences(data, functionNames, /* @__PURE__ */ new Map())
59784
59859
  });
59785
59860
  }
59786
59861
  return result;
59787
59862
  }
59788
- 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) {
59789
59894
  const result = /* @__PURE__ */ new Map();
59790
- if (triggerId !== null) result.set(triggerId, "Trigger");
59791
- const bodyNodes = orderedBodyNodes(nodes, triggerId);
59792
59895
  const descriptors = bodyNodes.map((node) => ({
59793
- node,
59794
59896
  id: stringField(node, "id"),
59795
59897
  displayName: optionalMeaningfulString(node.name),
59796
59898
  kind: dialogueNodeKindName(node.type)
59797
59899
  }));
59798
59900
  for (const descriptor of descriptors) {
59799
- 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;
59800
59906
  result.set(
59801
59907
  descriptor.id,
59802
59908
  allocator.claim(
@@ -59816,111 +59922,79 @@ function regeneratedNodeNames(nodes, triggerId, allocator) {
59816
59922
  }
59817
59923
  return result;
59818
59924
  }
59819
- function regenerateBindingSourceNames(rawLinks, allocator, placements, casing) {
59820
- if (!Array.isArray(rawLinks)) return [];
59821
- return rawLinks.map((rawLink, index) => {
59822
- if (!isObjectRecord2(rawLink) || typeof rawLink.valueId !== "string") {
59823
- throw new Error(`Dialogue linked value ${index} is malformed.`);
59824
- }
59825
- const preferred = placements.preferredBindingName(
59826
- rawLink.valueId,
59827
- optionalString2(rawLink.declaredTypeName) ?? `value${index + 1}`,
59828
- 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
+ )
59829
59934
  );
59830
- return { ...rawLink, sourceName: allocator.claim(preferred) };
59935
+ return { ...link, sourceName };
59831
59936
  });
59832
59937
  }
59833
- function regenerateNodeBindingSourceNames(rawLinks, placements, dialogueNamesByValueId) {
59834
- if (!Array.isArray(rawLinks)) return [];
59835
- const links = rawLinks.map((rawLink, index) => {
59836
- if (!isObjectRecord2(rawLink) || typeof rawLink.valueId !== "string") {
59837
- throw new Error(`Dialogue-node linked value ${index} is malformed.`);
59838
- }
59839
- return rawLink;
59840
- });
59841
- const inheritedValueIds = new Set(
59842
- links.flatMap((link) => {
59843
- const valueId = stringField(link, "valueId");
59844
- return dialogueNamesByValueId.has(valueId) ? [valueId] : [];
59845
- })
59846
- );
59847
- const allocator = new DialogueSourceNameAllocator(
59848
- [...dialogueNamesByValueId].filter(([valueId]) => !inheritedValueIds.has(valueId)).map(([, name]) => name)
59849
- );
59850
- const namesByValueId = /* @__PURE__ */ new Map();
59851
- 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;
59852
59941
  const valueId = stringField(link, "valueId");
59853
- namesByValueId.set(
59854
- valueId,
59855
- allocator.claim(
59856
- placements.preferredBindingName(
59857
- valueId,
59858
- optionalString2(link.declaredTypeName) ?? `value${index + 1}`,
59859
- "camel"
59860
- )
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"
59861
59950
  )
59862
59951
  );
59863
- };
59864
- links.forEach((link, index) => {
59865
- if (dialogueNamesByValueId.has(stringField(link, "valueId"))) {
59866
- allocate(link, index);
59867
- }
59952
+ if (oldName !== null) namesByOldName.set(oldName, sourceName);
59953
+ return { ...link, sourceName };
59868
59954
  });
59869
- links.forEach((link, index) => {
59870
- if (!dialogueNamesByValueId.has(stringField(link, "valueId"))) {
59871
- allocate(link, index);
59872
- }
59873
- });
59874
- return links.map((link) => ({
59875
- ...link,
59876
- sourceName: requiredMap(
59877
- namesByValueId,
59878
- stringField(link, "valueId"),
59879
- "dialogue-node linked value"
59880
- )
59881
- }));
59882
59955
  }
59883
59956
  function regenerateTextChildren(node) {
59884
59957
  if (!isObjectRecord2(node.optionSettings)) return node;
59885
59958
  if (!Array.isArray(node.optionSettings.options)) return node;
59886
- const allocator = new DialogueSourceNameAllocator();
59887
- const options = node.optionSettings.options.map((rawOption, index) => {
59888
- if (!isObjectRecord2(rawOption)) return rawOption;
59889
- return {
59890
- ...rawOption,
59891
- sourceName: allocator.claim(
59892
- dialogueSourceNameFromDisplayName(
59893
- optionalMeaningfulString(rawOption.name),
59894
- `Option${index + 1}`
59895
- )
59896
- )
59897
- };
59898
- });
59899
59959
  return {
59900
59960
  ...node,
59901
- optionSettings: { ...node.optionSettings, options }
59961
+ optionSettings: {
59962
+ ...node.optionSettings,
59963
+ options: regenerateEntrySourceNames(
59964
+ node.optionSettings.options,
59965
+ "Option"
59966
+ )
59967
+ }
59902
59968
  };
59903
59969
  }
59904
59970
  function regenerateConditionOutcomes(node) {
59905
59971
  if (!Array.isArray(node.outcomes)) return node;
59906
- const allocator = new DialogueSourceNameAllocator();
59907
59972
  return {
59908
59973
  ...node,
59909
- outcomes: node.outcomes.map((rawOutcome, index) => {
59910
- if (!isObjectRecord2(rawOutcome)) return rawOutcome;
59911
- return {
59912
- ...rawOutcome,
59913
- sourceName: allocator.claim(
59914
- dialogueSourceNameFromDisplayName(
59915
- optionalMeaningfulString(rawOutcome.name),
59916
- `Outcome${index + 1}`
59917
- )
59918
- )
59919
- };
59920
- })
59974
+ outcomes: regenerateEntrySourceNames(node.outcomes, "Outcome")
59921
59975
  };
59922
59976
  }
59923
- 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) {
59924
59998
  const functions = /* @__PURE__ */ new Map();
59925
59999
  const register = (raw, role) => {
59926
60000
  if (!isObjectRecord2(raw)) return;
@@ -59962,7 +60036,7 @@ function regeneratedSourceFunctionNames(nodes, allocator) {
59962
60036
  registerConditions(option.settings.selectableConditions);
59963
60037
  }
59964
60038
  }
59965
- return allocateSourceFunctionNames(functions, allocator);
60039
+ return functions;
59966
60040
  }
59967
60041
  function regeneratedGroupFunctionNames(rawConditions) {
59968
60042
  const functions = /* @__PURE__ */ new Map();
@@ -59977,40 +60051,50 @@ function regeneratedGroupFunctionNames(rawConditions) {
59977
60051
  });
59978
60052
  }
59979
60053
  }
59980
- return allocateSourceFunctionNames(
59981
- functions,
59982
- new DialogueSourceNameAllocator()
60054
+ const allocator = new DialogueSourceNameAllocator();
60055
+ const kept = claimKeptNames(
60056
+ allocator,
60057
+ [...functions].map(([id2, fn]) => [`function:${id2}`, fn.existingName])
59983
60058
  );
60059
+ return allocateSourceFunctionNames(functions, allocator, kept);
59984
60060
  }
59985
- function allocateSourceFunctionNames(functions, allocator) {
60061
+ function allocateSourceFunctionNames(functions, allocator, kept) {
59986
60062
  const result = /* @__PURE__ */ new Map();
59987
60063
  const roleCounts = /* @__PURE__ */ new Map();
59988
60064
  for (const [id2, fn] of functions) {
59989
- const keepExisting = fn.existingName !== null && isDialogueSourceIdentifier(fn.existingName) && !isMachineGeneratedDialogueSourceName(fn.existingName);
59990
60065
  const ordinal = (roleCounts.get(fn.role) ?? 0) + 1;
59991
60066
  roleCounts.set(fn.role, ordinal);
59992
- const preferred = keepExisting ? fn.existingName : `${fn.role}${ordinal}`;
59993
- result.set(id2, allocator.claim(preferred));
60067
+ result.set(
60068
+ id2,
60069
+ kept.get(`function:${id2}`) ?? allocator.claim(`${fn.role}${ordinal}`)
60070
+ );
59994
60071
  }
59995
60072
  return result;
59996
60073
  }
59997
- function rewriteFunctionSourceNames(value, names) {
60074
+ function rewriteSourceReferences(value, functionNames, bindingRenames) {
59998
60075
  if (Array.isArray(value)) {
59999
- return value.map((entry) => rewriteFunctionSourceNames(entry, names));
60076
+ return value.map(
60077
+ (entry) => rewriteSourceReferences(entry, functionNames, bindingRenames)
60078
+ );
60000
60079
  }
60001
60080
  if (!isObjectRecord2(value)) return value;
60002
60081
  const result = Object.fromEntries(
60003
60082
  Object.entries(value).map(([key, entry]) => [
60004
60083
  key,
60005
- rewriteFunctionSourceNames(entry, names)
60084
+ renamedBindingReference(key, entry, bindingRenames) ?? rewriteSourceReferences(entry, functionNames, bindingRenames)
60006
60085
  ])
60007
60086
  );
60008
60087
  const id2 = optionalString2(value.sourceFunctionId);
60009
- const name = id2 === null ? null : names.get(id2);
60088
+ const name = id2 === null ? null : functionNames.get(id2);
60010
60089
  return name === void 0 || name === null ? result : { ...result, sourceFunctionName: name };
60011
60090
  }
60012
- function isMachineGeneratedDialogueSourceName(value) {
60013
- 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);
60014
60098
  }
60015
60099
  function dialogueNodeKindName(value) {
60016
60100
  if (value === 1) return "Text";
@@ -60568,7 +60652,7 @@ function recoverableProse(context, textId, rawVariables, owner, nodeBindings, pr
60568
60652
  }
60569
60653
  }
60570
60654
  function authoredVariablePath(sourcePath, nodeBindings, dialogueBindings, memberNames) {
60571
- const persisted = /^(?:ref|linked):([^.]+)(\..+)?$/u.exec(sourcePath);
60655
+ const persisted = PERSISTED_VARIABLE_PATH.exec(sourcePath);
60572
60656
  const lexicalPath = persisted ? (() => {
60573
60657
  const valueId = persisted[1];
60574
60658
  const binding = nodeBindings.get(valueId) ?? dialogueBindings.get(valueId);
@@ -60950,7 +61034,7 @@ function requiredMap(map, id2, label) {
60950
61034
  function quote3(value) {
60951
61035
  return quoteNeoString(value);
60952
61036
  }
60953
- var DIALOGUE_SYSTEM_TYPE_NAMES, ValuePlacementIndex, UniqueNames;
61037
+ var PERSISTED_VARIABLE_PATH, DIALOGUE_SYSTEM_TYPE_NAMES, ValuePlacementIndex, UniqueNames;
60954
61038
  var init_dialogue_sources = __esm({
60955
61039
  "src/project-source/dialogue-sources.ts"() {
60956
61040
  "use strict";
@@ -60963,6 +61047,7 @@ var init_dialogue_sources = __esm({
60963
61047
  init_neoscript_source_rewrite();
60964
61048
  init_source_format();
60965
61049
  init_ui_action_source();
61050
+ PERSISTED_VARIABLE_PATH = /^(?:ref|linked):([^.]+)(\..+)?$/u;
60966
61051
  DIALOGUE_SYSTEM_TYPE_NAMES = [
60967
61052
  "Dialogue",
60968
61053
  "Trigger",
@@ -138010,7 +138095,7 @@ import {
138010
138095
  import { dirname as dirname9 } from "node:path";
138011
138096
  async function runPull(workspace, options, progress = spinner("Checking local workspace\u2026")) {
138012
138097
  if (options.reset) {
138013
- await runResetPull(workspace, progress);
138098
+ await runResetPull(workspace, options.regenerateSourceNames, progress);
138014
138099
  return;
138015
138100
  }
138016
138101
  try {
@@ -138252,18 +138337,18 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
138252
138337
  conflictKeys,
138253
138338
  localStatus,
138254
138339
  destructive,
138255
- regenerateSourceNames: destructive || options.regenerateSourceNames,
138340
+ regenerateSourceNames: options.regenerateSourceNames,
138256
138341
  changedRecordKeys,
138257
138342
  nextCursor,
138258
138343
  progress
138259
138344
  });
138260
138345
  }
138261
- async function runResetPull(workspace, progress) {
138346
+ async function runResetPull(workspace, regenerateSourceNames, progress) {
138262
138347
  progress.update("Pulling project snapshot\u2026");
138263
138348
  try {
138264
138349
  const document = await fetchProjectDocument(workspace);
138265
138350
  const preparation = prepareWorkspaceResetV4(workspace, document, {
138266
- regenerateSourceNames: true
138351
+ regenerateSourceNames
138267
138352
  });
138268
138353
  progress.update("Downloading project files\u2026");
138269
138354
  const previousRecords = workspace.state.records;
@@ -138277,7 +138362,7 @@ async function runResetPull(workspace, progress) {
138277
138362
  });
138278
138363
  progress.update("Regenerating Neo sources\u2026");
138279
138364
  const reset = resetWorkspaceToProjectSourcesV4(workspace, document, {
138280
- regenerateSourceNames: true,
138365
+ regenerateSourceNames,
138281
138366
  preparation
138282
138367
  });
138283
138368
  for (const recovery of reset.staticMemberOwnershipRecoveries ?? []) {
@@ -156725,9 +156810,9 @@ ${h("Usage")}
156725
156810
  neo pull ${d("[--force|--reset] [--regenerate-source-names]")}
156726
156811
 
156727
156812
  ${h("Flags")}
156728
- --force ${d("Discard local edits, take the current server schema, and regenerate readable dialogue symbols.")}
156729
- --reset ${d("Destructively regenerate the entire working copy, binaries, and readable dialogue symbols, even when the sync head is unchanged.")}
156730
- --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.")}
156731
156816
 
156732
156817
  Use ${h("--reset")} to reconstruct every managed ${h(".neo")}/${h(".neoflow")} source file
156733
156818
  and tracked project binary from the authoritative server records.
@@ -156852,7 +156937,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
156852
156937
  async function main() {
156853
156938
  const args = parseArgs(process.argv.slice(2));
156854
156939
  if (args.command === "--version") {
156855
- console.log("0.67.2");
156940
+ console.log("0.67.3");
156856
156941
  return;
156857
156942
  }
156858
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.2",
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.2 -->
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.2 -->
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.