@neocompose/cli 0.24.0 → 0.24.1

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,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.24.1] - 2026-08-07
4
+
5
+ ### Fixed
6
+
7
+ - Allow new idless rows and explicitly identified UUID rows inside existing
8
+ row-backed member-default lists. Changed ancestors and pending children now
9
+ reach prospective animation validation and trusted server materialization,
10
+ which preserves existing rows and creates only genuinely new children.
11
+ - Keep project constructor declarations in the `neo test` compiler context so
12
+ named calls to required constructors compile instead of falling back to a
13
+ schema-derived constructor.
14
+
15
+ ### Changed
16
+
17
+ - Update the bundled CLI skill for P60 selector targeting, project-owned stable
18
+ selector identity, and frugal `@id` authoring for new declarations and rows.
19
+
3
20
  ## [0.24.0] - 2026-08-06
4
21
 
5
22
  ### Added
package/dist/neo.mjs CHANGED
@@ -685,12 +685,13 @@ var init_args = __esm({
685
685
  }
686
686
  });
687
687
 
688
- // ../node_modules/ignore/index.js
688
+ // node_modules/ignore/index.js
689
689
  var require_ignore = __commonJS({
690
- "../node_modules/ignore/index.js"(exports, module) {
690
+ "node_modules/ignore/index.js"(exports, module) {
691
691
  function makeArray(subject) {
692
692
  return Array.isArray(subject) ? subject : [subject];
693
693
  }
694
+ var UNDEFINED = void 0;
694
695
  var EMPTY = "";
695
696
  var SPACE = " ";
696
697
  var ESCAPE = "\\";
@@ -699,27 +700,32 @@ var require_ignore = __commonJS({
699
700
  var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
700
701
  var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
701
702
  var REGEX_SPLITALL_CRLF = /\r?\n/g;
702
- var REGEX_TEST_INVALID_PATH = /^\.*\/|^\.+$/;
703
+ var REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/;
704
+ var REGEX_TEST_TRAILING_SLASH = /\/$/;
703
705
  var SLASH = "/";
704
706
  var TMP_KEY_IGNORE = "node-ignore";
705
707
  if (typeof Symbol !== "undefined") {
706
708
  TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
707
709
  }
708
710
  var KEY_IGNORE = TMP_KEY_IGNORE;
709
- var define = (object2, key, value) => Object.defineProperty(object2, key, { value });
711
+ var define = (object2, key, value) => {
712
+ Object.defineProperty(object2, key, { value });
713
+ return value;
714
+ };
710
715
  var REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
711
716
  var RETURN_FALSE = () => false;
712
717
  var sanitizeRange = (range2) => range2.replace(
713
718
  REGEX_REGEXP_RANGE,
714
719
  (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) ? match : EMPTY
715
720
  );
721
+ var negateRange = (range2) => range2.startsWith("!") || range2.startsWith("\\^") ? `^${range2.slice(range2[0] === "!" ? 1 : 2)}` : range2;
716
722
  var cleanRangeBackSlash = (slashes) => {
717
723
  const { length } = slashes;
718
724
  return slashes.slice(0, length - length % 2);
719
725
  };
720
726
  var REPLACERS = [
721
727
  [
722
- // remove BOM
728
+ // Remove BOM
723
729
  // TODO:
724
730
  // Other similar zero-width characters?
725
731
  /^\uFEFF/,
@@ -734,7 +740,7 @@ var require_ignore = __commonJS({
734
740
  /((?:\\\\)*?)(\\?\s+)$/,
735
741
  (_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)
736
742
  ],
737
- // replace (\ ) with ' '
743
+ // Replace (\ ) with ' '
738
744
  // (\ ) -> ' '
739
745
  // (\\ ) -> '\\ '
740
746
  // (\\\ ) -> '\\ '
@@ -790,7 +796,7 @@ var require_ignore = __commonJS({
790
796
  // > "**/foo/bar" matches file or directory "bar" anywhere that is directly
791
797
  // > under directory "foo".
792
798
  // Notice that the '*'s have been replaced as '\\*'
793
- /^\^*\\\*\\\*\\\//,
799
+ /^\^*(?:\\\*\\\*\\\/)+/,
794
800
  // '**/foo' <-> 'foo'
795
801
  () => "^(?:.*\\/)?"
796
802
  ],
@@ -845,7 +851,7 @@ var require_ignore = __commonJS({
845
851
  // > can be used to match one of the characters in a range.
846
852
  // `\` is escaped by step 3
847
853
  /(\\)?\[([^\]/]*?)(\\*)($|\])/g,
848
- (match, leadEscape, range2, endEscape, close) => leadEscape === ESCAPE ? `\\[${range2}${cleanRangeBackSlash(endEscape)}${close}` : close === "]" ? endEscape.length % 2 === 0 ? `[${sanitizeRange(range2)}${endEscape}]` : "[]" : "[]"
854
+ (match, leadEscape, range2, endEscape, close) => leadEscape === ESCAPE ? `\\[${range2}${cleanRangeBackSlash(endEscape)}${close}` : close === "]" ? endEscape.length % 2 === 0 ? `[${negateRange(sanitizeRange(range2))}${endEscape}]` : "[]" : "[]"
849
855
  ],
850
856
  // ending
851
857
  [
@@ -863,55 +869,147 @@ var require_ignore = __commonJS({
863
869
  // 'js/' will not match 'a.js'
864
870
  // 'js' will match 'a.js' and 'a.js/'
865
871
  (match) => /\/$/.test(match) ? `${match}$` : `${match}(?=$|\\/$)`
866
- ],
867
- // trailing wildcard
868
- [
869
- /(\^|\\\/)?\\\*$/,
870
- (_, p1) => {
871
- const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
872
- return `${prefix}(?=$|\\/$)`;
873
- }
874
872
  ]
875
873
  ];
876
- var regexCache = /* @__PURE__ */ Object.create(null);
877
- var makeRegex = (pattern, ignoreCase) => {
878
- let source = regexCache[pattern];
879
- if (!source) {
880
- source = REPLACERS.reduce(
881
- (prev, [matcher, replacer]) => prev.replace(matcher, replacer.bind(pattern)),
882
- pattern
883
- );
884
- regexCache[pattern] = source;
874
+ var REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\\\*$/;
875
+ var MODE_IGNORE = "regex";
876
+ var MODE_CHECK_IGNORE = "checkRegex";
877
+ var UNDERSCORE = "_";
878
+ var TRAILING_WILD_CARD_REPLACERS = {
879
+ [MODE_IGNORE](_, p1) {
880
+ const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
881
+ return `${prefix}(?=$|\\/$)`;
882
+ },
883
+ [MODE_CHECK_IGNORE](_, p1) {
884
+ const prefix = p1 ? `${p1}[^/]*` : "[^/]*";
885
+ return `${prefix}(?=$|\\/$)`;
885
886
  }
886
- return ignoreCase ? new RegExp(source, "i") : new RegExp(source);
887
887
  };
888
+ var makeRegexPrefix = (pattern) => REPLACERS.reduce(
889
+ (prev, [matcher, replacer]) => prev.replace(matcher, replacer.bind(pattern)),
890
+ pattern
891
+ );
888
892
  var isString2 = (subject) => typeof subject === "string";
889
893
  var checkPattern = (pattern) => pattern && isString2(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0;
890
- var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF);
894
+ var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF).filter(Boolean);
891
895
  var IgnoreRule = class {
892
- constructor(origin, pattern, negative, regex) {
893
- this.origin = origin;
896
+ constructor(pattern, mark, body, ignoreCase, negative, prefix) {
894
897
  this.pattern = pattern;
898
+ this.mark = mark;
895
899
  this.negative = negative;
896
- this.regex = regex;
900
+ define(this, "body", body);
901
+ define(this, "ignoreCase", ignoreCase);
902
+ define(this, "regexPrefix", prefix);
903
+ }
904
+ get regex() {
905
+ const key = UNDERSCORE + MODE_IGNORE;
906
+ if (this[key]) {
907
+ return this[key];
908
+ }
909
+ return this._make(MODE_IGNORE, key);
910
+ }
911
+ get checkRegex() {
912
+ const key = UNDERSCORE + MODE_CHECK_IGNORE;
913
+ if (this[key]) {
914
+ return this[key];
915
+ }
916
+ return this._make(MODE_CHECK_IGNORE, key);
917
+ }
918
+ _make(mode, key) {
919
+ const str = this.regexPrefix.replace(
920
+ REGEX_REPLACE_TRAILING_WILDCARD,
921
+ // It does not need to bind pattern
922
+ TRAILING_WILD_CARD_REPLACERS[mode]
923
+ );
924
+ const regex = this.ignoreCase ? new RegExp(str, "i") : new RegExp(str);
925
+ return define(this, key, regex);
897
926
  }
898
927
  };
899
- var createRule = (pattern, ignoreCase) => {
900
- const origin = pattern;
928
+ var createRule = ({
929
+ pattern,
930
+ mark
931
+ }, ignoreCase) => {
901
932
  let negative = false;
902
- if (pattern.indexOf("!") === 0) {
933
+ let body = pattern;
934
+ if (body.indexOf("!") === 0) {
903
935
  negative = true;
904
- pattern = pattern.substr(1);
936
+ body = body.substr(1);
905
937
  }
906
- pattern = pattern.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
907
- const regex = makeRegex(pattern, ignoreCase);
938
+ body = body.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
939
+ const regexPrefix = makeRegexPrefix(body);
908
940
  return new IgnoreRule(
909
- origin,
910
941
  pattern,
942
+ mark,
943
+ body,
944
+ ignoreCase,
911
945
  negative,
912
- regex
946
+ regexPrefix
913
947
  );
914
948
  };
949
+ var RuleManager = class {
950
+ constructor(ignoreCase) {
951
+ this._ignoreCase = ignoreCase;
952
+ this._rules = [];
953
+ }
954
+ _add(pattern) {
955
+ if (pattern && pattern[KEY_IGNORE]) {
956
+ this._rules = this._rules.concat(pattern._rules._rules);
957
+ this._added = true;
958
+ return;
959
+ }
960
+ if (isString2(pattern)) {
961
+ pattern = {
962
+ pattern
963
+ };
964
+ }
965
+ if (checkPattern(pattern.pattern)) {
966
+ const rule = createRule(pattern, this._ignoreCase);
967
+ this._added = true;
968
+ this._rules.push(rule);
969
+ }
970
+ }
971
+ // @param {Array<string> | string | Ignore} pattern
972
+ add(pattern) {
973
+ this._added = false;
974
+ makeArray(
975
+ isString2(pattern) ? splitPattern(pattern) : pattern
976
+ ).forEach(this._add, this);
977
+ return this._added;
978
+ }
979
+ // Test one single path without recursively checking parent directories
980
+ //
981
+ // - checkUnignored `boolean` whether should check if the path is unignored,
982
+ // setting `checkUnignored` to `false` could reduce additional
983
+ // path matching.
984
+ // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
985
+ // @returns {TestResult} true if a file is ignored
986
+ test(path, checkUnignored, mode) {
987
+ let ignored = false;
988
+ let unignored = false;
989
+ let matchedRule;
990
+ this._rules.forEach((rule) => {
991
+ const { negative } = rule;
992
+ if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
993
+ return;
994
+ }
995
+ const matched = rule[mode].test(path);
996
+ if (!matched) {
997
+ return;
998
+ }
999
+ ignored = !negative;
1000
+ unignored = negative;
1001
+ matchedRule = negative ? UNDEFINED : rule;
1002
+ });
1003
+ const ret = {
1004
+ ignored,
1005
+ unignored
1006
+ };
1007
+ if (matchedRule) {
1008
+ ret.rule = matchedRule;
1009
+ }
1010
+ return ret;
1011
+ }
1012
+ };
915
1013
  var throwError = (message, Ctor) => {
916
1014
  throw new Ctor(message);
917
1015
  };
@@ -944,34 +1042,16 @@ var require_ignore = __commonJS({
944
1042
  allowRelativePaths = false
945
1043
  } = {}) {
946
1044
  define(this, KEY_IGNORE, true);
947
- this._rules = [];
948
- this._ignoreCase = ignoreCase;
949
- this._allowRelativePaths = allowRelativePaths;
1045
+ this._rules = new RuleManager(ignoreCase);
1046
+ this._strictPathCheck = !allowRelativePaths;
950
1047
  this._initCache();
951
1048
  }
952
1049
  _initCache() {
953
1050
  this._ignoreCache = /* @__PURE__ */ Object.create(null);
954
1051
  this._testCache = /* @__PURE__ */ Object.create(null);
955
1052
  }
956
- _addPattern(pattern) {
957
- if (pattern && pattern[KEY_IGNORE]) {
958
- this._rules = this._rules.concat(pattern._rules);
959
- this._added = true;
960
- return;
961
- }
962
- if (checkPattern(pattern)) {
963
- const rule = createRule(pattern, this._ignoreCase);
964
- this._added = true;
965
- this._rules.push(rule);
966
- }
967
- }
968
- // @param {Array<string> | string | Ignore} pattern
969
1053
  add(pattern) {
970
- this._added = false;
971
- makeArray(
972
- isString2(pattern) ? splitPattern(pattern) : pattern
973
- ).forEach(this._addPattern, this);
974
- if (this._added) {
1054
+ if (this._rules.add(pattern)) {
975
1055
  this._initCache();
976
1056
  }
977
1057
  return this;
@@ -980,58 +1060,45 @@ var require_ignore = __commonJS({
980
1060
  addPattern(pattern) {
981
1061
  return this.add(pattern);
982
1062
  }
983
- // | ignored : unignored
984
- // negative | 0:0 | 0:1 | 1:0 | 1:1
985
- // -------- | ------- | ------- | ------- | --------
986
- // 0 | TEST | TEST | SKIP | X
987
- // 1 | TESTIF | SKIP | TEST | X
988
- // - SKIP: always skip
989
- // - TEST: always test
990
- // - TESTIF: only test if checkUnignored
991
- // - X: that never happen
992
- // @param {boolean} whether should check if the path is unignored,
993
- // setting `checkUnignored` to `false` could reduce additional
994
- // path matching.
995
- // @returns {TestResult} true if a file is ignored
996
- _testOne(path, checkUnignored) {
997
- let ignored = false;
998
- let unignored = false;
999
- this._rules.forEach((rule) => {
1000
- const { negative } = rule;
1001
- if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
1002
- return;
1003
- }
1004
- const matched = rule.regex.test(path);
1005
- if (matched) {
1006
- ignored = !negative;
1007
- unignored = negative;
1008
- }
1009
- });
1010
- return {
1011
- ignored,
1012
- unignored
1013
- };
1014
- }
1015
1063
  // @returns {TestResult}
1016
1064
  _test(originalPath, cache, checkUnignored, slices) {
1017
1065
  const path = originalPath && checkPath.convert(originalPath);
1018
1066
  checkPath(
1019
1067
  path,
1020
1068
  originalPath,
1021
- this._allowRelativePaths ? RETURN_FALSE : throwError
1069
+ this._strictPathCheck ? throwError : RETURN_FALSE
1022
1070
  );
1023
1071
  return this._t(path, cache, checkUnignored, slices);
1024
1072
  }
1073
+ checkIgnore(path) {
1074
+ if (!REGEX_TEST_TRAILING_SLASH.test(path)) {
1075
+ return this.test(path);
1076
+ }
1077
+ const slices = path.split(SLASH).filter(Boolean);
1078
+ slices.pop();
1079
+ if (slices.length) {
1080
+ const parent = this._t(
1081
+ slices.join(SLASH) + SLASH,
1082
+ this._testCache,
1083
+ true,
1084
+ slices
1085
+ );
1086
+ if (parent.ignored) {
1087
+ return parent;
1088
+ }
1089
+ }
1090
+ return this._rules.test(path, false, MODE_CHECK_IGNORE);
1091
+ }
1025
1092
  _t(path, cache, checkUnignored, slices) {
1026
1093
  if (path in cache) {
1027
1094
  return cache[path];
1028
1095
  }
1029
1096
  if (!slices) {
1030
- slices = path.split(SLASH);
1097
+ slices = path.split(SLASH).filter(Boolean);
1031
1098
  }
1032
1099
  slices.pop();
1033
1100
  if (!slices.length) {
1034
- return cache[path] = this._testOne(path, checkUnignored);
1101
+ return cache[path] = this._rules.test(path, checkUnignored, MODE_IGNORE);
1035
1102
  }
1036
1103
  const parent = this._t(
1037
1104
  slices.join(SLASH) + SLASH,
@@ -1039,7 +1106,7 @@ var require_ignore = __commonJS({
1039
1106
  checkUnignored,
1040
1107
  slices
1041
1108
  );
1042
- return cache[path] = parent.ignored ? parent : this._testOne(path, checkUnignored);
1109
+ return cache[path] = parent.ignored ? parent : this._rules.test(path, checkUnignored, MODE_IGNORE);
1043
1110
  }
1044
1111
  ignores(path) {
1045
1112
  return this._test(path, this._ignoreCache, false).ignored;
@@ -1057,18 +1124,22 @@ var require_ignore = __commonJS({
1057
1124
  };
1058
1125
  var factory = (options) => new Ignore(options);
1059
1126
  var isPathValid = (path) => checkPath(path && checkPath.convert(path), path, RETURN_FALSE);
1060
- factory.isPathValid = isPathValid;
1061
- factory.default = factory;
1062
- module.exports = factory;
1127
+ var setupWindows = () => {
1128
+ const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
1129
+ checkPath.convert = makePosix;
1130
+ const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
1131
+ checkPath.isNotRelative = (path) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path) || isNotRelative(path);
1132
+ };
1063
1133
  if (
1064
1134
  // Detect `process` so that it can run in browsers.
1065
- typeof process !== "undefined" && (process.env && process.env.IGNORE_TEST_WIN32 || process.platform === "win32")
1135
+ typeof process !== "undefined" && process.platform === "win32"
1066
1136
  ) {
1067
- const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
1068
- checkPath.convert = makePosix;
1069
- const REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
1070
- checkPath.isNotRelative = (path) => REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path) || isNotRelative(path);
1137
+ setupWindows();
1071
1138
  }
1139
+ module.exports = factory;
1140
+ factory.default = factory;
1141
+ module.exports.isPathValid = isPathValid;
1142
+ define(module.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
1072
1143
  }
1073
1144
  });
1074
1145
 
@@ -35344,27 +35415,27 @@ var init_source_format = __esm({
35344
35415
  }
35345
35416
  });
35346
35417
 
35347
- // ../node_modules/uuid/dist-node/regex.js
35418
+ // node_modules/uuid/dist-node/regex.js
35348
35419
  var regex_default;
35349
35420
  var init_regex = __esm({
35350
- "../node_modules/uuid/dist-node/regex.js"() {
35421
+ "node_modules/uuid/dist-node/regex.js"() {
35351
35422
  regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
35352
35423
  }
35353
35424
  });
35354
35425
 
35355
- // ../node_modules/uuid/dist-node/validate.js
35426
+ // node_modules/uuid/dist-node/validate.js
35356
35427
  function validate(uuid) {
35357
35428
  return typeof uuid === "string" && regex_default.test(uuid);
35358
35429
  }
35359
35430
  var validate_default;
35360
35431
  var init_validate2 = __esm({
35361
- "../node_modules/uuid/dist-node/validate.js"() {
35432
+ "node_modules/uuid/dist-node/validate.js"() {
35362
35433
  init_regex();
35363
35434
  validate_default = validate;
35364
35435
  }
35365
35436
  });
35366
35437
 
35367
- // ../node_modules/uuid/dist-node/parse.js
35438
+ // node_modules/uuid/dist-node/parse.js
35368
35439
  function parse(uuid) {
35369
35440
  if (!validate_default(uuid)) {
35370
35441
  throw TypeError("Invalid UUID");
@@ -35374,19 +35445,19 @@ function parse(uuid) {
35374
35445
  }
35375
35446
  var parse_default;
35376
35447
  var init_parse = __esm({
35377
- "../node_modules/uuid/dist-node/parse.js"() {
35448
+ "node_modules/uuid/dist-node/parse.js"() {
35378
35449
  init_validate2();
35379
35450
  parse_default = parse;
35380
35451
  }
35381
35452
  });
35382
35453
 
35383
- // ../node_modules/uuid/dist-node/stringify.js
35454
+ // node_modules/uuid/dist-node/stringify.js
35384
35455
  function unsafeStringify(arr, offset = 0) {
35385
35456
  return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
35386
35457
  }
35387
35458
  var byteToHex;
35388
35459
  var init_stringify = __esm({
35389
- "../node_modules/uuid/dist-node/stringify.js"() {
35460
+ "node_modules/uuid/dist-node/stringify.js"() {
35390
35461
  byteToHex = [];
35391
35462
  for (let i = 0; i < 256; ++i) {
35392
35463
  byteToHex.push((i + 256).toString(16).slice(1));
@@ -35394,18 +35465,18 @@ var init_stringify = __esm({
35394
35465
  }
35395
35466
  });
35396
35467
 
35397
- // ../node_modules/uuid/dist-node/rng.js
35468
+ // node_modules/uuid/dist-node/rng.js
35398
35469
  function rng() {
35399
35470
  return crypto.getRandomValues(rnds8);
35400
35471
  }
35401
35472
  var rnds8;
35402
35473
  var init_rng = __esm({
35403
- "../node_modules/uuid/dist-node/rng.js"() {
35474
+ "node_modules/uuid/dist-node/rng.js"() {
35404
35475
  rnds8 = new Uint8Array(16);
35405
35476
  }
35406
35477
  });
35407
35478
 
35408
- // ../node_modules/uuid/dist-node/v35.js
35479
+ // node_modules/uuid/dist-node/v35.js
35409
35480
  function stringToBytes(str) {
35410
35481
  str = unescape(encodeURIComponent(str));
35411
35482
  const bytes = new Uint8Array(str.length);
@@ -35443,7 +35514,7 @@ function v35(version, hash, value, namespace, buf, offset) {
35443
35514
  }
35444
35515
  var DNS, URL2;
35445
35516
  var init_v35 = __esm({
35446
- "../node_modules/uuid/dist-node/v35.js"() {
35517
+ "node_modules/uuid/dist-node/v35.js"() {
35447
35518
  init_parse();
35448
35519
  init_stringify();
35449
35520
  DNS = "6ba7b810-9dad-11d1-80b4-00c04fd430c8";
@@ -35451,7 +35522,7 @@ var init_v35 = __esm({
35451
35522
  }
35452
35523
  });
35453
35524
 
35454
- // ../node_modules/uuid/dist-node/v4.js
35525
+ // node_modules/uuid/dist-node/v4.js
35455
35526
  function v4(options, buf, offset) {
35456
35527
  if (!buf && !options && crypto.randomUUID) {
35457
35528
  return crypto.randomUUID();
@@ -35480,14 +35551,14 @@ function _v4(options, buf, offset) {
35480
35551
  }
35481
35552
  var v4_default;
35482
35553
  var init_v4 = __esm({
35483
- "../node_modules/uuid/dist-node/v4.js"() {
35554
+ "node_modules/uuid/dist-node/v4.js"() {
35484
35555
  init_rng();
35485
35556
  init_stringify();
35486
35557
  v4_default = v4;
35487
35558
  }
35488
35559
  });
35489
35560
 
35490
- // ../node_modules/uuid/dist-node/sha1.js
35561
+ // node_modules/uuid/dist-node/sha1.js
35491
35562
  import { createHash } from "node:crypto";
35492
35563
  function sha1(bytes) {
35493
35564
  if (Array.isArray(bytes)) {
@@ -35499,18 +35570,18 @@ function sha1(bytes) {
35499
35570
  }
35500
35571
  var sha1_default;
35501
35572
  var init_sha1 = __esm({
35502
- "../node_modules/uuid/dist-node/sha1.js"() {
35573
+ "node_modules/uuid/dist-node/sha1.js"() {
35503
35574
  sha1_default = sha1;
35504
35575
  }
35505
35576
  });
35506
35577
 
35507
- // ../node_modules/uuid/dist-node/v5.js
35578
+ // node_modules/uuid/dist-node/v5.js
35508
35579
  function v5(value, namespace, buf, offset) {
35509
35580
  return v35(80, sha1_default, value, namespace, buf, offset);
35510
35581
  }
35511
35582
  var v5_default;
35512
35583
  var init_v5 = __esm({
35513
- "../node_modules/uuid/dist-node/v5.js"() {
35584
+ "node_modules/uuid/dist-node/v5.js"() {
35514
35585
  init_sha1();
35515
35586
  init_v35();
35516
35587
  v5.DNS = DNS;
@@ -35519,9 +35590,9 @@ var init_v5 = __esm({
35519
35590
  }
35520
35591
  });
35521
35592
 
35522
- // ../node_modules/uuid/dist-node/index.js
35593
+ // node_modules/uuid/dist-node/index.js
35523
35594
  var init_dist_node = __esm({
35524
- "../node_modules/uuid/dist-node/index.js"() {
35595
+ "node_modules/uuid/dist-node/index.js"() {
35525
35596
  init_v4();
35526
35597
  init_v5();
35527
35598
  }
@@ -78941,8 +79012,7 @@ function materializeAuthoredValueSeeds(args) {
78941
79012
  document: args.document,
78942
79013
  prepared: args.prepared,
78943
79014
  pendingGraphValidations,
78944
- changeIndex: index,
78945
- change,
79015
+ memberChange: { index, change },
78946
79016
  member,
78947
79017
  seed
78948
79018
  });
@@ -79088,6 +79158,21 @@ function materializeAuthoredValueSeeds(args) {
79088
79158
  }
79089
79159
  for (const seed of args.authoredValueSeeds) {
79090
79160
  if (consumedSeedMemberIds.has(seed.memberId)) continue;
79161
+ const member = args.document.members.find(
79162
+ (candidate) => candidate.id === seed.memberId
79163
+ );
79164
+ if (member !== void 0 && member.isStatic !== true && !isProjectRootMember(args.document.project, member.id)) {
79165
+ materializeInstanceDefaultSeed({
79166
+ document: args.document,
79167
+ prepared: args.prepared,
79168
+ pendingGraphValidations,
79169
+ memberChange: null,
79170
+ member,
79171
+ seed
79172
+ });
79173
+ consumedSeedMemberIds.add(seed.memberId);
79174
+ continue;
79175
+ }
79091
79176
  materializeExistingAuthoredValueSeed({
79092
79177
  document: args.document,
79093
79178
  prepared: args.prepared,
@@ -79308,10 +79393,17 @@ function materializeInstanceDefaultSeed(args) {
79308
79393
  ...args.member,
79309
79394
  defaultValue: seedValueContent(args.seed)
79310
79395
  };
79311
- const { createdValues, localizedTexts, storageKeyDeclarations, rootValue } = materializeAuthoredStaticSeed({
79396
+ const {
79397
+ createdValues,
79398
+ localizedTexts,
79399
+ storageKeyDeclarations,
79400
+ rootValue,
79401
+ existingValueIds
79402
+ } = materializeAuthoredStaticSeed({
79312
79403
  document: args.document,
79313
79404
  member: materializationMember,
79314
- seed: { ...args.seed, values: args.seed.values }
79405
+ seed: { ...args.seed, values: args.seed.values },
79406
+ allowExistingRows: args.memberChange === null || args.memberChange.change.operation === "update"
79315
79407
  });
79316
79408
  stampCreatedValuesMapKey(createdValues, null);
79317
79409
  applyDeclaredStorageKeyOverrides({
@@ -79346,12 +79438,21 @@ function materializeInstanceDefaultSeed(args) {
79346
79438
  ...typeof rootValue.classId === "string" ? { classId: rootValue.classId } : {}
79347
79439
  }
79348
79440
  };
79349
- args.prepared[args.changeIndex] = {
79350
- ...args.change,
79351
- nextData: nextMember
79352
- };
79441
+ if (args.memberChange === null) {
79442
+ if (canonicalJsonStringify(seedValueContent(args.seed)) !== canonicalJsonStringify(args.member.defaultValue)) {
79443
+ throw new Error(
79444
+ `Existing default value seed for member "${args.member.name}" (${args.member.id}) declares ${canonicalJsonStringify(seedValueContent(args.seed))}, but the unchanged member declares ${canonicalJsonStringify(args.member.defaultValue)}.`
79445
+ );
79446
+ }
79447
+ } else {
79448
+ args.prepared[args.memberChange.index] = {
79449
+ ...args.memberChange.change,
79450
+ nextData: nextMember
79451
+ };
79452
+ }
79353
79453
  for (const value of createdValues) {
79354
79454
  if (value.id === rootValue.id) continue;
79455
+ if (existingValueIds.has(value.id)) continue;
79355
79456
  args.prepared.push({
79356
79457
  recordKind: "value",
79357
79458
  recordId: value.id,
@@ -84972,10 +85073,52 @@ function lowerMemberDefaultSourcesV4(state, analysis, manifest, options = {}) {
84972
85073
  const baseData3 = stateData(context, "member", memberId);
84973
85074
  const backed = baseData3 === null || requiresEvaluation ? null : rowBackedDefaultBody(baseData3, (id2) => pulledValueIds.has(id2));
84974
85075
  if (backed !== null && baseData3 !== null) {
84975
- memberDefaultValues.set(
84976
- memberId,
84977
- lowerRowBackedDefault(context, member, backed, baseData3, binding)
85076
+ const existingReconstructedKeys = new Set(context.reconstructed.keys());
85077
+ const existingPendingValueIds = new Set(context.pendingValues.keys());
85078
+ const existingPendingLocalizedTextIds = new Set(
85079
+ context.pendingLocalizedTexts.keys()
84978
85080
  );
85081
+ const existingPendingBindingMemberIds = new Set(
85082
+ [...context.pendingBindingMembersByClassId.values()].map(
85083
+ (pending) => pending.id
85084
+ )
85085
+ );
85086
+ const lowered = lowerRowBackedDefault(
85087
+ context,
85088
+ member,
85089
+ backed,
85090
+ baseData3,
85091
+ binding
85092
+ );
85093
+ memberDefaultValues.set(memberId, lowered);
85094
+ const pendingRows = [...context.pendingValues.values()].filter(
85095
+ (row) => !existingPendingValueIds.has(row.id)
85096
+ );
85097
+ if (pendingRows.length > 0) {
85098
+ const existingRows = [...context.reconstructed].filter(
85099
+ ([key, record3]) => !existingReconstructedKeys.has(key) && record3.recordKind === "value"
85100
+ ).map(
85101
+ ([, record3]) => staticValueSeedRow(
85102
+ record3.fileFields,
85103
+ context.loweredMemberIdByValueId.get(record3.recordId)
85104
+ )
85105
+ );
85106
+ const bindingMembers = [
85107
+ ...context.pendingBindingMembersByClassId.values()
85108
+ ].filter(
85109
+ (pending) => !existingPendingBindingMemberIds.has(pending.id)
85110
+ );
85111
+ const localizedTexts = [
85112
+ ...context.pendingLocalizedTexts.values()
85113
+ ].filter((text) => !existingPendingLocalizedTextIds.has(text.id));
85114
+ seeds.set(memberId, {
85115
+ ...lowered,
85116
+ valueId: pendingValueId(binding, `${label}.default`),
85117
+ values: [...existingRows, ...pendingRows],
85118
+ ...bindingMembers.length === 0 ? {} : { bindingMembers },
85119
+ ...localizedTexts.length === 0 ? {} : { localizedTexts }
85120
+ });
85121
+ }
84979
85122
  continue;
84980
85123
  }
84981
85124
  if (!defaultRequiresOwnedRows(context, member, expression, binding)) {
@@ -85582,9 +85725,22 @@ function lowerRowBackedDefault(context, member, backed, baseData3, binding) {
85582
85725
  return symbolId;
85583
85726
  }
85584
85727
  const itemId = annotatedValue(element).id;
85585
- if (itemId === null) {
85586
- throw new Error(
85587
- `Default list ${binding.label}[${index}] has no @id. Creating new default rows from source is not implemented by this slice.`
85728
+ if (itemId === null || context.state[`value:${itemId}`] === void 0) {
85729
+ if (itemId !== null && !isPendingId(itemId) && !isUuidV4Id(itemId)) {
85730
+ context.loweringFailures.push({
85731
+ message: `Default list ${binding.label} entry @id("${itemId}") names no existing row, and a new row's durable identity must be a UUID v4. Fix the id if it meant an existing row, or drop the @id to mint a fresh identity.`,
85732
+ site: referenceSite(binding, element)
85733
+ });
85734
+ }
85735
+ return lowerPendingListItem(
85736
+ context,
85737
+ entryMember,
85738
+ element,
85739
+ binding,
85740
+ `${binding.label}[${index}]`,
85741
+ void 0,
85742
+ bindingEnvironment,
85743
+ entrySlices[index]
85588
85744
  );
85589
85745
  }
85590
85746
  return lowerValueRow(
@@ -87114,11 +87270,6 @@ function lowerListValue(context, member, expression, base, source, inheritedEnvi
87114
87270
  }
87115
87271
  const annotatedItemId = annotatedValue(element).id;
87116
87272
  if (annotatedItemId === null) {
87117
- if (source.purpose === "memberDefault") {
87118
- throw new Error(
87119
- `List ${member.name} contains an idless default item. New default list item creation is not implemented by this slice.`
87120
- );
87121
- }
87122
87273
  ids.push(
87123
87274
  lowerPendingListItem(
87124
87275
  context,
@@ -94410,6 +94561,7 @@ function createCliNeoScriptContext(options) {
94410
94561
  };
94411
94562
  const compilerContext = createNeoScriptDocumentContext({
94412
94563
  vm,
94564
+ constructors: [...options.documents.constructors],
94413
94565
  scriptKind: options.kind,
94414
94566
  thisClass: options.thisClass,
94415
94567
  ...options.returnTypeInfo ? { returnTypeInfo: options.returnTypeInfo } : {},
@@ -96117,6 +96269,7 @@ function readDocumentArrays(raw) {
96117
96269
  project: raw.project,
96118
96270
  members: arrayOf2("members"),
96119
96271
  classes: arrayOf2("classes"),
96272
+ constructors: arrayOf2("constructors"),
96120
96273
  enums: arrayOf2("enums"),
96121
96274
  interfaces: arrayOf2("interfaces"),
96122
96275
  values: arrayOf2("values"),
@@ -101243,7 +101396,7 @@ var init_registry2 = __esm({
101243
101396
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
101244
101397
  formatVersion: 3,
101245
101398
  contractVersion: "3.9",
101246
- cliVersion: "0.24.0",
101399
+ cliVersion: "0.24.1",
101247
101400
  projectFileUploadBatchSize: 32,
101248
101401
  documentRecords: {
101249
101402
  member: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -40,6 +40,12 @@
40
40
  },
41
41
  "devDependencies": {
42
42
  "esbuild": "^0.28.1",
43
- "prettier": "^3.9.5"
43
+ "prettier": "^3.9.5",
44
+ "uuid": "^14.0.1"
45
+ },
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/ryanbliss/neo-compose.git",
49
+ "directory": "cli"
44
50
  }
45
51
  }
@@ -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.24.0 -->
12
+ <!-- reviewed-through-cli: 0.24.1 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -50,8 +50,12 @@ opaque JSON or guessed syntax.
50
50
  values, files, or other records. `@system` is reserved for platform-owned
51
51
  system records; authoring it can make the record uneditable.
52
52
  - Preserve existing `@id` annotations through rename, reorder, and file moves.
53
+ - Omit `@id` on new ordinary declarations and nested rows unless a same-push
54
+ structural reference genuinely requires an explicit UUID. A successful push
55
+ assigns the durable IDs and rewrites source.
53
56
  - Do not add a second `@id` to a root or static member initializer. The member
54
- ID owns that stored binding. Nested class/list rows still carry their own IDs.
57
+ ID owns that stored binding. Existing nested class/list rows retain their
58
+ inline IDs; new rows normally omit them until push.
55
59
  - Treat `.neo/` as private state and tracked Neo source as the authorable input.
56
60
  - Never seek a force-CAS bypass. Pull, resolve, review, and retry.
57
61
 
@@ -109,9 +109,12 @@ override replaces the whole leaf.
109
109
  Frame overrides may change `Enabled`, `FlipX`, and `SortingOrder`; they may
110
110
  not add, remove, or reorder `Children`.
111
111
 
112
- Address direct children with stable row IDs. Missing optional child slots are
113
- skipped with diagnostics when the authored slot is absent, while stale
114
- pre-provenance placements may still fail closed.
112
+ Address direct children through selector delegates. A selector should implement
113
+ the project's own stable identity contract, such as an immutable `Name`, slug,
114
+ or semantic position; do not turn a mutable object-row ID back into identity by
115
+ returning `Reference<T>(id: "...")`. Missing optional child slots are skipped
116
+ with diagnostics when the authored slot is absent, while stale pre-provenance
117
+ placements may still fail closed.
115
118
 
116
119
  A placement row carries its `assetClassId` binding and optional `assetValueId`
117
120
  override beside its schema keys. They are row provenance, not declared schema:
@@ -127,8 +130,8 @@ sprite specialization is `NeoSpriteAnimationSegment`, whose
127
130
 
128
131
  Use one polymorphic `NeoAnimationClip.Tracks` list:
129
132
 
130
- - `NeoAnimationChildTrack` schedules a child clip selected by `Child` and
131
- `ClipKey`.
133
+ - `NeoAnimationChildTrack` schedules `ClipKey` on the child returned by its
134
+ `Selector` delegate.
132
135
  - A project subclass of
133
136
  `NeoAnimationSegmentTrack<TChild, TValue>` schedules a segment onto one
134
137
  target member. Implement its abstract `Segment` member as a stored value,
@@ -137,32 +140,54 @@ Use one polymorphic `NeoAnimationClip.Tracks` list:
137
140
  `NeoSpriteAnimationSegmentTrack<TChild extends NeoSpriteObject>` pairing
138
141
  targets `NeoSpriteObject.Sprite`.
139
142
 
140
- Declare a concrete project track by implementing `Segment`, then construct its
141
- row with the target child ID:
143
+ Declare a concrete project track by implementing `Segment`, expose a compatible
144
+ selector function on the owning object, then pass that function to each row:
142
145
 
143
146
  ```neo
144
147
  class StoredPantsTrack : NeoSpriteAnimationSegmentTrack<PantsSprite> {
145
148
  public override NeoSpriteAnimationSegment Segment = new();
146
149
  }
147
150
 
148
- Tracks = [
149
- @id("pants-track-row-id")
150
- new StoredPantsTrack(id: "pants-child-row-id") {
151
- StartFrame = 0,
152
- Direction = .Reverse,
153
- OffsetEndIndex = 3,
154
- },
155
- ];
151
+ class LegPart : NeoObject {
152
+ public PantsSprite SelectPants() {
153
+ var child = this.Children.FirstOrDefault((candidate) => {
154
+ return candidate.Name == "Pants";
155
+ });
156
+ if (child is PantsSprite pants) {
157
+ return pants;
158
+ }
159
+ throw "Pants child is missing or has the wrong type";
160
+ }
161
+
162
+ public NeoAnimationClip<LegPart> Walk = new NeoAnimationClip<LegPart> {
163
+ Tracks = [
164
+ new StoredPantsTrack(selector: this.SelectPants) {
165
+ StartFrame = 0,
166
+ Direction = .Reverse,
167
+ OffsetEndIndex = 3,
168
+ },
169
+ ]
170
+ };
171
+ }
156
172
  ```
157
173
 
174
+ Selectors may also be compatible inline closures. Prefer a named function when
175
+ the identity rule is reused or deserves an explicit failure message. Never use
176
+ a selector merely to wrap `Reference<T>(id: "...")`; that recreates the
177
+ row-ID coupling selectors were introduced to remove. For new track rows, omit
178
+ `@id` and let the successful push assign it. Preserve IDs already present on
179
+ pulled rows.
180
+
158
181
  Each track has `StartFrame`, `Direction` (`.Forward` or `.Reverse`), and a
159
182
  crop window `OffsetStartIndex`/`OffsetEndIndex`. Crop before reversing, then
160
183
  schedule on the owning clip. Content beyond the owning clip truncates. Reject
161
184
  empty/inverted crop windows and tracks that can never enter the clip.
162
185
 
163
- Treat track target selection as class metadata, not row data. A target must be
164
- compatible with `TChild` and `TValue`. Track reads and segment getters are
165
- re-resolved for the next applied frame after a watched dependency changes.
186
+ Treat track target selection as project-authored identity logic, not row data.
187
+ The selector return type must be compatible with `TChild`; the segment value
188
+ must be compatible with `TValue`. `Refresh` controls selector reevaluation,
189
+ while track reads and segment getters are re-resolved for the next applied
190
+ frame after a watched dependency changes.
166
191
 
167
192
  When multiple tracks write the same member in one frame, apply list order and
168
193
  let the last write win.
@@ -41,10 +41,12 @@ Read the relevant specs in full:
41
41
  - [P50 loops](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p50-neoscript-for-and-foreach-loops.md)
42
42
  - [P51 switch](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p51-neoscript-switch-statements.md)
43
43
  - [P52 try/catch](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p52-neoscript-try-catch-blocks.md)
44
+ - [P60 delegate parameters and selector targeting](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p60-closure-parameters-and-selector-targeting.md)
45
+ - [P61 initializer materialization](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/complete/p61-initializer-materialization.md)
44
46
  - [P64 NeoScript unit testing and push hooks](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p64-neoscript-unit-testing-and-push-hooks.md)
45
47
 
46
- P38–P44, P47–P52, and the initial P64 test/push-hook vertical slice are
47
- implemented. P45 remains deliberately deferred; do not
48
+ P38–P44, P47–P52, P60–P61, and the initial P64 test/push-hook vertical slice
49
+ are implemented. P45 remains deliberately deferred; do not
48
50
  build or teach runtime-child provenance. P46 is a hardening proposal, not an
49
51
  authoring capability. P52's document header still says proposed, but try/catch
50
52
  shipped in CLI 0.18; implementation and parity tests take precedence over that
@@ -80,7 +82,7 @@ wrappers.
80
82
  The marker near the top of `SKILL.md` must exactly match the package version:
81
83
 
82
84
  ```html
83
- <!-- reviewed-through-cli: 0.24.0 -->
85
+ <!-- reviewed-through-cli: 0.24.1 -->
84
86
  ```
85
87
 
86
88
  The quoted version above is checked too, so this instruction cannot go stale
@@ -98,8 +98,11 @@ Reference<Dialogue>(id: "capitol-dialogue-id")
98
98
  - Both forms are legal in a member's declaration default, not only inside a
99
99
  value graph. A key written there resolves after every pass has run, so it may
100
100
  name a row the same push creates.
101
- - Keep structural references, such as child-track targets, ID-based when their
102
- collection has no stable key/path surface.
101
+ - Keep genuine structural references ID-based when their collection has no
102
+ stable symbol, path, or key surface. Animation track and frame-override
103
+ targets are selectors, not structural references: encode a stable
104
+ project-owned identity such as `Name` or slug in the selector instead of
105
+ wrapping a row ID in `Reference<T>(id: "...")`.
103
106
 
104
107
  Declaration and file order do not affect resolution within one push. Cyclic
105
108
  identity references are legal because they resolve IDs rather than evaluate a