@neocompose/cli 0.36.4 → 0.36.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.36.5] - 2026-08-20
4
+
5
+ ### Fixed
6
+
7
+ - Recover malformed persisted static members during pull without weakening
8
+ authored schema validation: orphaned rows are queued for deletion and
9
+ structurally owned rows are queued for an `isStatic` correction on the next
10
+ push. Diagnostics now identify the member id, name, and inferred owner kind.
11
+
3
12
  ## [0.36.4] - 2026-08-19
4
13
 
5
14
  ### Fixed
package/dist/neo.mjs CHANGED
@@ -39618,9 +39618,11 @@ function assertStaticMemberInvariants(manifest) {
39618
39618
  const path = `$.members[${memberIndex}]`;
39619
39619
  const owner = requireRecord(member.owner, `${path}.owner`);
39620
39620
  if (owner.kind !== "classMember") {
39621
+ const memberName = typeof member.name === "string" ? member.name : "(unnamed)";
39622
+ const ownerKind = typeof owner.kind === "string" ? owner.kind : "unknown";
39621
39623
  invalid(
39622
39624
  `${path}.owner`,
39623
- "a static member must have exactly one direct Class owner."
39625
+ `static member ${JSON.stringify(memberName)} (${memberId}) has owner kind ${JSON.stringify(ownerKind)}; expected exactly one direct Class owner.`
39624
39626
  );
39625
39627
  }
39626
39628
  const ownerClassId = nonEmptyString(owner.classId, `${path}.owner.classId`);
@@ -41593,6 +41595,18 @@ function documentsToProjectSchemaManifest(records2, optionsOrContext = {}) {
41593
41595
  records2,
41594
41596
  options.sourceContext ?? DEFAULT_DOCUMENT_TO_MANIFEST_CONTEXT
41595
41597
  );
41598
+ let projectionContext = context;
41599
+ let recoveredMemberRecords;
41600
+ if (options.onInvalidStaticMemberOwnership !== void 0) {
41601
+ const normalizedMembers = new Map(context.normalizedMembers);
41602
+ recoveredMemberRecords = recoverInvalidStaticMemberOwnership(
41603
+ context.recordsByKind.get("member") ?? [],
41604
+ context,
41605
+ normalizedMembers,
41606
+ options.onInvalidStaticMemberOwnership
41607
+ );
41608
+ projectionContext = { ...context, normalizedMembers };
41609
+ }
41596
41610
  const collections = {
41597
41611
  classes: [],
41598
41612
  constructors: [],
@@ -41608,7 +41622,8 @@ function documentsToProjectSchemaManifest(records2, optionsOrContext = {}) {
41608
41622
  internalRecordRelations: []
41609
41623
  };
41610
41624
  for (const adapter of SCHEMA_RECORD_ADAPTERS) {
41611
- const adapterRecords = context.recordsByKind.get(adapter.recordKind) ?? [];
41625
+ const rawAdapterRecords = context.recordsByKind.get(adapter.recordKind) ?? [];
41626
+ const adapterRecords = adapter.recordKind === "member" && recoveredMemberRecords !== void 0 ? recoveredMemberRecords : rawAdapterRecords;
41612
41627
  if (adapter.cardinality === "one") {
41613
41628
  if (adapterRecords.length !== 1) {
41614
41629
  throw new Error(
@@ -41617,12 +41632,12 @@ function documentsToProjectSchemaManifest(records2, optionsOrContext = {}) {
41617
41632
  }
41618
41633
  collections[adapter.manifestCollection] = adapter.fromDocument(
41619
41634
  adapterRecords[0],
41620
- context
41635
+ projectionContext
41621
41636
  );
41622
41637
  continue;
41623
41638
  }
41624
41639
  collections[adapter.manifestCollection] = adapterRecords.map(
41625
- (record3) => adapter.fromDocument(record3, context)
41640
+ (record3) => adapter.fromDocument(record3, projectionContext)
41626
41641
  );
41627
41642
  }
41628
41643
  return parseProjectSchemaManifest({
@@ -41632,6 +41647,50 @@ function documentsToProjectSchemaManifest(records2, optionsOrContext = {}) {
41632
41647
  ...collections
41633
41648
  });
41634
41649
  }
41650
+ function recoverInvalidStaticMemberOwnership(records2, context, normalizedMembers, onRecovery) {
41651
+ const recovered = [];
41652
+ for (const record3 of records2) {
41653
+ const member = context.normalizedMembers.get(record3.recordId);
41654
+ if (member?.isStatic !== true) {
41655
+ recovered.push(record3);
41656
+ continue;
41657
+ }
41658
+ const owner = context.memberOwners.get(record3.recordId) ?? {
41659
+ kind: "loose"
41660
+ };
41661
+ if (owner.kind === "classMember") {
41662
+ recovered.push(record3);
41663
+ continue;
41664
+ }
41665
+ const ownerKind = typeof owner.kind === "string" ? owner.kind : "unknown";
41666
+ const memberName = typeof member.name === "string" ? member.name : record3.recordId;
41667
+ if (ownerKind === "loose") {
41668
+ normalizedMembers.delete(record3.recordId);
41669
+ onRecovery({
41670
+ memberId: record3.recordId,
41671
+ memberName,
41672
+ ownerKind,
41673
+ action: "delete"
41674
+ });
41675
+ continue;
41676
+ }
41677
+ onRecovery({
41678
+ memberId: record3.recordId,
41679
+ memberName,
41680
+ ownerKind,
41681
+ action: "demote"
41682
+ });
41683
+ normalizedMembers.set(record3.recordId, {
41684
+ ...member,
41685
+ isStatic: false
41686
+ });
41687
+ recovered.push({
41688
+ ...record3,
41689
+ data: { ...record3.data, isStatic: false }
41690
+ });
41691
+ }
41692
+ return recovered;
41693
+ }
41635
41694
  function projectSchemaManifestToDocuments(manifestInput, context) {
41636
41695
  const manifest = parseProjectSchemaManifest(manifestInput);
41637
41696
  const emissionContext = {
@@ -80398,6 +80457,19 @@ var init_root_source = __esm({
80398
80457
  }
80399
80458
  });
80400
80459
 
80460
+ // src/project-source/static-member-ownership-recovery.ts
80461
+ function staticMemberOwnershipRecoveryMessage(recovery) {
80462
+ const identity2 = `${JSON.stringify(recovery.memberName)} (${recovery.memberId})`;
80463
+ return recovery.action === "delete" ? `Recovered malformed static member ${identity2}: it has no Class declaration, so no source was emitted and the next push will delete the orphaned record.` : `Recovered malformed static member ${identity2}: it is owned as ${recovery.ownerKind}, so source projected it as instance metadata and the next push will correct isStatic.`;
80464
+ }
80465
+ var STATIC_MEMBER_OWNERSHIP_RECOVERY_SOURCE;
80466
+ var init_static_member_ownership_recovery = __esm({
80467
+ "src/project-source/static-member-ownership-recovery.ts"() {
80468
+ "use strict";
80469
+ STATIC_MEMBER_OWNERSHIP_RECOVERY_SOURCE = "<project>";
80470
+ }
80471
+ });
80472
+
80401
80473
  // ../src/models/animation/animation-clips.ts
80402
80474
  function isWorldAnimationStructuralMember(memberId, members) {
80403
80475
  const membersById = new Map(members.map((member) => [member.id, member]));
@@ -95029,7 +95101,13 @@ function computeWorkspaceStatus(workspace, options) {
95029
95101
  };
95030
95102
  }
95031
95103
  const baseDocuments = schemaBaseDocuments(workspace);
95032
- const baseManifest = baseDocuments.length === 0 ? void 0 : documentsToProjectSchemaManifest(baseDocuments);
95104
+ const staticMemberOwnershipRecoveries = [];
95105
+ const baseManifest = baseDocuments.length === 0 ? void 0 : documentsToProjectSchemaManifest(baseDocuments, {
95106
+ onInvalidStaticMemberOwnership: (recovery) => staticMemberOwnershipRecoveries.push(recovery)
95107
+ });
95108
+ const orphanedStaticMemberKeys = new Set(
95109
+ staticMemberOwnershipRecoveries.filter((recovery) => recovery.action === "delete").map((recovery) => recordStateKey("member", recovery.memberId))
95110
+ );
95033
95111
  let manifest;
95034
95112
  let authoredValueSeeds = /* @__PURE__ */ new Map();
95035
95113
  let staticMemberValueIds = /* @__PURE__ */ new Map();
@@ -95455,11 +95533,12 @@ function computeWorkspaceStatus(workspace, options) {
95455
95533
  }
95456
95534
  const deletedValueIds = /* @__PURE__ */ new Set();
95457
95535
  for (const [key, recordState] of Object.entries(workspace.state.records)) {
95458
- if (recordState.file === void 0) continue;
95536
+ const recordFile = recordState.file ?? (orphanedStaticMemberKeys.has(key) ? STATIC_MEMBER_OWNERSHIP_RECOVERY_SOURCE : void 0);
95537
+ if (recordFile === void 0) continue;
95459
95538
  if (reconstructed3.has(key)) continue;
95460
95539
  const fileStillBroken = parseErrors.some(
95461
- (error) => error.file === recordState.file && isBlockingSchemaSourceError(error)
95462
- ) || conflictedFiles.includes(recordState.file);
95540
+ (error) => error.file === recordFile && isBlockingSchemaSourceError(error)
95541
+ ) || conflictedFiles.includes(recordFile);
95463
95542
  if (fileStillBroken) continue;
95464
95543
  if (recordState.recordKind === "value") {
95465
95544
  deletedValueIds.add(recordState.recordId);
@@ -95468,7 +95547,7 @@ function computeWorkspaceStatus(workspace, options) {
95468
95547
  kind: "delete",
95469
95548
  recordKind: recordState.recordKind,
95470
95549
  recordId: recordState.recordId,
95471
- file: recordState.file,
95550
+ file: recordFile,
95472
95551
  baseData: recordState.data,
95473
95552
  baseContentHash: recordState.contentHash,
95474
95553
  casBaseHash: recordState.conflictServerHash !== void 0 ? recordState.conflictServerHash : recordState.contentHash
@@ -96274,6 +96353,7 @@ var init_workspace_status_core = __esm({
96274
96353
  init_value_sources();
96275
96354
  init_lower_variants();
96276
96355
  init_root_source();
96356
+ init_static_member_ownership_recovery();
96277
96357
  init_root_value_paths();
96278
96358
  init_project_documents();
96279
96359
  init_animation_clips();
@@ -104152,7 +104232,10 @@ function emitProjectDocumentFilesV4(records2) {
104152
104232
  data: record3.data
104153
104233
  });
104154
104234
  }
104155
- const manifest = documentsToProjectSchemaManifest(schemaRecords);
104235
+ const staticMemberOwnershipRecoveries = [];
104236
+ const manifest = documentsToProjectSchemaManifest(schemaRecords, {
104237
+ onInvalidStaticMemberOwnership: (recovery) => staticMemberOwnershipRecoveries.push(recovery)
104238
+ });
104156
104239
  const staticValues = emitStaticValueSourcesV4(records2, manifest);
104157
104240
  const memberDefaults = emitMemberDefaultSourcesV4(records2, manifest);
104158
104241
  const variantValues = emitVariantValueSourcesV4(records2, manifest);
@@ -104225,6 +104308,13 @@ function emitProjectDocumentFilesV4(records2) {
104225
104308
  ]) {
104226
104309
  for (const key of file.recordKeys) recordFiles.set(key, file.path);
104227
104310
  }
104311
+ for (const recovery of staticMemberOwnershipRecoveries) {
104312
+ if (recovery.action !== "delete") continue;
104313
+ recordFiles.set(
104314
+ `member:${recovery.memberId}`,
104315
+ STATIC_MEMBER_OWNERSHIP_RECOVERY_SOURCE
104316
+ );
104317
+ }
104228
104318
  const analysis = compileNeoProjectSources(
104229
104319
  files.map((file) => {
104230
104320
  const kind = neoProjectSourceKind(file.path);
@@ -104252,7 +104342,8 @@ ${errors.map(
104252
104342
  recordFiles,
104253
104343
  analysis,
104254
104344
  materializedConstructors,
104255
- formAlternates: source.formAlternates
104345
+ formAlternates: source.formAlternates,
104346
+ staticMemberOwnershipRecoveries
104256
104347
  };
104257
104348
  }
104258
104349
  function relationEndpointExpressions(records2) {
@@ -104305,6 +104396,7 @@ var init_project_documents = __esm({
104305
104396
  init_root_value_paths();
104306
104397
  init_source_diagnostics();
104307
104398
  init_source_format();
104399
+ init_static_member_ownership_recovery();
104308
104400
  PROJECT_SOURCE_ANALYSIS_V4_CACHE_PATH = ".neo/project-source-analysis-v4.json";
104309
104401
  SCHEMA_RECORD_KINDS2 = /* @__PURE__ */ new Set([
104310
104402
  "class",
@@ -105968,7 +106060,8 @@ function resetWorkspaceToProjectSourcesV4(workspace, document, options = {}) {
105968
106060
  return {
105969
106061
  written: emitted.files.length,
105970
106062
  removed,
105971
- fileCount: emitted.files.length
106063
+ fileCount: emitted.files.length,
106064
+ staticMemberOwnershipRecoveries: emitted.staticMemberOwnershipRecoveries
105972
106065
  };
105973
106066
  }
105974
106067
  function preserveManagedSpecs(root) {
@@ -106690,6 +106783,9 @@ async function runResetPull(workspace) {
106690
106783
  const reset = resetWorkspaceToProjectSourcesV4(workspace, document, {
106691
106784
  regenerateSourceNames: true
106692
106785
  });
106786
+ for (const recovery of reset.staticMemberOwnershipRecoveries ?? []) {
106787
+ warn(staticMemberOwnershipRecoveryMessage(recovery));
106788
+ }
106693
106789
  for (const [key, state] of binaries.states) {
106694
106790
  const record3 = workspace.state.records[key];
106695
106791
  if (record3) record3.projectBinary = state;
@@ -106769,6 +106865,9 @@ async function finishFormat4Pull(args) {
106769
106865
  )
106770
106866
  );
106771
106867
  const localResult = emitProjectDocumentFilesV4(localRecords);
106868
+ for (const recovery of localResult.staticMemberOwnershipRecoveries ?? []) {
106869
+ warn(staticMemberOwnershipRecoveryMessage(recovery));
106870
+ }
106772
106871
  const serverResult = conflictCount === 0 ? null : emitProjectDocumentFilesV4(
106773
106872
  buildEmitRecordSet(document, plans, "server")
106774
106873
  );
@@ -107206,6 +107305,7 @@ var init_pull = __esm({
107206
107305
  init_project_file_pull();
107207
107306
  init_dialogue_sources();
107208
107307
  init_static_value_seed_records();
107308
+ init_static_member_ownership_recovery();
107209
107309
  }
107210
107310
  });
107211
107311
 
@@ -114040,7 +114140,7 @@ var init_registry2 = __esm({
114040
114140
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
114041
114141
  formatVersion: 3,
114042
114142
  contractVersion: "3.14",
114043
- cliVersion: "0.36.4",
114143
+ cliVersion: "0.36.5",
114044
114144
  projectFileUploadBatchSize: 32,
114045
114145
  documentRecords: {
114046
114146
  member: {
@@ -120642,7 +120742,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
120642
120742
  async function main() {
120643
120743
  const args = parseArgs(process.argv.slice(2));
120644
120744
  if (args.command === "--version") {
120645
- console.log("0.36.4");
120745
+ console.log("0.36.5");
120646
120746
  return;
120647
120747
  }
120648
120748
  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.36.4",
3
+ "version": "0.36.5",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,7 @@ description: >-
9
9
  `@neocompose/cli` or `node cli/bin/neo.mjs` in the neo-compose repository.
10
10
  ---
11
11
 
12
- <!-- reviewed-through-cli: 0.36.4 -->
12
+ <!-- reviewed-through-cli: 0.36.5 -->
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.36.4 -->
86
+ <!-- reviewed-through-cli: 0.36.5 -->
87
87
  ```
88
88
 
89
89
  The quoted version above is checked too, so this instruction cannot go stale