@neocompose/cli 0.38.5 → 0.38.6

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,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.38.6] - 2026-08-29
4
+
5
+ ### Fixed
6
+
7
+ - Preserve locally edited nested records when the server deletes their owning
8
+ graph, restore kept records as explicit absent-CAS creates, and accept a
9
+ later server recreation cleanly after local deletion wins.
10
+ - Warn during pull when a conflicted record's retained local base strands
11
+ newly downloaded descendant value rows, before a later push reaches the
12
+ stale-base safety refusal.
13
+
3
14
  ## [0.38.5] - 2026-08-29
4
15
 
5
16
  ### Fixed
package/dist/neo.mjs CHANGED
@@ -100786,6 +100786,28 @@ var init_value_base_desync = __esm({
100786
100786
  });
100787
100787
 
100788
100788
  // src/project-source/workspace-status-core.ts
100789
+ function recordUpsertChange(args) {
100790
+ if (args.base.conflictServerHash === null) {
100791
+ return {
100792
+ kind: "create",
100793
+ recordKind: args.recordKind,
100794
+ recordId: args.recordId,
100795
+ file: args.file,
100796
+ nextData: args.nextData
100797
+ };
100798
+ }
100799
+ const inConflictState = typeof args.base.conflictServerHash === "string";
100800
+ return {
100801
+ kind: "update",
100802
+ recordKind: args.recordKind,
100803
+ recordId: args.recordId,
100804
+ file: args.file,
100805
+ nextData: args.nextData,
100806
+ baseData: inConflictState ? args.base.conflictServerData : args.base.data,
100807
+ baseContentHash: args.base.contentHash,
100808
+ casBaseHash: inConflictState ? args.base.conflictServerHash : args.base.contentHash
100809
+ };
100810
+ }
100789
100811
  function listVirtualProjectSourceFilesV4(files) {
100790
100812
  const selected = files.filter((file) => {
100791
100813
  const kind = neoProjectSourceKind(file.path);
@@ -101269,16 +101291,15 @@ function computeWorkspaceStatus(workspace, options) {
101269
101291
  }
101270
101292
  const inConflictState = baseState.conflictServerHash !== void 0;
101271
101293
  if (!recordsSemanticallyEqual(record3.recordKind, fullData, baseState.data) || inConflictState) {
101272
- changes.push({
101273
- kind: "update",
101274
- recordKind: record3.recordKind,
101275
- recordId: record3.recordId,
101276
- file: record3.file,
101277
- nextData: fullData,
101278
- baseData: inConflictState ? baseState.conflictServerData : baseState.data,
101279
- baseContentHash: baseState.contentHash,
101280
- casBaseHash: inConflictState ? baseState.conflictServerHash : baseState.contentHash
101281
- });
101294
+ changes.push(
101295
+ recordUpsertChange({
101296
+ base: baseState,
101297
+ recordKind: record3.recordKind,
101298
+ recordId: record3.recordId,
101299
+ file: record3.file,
101300
+ nextData: fullData
101301
+ })
101302
+ );
101282
101303
  }
101283
101304
  }
101284
101305
  const deletedValueIds = /* @__PURE__ */ new Set();
@@ -101290,6 +101311,7 @@ function computeWorkspaceStatus(workspace, options) {
101290
101311
  (error) => error.file === recordFile && isBlockingSchemaSourceError(error)
101291
101312
  ) || conflictedFiles.includes(recordFile);
101292
101313
  if (fileStillBroken) continue;
101314
+ if (recordState.conflictServerHash === null) continue;
101293
101315
  if (recordState.recordKind === "value") {
101294
101316
  deletedValueIds.add(recordState.recordId);
101295
101317
  }
@@ -101450,16 +101472,15 @@ function computeWorkspaceStatus(workspace, options) {
101450
101472
  )) {
101451
101473
  continue;
101452
101474
  }
101453
- changes.push({
101454
- kind: "update",
101455
- recordKind: "project-file",
101456
- recordId: fileId,
101457
- file: record3.file,
101458
- nextData: record3.fullData,
101459
- baseData: base.data,
101460
- baseContentHash: base.contentHash,
101461
- casBaseHash: base.contentHash
101462
- });
101475
+ changes.push(
101476
+ recordUpsertChange({
101477
+ base,
101478
+ recordKind: "project-file",
101479
+ recordId: fileId,
101480
+ file: record3.file,
101481
+ nextData: record3.fullData
101482
+ })
101483
+ );
101463
101484
  }
101464
101485
  let binaryFiles = [];
101465
101486
  let binaryChanges = [];
@@ -113587,7 +113608,8 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
113587
113608
  if (serverRecord.deleted) continue;
113588
113609
  const baseState = workspace.state.records[key];
113589
113610
  const local = localByKey.get(key);
