@neocompose/cli 0.36.4 → 0.36.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,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.36.6] - 2026-08-20
4
+
5
+ ### Fixed
6
+
7
+ - Keep normal pull's in-memory overlay of uncommitted static value seeds valid
8
+ for computed-initializer replay. This lets a pull reconcile source that
9
+ still has pending member identities after a successful remote commit without
10
+ requiring a destructive `neo pull --reset`.
11
+
12
+ ## [0.36.5] - 2026-08-20
13
+
14
+ ### Fixed
15
+
16
+ - Recover malformed persisted static members during pull without weakening
17
+ authored schema validation: orphaned rows are queued for deletion and
18
+ structurally owned rows are queued for an `isStatic` correction on the next
19
+ push. Diagnostics now identify the member id, name, and inferred owner kind.
20
+
3
21
  ## [0.36.4] - 2026-08-19
4
22
 
5
23
  ### 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) {
@@ -106393,25 +106486,25 @@ var init_project_file_pull = __esm({
106393
106486
  });
106394
106487
 
106395
106488
  // src/project-source/static-value-seed-records.ts
106396
- function staticValueSeedEmitRecords(status, present) {
106489
+ function staticValueSeedEmitRecords(status, present, projectId) {
106397
106490
  const records2 = /* @__PURE__ */ new Map();
106398
106491
  if (status === null) return records2;
106399
106492
  for (const [memberId, seed] of status.authoredValueSeeds) {
106400
106493
  const rootId = seed.valueId ?? memberValueIdFromRecord(status, memberId);
106401
106494
  if (rootId !== null) {
106402
- addSeedRecord(records2, present, {
106495
+ addSeedRecord(records2, present, projectId, {
106403
106496
  id: rootId,
106404
106497
  memberId,
106405
106498
  ...seed.init === void 0 ? { value: seed.value, classId: seed.classId } : { init: seed.init }
106406
106499
  });
106407
106500
  }
106408
106501
  for (const row of seed.values ?? []) {
106409
- addSeedRecord(records2, present, { ...row });
106502
+ addSeedRecord(records2, present, projectId, { ...row });
106410
106503
  }
106411
106504
  }
106412
106505
  return records2;
106413
106506
  }
106414
- function addSeedRecord(records2, present, data) {
106507
+ function addSeedRecord(records2, present, projectId, data) {
106415
106508
  const key = `value:${data.id}`;
106416
106509
  if (present.has(key)) return;
106417
106510
  if (records2.has(key)) return;
@@ -106421,7 +106514,18 @@ function addSeedRecord(records2, present, data) {
106421
106514
  // A seed has no committed content, so it has no server hash to carry.
106422
106515
  contentHash: "",
106423
106516
  deleted: false,
106424
- data
106517
+ // These records exist only in normal pull's in-memory local projection,
106518
+ // but the value emitter deliberately validates the entire graph before it
106519
+ // replays any computed initializer. Give the seed the same envelope as a
106520
+ // persisted value row so an unrelated replay does not reject a valid
106521
+ // locally-authored static initializer. The overlay is never written to
106522
+ // workspace state or sent to the server.
106523
+ data: {
106524
+ ...data,
106525
+ projectId,
106526
+ createdAt: 0,
106527
+ updatedAt: 0
106528
+ }
106425
106529
  });
106426
106530
  }
106427
106531
  function memberValueIdFromRecord(status, memberId) {
@@ -106690,6 +106794,9 @@ async function runResetPull(workspace) {
106690
106794
  const reset = resetWorkspaceToProjectSourcesV4(workspace, document, {
106691
106795
  regenerateSourceNames: true
106692
106796
  });
106797
+ for (const recovery of reset.staticMemberOwnershipRecoveries ?? []) {
106798
+ warn(staticMemberOwnershipRecoveryMessage(recovery));
106799
+ }
106693
106800
  for (const [key, state] of binaries.states) {
106694
106801
  const record3 = workspace.state.records[key];
106695
106802
  if (record3) record3.projectBinary = state;
@@ -106758,7 +106865,8 @@ async function finishFormat4Pull(args) {
106758
106865
  const plannedLocalRecords = buildEmitRecordSet(document, plans, "emit");
106759
106866
  for (const [key, record3] of staticValueSeedEmitRecords(
106760
106867
  localStatus,
106761
- new Set(plannedLocalRecords.keys())
106868
+ new Set(plannedLocalRecords.keys()),
106869
+ workspace.config.projectId
106762
106870
  )) {
106763
106871
  plannedLocalRecords.set(key, record3);
106764
106872
  }
@@ -106769,6 +106877,9 @@ async function finishFormat4Pull(args) {
106769
106877
  )
106770
106878
  );
106771
106879
  const localResult = emitProjectDocumentFilesV4(localRecords);
106880
+ for (const recovery of localResult.staticMemberOwnershipRecoveries ?? []) {
106881
+ warn(staticMemberOwnershipRecoveryMessage(recovery));
106882
+ }
106772
106883
  const serverResult = conflictCount === 0 ? null : emitProjectDocumentFilesV4(
106773
106884
  buildEmitRecordSet(document, plans, "server")
106774
106885
  );
@@ -107206,6 +107317,7 @@ var init_pull = __esm({
107206
107317
  init_project_file_pull();
107207
107318
  init_dialogue_sources();
107208
107319
  init_static_value_seed_records();
107320
+ init_static_member_ownership_recovery();
107209
107321
  }
107210
107322
  });
107211
107323
 
@@ -114040,7 +114152,7 @@ var init_registry2 = __esm({
114040
114152
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
114041
114153
  formatVersion: 3,
114042
114154
  contractVersion: "3.14",
114043
- cliVersion: "0.36.4",
114155
+ cliVersion: "0.36.6",
114044
114156
  projectFileUploadBatchSize: 32,
114045
114157
  documentRecords: {
114046
114158
  member: {
@@ -120642,7 +120754,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
120642
120754
  async function main() {
120643
120755
  const args = parseArgs(process.argv.slice(2));
120644
120756
  if (args.command === "--version") {
120645
- console.log("0.36.4");
120757
+ console.log("0.36.6");
120646
120758
  return;
120647
120759
  }
120648
120760
  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.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.36.4 -->
12
+ <!-- reviewed-through-cli: 0.36.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.36.4 -->
86
+ <!-- reviewed-through-cli: 0.36.6 -->
87
87
  ```
88
88
 
89
89
  The quoted version above is checked too, so this instruction cannot go stale