@neocompose/cli 0.36.6 → 0.36.8

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,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.36.8] - 2026-08-20
4
+
5
+ ### Fixed
6
+
7
+ - Ignore server-compiled initializer IR when deciding whether both sides of a
8
+ pull changed authored value source, preventing compiler-only refreshes from
9
+ producing false conflicts.
10
+ - Let `neo resolve --mine` and `neo resolve --theirs` clear metadata-only
11
+ record conflicts retained by older pulls even when no textual marker was
12
+ written.
13
+
14
+ ## [0.36.7] - 2026-08-20
15
+
16
+ ### Fixed
17
+
18
+ - Keep braces and parentheses written inside string literals from changing the
19
+ extracted block getter/setter boundary. Project compilation, CLI checks, and
20
+ editor diagnostics now compile the complete authored accessor body.
21
+
3
22
  ## [0.36.6] - 2026-08-20
4
23
 
5
24
  ### Fixed
package/dist/neo.mjs CHANGED
@@ -25236,29 +25236,31 @@ function findProjectSourceAccessorBody(source, keyword, containingRange) {
25236
25236
  const body = source.slice(bodyStart, bodyEnd);
25237
25237
  const local = new SourceText(body);
25238
25238
  const tokens = lex2(body);
25239
- if (tokens[0]?.text === "=>") {
25239
+ if (isToken(tokens[0], "op", "=>")) {
25240
25240
  if (keyword !== "get") return null;
25241
25241
  return expressionAccessor(source, full, local, tokens, bodyStart, 1);
25242
25242
  }
25243
25243
  let depth = 0;
25244
25244
  for (let index = 0; index < tokens.length; index++) {
25245
25245
  const token = tokens[index];
25246
- if (token.text === "{") {
25246
+ if (isToken(token, "punct", "{")) {
25247
25247
  depth++;
25248
25248
  continue;
25249
25249
  }
25250
- if (token.text === "}") {
25250
+ if (isToken(token, "punct", "}")) {
25251
25251
  depth--;
25252
25252
  continue;
25253
25253
  }
25254
- if (depth !== 1 || token.text !== keyword) continue;
25254
+ if (depth !== 1 || token.kind !== "ident" && token.kind !== "keyword" || token.text !== keyword) {
25255
+ continue;
25256
+ }
25255
25257
  let openIndex = index + 1;
25256
- if (tokens[openIndex]?.text === "(") {
25258
+ if (isToken(tokens[openIndex], "punct", "(")) {
25257
25259
  let parameterDepth = 1;
25258
25260
  for (let cursor = openIndex + 1; cursor < tokens.length; cursor++) {
25259
25261
  const candidate = tokens[cursor];
25260
- if (candidate.text === "(") parameterDepth++;
25261
- if (candidate.text === ")") parameterDepth--;
25262
+ if (isToken(candidate, "punct", "(")) parameterDepth++;
25263
+ if (isToken(candidate, "punct", ")")) parameterDepth--;
25262
25264
  if (parameterDepth !== 0) continue;
25263
25265
  openIndex = cursor + 1;
25264
25266
  break;
@@ -25266,7 +25268,7 @@ function findProjectSourceAccessorBody(source, keyword, containingRange) {
25266
25268
  if (parameterDepth !== 0) return null;
25267
25269
  }
25268
25270
  const open = tokens[openIndex];
25269
- if (open?.text === "=>") {
25271
+ if (isToken(open, "op", "=>")) {
25270
25272
  return expressionAccessor(
25271
25273
  source,
25272
25274
  full,
@@ -25276,12 +25278,12 @@ function findProjectSourceAccessorBody(source, keyword, containingRange) {
25276
25278
  openIndex + 1
25277
25279
  );
25278
25280
  }
25279
- if (open?.text !== "{") return null;
25281
+ if (open === void 0 || !isToken(open, "punct", "{")) return null;
25280
25282
  let accessorDepth = 1;
25281
25283
  for (let cursor = openIndex + 1; cursor < tokens.length; cursor++) {
25282
25284
  const candidate = tokens[cursor];
25283
- if (candidate.text === "{") accessorDepth++;
25284
- if (candidate.text === "}") accessorDepth--;
25285
+ if (isToken(candidate, "punct", "{")) accessorDepth++;
25286
+ if (isToken(candidate, "punct", "}")) accessorDepth--;
25285
25287
  if (accessorDepth !== 0) continue;
25286
25288
  const start = bodyStart + sourceOffset2(local, open.pos) + 1;
25287
25289
  const end = bodyStart + sourceOffset2(local, candidate.pos);
@@ -25295,6 +25297,9 @@ function findProjectSourceAccessorBody(source, keyword, containingRange) {
25295
25297
  }
25296
25298
  return null;
25297
25299
  }
25300
+ function isToken(token, kind, text) {
25301
+ return token?.kind === kind && token.text === text;
25302
+ }
25298
25303
  function expressionAccessor(source, full, local, tokens, bodyStart, firstIndex) {
25299
25304
  const first = tokens[firstIndex];
25300
25305
  if (!first) return null;
@@ -76384,6 +76389,23 @@ var init_workspace = __esm({
76384
76389
  }
76385
76390
  });
76386
76391
 
76392
+ // src/project-sync/value-record-comparison.ts
76393
+ function valueRecordComparisonBody(value) {
76394
+ if (!isObjectRecord2(value)) return value;
76395
+ const init = value.init;
76396
+ const body = value.value;
76397
+ const normalizedInit = isObjectRecord2(init) && init.compiled !== void 0 ? { ...init, compiled: void 0 } : init;
76398
+ const normalizedBody = isObjectRecord2(body) && typeof body.code === "string" && body.action !== void 0 ? { ...body, action: void 0 } : body;
76399
+ if (normalizedInit === init && normalizedBody === body) return value;
76400
+ return { ...value, init: normalizedInit, value: normalizedBody };
76401
+ }
76402
+ var init_value_record_comparison = __esm({
76403
+ "src/project-sync/value-record-comparison.ts"() {
76404
+ "use strict";
76405
+ init_projection();
76406
+ }
76407
+ });
76408
+
76387
76409
  // src/project-source/lower-configuration.ts
76388
76410
  function lowerConfiguration(base, analysis) {
76389
76411
  const globals = analysis.configuration.globals;
@@ -96303,15 +96325,6 @@ function recordsSemanticallyEqual(recordKind, left, right) {
96303
96325
  }
96304
96326
  return canonicallyEqual(left, right);
96305
96327
  }
96306
- function valueRecordComparisonBody(value) {
96307
- if (!isObjectRecord2(value)) return value;
96308
- const init = value.init;
96309
- const body = value.value;
96310
- const normalizedInit = isObjectRecord2(init) && init.compiled !== void 0 ? { ...init, compiled: void 0 } : init;
96311
- const normalizedBody = isObjectRecord2(body) && typeof body.code === "string" && body.action !== void 0 ? { ...body, action: void 0 } : body;
96312
- if (normalizedInit === init && normalizedBody === body) return value;
96313
- return { ...value, init: normalizedInit, value: normalizedBody };
96314
- }
96315
96328
  function describeChange(change) {
96316
96329
  const name = isObjectRecord2(change.nextData) && typeof change.nextData.name === "string" ? change.nextData.name : isObjectRecord2(change.baseData) && typeof change.baseData.name === "string" ? change.baseData.name : change.recordId;
96317
96330
  const location3 = change.file === null ? "" : ` (${change.file})`;
@@ -96342,6 +96355,7 @@ var init_workspace_status_core = __esm({
96342
96355
  init_source_diagnostics();
96343
96356
  init_migration_files();
96344
96357
  init_projection();
96358
+ init_value_record_comparison();
96345
96359
  init_project_manifest();
96346
96360
  init_src();
96347
96361
  init_lower_schema();
@@ -105992,6 +106006,104 @@ var init_conflict_markers = __esm({
105992
106006
  }
105993
106007
  });
105994
106008
 
106009
+ // src/project-sync/source-record-comparison.ts
106010
+ function sourceAuthoredRecordsSemanticallyEqual(recordKind, left, right, mainLocale) {
106011
+ if (isSchemaRecordKindV4(recordKind)) {
106012
+ return schemaDocumentAuthoredSemanticallyEqual(recordKind, left, right);
106013
+ }
106014
+ return canonicallyEqual(
106015
+ sourceComparableRecord(recordKind, left, mainLocale),
106016
+ sourceComparableRecord(recordKind, right, mainLocale)
106017
+ );
106018
+ }
106019
+ function sourceComparableObjectRecord(recordKind, value, mainLocale) {
106020
+ const comparable = sourceComparableRecord(recordKind, value, mainLocale);
106021
+ if (!isObjectRecord2(comparable)) {
106022
+ throw new Error(
106023
+ `Cannot merge ${recordKind} record because its source-comparable payload is not an object.`
106024
+ );
106025
+ }
106026
+ return comparable;
106027
+ }
106028
+ function workspaceMainLocale(records2) {
106029
+ const config = Object.values(records2).find(
106030
+ (record3) => record3.recordKind === "localization-config"
106031
+ );
106032
+ const data = config?.data;
106033
+ return isObjectRecord2(data) && typeof data.mainLocale === "string" ? data.mainLocale : "en-US";
106034
+ }
106035
+ function acceptSourceEquivalentConflictBases(workspace, mainLocale = workspaceMainLocale(workspace.state.records)) {
106036
+ let accepted = 0;
106037
+ for (const [key, record3] of Object.entries(workspace.state.records)) {
106038
+ if (typeof record3.conflictServerHash !== "string") continue;
106039
+ if (!sourceAuthoredRecordsSemanticallyEqual(
106040
+ record3.recordKind,
106041
+ record3.data,
106042
+ record3.conflictServerData,
106043
+ mainLocale
106044
+ )) {
106045
+ continue;
106046
+ }
106047
+ const next = {
106048
+ ...record3,
106049
+ contentHash: record3.conflictServerHash,
106050
+ data: record3.conflictServerData
106051
+ };
106052
+ delete next.conflictServerHash;
106053
+ delete next.conflictServerData;
106054
+ workspace.state.records[key] = next;
106055
+ accepted += 1;
106056
+ }
106057
+ return accepted;
106058
+ }
106059
+ function sourceComparableRecord(recordKind, value, mainLocale) {
106060
+ return sourceComparableValue(
106061
+ recordKind,
106062
+ recordKind === "value" ? valueRecordComparisonBody(value) : value,
106063
+ mainLocale
106064
+ );
106065
+ }
106066
+ function sourceComparableValue(recordKind, value, mainLocale) {
106067
+ if (Array.isArray(value)) {
106068
+ return value.map(
106069
+ (entry) => sourceComparableValue(recordKind, entry, mainLocale)
106070
+ );
106071
+ }
106072
+ if (!isObjectRecord2(value)) return value;
106073
+ const result = {};
106074
+ const hasAuthoredCode = typeof value.code === "string" || typeof value.setterCode === "string";
106075
+ for (const [key, entry] of Object.entries(value)) {
106076
+ if (key === "projectId" || key === "createdAt" || key === "updatedAt") {
106077
+ continue;
106078
+ }
106079
+ if (recordKind === "dialogue-node" && key === "layout") continue;
106080
+ if (hasAuthoredCode && (key === "getter" || key === "setter" || key === "action")) {
106081
+ continue;
106082
+ }
106083
+ if (recordKind === "localized-text" && key === "localeValues" && isObjectRecord2(entry)) {
106084
+ result[key] = Object.hasOwn(entry, mainLocale) ? {
106085
+ [mainLocale]: sourceComparableValue(
106086
+ recordKind,
106087
+ entry[mainLocale],
106088
+ mainLocale
106089
+ )
106090
+ } : {};
106091
+ continue;
106092
+ }
106093
+ result[key] = sourceComparableValue(recordKind, entry, mainLocale);
106094
+ }
106095
+ return result;
106096
+ }
106097
+ var init_source_record_comparison = __esm({
106098
+ "src/project-sync/source-record-comparison.ts"() {
106099
+ "use strict";
106100
+ init_project_manifest();
106101
+ init_project_documents();
106102
+ init_projection();
106103
+ init_value_record_comparison();
106104
+ }
106105
+ });
106106
+
105995
106107
  // src/project-source/reset.ts
105996
106108
  import {
105997
106109
  existsSync as existsSync5,
@@ -107157,77 +107269,6 @@ function projectSourcePathsRequiringRewriteV4(args) {
107157
107269
  }
107158
107270
  return result;
107159
107271
  }
107160
- function sourceAuthoredRecordsSemanticallyEqual(recordKind, left, right, mainLocale) {
107161
- if (isSchemaRecordKindV4(recordKind)) {
107162
- return schemaDocumentAuthoredSemanticallyEqual(recordKind, left, right);
107163
- }
107164
- return canonicallyEqual(
107165
- sourceComparableRecord(recordKind, left, mainLocale),
107166
- sourceComparableRecord(recordKind, right, mainLocale)
107167
- );
107168
- }
107169
- function sourceComparableRecord(recordKind, value, mainLocale) {
107170
- if (Array.isArray(value)) {
107171
- return value.map(
107172
- (entry) => sourceComparableRecord(recordKind, entry, mainLocale)
107173
- );
107174
- }
107175
- if (!isObjectRecord2(value)) return value;
107176
- const result = {};
107177
- const hasAuthoredCode = typeof value.code === "string" || typeof value.setterCode === "string";
107178
- for (const [key, entry] of Object.entries(value)) {
107179
- if (key === "projectId" || key === "createdAt" || key === "updatedAt") {
107180
- continue;
107181
- }
107182
- if (recordKind === "dialogue-node" && key === "layout") continue;
107183
- if (hasAuthoredCode && (key === "getter" || key === "setter" || key === "action")) {
107184
- continue;
107185
- }
107186
- if (recordKind === "localized-text" && key === "localeValues" && isObjectRecord2(entry)) {
107187
- result[key] = Object.hasOwn(entry, mainLocale) ? {
107188
- [mainLocale]: sourceComparableRecord(
107189
- recordKind,
107190
- entry[mainLocale],
107191
- mainLocale
107192
- )
107193
- } : {};
107194
- continue;
107195
- }
107196
- result[key] = sourceComparableRecord(recordKind, entry, mainLocale);
107197
- }
107198
- return result;
107199
- }
107200
- function acceptSourceEquivalentConflictBases(workspace, mainLocale) {
107201
- let accepted = 0;
107202
- for (const [key, record3] of Object.entries(workspace.state.records)) {
107203
- if (typeof record3.conflictServerHash !== "string") continue;
107204
- if (!sourceAuthoredRecordsSemanticallyEqual(
107205
- record3.recordKind,
107206
- record3.data,
107207
- record3.conflictServerData,
107208
- mainLocale
107209
- )) {
107210
- continue;
107211
- }
107212
- const next = {
107213
- ...record3,
107214
- contentHash: record3.conflictServerHash,
107215
- data: record3.conflictServerData
107216
- };
107217
- delete next.conflictServerHash;
107218
- delete next.conflictServerData;
107219
- workspace.state.records[key] = next;
107220
- accepted += 1;
107221
- }
107222
- return accepted;
107223
- }
107224
- function workspaceMainLocale(records2) {
107225
- const config = Object.values(records2).find(
107226
- (record3) => record3.recordKind === "localization-config"
107227
- );
107228
- const data = config?.data;
107229
- return isObjectRecord2(data) && typeof data.mainLocale === "string" ? data.mainLocale : "en-US";
107230
- }
107231
107272
  function mergeProjectDocumentRecord(recordKind, base, server, local, mainLocale) {
107232
107273
  if (isSchemaRecordKindV4(recordKind)) {
107233
107274
  return mergeSchemaDocumentRecord(recordKind, base, server, local);
@@ -107241,15 +107282,6 @@ function mergeProjectDocumentRecord(recordKind, base, server, local, mainLocale)
107241
107282
  }
107242
107283
  });
107243
107284
  }
107244
- function sourceComparableObjectRecord(recordKind, value, mainLocale) {
107245
- const comparable = sourceComparableRecord(recordKind, value, mainLocale);
107246
- if (!isObjectRecord2(comparable)) {
107247
- throw new Error(
107248
- `Cannot merge ${recordKind} record because its source-comparable payload is not an object.`
107249
- );
107250
- }
107251
- return comparable;
107252
- }
107253
107285
  function buildEmitRecordSet(document, plans, side) {
107254
107286
  const records2 = /* @__PURE__ */ new Map();
107255
107287
  for (const [key, plan] of plans) {
@@ -107308,6 +107340,7 @@ var init_pull = __esm({
107308
107340
  init_conflict_markers();
107309
107341
  init_merge();
107310
107342
  init_projection();
107343
+ init_source_record_comparison();
107311
107344
  init_reset();
107312
107345
  init_project_documents();
107313
107346
  init_project_document_cache();
@@ -114152,7 +114185,7 @@ var init_registry2 = __esm({
114152
114185
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
114153
114186
  formatVersion: 3,
114154
114187
  contractVersion: "3.14",
114155
- cliVersion: "0.36.6",
114188
+ cliVersion: "0.36.8",
114156
114189
  projectFileUploadBatchSize: 32,
114157
114190
  documentRecords: {
114158
114191
  member: {
@@ -120468,6 +120501,7 @@ __export(resolve_exports, {
120468
120501
  import { readFileSync as readFileSync19, rmSync as rmSync8, writeFileSync as writeFileSync14 } from "node:fs";
120469
120502
  import { join as join21 } from "node:path";
120470
120503
  function runResolve(workspace, side) {
120504
+ const resolvedRecords = acceptSourceEquivalentConflictBases(workspace);
120471
120505
  let resolvedFiles = 0;
120472
120506
  for (const filePath of listProjectSourceFilesV4(workspace.root)) {
120473
120507
  const source = readFileSync19(filePath, "utf8");
@@ -120501,15 +120535,15 @@ function runResolve(workspace, side) {
120501
120535
  delete binary.conflict;
120502
120536
  resolvedBinaries += 1;
120503
120537
  }
120504
- if (resolvedBinaries > 0) {
120538
+ if (resolvedRecords > 0 || resolvedBinaries > 0) {
120505
120539
  writeWorkspaceState(workspace.root, workspace.state);
120506
120540
  }
120507
- if (resolvedFiles === 0 && resolvedBinaries === 0) {
120508
- console.log("No conflict markers found.");
120541
+ if (resolvedFiles === 0 && resolvedRecords === 0 && resolvedBinaries === 0) {
120542
+ console.log("No conflicts found.");
120509
120543
  return;
120510
120544
  }
120511
120545
  console.log(
120512
- `Resolved ${resolvedFiles} source file(s) and ${resolvedBinaries} binary file(s) keeping the "${side}" side. Review with "neo diff", then "neo push".`
120546
+ `Resolved ${resolvedFiles} source file(s), ${resolvedRecords} record conflict(s), and ${resolvedBinaries} binary file(s) keeping the "${side}" side. Review with "neo diff", then "neo push".`
120513
120547
  );
120514
120548
  }
120515
120549
  function resolveMarkers(source, side) {
@@ -120558,6 +120592,7 @@ var init_resolve = __esm({
120558
120592
  init_workspace_status();
120559
120593
  init_source_diagnostics();
120560
120594
  init_project_files();
120595
+ init_source_record_comparison();
120561
120596
  }
120562
120597
  });
120563
120598
 
@@ -120754,7 +120789,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
120754
120789
  async function main() {
120755
120790
  const args = parseArgs(process.argv.slice(2));
120756
120791
  if (args.command === "--version") {
120757
- console.log("0.36.6");
120792
+ console.log("0.36.8");
120758
120793
  return;
120759
120794
  }
120760
120795
  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.6",
3
+ "version": "0.36.8",
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.6 -->
12
+ <!-- reviewed-through-cli: 0.36.8 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -210,6 +210,11 @@ Conflict source deliberately fails compilation. Edit the desired final source,
210
210
  or use `neo resolve --mine|--theirs` as a whole-side convenience. Then pull,
211
211
  review, dry-run, and push again.
212
212
 
213
+ Server-derived compilation metadata is not authored source and does not create
214
+ a pull conflict by itself. If an older CLI retained such a metadata-only
215
+ conflict without writing source markers, either `neo resolve` side accepts the
216
+ newer server metadata as the base while preserving any real local source edit.
217
+
213
218
  `neo push` sends the authenticated semantic diff plus a deterministic source
214
219
  hash, while the server recompiles the changed NeoScript bodies and
215
220
  schema-affected callers and commits only that diff under CAS. Use
@@ -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.6 -->
86
+ <!-- reviewed-through-cli: 0.36.8 -->
87
87
  ```
88
88
 
89
89
  The quoted version above is checked too, so this instruction cannot go stale
@@ -79,6 +79,11 @@ Resolve source conflicts by editing the final intended source. Use
79
79
  `neo resolve --mine` or `neo resolve --theirs` only for a deliberate whole-side
80
80
  choice. Pull, inspect, dry-run, and retry; there is no force-CAS path.
81
81
 
82
+ Compiled initializer IR is server-owned and does not participate in authored
83
+ three-way merge decisions. `neo resolve` also repairs metadata-only conflict
84
+ bookkeeping left by an older pull when no source marker exists; accepting the
85
+ newer server metadata does not overwrite the tracked source.
86
+
82
87
  For `base-hash-conflict`, pull and merge. For `version-bump-required`, inspect
83
88
  the classification and use `neo push --accept-bump` only when the bump is
84
89
  intended.