113590
- if (destructive || baseState === void 0) {
113611
+ const acceptedServerDeletionRecreated = baseState?.conflictServerHash === null && local === void 0;
113612
+ if (destructive || baseState === void 0 || acceptedServerDeletionRecreated) {
113591
113613
  plans.set(key, {
113592
113614
  emitData: serverRecord.data,
113593
113615
  serverHash: serverRecord.contentHash,
@@ -113834,13 +113856,23 @@ async function finishFormat4Pull(args) {
113834
113856
  (serverResult?.files ?? []).map((file) => [file.path, file])
113835
113857
  );
113836
113858
  const conflictPaths = /* @__PURE__ */ new Set();
113859
+ const authoredLocalConflictKeysByPath = /* @__PURE__ */ new Map();
113837
113860
  for (const key of conflictKeys) {
113838
- const path = localResult.recordFiles.get(key) ?? serverResult?.recordFiles.get(key);
113861
+ const localPath = localResult.recordFiles.get(key);
113862
+ const serverPath = serverResult?.recordFiles.get(key);
113863
+ const path = localPath ?? serverPath ?? workspace.state.records[key]?.file;
113839
113864
  if (path) conflictPaths.add(path);
113865
+ if (path === void 0 || localPath !== void 0 || serverPath !== void 0) {
113866
+ continue;
113867
+ }
113868
+ const keys = authoredLocalConflictKeysByPath.get(path) ?? [];
113869
+ keys.push(key);
113870
+ authoredLocalConflictKeysByPath.set(path, keys);
113840
113871
  }
113841
113872
  const emittedPaths = /* @__PURE__ */ new Set([
113842
113873
  ...localByPath.keys(),
113843
- ...[...conflictPaths].filter((path) => serverByPath.has(path))
113874
+ ...[...conflictPaths].filter((path) => serverByPath.has(path)),
113875
+ ...authoredLocalConflictKeysByPath.keys()
113844
113876
  ]);
113845
113877
  const rewritePaths = projectSourcePathsRequiringRewriteV4({
113846
113878
  destructive,
@@ -113860,7 +113892,17 @@ async function finishFormat4Pull(args) {
113860
113892
  const resolveSourcePath = createWorkspaceSourcePathResolver(workspace.root);
113861
113893
  let written = 0;
113862
113894
  for (const path of emittedPaths) {
113863
- const local = localByPath.get(path)?.content;
113895
+ const absolute = resolveSourcePath(path);
113896
+ const existing = existsSync8(absolute) ? readFileSync9(absolute, "utf8") : null;
113897
+ const unprojectedConflictKeys = authoredLocalConflictKeysByPath.get(path) ?? [];
113898
+ if (unprojectedConflictKeys.length > 0 && existing === null) {
113899
+ throw new Error(
113900
+ `Cannot preserve deleted-server conflict ${unprojectedConflictKeys.join(
113901
+ ", "
113902
+ )}: its prior source file ${JSON.stringify(path)} no longer exists.`
113903
+ );
113904
+ }
113905
+ const local = unprojectedConflictKeys.length > 0 ? existing ?? void 0 : localByPath.get(path)?.content;
113864
113906
  const server = serverByPath.get(path)?.content;
113865
113907
  const content = conflictPaths.has(path) ? renderConflictFileV4({
113866
113908
  localContent: local,
@@ -113868,9 +113910,7 @@ async function finishFormat4Pull(args) {
113868
113910
  versionId: workspace.config.versionId
113869
113911
  }) : local;
113870
113912
  if (content === void 0) continue;
113871
- const absolute = resolveSourcePath(path);
113872
113913
  mkdirSync8(dirname8(absolute), { recursive: true });
113873
- const existing = existsSync8(absolute) ? readFileSync9(absolute, "utf8") : null;
113874
113914
  if (existing !== null && !rewritePaths.has(path)) continue;
113875
113915
  if (existing !== content) {
113876
113916
  writeFileSync8(absolute, content, "utf8");
@@ -113892,6 +113932,7 @@ async function finishFormat4Pull(args) {
113892
113932
  }
113893
113933
  }
113894
113934
  const records2 = {};
113935
+ const previousRecords = workspace.state.records;
113895
113936
  for (const [key, plan] of plans) {
113896
113937
  const baseState = workspace.state.records[key];
113897
113938
  const serverRecord = document.records.get(key);
@@ -113934,6 +113975,10 @@ async function finishFormat4Pull(args) {
113934
113975
  const record3 = records2[key];
113935
113976
  if (record3) record3.projectBinary = state;
113936
113977
  }
113978
+ const staleConflictTears = conflictCount === 0 ? [] : findNewlyUnanchoredConflictValues({
113979
+ previousRecords,
113980
+ records: records2
113981
+ });
113937
113982
  workspace.state.records = records2;
113938
113983
  if (nextCursor !== null) {
113939
113984
  workspace.state.documentRevisionCursor = mutableCursor(nextCursor);
@@ -113985,6 +114030,12 @@ async function finishFormat4Pull(args) {
113985
114030
  'Conflict markers were written into the affected files. Edit them to the desired final state and push \u2014 the push is the resolution. To keep one side everywhere, use "neo resolve --mine" or "neo resolve --theirs".'
113986
114031
  );
113987
114032
  for (const key of conflictKeys) console.log(` ${sym.fail} ${key}`);
114033
+ for (const tear of staleConflictTears) {
114034
+ const rowLabel = tear.unanchoredValueCount === 1 ? "row" : "rows";
114035
+ warn(
114036
+ `Conflict ${tear.recordKind} "${tear.recordId}" retained a local base that does not anchor ${tear.unanchoredValueCount} newly pulled descendant value ${rowLabel}. Resolve it with "neo resolve --mine" or "neo resolve --theirs" before pushing.`
114037
+ );
114038
+ }
113988
114039
  }
113989
114040
  if (binaries.conflicted > 0) {
113990
114041
  warn(
@@ -113993,6 +114044,65 @@ async function finishFormat4Pull(args) {
113993
114044
  for (const path of binaries.conflicts) console.log(` ${sym.fail} ${path}`);
113994
114045
  }
113995
114046
  }
114047
+ function findNewlyUnanchoredConflictValues(args) {
114048
+ const newlyLandedValueIds = /* @__PURE__ */ new Set();
114049
+ for (const [key, record3] of Object.entries(args.records)) {
114050
+ if (args.previousRecords[key] !== void 0) continue;
114051
+ if (record3.recordKind !== "value") continue;
114052
+ if (record3.file === void 0) continue;
114053
+ if (record3.conflictServerHash !== void 0) continue;
114054
+ newlyLandedValueIds.add(record3.recordId);
114055
+ }
114056
+ if (newlyLandedValueIds.size === 0) return [];
114057
+ const unanchored = new Set(
114058
+ unanchoredValueDeleteIds({
114059
+ records: args.records,
114060
+ deletedValueIds: newlyLandedValueIds
114061
+ })
114062
+ );
114063
+ if (unanchored.size === 0) return [];
114064
+ const tears = [];
114065
+ for (const conflict2 of Object.values(args.records)) {
114066
+ if (typeof conflict2.conflictServerHash !== "string") continue;
114067
+ if (conflict2.file === void 0) continue;
114068
+ const serverAnchored = /* @__PURE__ */ new Set();
114069
+ collectNamedCandidateValues(
114070
+ conflict2.conflictServerData,
114071
+ newlyLandedValueIds,
114072
+ serverAnchored
114073
+ );
114074
+ let unanchoredValueCount = 0;
114075
+ for (const valueId of serverAnchored) {
114076
+ if (!unanchored.has(valueId)) continue;
114077
+ const value = args.records[`value:${valueId}`];
114078
+ if (value?.file !== conflict2.file) continue;
114079
+ unanchoredValueCount += 1;
114080
+ }
114081
+ if (unanchoredValueCount === 0) continue;
114082
+ tears.push({
114083
+ recordKind: conflict2.recordKind,
114084
+ recordId: conflict2.recordId,
114085
+ unanchoredValueCount
114086
+ });
114087
+ }
114088
+ return tears;
114089
+ }
114090
+ function collectNamedCandidateValues(value, candidates, found) {
114091
+ if (typeof value === "string") {
114092
+ if (candidates.has(value)) found.add(value);
114093
+ return;
114094
+ }
114095
+ if (Array.isArray(value)) {
114096
+ for (const element of value) {
114097
+ collectNamedCandidateValues(element, candidates, found);
114098
+ }
114099
+ return;
114100
+ }
114101
+ if (!isObjectRecord2(value)) return;
114102
+ for (const nested of Object.values(value)) {
114103
+ collectNamedCandidateValues(nested, candidates, found);
114104
+ }
114105
+ }
113996
114106
  function deltaRequiresSourceProjectionV4(args) {
113997
114107
  const mainLocale = workspaceMainLocale(args.workspace.state.records);
113998
114108
  for (const key of args.changedRecordKeys) {
@@ -114185,6 +114295,7 @@ var init_pull = __esm({
114185
114295
  init_dialogue_sources();
114186
114296
  init_static_value_seed_records();
114187
114297
  init_static_member_ownership_recovery();
114298
+ init_value_base_desync();
114188
114299
  }
114189
114300
  });
114190
114301
 
@@ -114194,7 +114305,7 @@ __export(init_exports, {
114194
114305
  runInit: () => runInit
114195
114306
  });
114196
114307
  import { existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync9 } from "node:fs";
114197
- import { join as join11, resolve as resolve3 } from "node:path";
114308
+ import { join as join10, resolve as resolve3 } from "node:path";
114198
114309
  async function runInit(options) {
114199
114310
  let projectId = options.projectId;
114200
114311
  let projects = [];
@@ -114291,8 +114402,8 @@ async function runInit(options) {
114291
114402
  }) : "neo";
114292
114403
  }
114293
114404
  const root = resolve3(directory);
114294
- if (existsSync9(join11(root, NEO_CONFIG_FILE))) {
114295
- throw new Error(`"${join11(root, NEO_CONFIG_FILE)}" already exists.`);
114405
+ if (existsSync9(join10(root, NEO_CONFIG_FILE))) {
114406
+ throw new Error(`"${join10(root, NEO_CONFIG_FILE)}" already exists.`);
114296
114407
  }
114297
114408
  mkdirSync9(root, { recursive: true });
114298
114409
  ensurePrivateStateIgnored(root);
@@ -114317,7 +114428,7 @@ async function runInit(options) {
114317
114428
  });
114318
114429
  }
114319
114430
  function ensurePrivateStateIgnored(root) {
114320
- const path = join11(root, ".gitignore");
114431
+ const path = join10(root, ".gitignore");
114321
114432
  const existing = existsSync9(path) ? readFileSync10(path, "utf8") : "";
114322
114433
  if (existing.split(/\r?\n/u).some((line) => line.trim() === ".neo/" || line.trim() === ".neo")) {
114323
114434
  return;
@@ -115724,7 +115835,7 @@ var init_save_overlay_resolution = __esm({
115724
115835
 
115725
115836
  // src/commands/push-body-diagnostics.ts
115726
115837
  import { existsSync as existsSync10, readFileSync as readFileSync11 } from "node:fs";
115727
- import { join as join12 } from "node:path";
115838
+ import { join as join11 } from "node:path";
115728
115839
  function createNeoScriptBodySourceLocator(workspace, status) {
115729
115840
  const textByFile = /* @__PURE__ */ new Map();
115730
115841
  return {
@@ -115746,7 +115857,7 @@ function createNeoScriptBodySourceLocator(workspace, status) {
115746
115857
  textOf(file) {
115747
115858
  const cached = textByFile.get(file);
115748
115859
  if (cached !== void 0) return cached;
115749
- const path = join12(workspace.root, file);
115860
+ const path = join11(workspace.root, file);
115750
115861
  const text = existsSync10(path) ? readFileSync11(path, "utf8") : null;
115751
115862
  textByFile.set(file, text);
115752
115863
  return text;
@@ -117550,25 +117661,25 @@ var init_project_source_identity = __esm({
117550
117661
  import { spawn } from "node:child_process";
117551
117662
  import { createHash as createHash9 } from "node:crypto";
117552
117663
  import { existsSync as existsSync11, readFileSync as readFileSync13, readdirSync as readdirSync4 } from "node:fs";
117553
- import { join as join13, relative as relative4, sep as sep4 } from "node:path";
117664
+ import { join as join12, relative as relative4, sep as sep4 } from "node:path";
117554
117665
  function fingerprintPushInputs(workspace, options = {}) {
117555
117666
  const paths = /* @__PURE__ */ new Set([
117556
- join13(workspace.root, "neo.json"),
117667
+ join12(workspace.root, "neo.json"),
117557
117668
  ...listProjectSourceFilesV4(workspace.root),
117558
117669
  ...options.includeTests === false ? [] : listProjectTestFilesV1(workspace.root),
117559
- ...workspace.config.unityConfigPath === void 0 ? [] : [join13(workspace.root, workspace.config.unityConfigPath)]
117670
+ ...workspace.config.unityConfigPath === void 0 ? [] : [join12(workspace.root, workspace.config.unityConfigPath)]
117560
117671
  ]);
117561
117672
  const visitManaged = (directory) => {
117562
117673
  if (!existsSync11(directory)) return;
117563
117674
  for (const entry of readdirSync4(directory, { withFileTypes: true })) {
117564
117675
  if (entry.isSymbolicLink()) continue;
117565
- const path = join13(directory, entry.name);
117676
+ const path = join12(directory, entry.name);
117566
117677
  if (entry.isDirectory()) visitManaged(path);
117567
117678
  else if (entry.isFile()) paths.add(path);
117568
117679
  }
117569
117680
  };
117570
- visitManaged(join13(workspace.root, "Files", "Images"));
117571
- visitManaged(join13(workspace.root, "Files", "AudioClips"));
117681
+ visitManaged(join12(workspace.root, "Files", "Images"));
117682
+ visitManaged(join12(workspace.root, "Files", "AudioClips"));
117572
117683
  const hash = createHash9("sha256");
117573
117684
  for (const path of [...paths].sort()) {
117574
117685
  const name = relative4(workspace.root, path).split(sep4).join("/");
@@ -117713,7 +117824,7 @@ var init_push_hook = __esm({
117713
117824
  });
117714
117825
 
117715
117826
  // src/project-source/project-file-push.ts
117716
- import { basename as basename2, join as join14 } from "node:path";
117827
+ import { basename as basename2, join as join13 } from "node:path";
117717
117828
  import { readFileSync as readFileSync14 } from "node:fs";
117718
117829
  function ensureProjectFileBinaryChangesV4(args) {
117719
117830
  for (const binary of args.binaryChanges) {
@@ -117759,7 +117870,7 @@ function prepareProjectFilePushesV4(args) {
117759
117870
  `Project file ${recordId} has upload bytes but its source change has no record data.`
117760
117871
  );
117761
117872
  }
117762
- const absolute = join14(args.workspace.root, binary.path);
117873
+ const absolute = join13(args.workspace.root, binary.path);
117763
117874
  const bytes = new Uint8Array(readFileSync14(absolute));
117764
117875
  const digest = sha256Bytes(bytes);
117765
117876
  if (binary.localSha256 !== null && digest !== binary.localSha256) {
@@ -118243,7 +118354,7 @@ import {
118243
118354
  readFileSync as readFileSync15,
118244
118355
  statSync as statSync2
118245
118356
  } from "node:fs";
118246
- import { dirname as dirname9, join as join15, relative as relative5, sep as sep5 } from "node:path";
118357
+ import { dirname as dirname9, join as join14, relative as relative5, sep as sep5 } from "node:path";
118247
118358
  function assignPendingIds(changes, authoredValueSeeds, reconstructed3, localInitializerMaterialization) {
118248
118359
  const assigned = /* @__PURE__ */ new Map();
118249
118360
  const assign = (pendingId2) => {
@@ -119020,7 +119131,7 @@ async function runPush(workspace, options, preparationOverride) {
119020
119131
  );
119021
119132
  }
119022
119133
  if (options.noVerify !== true) {
119023
- const preparedBuildDir = configuredHook === void 0 ? void 0 : join15(
119134
+ const preparedBuildDir = configuredHook === void 0 ? void 0 : join14(
119024
119135
  workspace.root,
119025
119136
  ".neo",
119026
119137
  "test-build",
@@ -119037,7 +119148,7 @@ async function runPush(workspace, options, preparationOverride) {
119037
119148
  const documentJson = JSON.stringify(candidate.document);
119038
119149
  mkdirSync10(preparedBuildDir, { recursive: true });
119039
119150
  writeFileSync10(
119040
- join15(preparedBuildDir, "candidate.json"),
119151
+ join14(preparedBuildDir, "candidate.json"),
119041
119152
  `${JSON.stringify({
119042
119153
  version: 1,
119043
119154
  projectFingerprint: `sha256:${preparedLocal.source.sourceHash}`,
@@ -120296,7 +120407,7 @@ function authoredMemberFormsByFile(root, alternates) {
120296
120407
  for (const alternate of alternates) {
120297
120408
  let existing = contents.get(alternate.path);
120298
120409
  if (existing === void 0) {
120299
- const absolute = join15(root, alternate.path);
120410
+ const absolute = join14(root, alternate.path);
120300
120411
  existing = existsSync12(absolute) ? readFileSync15(absolute, "utf8") : null;
120301
120412
  contents.set(alternate.path, existing);
120302
120413
  }
@@ -120867,16 +120978,13 @@ function prepareCompleteNeoScriptBodyChanges(workspace, status, schema, options
120867
120978
  `Cannot include server-derived NeoScript refresh for member "${compiled.id}": its reconstructed working-copy row is missing.`
120868
120979
  );
120869
120980
  }
120870
- const cascadeChange = {
120871
- kind: "update",
120981
+ const cascadeChange = recordUpsertChange({
120982
+ base,
120872
120983
  recordKind: "member",
120873
120984
  recordId: compiled.id,
120874
120985
  file: reconstructed3.file,
120875
- nextData,
120876
- baseData: base.data,
120877
- baseContentHash: base.contentHash,
120878
- casBaseHash: base.conflictServerHash ?? base.contentHash
120879
- };
120986
+ nextData
120987
+ });
120880
120988
  status.changes.push(cascadeChange);
120881
120989
  changesById.set(compiled.id, cascadeChange);
120882
120990
  }
@@ -120945,16 +121053,13 @@ function prepareNSPropertySetterChanges(workspace, status) {
120945
121053
  `Cannot cascade removal of NeoScript property setter to override "${memberId}": its working-copy state is missing.`
120946
121054
  );
120947
121055
  }
120948
- const cascadeChange = {
120949
- kind: "update",
121056
+ const cascadeChange = recordUpsertChange({
121057
+ base,
120950
121058
  recordKind: "member",
120951
121059
  recordId: memberId,
120952
121060
  file: reconstructed3.file,
120953
- nextData: next,
120954
- baseData: base.data,
120955
- baseContentHash: base.contentHash,
120956
- casBaseHash: base.conflictServerHash ?? base.contentHash
120957
- };
121061
+ nextData: next
121062
+ });
120958
121063
  status.changes.push(cascadeChange);
120959
121064
  changesById.set(memberId, cascadeChange);
120960
121065
  }
@@ -121021,16 +121126,13 @@ function prepareNSFunctionBodyChanges(workspace, status) {
121021
121126
  `Cannot recompile NeoScript function override "${memberId}": its working-copy state is missing.`
121022
121127
  );
121023
121128
  }
121024
- const cascadeChange = {
121025
- kind: "update",
121129
+ const cascadeChange = recordUpsertChange({
121130
+ base,
121026
121131
  recordKind: "member",
121027
121132
  recordId: memberId,
121028
121133
  file: reconstructed3.file,
121029
- nextData: member,
121030
- baseData: base.data,
121031
- baseContentHash: base.contentHash,
121032
- casBaseHash: base.conflictServerHash ?? base.contentHash
121033
- };
121134
+ nextData: member
121135
+ });
121034
121136
  status.changes.push(cascadeChange);
121035
121137
  changesById.set(memberId, cascadeChange);
121036
121138
  }
@@ -121241,7 +121343,7 @@ var init_registry2 = __esm({
121241
121343
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
121242
121344
  formatVersion: 3,
121243
121345
  contractVersion: "3.15",
121244
- cliVersion: "0.38.5",
121346
+ cliVersion: "0.38.6",
121245
121347
  projectFileUploadBatchSize: 32,
121246
121348
  documentRecords: {
121247
121349
  member: {
@@ -122578,7 +122680,7 @@ import {
122578
122680
  basename as basename3,
122579
122681
  dirname as dirname10,
122580
122682
  isAbsolute as isAbsolute3,
122581
- join as join16,
122683
+ join as join15,
122582
122684
  relative as relative6,
122583
122685
  resolve as resolve4,
122584
122686
  sep as sep6
@@ -122785,7 +122887,7 @@ function preparedHookCandidate(workspace) {
122785
122887
  );
122786
122888
  }
122787
122889
  const parsed = JSON.parse(
122788
- readFileSync16(join16(directory, "candidate.json"), "utf8")
122890
+ readFileSync16(join15(directory, "candidate.json"), "utf8")
122789
122891
  );
122790
122892
  if (!isRecord10(parsed)) {
122791
122893
  throw new NeoTestPreparedCandidateError(
@@ -122842,7 +122944,7 @@ function fingerprintTestCandidateInputs(workspace) {
122842
122944
  }
122843
122945
  function testCandidateCachePath(workspace, inputFingerprint) {
122844
122946
  const cacheKey = createHash11("sha256").update(inputFingerprint).digest("hex");
122845
- return join16(
122947
+ return join15(
122846
122948
  workspace.root,
122847
122949
  ".neo",
122848
122950
  "test-build",
@@ -122867,7 +122969,7 @@ function cachedTestCandidate(workspace, inputFingerprint) {
122867
122969
  }
122868
122970
  if (typeof parsed.documentSha256 !== "string") return null;
122869
122971
  const documentJson = readFileSync16(
122870
- join16(dirname10(candidatePath), "document.json"),
122972
+ join15(dirname10(candidatePath), "document.json"),
122871
122973
  "utf8"
122872
122974
  );
122873
122975
  if (createHash11("sha256").update(documentJson).digest("hex") !== parsed.documentSha256) {
@@ -122888,7 +122990,7 @@ function cacheTestCandidate(workspace, inputFingerprint, candidate) {
122888
122990
  const documentJson = JSON.stringify(candidate.document);
122889
122991
  const documentSha256 = createHash11("sha256").update(documentJson).digest("hex");
122890
122992
  const candidatePath = testCandidateCachePath(workspace, inputFingerprint);
122891
- atomicWrite(join16(dirname10(candidatePath), "document.json"), documentJson);
122993
+ atomicWrite(join15(dirname10(candidatePath), "document.json"), documentJson);
122892
122994
  atomicWrite(
122893
122995
  candidatePath,
122894
122996
  JSON.stringify({
@@ -122908,7 +123010,7 @@ function compileSpec(workspace, document, absolutePath, projectCompilationHash2)
122908
123010
  const path = relative6(workspace.root, absolutePath).split(sep6).join("/");
122909
123011
  const source = readFileSync16(absolutePath, "utf8");
122910
123012
  const sourceHash = createHash11("sha256").update(source).digest("hex");
122911
- const artifactPath = join16(
123013
+ const artifactPath = join15(
122912
123014
  workspace.root,
122913
123015
  ".neo",
122914
123016
  "test-build",
@@ -123743,7 +123845,7 @@ function testBuildFiles(root) {
123743
123845
  const visit = (directory) => {
123744
123846
  for (const entry of readdirSync5(directory, { withFileTypes: true })) {
123745
123847
  if (entry.isSymbolicLink()) continue;
123746
- const path = join16(directory, entry.name);
123848
+ const path = join15(directory, entry.name);
123747
123849
  if (entry.isDirectory()) visit(path);
123748
123850
  else if (entry.isFile()) {
123749
123851
  const stats = statSync3(path);
@@ -123804,7 +123906,7 @@ function formatStatusCounts(passed, failed, total, skipped = 0) {
123804
123906
  async function runTest(workspace, options, dependencies = {}) {
123805
123907
  const started = Date.now();
123806
123908
  const performanceStarted = performance.now();
123807
- const testBuildRoot = join16(workspace.root, ".neo", "test-build");
123909
+ const testBuildRoot = join15(workspace.root, ".neo", "test-build");
123808
123910
  maintainNeoTestBuildCache(testBuildRoot);
123809
123911
  const startedAt = new Date(started).toISOString();
123810
123912
  let projectFingerprint = null;
@@ -123983,7 +124085,7 @@ async function runTest(workspace, options, dependencies = {}) {
123983
124085
  }))
123984
124086
  };
123985
124087
  atomicWrite(
123986
- join16(workspace.root, ".neo", "test-build", "v1", "manifest.json"),
124088
+ join15(workspace.root, ".neo", "test-build", "v1", "manifest.json"),
123987
124089
  `${JSON.stringify(buildManifest, null, 2)}
123988
124090
  `
123989
124091
  );
@@ -124072,7 +124174,7 @@ async function runTest(workspace, options, dependencies = {}) {
124072
124174
  if (options.outputFile !== null) {
124073
124175
  try {
124074
124176
  atomicWrite(
124075
- isAbsolute3(options.outputFile) ? options.outputFile : join16(workspace.root, options.outputFile),
124177
+ isAbsolute3(options.outputFile) ? options.outputFile : join15(workspace.root, options.outputFile),
124076
124178
  serialized
124077
124179
  );
124078
124180
  } catch (error) {
@@ -124286,7 +124388,7 @@ import {
124286
124388
  existsSync as existsSync14,
124287
124389
  readFileSync as readFileSync17
124288
124390
  } from "node:fs";
124289
- import { extname as extname2, isAbsolute as isAbsolute4, join as join17, relative as relative7, sep as sep7 } from "node:path";
124391
+ import { extname as extname2, isAbsolute as isAbsolute4, join as join16, relative as relative7, sep as sep7 } from "node:path";
124290
124392
  function inspectNeoDoctor(workspace) {
124291
124393
  const formatCompatible = workspace.config.formatVersion === CURRENT_FORMAT_VERSION;
124292
124394
  const compiler = inspectCompilerContract();
@@ -124402,7 +124504,7 @@ function inspectSourceContract(workspace) {
124402
124504
  }
124403
124505
  }
124404
124506
  function inspectExtensionContract(root) {
124405
- const cachePath = join17(root, PROJECT_SOURCE_ANALYSIS_V4_CACHE_PATH);
124507
+ const cachePath = join16(root, PROJECT_SOURCE_ANALYSIS_V4_CACHE_PATH);
124406
124508
  if (!existsSync14(cachePath)) {
124407
124509
  return {
124408
124510
  id: NEO_VSCODE_EXTENSION_ID,
@@ -124486,7 +124588,7 @@ function inspectTrackedBinary(root, record3, errors) {
124486
124588
  `Project file ${record3.recordId} has an invalid SHA-256 base digest.`
124487
124589
  );
124488
124590
  }
124489
- const absolute = join17(root, path);
124591
+ const absolute = join16(root, path);
124490
124592
  if (existsSync14(absolute) && !canAccess(absolute, fsConstants.R_OK)) {
124491
124593
  errors.push(`Tracked project file ${path} is not readable.`);
124492
124594
  }
@@ -124629,7 +124731,7 @@ __export(migrate_exports, {
124629
124731
  });
124630
124732
  import { randomUUID as randomUUID4 } from "node:crypto";
124631
124733
  import { existsSync as existsSync15, mkdirSync as mkdirSync12, readdirSync as readdirSync6, writeFileSync as writeFileSync12 } from "node:fs";
124632
- import { join as join18 } from "node:path";
124734
+ import { join as join17 } from "node:path";
124633
124735
  async function runMigrate(workspace, subcommand, positional, targetRef, json, dependencies = {}) {
124634
124736
  if (subcommand === "new") {
124635
124737
  const name = positional[0];
@@ -124638,7 +124740,7 @@ async function runMigrate(workspace, subcommand, positional, targetRef, json, de
124638
124740
  "Usage: neo migrate new <name> [--target <ClassName|project>]"
124639
124741
  );
124640
124742
  }
124641
- const migrationsDir = join18(workspace.root, "Migrations");
124743
+ const migrationsDir = join17(workspace.root, "Migrations");
124642
124744
  mkdirSync12(migrationsDir, { recursive: true });
124643
124745
  let nextOrder = 1;
124644
124746
  if (existsSync15(migrationsDir)) {
@@ -124650,7 +124752,7 @@ async function runMigrate(workspace, subcommand, positional, targetRef, json, de
124650
124752
  }
124651
124753
  }
124652
124754
  const relPath = migrationFileName(nextOrder, name);
124653
- const absolute = join18(workspace.root, relPath);
124755
+ const absolute = join17(workspace.root, relPath);
124654
124756
  if (existsSync15(absolute)) {
124655
124757
  throw new Error(`"${relPath}" already exists.`);
124656
124758
  }
@@ -127444,7 +127546,7 @@ __export(export_exports, {
127444
127546
  runExportUnity: () => runExportUnity
127445
127547
  });
127446
127548
  import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync13 } from "node:fs";
127447
- import { join as join19 } from "node:path";
127549
+ import { join as join18 } from "node:path";
127448
127550
  async function runExportUnity(workspace, outDir) {
127449
127551
  if (outDir === null) {
127450
127552
  throw new Error(
@@ -127456,23 +127558,23 @@ async function runExportUnity(workspace, outDir) {
127456
127558
  `/api/projects/${workspace.config.projectId}/export`,
127457
127559
  { versionId: workspace.config.versionId }
127458
127560
  );
127459
- const resourcesDir = join19(outDir, "Resources", "Neo");
127460
- const localizationDir = join19(resourcesDir, "Localization");
127461
- const scriptsDir = join19(outDir, "Scripts", "Neo");
127561
+ const resourcesDir = join18(outDir, "Resources", "Neo");
127562
+ const localizationDir = join18(resourcesDir, "Localization");
127563
+ const scriptsDir = join18(outDir, "Scripts", "Neo");
127462
127564
  mkdirSync13(localizationDir, { recursive: true });
127463
127565
  mkdirSync13(scriptsDir, { recursive: true });
127464
- writeFileSync13(join19(resourcesDir, "project.json"), response.projectJson);
127566
+ writeFileSync13(join18(resourcesDir, "project.json"), response.projectJson);
127465
127567
  writeFileSync13(
127466
- join19(scriptsDir, "NeoGeneratedTypes.cs"),
127568
+ join18(scriptsDir, "NeoGeneratedTypes.cs"),
127467
127569
  response.generatedTypes
127468
127570
  );
127469
127571
  for (const file of response.localizationFiles ?? []) {
127470
- writeFileSync13(join19(localizationDir, file.fileName), file.content);
127572
+ writeFileSync13(join18(localizationDir, file.fileName), file.content);
127471
127573
  }
127472
- console.log(`wrote ${join19(resourcesDir, "project.json")}`);
127473
- console.log(`wrote ${join19(scriptsDir, "NeoGeneratedTypes.cs")}`);
127574
+ console.log(`wrote ${join18(resourcesDir, "project.json")}`);
127575
+ console.log(`wrote ${join18(scriptsDir, "NeoGeneratedTypes.cs")}`);
127474
127576
  for (const file of response.localizationFiles ?? []) {
127475
- console.log(`wrote ${join19(localizationDir, file.fileName)}`);
127577
+ console.log(`wrote ${join18(localizationDir, file.fileName)}`);
127476
127578
  }
127477
127579
  const diagnostics = response.diagnostics ?? [];
127478
127580
  for (const diagnostic of diagnostics) {
@@ -127495,7 +127597,7 @@ __export(dev_exports, {
127495
127597
  runDev: () => runDev
127496
127598
  });
127497
127599
  import { watch } from "node:fs";
127498
- import { join as join20 } from "node:path";
127600
+ import { join as join19 } from "node:path";
127499
127601
  import { emitKeypressEvents } from "node:readline";
127500
127602
  import { ConvexClient } from "convex/browser";
127501
127603
  function isSchemaSignal(value) {
@@ -127605,7 +127707,7 @@ async function runDev(workspace, options) {
127605
127707
  };
127606
127708
  for (const dir of ["Classes", "Enums"]) {
127607
127709
  try {
127608
- watch(join20(workspace.root, dir), { persistent: true }, onFileChange);
127710
+ watch(join19(workspace.root, dir), { persistent: true }, onFileChange);
127609
127711
  } catch {
127610
127712
  }
127611
127713
  }
@@ -127661,7 +127763,7 @@ __export(resolve_exports, {
127661
127763
  workspaceFilePath: () => workspaceFilePath
127662
127764
  });
127663
127765
  import { readFileSync as readFileSync19, rmSync as rmSync8, writeFileSync as writeFileSync14 } from "node:fs";
127664
- import { join as join21 } from "node:path";
127766
+ import { join as join20 } from "node:path";
127665
127767
  function runResolve(workspace, side) {
127666
127768
  const resolvedRecords = adoptServerConflictBases(workspace);
127667
127769
  let resolvedFiles = 0;
@@ -127677,12 +127779,12 @@ function runResolve(workspace, side) {
127677
127779
  const binary = state.projectBinary;
127678
127780
  const conflict2 = binary?.conflict;
127679
127781
  if (binary === void 0 || conflict2 === void 0) continue;
127680
- const destination = join21(workspace.root, binary.path);
127782
+ const destination = join20(workspace.root, binary.path);
127681
127783
  if (side === "theirs") {
127682
127784
  if (conflict2.remoteSha256 !== null && conflict2.artifactPath !== void 0) {
127683
127785
  writeVerifiedBinaryDownloadV4(
127684
127786
  destination,
127685
- readFileSync19(join21(workspace.root, conflict2.artifactPath)),
127787
+ readFileSync19(join20(workspace.root, conflict2.artifactPath)),
127686
127788
  conflict2.remoteSha256
127687
127789
  );
127688
127790
  binary.sha256 = conflict2.remoteSha256;
@@ -127692,7 +127794,7 @@ function runResolve(workspace, side) {
127692
127794
  }
127693
127795
  }
127694
127796
  if (conflict2.artifactPath !== void 0) {
127695
- rmSync8(join21(workspace.root, conflict2.artifactPath), { force: true });
127797
+ rmSync8(join20(workspace.root, conflict2.artifactPath), { force: true });
127696
127798
  }
127697
127799
  delete binary.conflict;
127698
127800
  resolvedBinaries += 1;
@@ -127761,7 +127863,7 @@ function resolveMarkers(source, side) {
127761
127863
  return output.join("\n");
127762
127864
  }
127763
127865
  function workspaceFilePath(workspace, file) {
127764
- return join21(workspace.root, file);
127866
+ return join20(workspace.root, file);
127765
127867
  }
127766
127868
  var init_resolve = __esm({
127767
127869
  "src/commands/resolve.ts"() {
@@ -127966,7 +128068,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
127966
128068
  async function main() {
127967
128069
  const args = parseArgs(process.argv.slice(2));
127968
128070
  if (args.command === "--version") {
127969
- console.log("0.38.5");
128071
+ console.log("0.38.6");
127970
128072
  return;
127971
128073
  }
127972
128074
  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.38.5",
3
+ "version": "0.38.6",
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.38.5 -->
12
+ <!-- reviewed-through-cli: 0.38.6 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -83,7 +83,7 @@ wrappers.
83
83
  The marker near the top of `SKILL.md` must exactly match the package version:
84
84
 
85
85
  ```html
86
- <!-- reviewed-through-cli: 0.38.5 -->
86
+ <!-- reviewed-through-cli: 0.38.6 -->
87
87
  ```
88
88
 
89
89
  The quoted version above is checked too, so this instruction cannot go stale
@@ -94,6 +94,13 @@ rows its own file projection holds, so the plan would delete live server rows.
94
94
  Nothing is planned in that state; run `neo pull` and retry. The same message
95
95
  as a warning reports the inconsistency without blocking.
96
96
 
97
+ When pull conflicts with a server-side deletion, keeping the authored record
98
+ restores it as an explicit create guarded by an absent-record CAS check;
99
+ removing it from the resolved source accepts the deletion without sending a
100
+ write. Pull also warns immediately when a conflicted record's retained local
101
+ base strands newly downloaded descendant value rows. Resolve that conflict
102
+ with `neo resolve --mine` or `neo resolve --theirs` before pushing.
103
+
97
104
  For `base-hash-conflict`, pull and merge. For `version-bump-required`, inspect
98
105
  the classification and use `neo push --accept-bump` only when the bump is
99
106
  intended.