unity-agentic-tools 0.3.1 → 0.3.2

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/dist/index.js CHANGED
@@ -76,6 +76,7 @@ __export(exports_scanner, {
76
76
  getNativeExtractCsharpTypes: () => getNativeExtractCsharpTypes,
77
77
  getNativeBuildTypeRegistry: () => getNativeBuildTypeRegistry,
78
78
  getNativeBuildPackageGuidCache: () => getNativeBuildPackageGuidCache,
79
+ getNativeBuildLocalPackageGuidCache: () => getNativeBuildLocalPackageGuidCache,
79
80
  getNativeBuildGuidCache: () => getNativeBuildGuidCache,
80
81
  UnityScanner: () => UnityScanner
81
82
  });
@@ -156,13 +157,16 @@ function getNativeExtractDllTypes() {
156
157
  function getNativeBuildPackageGuidCache() {
157
158
  return nativeBuildPackageGuidCache;
158
159
  }
160
+ function getNativeBuildLocalPackageGuidCache() {
161
+ return nativeBuildLocalPackageGuidCache;
162
+ }
159
163
  function getNativeExtractSerializedFields() {
160
164
  return nativeExtractSerializedFields;
161
165
  }
162
166
  function getNativeExtractDllFields() {
163
167
  return nativeExtractDllFields;
164
168
  }
165
- var import_module, import_fs, import_path, __dirname = "/home/runner/work/unity-agentic-tools/unity-agentic-tools/unity-agentic-tools/src", RustScanner = null, nativeModuleError = null, nativeWalkProjectFiles = null, nativeGrepProject = null, nativeBuildGuidCache = null, nativeExtractCsharpTypes = null, nativeBuildTypeRegistry = null, nativeExtractDllTypes = null, nativeBuildPackageGuidCache = null, nativeExtractSerializedFields = null, nativeExtractDllFields = null;
169
+ var import_module, import_fs, import_path, __dirname = "/home/runner/work/unity-agentic-tools/unity-agentic-tools/unity-agentic-tools/src", RustScanner = null, nativeModuleError = null, nativeWalkProjectFiles = null, nativeGrepProject = null, nativeBuildGuidCache = null, nativeExtractCsharpTypes = null, nativeBuildTypeRegistry = null, nativeExtractDllTypes = null, nativeBuildPackageGuidCache = null, nativeBuildLocalPackageGuidCache = null, nativeExtractSerializedFields = null, nativeExtractDllFields = null;
166
170
  var init_scanner = __esm(() => {
167
171
  import_module = require("module");
168
172
  import_fs = require("fs");
@@ -186,6 +190,7 @@ var init_scanner = __esm(() => {
186
190
  nativeBuildTypeRegistry = rustModule.buildTypeRegistry || null;
187
191
  nativeExtractDllTypes = rustModule.extractDllTypes || null;
188
192
  nativeBuildPackageGuidCache = rustModule.buildPackageGuidCache || null;
193
+ nativeBuildLocalPackageGuidCache = rustModule.buildLocalPackageGuidCache || null;
189
194
  nativeExtractSerializedFields = rustModule.extractSerializedFields || null;
190
195
  nativeExtractDllFields = rustModule.extractDllFields || null;
191
196
  } catch (err) {
@@ -200,7 +205,7 @@ var require_package = __commonJS((exports2, module2) => {
200
205
  module2.exports = {
201
206
  name: "unity-agentic-tools",
202
207
  packageManager: "bun@latest",
203
- version: "0.3.1",
208
+ version: "0.3.2",
204
209
  description: "Fast, token-efficient Unity YAML parser for AI agents",
205
210
  exports: {
206
211
  ".": {
@@ -330,6 +335,7 @@ var CONFIG_DIR = ".unity-agentic";
330
335
  var CONFIG_FILE = "config.json";
331
336
  var GUID_CACHE_FILE = "guid-cache.json";
332
337
  var PACKAGE_CACHE_FILE = "package-cache.json";
338
+ var LOCAL_PACKAGE_CACHE_FILE = "local-package-cache.json";
333
339
  var TYPE_REGISTRY_FILE = "type-registry.json";
334
340
  var DOC_INDEX_FILE = "doc-index.json";
335
341
  function setup(options = {}) {
@@ -373,6 +379,17 @@ function setup(options = {}) {
373
379
  packageGuidCount = Object.keys(packageCache).length;
374
380
  } catch {}
375
381
  }
382
+ const nativeLocalPkgBuild = getNativeBuildLocalPackageGuidCache();
383
+ if (nativeLocalPkgBuild) {
384
+ try {
385
+ const localPkgCache = nativeLocalPkgBuild(projectPath);
386
+ const localPkgCount = Object.keys(localPkgCache).length;
387
+ if (localPkgCount > 0) {
388
+ const localPkgCachePath = import_path2.join(configPath, LOCAL_PACKAGE_CACHE_FILE);
389
+ import_fs2.writeFileSync(localPkgCachePath, JSON.stringify(localPkgCache, null, 2));
390
+ }
391
+ } catch {}
392
+ }
376
393
  let typeRegistryCreated = false;
377
394
  let typeCount;
378
395
  const nativeRegistryBuild = getNativeBuildTypeRegistry();
@@ -555,31 +572,44 @@ function atomicWrite(filePath, content) {
555
572
  };
556
573
  }
557
574
  }
558
- const tmpPath = `${filePath}.tmp`;
575
+ const suffix = `${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
576
+ const tmpPath = `${filePath}.${suffix}.tmp`;
577
+ const bakPath = `${filePath}.${suffix}.bak`;
578
+ let bakCreated = false;
559
579
  try {
560
580
  import_fs4.writeFileSync(tmpPath, content, "utf-8");
561
- if (import_fs4.existsSync(filePath)) {
562
- import_fs4.renameSync(filePath, `${filePath}.bak`);
563
- }
564
- import_fs4.renameSync(tmpPath, filePath);
565
581
  try {
566
- if (import_fs4.existsSync(`${filePath}.bak`)) {
567
- import_fs4.unlinkSync(`${filePath}.bak`);
582
+ import_fs4.renameSync(filePath, bakPath);
583
+ bakCreated = true;
584
+ } catch (err) {
585
+ if (is_enoent(err)) {
586
+ bakCreated = false;
587
+ } else {
588
+ throw err;
568
589
  }
569
- } catch {}
590
+ }
591
+ import_fs4.renameSync(tmpPath, filePath);
592
+ if (bakCreated) {
593
+ try {
594
+ import_fs4.unlinkSync(bakPath);
595
+ } catch {}
596
+ }
570
597
  return {
571
598
  success: true,
572
599
  file_path: filePath,
573
600
  bytes_written: Buffer.byteLength(content, "utf-8")
574
601
  };
575
602
  } catch (error) {
576
- if (import_fs4.existsSync(`${filePath}.bak`)) {
603
+ if (bakCreated) {
577
604
  try {
578
- import_fs4.renameSync(`${filePath}.bak`, filePath);
605
+ import_fs4.renameSync(bakPath, filePath);
579
606
  } catch (restoreError) {
580
607
  console.error("Failed to restore backup:", restoreError);
581
608
  }
582
609
  }
610
+ try {
611
+ import_fs4.unlinkSync(tmpPath);
612
+ } catch {}
583
613
  return {
584
614
  success: false,
585
615
  file_path: filePath,
@@ -587,6 +617,12 @@ function atomicWrite(filePath, content) {
587
617
  };
588
618
  }
589
619
  }
620
+ function is_enoent(err) {
621
+ return err instanceof Error && "code" in err && err.code === "ENOENT";
622
+ }
623
+ function normalize_property_path(path) {
624
+ return path.replace(/\.Array\.data\.(\d+)/g, ".Array.data[$1]");
625
+ }
590
626
  function is_match_all(pattern) {
591
627
  return pattern === "*" || pattern === "." || pattern === "**" || pattern === ".*";
592
628
  }
@@ -2076,6 +2112,31 @@ function extractGuidFromMeta(metaPath) {
2076
2112
  return null;
2077
2113
  }
2078
2114
  }
2115
+ function resolve_source_prefab(doc, file_path, project_path) {
2116
+ const pi_blocks = doc.find_by_class_id(1001);
2117
+ if (pi_blocks.length === 0)
2118
+ return null;
2119
+ const pi_block = pi_blocks[0];
2120
+ const source_match = pi_block.raw.match(/m_SourcePrefab:[ \t]*\{[^}]*guid:[ \t]*([a-f0-9]{32})/);
2121
+ if (!source_match)
2122
+ return null;
2123
+ const source_guid = source_match[1];
2124
+ const resolved_project = project_path || find_unity_project_root(path3.dirname(file_path));
2125
+ if (!resolved_project)
2126
+ return null;
2127
+ const cache = load_guid_cache_for_file(file_path, resolved_project);
2128
+ if (!cache)
2129
+ return null;
2130
+ const source_path = cache.resolve_absolute(source_guid);
2131
+ if (!source_path || !import_fs8.existsSync(source_path))
2132
+ return null;
2133
+ return {
2134
+ source_guid,
2135
+ source_path,
2136
+ prefab_instance_id: pi_block.file_id,
2137
+ prefab_instance_block: pi_block
2138
+ };
2139
+ }
2079
2140
  function resolveScriptGuid(script, projectPath) {
2080
2141
  if (/^[a-f0-9]{32}$/i.test(script)) {
2081
2142
  return { guid: script.toLowerCase(), path: null };
@@ -2125,27 +2186,122 @@ function resolveScriptGuid(script, projectPath) {
2125
2186
  return false;
2126
2187
  return true;
2127
2188
  });
2128
- if (matches.length === 1 && matches[0].guid) {
2129
- return { guid: matches[0].guid, path: matches[0].filePath };
2189
+ if (matches.length === 1) {
2190
+ if (matches[0].guid) {
2191
+ return { guid: matches[0].guid, path: matches[0].filePath };
2192
+ }
2193
+ if (matches[0].filePath && projectPath) {
2194
+ const fullFilePath = path3.isAbsolute(matches[0].filePath) ? matches[0].filePath : path3.join(projectPath, matches[0].filePath);
2195
+ const metaPath = fullFilePath + ".meta";
2196
+ if (import_fs8.existsSync(metaPath)) {
2197
+ const guid = extractGuidFromMeta(metaPath);
2198
+ if (guid)
2199
+ return { guid, path: matches[0].filePath };
2200
+ }
2201
+ if (matches[0].filePath.startsWith("Packages/")) {
2202
+ const pkgPath = path3.join(projectPath, matches[0].filePath);
2203
+ const pkgMetaPath = pkgPath + ".meta";
2204
+ if (import_fs8.existsSync(pkgMetaPath)) {
2205
+ const guid = extractGuidFromMeta(pkgMetaPath);
2206
+ if (guid)
2207
+ return { guid, path: matches[0].filePath };
2208
+ }
2209
+ }
2210
+ if (matches[0].filePath?.endsWith(".dll")) {
2211
+ const dllBasename = path3.basename(matches[0].filePath).toLowerCase();
2212
+ for (const cacheName of ["package-cache.json", "local-package-cache.json"]) {
2213
+ const cachePath = path3.join(projectPath, ".unity-agentic", cacheName);
2214
+ if (!import_fs8.existsSync(cachePath))
2215
+ continue;
2216
+ try {
2217
+ const cache = JSON.parse(import_fs8.readFileSync(cachePath, "utf-8"));
2218
+ for (const [guid, assetPath] of Object.entries(cache)) {
2219
+ if (assetPath.toLowerCase().endsWith(".dll") && path3.basename(assetPath).toLowerCase() === dllBasename) {
2220
+ return { guid, path: assetPath };
2221
+ }
2222
+ }
2223
+ } catch {}
2224
+ }
2225
+ }
2226
+ }
2130
2227
  }
2131
2228
  if (matches.length > 1) {
2132
2229
  const withGuid = matches.filter((m) => m.guid);
2133
2230
  if (withGuid.length === 1) {
2134
2231
  return { guid: withGuid[0].guid, path: withGuid[0].filePath };
2135
2232
  }
2233
+ const resolvedFromMeta = matches.filter((m) => !m.guid && m.filePath).map((m) => {
2234
+ const fullPath = path3.isAbsolute(m.filePath) ? m.filePath : path3.join(projectPath, m.filePath);
2235
+ let guid = import_fs8.existsSync(fullPath + ".meta") ? extractGuidFromMeta(fullPath + ".meta") : null;
2236
+ if (!guid && m.filePath.startsWith("Packages/")) {
2237
+ const pkgPath = path3.join(projectPath, m.filePath);
2238
+ if (import_fs8.existsSync(pkgPath + ".meta")) {
2239
+ guid = extractGuidFromMeta(pkgPath + ".meta");
2240
+ }
2241
+ }
2242
+ if (!guid && m.filePath?.endsWith(".dll")) {
2243
+ const dllBase = path3.basename(m.filePath).toLowerCase();
2244
+ for (const cn of ["package-cache.json", "local-package-cache.json"]) {
2245
+ const cp = path3.join(projectPath, ".unity-agentic", cn);
2246
+ if (!import_fs8.existsSync(cp))
2247
+ continue;
2248
+ try {
2249
+ const c = JSON.parse(import_fs8.readFileSync(cp, "utf-8"));
2250
+ for (const [g, p] of Object.entries(c)) {
2251
+ if (p.toLowerCase().endsWith(".dll") && path3.basename(p).toLowerCase() === dllBase) {
2252
+ guid = g;
2253
+ break;
2254
+ }
2255
+ }
2256
+ } catch {}
2257
+ if (guid)
2258
+ break;
2259
+ }
2260
+ }
2261
+ return guid ? { guid, path: m.filePath } : null;
2262
+ }).filter((r) => r !== null);
2263
+ const allResolved = [...withGuid.map((m) => ({ guid: m.guid, path: m.filePath })), ...resolvedFromMeta];
2264
+ if (allResolved.length === 1) {
2265
+ return allResolved[0];
2266
+ }
2267
+ if (allResolved.length > 1) {
2268
+ const pkgCachePaths = [
2269
+ path3.join(projectPath, ".unity-agentic", "package-cache.json"),
2270
+ path3.join(projectPath, ".unity-agentic", "local-package-cache.json")
2271
+ ];
2272
+ for (const cachePath of pkgCachePaths) {
2273
+ if (!import_fs8.existsSync(cachePath))
2274
+ continue;
2275
+ try {
2276
+ const cache = JSON.parse(import_fs8.readFileSync(cachePath, "utf-8"));
2277
+ const inCache = allResolved.filter((r) => (r.guid in cache));
2278
+ if (inCache.length === 1)
2279
+ return inCache[0];
2280
+ } catch {}
2281
+ }
2282
+ const paths = allResolved.map((m) => m.path).join(", ");
2283
+ throw new Error(`Ambiguous type "${script}": found ${allResolved.length} matches (${paths}). ` + `Use a qualified name (e.g., "Namespace.${targetName}") or provide the full path.`);
2284
+ }
2136
2285
  }
2137
2286
  } catch {}
2138
2287
  }
2139
- const packageCachePath = path3.join(projectPath, ".unity-agentic", "package-cache.json");
2140
- if (import_fs8.existsSync(packageCachePath)) {
2288
+ const scriptNameLower = script.toLowerCase().replace(/\.cs$/, "");
2289
+ const dotIdx = scriptNameLower.lastIndexOf(".");
2290
+ const classNameOnly = dotIdx > 0 ? scriptNameLower.substring(dotIdx + 1) : null;
2291
+ const cachePaths = [
2292
+ path3.join(projectPath, ".unity-agentic", "package-cache.json"),
2293
+ path3.join(projectPath, ".unity-agentic", "local-package-cache.json")
2294
+ ];
2295
+ for (const cachePath of cachePaths) {
2296
+ if (!import_fs8.existsSync(cachePath))
2297
+ continue;
2141
2298
  try {
2142
- const packageCache = JSON.parse(import_fs8.readFileSync(packageCachePath, "utf-8"));
2143
- const scriptNameLower = script.toLowerCase().replace(/\.cs$/, "");
2144
- for (const [guid, assetPath] of Object.entries(packageCache)) {
2299
+ const cache = JSON.parse(import_fs8.readFileSync(cachePath, "utf-8"));
2300
+ for (const [guid, assetPath] of Object.entries(cache)) {
2145
2301
  if (!assetPath.endsWith(".cs"))
2146
2302
  continue;
2147
2303
  const fileName = path3.basename(assetPath, ".cs").toLowerCase();
2148
- if (fileName === scriptNameLower) {
2304
+ if (fileName === scriptNameLower || classNameOnly && fileName === classNameOnly) {
2149
2305
  return { guid, path: assetPath };
2150
2306
  }
2151
2307
  }
@@ -2196,6 +2352,8 @@ function resolve_script_with_fields(script, project_path) {
2196
2352
  result.fields = chosen.fields;
2197
2353
  result.base_class = chosen.baseClass ?? undefined;
2198
2354
  result.kind = chosen.kind;
2355
+ result.namespace = chosen.namespace ?? undefined;
2356
+ result.class_name = chosen.name;
2199
2357
  }
2200
2358
  } else {
2201
2359
  result.extraction_error = "Native extractDllFields function not available";
@@ -2391,6 +2549,16 @@ function yaml_default_for_type(csharp_type, version) {
2391
2549
  function generate_field_yaml(fields, version, indent = " ") {
2392
2550
  const lines = [];
2393
2551
  for (const field of fields) {
2552
+ if (field.hasSerializeReference) {
2553
+ const t = field.typeName;
2554
+ if (t.endsWith("[]") || t.startsWith("List<") && t.endsWith(">")) {
2555
+ lines.push(`${indent}${field.name}: []`);
2556
+ } else {
2557
+ lines.push(`${indent}${field.name}:`);
2558
+ lines.push(`${indent} rid: 0`);
2559
+ }
2560
+ continue;
2561
+ }
2394
2562
  const default_value = yaml_default_for_type(field.typeName, version);
2395
2563
  if (default_value === null) {
2396
2564
  continue;
@@ -2434,6 +2602,17 @@ function parse_header(raw) {
2434
2602
  function escape_regex(str) {
2435
2603
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2436
2604
  }
2605
+ function yaml_quote_if_needed(value) {
2606
+ if (value === "")
2607
+ return value;
2608
+ if (value.startsWith("{") && value.endsWith("}") || value.startsWith("[") && value.endsWith("]")) {
2609
+ return value;
2610
+ }
2611
+ if (value.includes(": ") || value.includes(" #") || /^[#\{\[*&!|>'"]/.test(value) || value !== value.trim()) {
2612
+ return `'${value.replace(/'/g, "''")}'`;
2613
+ }
2614
+ return value;
2615
+ }
2437
2616
 
2438
2617
  class UnityBlock {
2439
2618
  _raw;
@@ -2469,6 +2648,7 @@ class UnityBlock {
2469
2648
  return this._dirty;
2470
2649
  }
2471
2650
  get_property(path4) {
2651
+ path4 = normalize_property_path(path4);
2472
2652
  if (path4.includes("Array.data[")) {
2473
2653
  return this._get_array_element(path4);
2474
2654
  }
@@ -2495,13 +2675,17 @@ class UnityBlock {
2495
2675
  return match ? match[1].trim() : null;
2496
2676
  }
2497
2677
  set_property(path4, value, object_reference) {
2678
+ path4 = normalize_property_path(path4);
2498
2679
  const old_raw = this._raw;
2499
2680
  const effective_ref = object_reference && object_reference !== "{fileID: 0}" ? object_reference : undefined;
2500
2681
  if (!path4.includes(".") && !path4.includes("Array")) {
2501
2682
  const prop_pattern = new RegExp(`(^\\s*${escape_regex(path4)}:\\s*)(.*)$`, "m");
2502
2683
  if (prop_pattern.test(this._raw)) {
2503
- const replacement_value = effective_ref ?? value;
2504
- this._raw = this._raw.replace(prop_pattern, `$1${replacement_value}`);
2684
+ const replacement_value = effective_ref ?? yaml_quote_if_needed(value);
2685
+ this._raw = this._raw.replace(prop_pattern, (_match, prefix) => prefix + replacement_value);
2686
+ } else {
2687
+ const insert_value = effective_ref ?? yaml_quote_if_needed(value);
2688
+ this._insert_property(path4, insert_value);
2505
2689
  }
2506
2690
  return this._check_dirty(old_raw);
2507
2691
  }
@@ -2516,8 +2700,8 @@ class UnityBlock {
2516
2700
  const fields = inline_match[2];
2517
2701
  const field_pattern = new RegExp(`(${escape_regex(sub_field)}:\\s*)([^,}]+)`);
2518
2702
  if (field_pattern.test(fields)) {
2519
- const updated_fields = fields.replace(field_pattern, `$1${value}`);
2520
- this._raw = this._raw.replace(inline_pattern, `$1${updated_fields}$3`);
2703
+ const updated_fields = fields.replace(field_pattern, (_m, prefix) => prefix + value);
2704
+ this._raw = this._raw.replace(inline_pattern, (_m, g1, _g2, g3) => g1 + updated_fields + g3);
2521
2705
  inline_applied = true;
2522
2706
  }
2523
2707
  }
@@ -2526,6 +2710,8 @@ class UnityBlock {
2526
2710
  const block_result = this._resolve_block_style_path(parts, effective_value);
2527
2711
  if (block_result !== null) {
2528
2712
  this._raw = block_result;
2713
+ } else if (parts.length === 2) {
2714
+ this._insert_struct_property(parent_prop, sub_field, effective_value);
2529
2715
  }
2530
2716
  }
2531
2717
  return this._check_dirty(old_raw);
@@ -2543,7 +2729,7 @@ class UnityBlock {
2543
2729
  `).filter((l) => l.trim().startsWith("-"));
2544
2730
  if (index < lines.length) {
2545
2731
  const old_line = lines[index];
2546
- const new_line = old_line.replace(/-\s*.*/, `- ${ref_value}`);
2732
+ const new_line = old_line.replace(/-\s*.*/, () => `- ${ref_value}`);
2547
2733
  this._raw = this._raw.replace(old_line, new_line);
2548
2734
  }
2549
2735
  }
@@ -2552,6 +2738,44 @@ class UnityBlock {
2552
2738
  }
2553
2739
  return false;
2554
2740
  }
2741
+ _insert_property(path4, value) {
2742
+ const alt = path4.startsWith("m_") ? path4.slice(2) : "m_" + path4;
2743
+ const alt_pattern = new RegExp(`^\\s*${escape_regex(alt)}:`, "m");
2744
+ if (alt_pattern.test(this._raw)) {
2745
+ return;
2746
+ }
2747
+ const indented_line = ` ${path4}: ${value}`;
2748
+ const eci_pattern = /^([ \t]*m_EditorClassIdentifier:[ \t]*[^\n]*)/m;
2749
+ const eci_match = this._raw.match(eci_pattern);
2750
+ if (eci_match && eci_match.index !== undefined) {
2751
+ const insert_after = eci_match.index + eci_match[0].length;
2752
+ this._raw = this._raw.slice(0, insert_after) + `
2753
+ ` + indented_line + this._raw.slice(insert_after);
2754
+ return;
2755
+ }
2756
+ this._raw = this._raw.trimEnd() + `
2757
+ ` + indented_line + `
2758
+ `;
2759
+ }
2760
+ _insert_struct_property(parent, child, value) {
2761
+ const parent_pattern = new RegExp(`^[ ]*${escape_regex(parent)}:`, "m");
2762
+ if (parent_pattern.test(this._raw)) {
2763
+ return;
2764
+ }
2765
+ const struct_block = ` ${parent}:
2766
+ ${child}: ${value}`;
2767
+ const eci_pattern = /^([ \t]*m_EditorClassIdentifier:[ \t]*[^\n]*)/m;
2768
+ const eci_match = this._raw.match(eci_pattern);
2769
+ if (eci_match && eci_match.index !== undefined) {
2770
+ const insert_after = eci_match.index + eci_match[0].length;
2771
+ this._raw = this._raw.slice(0, insert_after) + `
2772
+ ` + struct_block + this._raw.slice(insert_after);
2773
+ return;
2774
+ }
2775
+ this._raw = this._raw.trimEnd() + `
2776
+ ` + struct_block + `
2777
+ `;
2778
+ }
2555
2779
  has_property(path4) {
2556
2780
  return this.get_property(path4) !== null;
2557
2781
  }
@@ -2607,7 +2831,7 @@ class UnityBlock {
2607
2831
  if (empty_match) {
2608
2832
  const base_indent = empty_match[2];
2609
2833
  const element_indent2 = base_indent + " ";
2610
- this._raw = this._raw.replace(empty_pattern, `$1
2834
+ this._raw = this._raw.replace(empty_pattern, (_m, g1) => `${g1}
2611
2835
  ${element_indent2}- ${value}`);
2612
2836
  return this._check_dirty(old_raw);
2613
2837
  }
@@ -3531,6 +3755,20 @@ function read_project_version(projectPath) {
3531
3755
  }
3532
3756
 
3533
3757
  // src/editor/create.ts
3758
+ function compute_dll_script_file_id(namespace, class_name) {
3759
+ const { createHash } = require("crypto");
3760
+ const full_name = namespace ? `${namespace}.${class_name}` : class_name;
3761
+ const name_bytes = Buffer.from(full_name, "utf-8");
3762
+ const input = Buffer.alloc(4 + name_bytes.length);
3763
+ input.writeInt32LE(114, 0);
3764
+ name_bytes.copy(input, 4);
3765
+ const hash = createHash("md4").update(input).digest();
3766
+ const a = hash.readInt32LE(0);
3767
+ const b = hash.readInt32LE(4);
3768
+ const c = hash.readInt32LE(8);
3769
+ const d = hash.readInt32LE(12);
3770
+ return a ^ b ^ c ^ d;
3771
+ }
3534
3772
  function get_layer_from_parent(doc, parentTransformId) {
3535
3773
  if (parentTransformId === "0")
3536
3774
  return 0;
@@ -3546,7 +3784,7 @@ function get_layer_from_parent(doc, parentTransformId) {
3546
3784
  const layerMatch = parentGo.raw.match(/m_Layer:\s*(\d+)/);
3547
3785
  return layerMatch ? parseInt(layerMatch[1], 10) : 0;
3548
3786
  }
3549
- function createGameObjectYAML(gameObjectId, transformId, name, parentTransformId = 0, rootOrder = 0, layer = 0) {
3787
+ function createGameObjectYAML(gameObjectId, transformId, name, parentTransformId = "0", rootOrder = 0, layer = 0) {
3550
3788
  return `--- !u!1 &${gameObjectId}
3551
3789
  GameObject:
3552
3790
  m_ObjectHideFlags: 0
@@ -3607,9 +3845,9 @@ function findStrippedRootTransform(doc, prefabInstanceId) {
3607
3845
  for (const block of doc.blocks) {
3608
3846
  if (block.class_id !== 4 || !block.is_stripped)
3609
3847
  continue;
3610
- const piMatch = block.raw.match(/m_PrefabInstance:\s*\{fileID:\s*(\d+)\}/);
3848
+ const piMatch = block.raw.match(/m_PrefabInstance:[ \t]*\{fileID:[ \t]*(-?\d+)\}/);
3611
3849
  if (piMatch && piMatch[1] === prefabInstanceId) {
3612
- const sourceMatch = block.raw.match(/m_CorrespondingSourceObject:\s*(\{[^}]+\})/);
3850
+ const sourceMatch = block.raw.match(/m_CorrespondingSourceObject:[ \t]*(\{[^}]+\})/);
3613
3851
  if (sourceMatch) {
3614
3852
  return {
3615
3853
  transformId: block.file_id,
@@ -3620,6 +3858,66 @@ function findStrippedRootTransform(doc, prefabInstanceId) {
3620
3858
  }
3621
3859
  return null;
3622
3860
  }
3861
+ function find_game_object_in_variant(doc, name, file_path, project_path) {
3862
+ const pi_blocks = doc.find_by_class_id(1001);
3863
+ for (const pi_block of pi_blocks) {
3864
+ const mod_pattern = /- target:[ \t]*(\{[^}]+\})\s*propertyPath:[ \t]*m_Name\s*value:[ \t]*([^\n]*)/g;
3865
+ let mod_match;
3866
+ while ((mod_match = mod_pattern.exec(pi_block.raw)) !== null) {
3867
+ const target_ref = mod_match[1];
3868
+ const mod_value = mod_match[2].trim();
3869
+ if (mod_value !== name)
3870
+ continue;
3871
+ for (const block of doc.blocks) {
3872
+ if (block.class_id !== 1 || !block.is_stripped)
3873
+ continue;
3874
+ const source_match = block.raw.match(/m_CorrespondingSourceObject:[ \t]*(\{[^}]+\})/);
3875
+ if (!source_match)
3876
+ continue;
3877
+ const normalized_source = source_match[1].replace(/\s+/g, " ");
3878
+ const normalized_target = target_ref.replace(/\s+/g, " ");
3879
+ if (normalized_source === normalized_target) {
3880
+ return {
3881
+ stripped_go_id: block.file_id,
3882
+ source_ref: target_ref,
3883
+ prefab_instance_id: pi_block.file_id
3884
+ };
3885
+ }
3886
+ }
3887
+ }
3888
+ }
3889
+ const source_info = resolve_source_prefab(doc, file_path, project_path);
3890
+ if (!source_info)
3891
+ return null;
3892
+ let source_doc;
3893
+ try {
3894
+ source_doc = UnityDocument.from_file(source_info.source_path);
3895
+ } catch {
3896
+ return null;
3897
+ }
3898
+ const source_gos = source_doc.find_game_objects_by_name(name);
3899
+ if (source_gos.length !== 1)
3900
+ return null;
3901
+ const source_go = source_gos[0];
3902
+ const source_go_file_id = source_go.file_id;
3903
+ const source_guid = source_info.source_guid;
3904
+ for (const block of doc.blocks) {
3905
+ if (block.class_id !== 1 || !block.is_stripped)
3906
+ continue;
3907
+ const source_match = block.raw.match(/m_CorrespondingSourceObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)[^}]*guid:[ \t]*([a-f0-9]{32})/);
3908
+ if (!source_match)
3909
+ continue;
3910
+ if (source_match[1] === source_go_file_id && source_match[2] === source_guid) {
3911
+ const source_ref = `{fileID: ${source_go_file_id}, guid: ${source_guid}, type: 3}`;
3912
+ return {
3913
+ stripped_go_id: block.file_id,
3914
+ source_ref,
3915
+ prefab_instance_id: source_info.prefab_instance_id
3916
+ };
3917
+ }
3918
+ }
3919
+ return null;
3920
+ }
3623
3921
  function appendToAddedGameObjects(doc, piId, targetSourceRef, newGoId) {
3624
3922
  const piBlock = doc.find_by_file_id(piId);
3625
3923
  if (!piBlock)
@@ -3629,13 +3927,34 @@ function appendToAddedGameObjects(doc, piId, targetSourceRef, newGoId) {
3629
3927
  insertIndex: -1
3630
3928
  addedObject: {fileID: ${newGoId}}`;
3631
3929
  let raw = piBlock.raw;
3632
- const emptyPattern = /m_AddedGameObjects:\s*\[\]/;
3930
+ const emptyPattern = /m_AddedGameObjects:[ \t]*\[\]/;
3633
3931
  if (emptyPattern.test(raw)) {
3634
3932
  raw = raw.replace(emptyPattern, `m_AddedGameObjects:${entry}`);
3635
3933
  piBlock.replace_raw(raw);
3636
3934
  return;
3637
3935
  }
3638
- const existingPattern = /(m_AddedGameObjects:\s*\n(?:\s+-[\s\S]*?(?=\n\s+m_|$))*)/;
3936
+ const existingPattern = /(m_AddedGameObjects:[ \t]*\n(?:[ \t]+-[\s\S]*?(?=\n[ \t]+m_|$))*)/;
3937
+ if (existingPattern.test(raw)) {
3938
+ raw = raw.replace(existingPattern, `$1${entry}`);
3939
+ piBlock.replace_raw(raw);
3940
+ }
3941
+ }
3942
+ function appendToAddedComponents(doc, piId, targetSourceRef, newComponentId) {
3943
+ const piBlock = doc.find_by_file_id(piId);
3944
+ if (!piBlock)
3945
+ return;
3946
+ const entry = `
3947
+ - targetCorrespondingSourceObject: ${targetSourceRef}
3948
+ insertIndex: -1
3949
+ addedObject: {fileID: ${newComponentId}}`;
3950
+ let raw = piBlock.raw;
3951
+ const emptyPattern = /m_AddedComponents:[ \t]*\[\]/;
3952
+ if (emptyPattern.test(raw)) {
3953
+ raw = raw.replace(emptyPattern, `m_AddedComponents:${entry}`);
3954
+ piBlock.replace_raw(raw);
3955
+ return;
3956
+ }
3957
+ const existingPattern = /(m_AddedComponents:[ \t]*\n(?:[ \t]+-[\s\S]*?(?=\n[ \t]+m_|$))*)/;
3639
3958
  if (existingPattern.test(raw)) {
3640
3959
  raw = raw.replace(existingPattern, `$1${entry}`);
3641
3960
  piBlock.replace_raw(raw);
@@ -3658,11 +3977,31 @@ var COMPONENT_DEFAULTS = {
3658
3977
  m_Materials:
3659
3978
  - {fileID: 0}`,
3660
3979
  33: ` m_Mesh: {fileID: 0}`,
3980
+ 50: ` m_BodyType: 0
3981
+ m_Mass: 1
3982
+ m_LinearDrag: 0
3983
+ m_AngularDrag: 0.05
3984
+ m_GravityScale: 1
3985
+ m_Interpolate: 0
3986
+ m_SleepingMode: 1
3987
+ m_CollisionDetection: 0`,
3661
3988
  54: ` m_Mass: 1
3662
3989
  m_Drag: 0
3663
3990
  m_AngularDrag: 0.05
3664
3991
  m_UseGravity: 1
3665
3992
  m_IsKinematic: 0`,
3993
+ 58: ` m_IsTrigger: 0
3994
+ m_Material: {fileID: 0}
3995
+ m_Offset: {x: 0, y: 0}
3996
+ m_Radius: 0.5`,
3997
+ 60: ` m_IsTrigger: 0
3998
+ m_Material: {fileID: 0}
3999
+ m_Offset: {x: 0, y: 0}
4000
+ m_Points: []`,
4001
+ 61: ` m_IsTrigger: 0
4002
+ m_Material: {fileID: 0}
4003
+ m_Offset: {x: 0, y: 0}
4004
+ m_Size: {x: 1, y: 1}`,
3666
4005
  64: ` m_IsTrigger: 0
3667
4006
  m_Convex: 0
3668
4007
  m_CookingOptions: 30
@@ -3671,6 +4010,19 @@ var COMPONENT_DEFAULTS = {
3671
4010
  m_Material: {fileID: 0}
3672
4011
  m_Center: {x: 0, y: 0, z: 0}
3673
4012
  m_Size: {x: 1, y: 1, z: 1}`,
4013
+ 66: ` m_IsTrigger: 0
4014
+ m_Material: {fileID: 0}
4015
+ m_Offset: {x: 0, y: 0}
4016
+ m_GeometryType: 0`,
4017
+ 68: ` m_IsTrigger: 0
4018
+ m_Material: {fileID: 0}
4019
+ m_Offset: {x: 0, y: 0}
4020
+ m_Points: []`,
4021
+ 70: ` m_IsTrigger: 0
4022
+ m_Material: {fileID: 0}
4023
+ m_Offset: {x: 0, y: 0}
4024
+ m_Size: {x: 1, y: 1}
4025
+ m_Direction: 0`,
3674
4026
  82: ` m_PlayOnAwake: 1
3675
4027
  m_Volume: 1
3676
4028
  m_Pitch: 1
@@ -3756,9 +4108,14 @@ function addComponentToGameObject(doc, gameObjectId, componentId) {
3756
4108
  `);
3757
4109
  goBlock.replace_raw(raw);
3758
4110
  }
3759
- function createMonoBehaviourYAML(componentId, gameObjectId, scriptGuid, fields, version) {
4111
+ function createMonoBehaviourYAML(componentId, gameObjectId, scriptGuid, fields, version, scriptFileId = 11500000) {
3760
4112
  const field_yaml = fields && fields.length > 0 ? generate_field_yaml(fields, version) : `
3761
4113
  `;
4114
+ const has_serialize_ref = fields?.some((f) => f.hasSerializeReference) ?? false;
4115
+ const references_section = has_serialize_ref ? ` references:
4116
+ version: 2
4117
+ RefIds: []
4118
+ ` : "";
3762
4119
  return `--- !u!114 &${componentId}
3763
4120
  MonoBehaviour:
3764
4121
  m_ObjectHideFlags: 0
@@ -3768,9 +4125,9 @@ MonoBehaviour:
3768
4125
  m_GameObject: {fileID: ${gameObjectId}}
3769
4126
  m_Enabled: 1
3770
4127
  m_EditorHideFlags: 0
3771
- m_Script: {fileID: 11500000, guid: ${scriptGuid}, type: 3}
4128
+ m_Script: {fileID: ${scriptFileId}, guid: ${scriptGuid}, type: 3}
3772
4129
  m_Name:
3773
- m_EditorClassIdentifier:${field_yaml}`;
4130
+ m_EditorClassIdentifier:${field_yaml}${references_section}`;
3774
4131
  }
3775
4132
  function createGameObject(options) {
3776
4133
  const { file_path, name, parent } = options;
@@ -3869,9 +4226,9 @@ function createGameObject(options) {
3869
4226
  const layer = get_layer_from_parent(doc, parentTransformIdStr);
3870
4227
  const gameObjectIdStr = doc.generate_file_id();
3871
4228
  const transformIdStr = doc.generate_file_id();
3872
- const gameObjectId = parseInt(gameObjectIdStr, 10);
3873
- const transformId = parseInt(transformIdStr, 10);
3874
- const parentTransformId = parseInt(parentTransformIdStr, 10);
4229
+ const gameObjectId = gameObjectIdStr;
4230
+ const transformId = transformIdStr;
4231
+ const parentTransformId = parentTransformIdStr;
3875
4232
  const newBlocks = createGameObjectYAML(gameObjectId, transformId, name.trim(), parentTransformId, rootOrder, layer);
3876
4233
  doc.append_raw(newBlocks);
3877
4234
  if (parentTransformIdStr !== "0" && !variantPiId) {
@@ -3893,7 +4250,7 @@ function createGameObject(options) {
3893
4250
  file_path,
3894
4251
  game_object_id: gameObjectId,
3895
4252
  transform_id: transformId,
3896
- prefab_instance_id: variantPiId ? parseInt(variantPiId, 10) : undefined
4253
+ prefab_instance_id: variantPiId || undefined
3897
4254
  };
3898
4255
  }
3899
4256
  function createScene(options) {
@@ -4339,9 +4696,9 @@ function createPrefabVariant(options) {
4339
4696
  const tempDoc = UnityDocument.from_string(`%YAML 1.1
4340
4697
  %TAG !u! tag:unity3d.com,2011:
4341
4698
  `);
4342
- const prefabInstanceId = parseInt(tempDoc.generate_file_id(), 10);
4343
- const strippedGoId = parseInt(tempDoc.generate_file_id(), 10);
4344
- const strippedTransformId = parseInt(tempDoc.generate_file_id(), 10);
4699
+ const prefabInstanceId = tempDoc.generate_file_id();
4700
+ const strippedGoId = tempDoc.generate_file_id();
4701
+ const strippedTransformId = tempDoc.generate_file_id();
4345
4702
  const finalName = variant_name || `${rootInfo.name} Variant`;
4346
4703
  const variantYaml = `%YAML 1.1
4347
4704
  %TAG !u! tag:unity3d.com,2011:
@@ -4457,7 +4814,7 @@ function createScriptableObject(options) {
4457
4814
  const baseName = path5.basename(output_path, ".asset");
4458
4815
  const field_yaml = resolved.fields && resolved.fields.length > 0 ? generate_field_yaml(resolved.fields, version) : `
4459
4816
  `;
4460
- const assetYaml = `%YAML 1.1
4817
+ let assetYaml = `%YAML 1.1
4461
4818
  %TAG !u! tag:unity3d.com,2011:
4462
4819
  --- !u!114 &11400000
4463
4820
  MonoBehaviour:
@@ -4471,6 +4828,16 @@ MonoBehaviour:
4471
4828
  m_Script: {fileID: 11500000, guid: ${resolved.guid}, type: 3}
4472
4829
  m_Name: ${baseName}
4473
4830
  m_EditorClassIdentifier:${field_yaml}`;
4831
+ if (options.initial_values && Object.keys(options.initial_values).length > 0) {
4832
+ const doc = UnityDocument.from_string(assetYaml);
4833
+ const block = doc.blocks[0];
4834
+ if (block) {
4835
+ for (const [key, val] of Object.entries(options.initial_values)) {
4836
+ block.set_property(key, val);
4837
+ }
4838
+ assetYaml = doc.serialize();
4839
+ }
4840
+ }
4474
4841
  try {
4475
4842
  import_fs10.writeFileSync(output_path, assetYaml, "utf-8");
4476
4843
  } catch (err) {
@@ -4551,6 +4918,80 @@ MonoImporter:
4551
4918
  guid
4552
4919
  };
4553
4920
  }
4921
+ var COMPONENT_BASE_CLASSES = new Set([
4922
+ "MonoBehaviour",
4923
+ "NetworkBehaviour",
4924
+ "StateMachineBehaviour"
4925
+ ]);
4926
+ function is_valid_component_base(base_class, project_path, depth = 0) {
4927
+ if (depth > 5)
4928
+ return false;
4929
+ if (COMPONENT_BASE_CLASSES.has(base_class))
4930
+ return true;
4931
+ if (!project_path)
4932
+ return false;
4933
+ const registryPath = path5.join(project_path, ".unity-agentic", "type-registry.json");
4934
+ if (import_fs10.existsSync(registryPath)) {
4935
+ try {
4936
+ const registry = JSON.parse(import_fs10.readFileSync(registryPath, "utf-8"));
4937
+ const match = registry.find((t) => t.name === base_class && t.kind === "class");
4938
+ if (match?.filePath) {
4939
+ const fullPath = path5.isAbsolute(match.filePath) ? match.filePath : path5.join(project_path, match.filePath);
4940
+ if (fullPath.endsWith(".cs") && import_fs10.existsSync(fullPath)) {
4941
+ const { getNativeExtractSerializedFields: getNativeExtractSerializedFields2 } = (init_scanner(), __toCommonJS(exports_scanner));
4942
+ const extract = getNativeExtractSerializedFields2();
4943
+ if (extract) {
4944
+ const typeInfos = extract(fullPath);
4945
+ const info = typeInfos?.find((t) => t.name === base_class);
4946
+ if (info?.baseClass) {
4947
+ return is_valid_component_base(info.baseClass, project_path, depth + 1);
4948
+ }
4949
+ }
4950
+ }
4951
+ if (!fullPath.endsWith(".cs") || !import_fs10.existsSync(fullPath)) {
4952
+ if (match.kind === "class") {
4953
+ return is_likely_component_base_name(base_class);
4954
+ }
4955
+ }
4956
+ }
4957
+ } catch {}
4958
+ }
4959
+ const cachePaths = [
4960
+ path5.join(project_path, ".unity-agentic", "package-cache.json"),
4961
+ path5.join(project_path, ".unity-agentic", "local-package-cache.json")
4962
+ ];
4963
+ for (const cachePath of cachePaths) {
4964
+ if (!import_fs10.existsSync(cachePath))
4965
+ continue;
4966
+ try {
4967
+ const cache = JSON.parse(import_fs10.readFileSync(cachePath, "utf-8"));
4968
+ const baseClassLower = base_class.toLowerCase();
4969
+ for (const [, assetPath] of Object.entries(cache)) {
4970
+ if (!assetPath.endsWith(".cs"))
4971
+ continue;
4972
+ if (path5.basename(assetPath, ".cs").toLowerCase() !== baseClassLower)
4973
+ continue;
4974
+ const fullPath = path5.join(project_path, assetPath);
4975
+ if (!import_fs10.existsSync(fullPath))
4976
+ continue;
4977
+ const { getNativeExtractSerializedFields: getNativeExtractSerializedFields2 } = (init_scanner(), __toCommonJS(exports_scanner));
4978
+ const extract = getNativeExtractSerializedFields2();
4979
+ if (!extract)
4980
+ continue;
4981
+ const typeInfos = extract(fullPath);
4982
+ const info = typeInfos?.find((t) => t.name === base_class);
4983
+ if (info?.baseClass) {
4984
+ return is_valid_component_base(info.baseClass, project_path, depth + 1);
4985
+ }
4986
+ }
4987
+ } catch {}
4988
+ }
4989
+ return false;
4990
+ }
4991
+ function is_likely_component_base_name(name) {
4992
+ const lower = name.toLowerCase();
4993
+ return lower.endsWith("behaviour") || lower.endsWith("behavior") || lower.includes("monobehaviour") || lower.includes("networkbehaviour");
4994
+ }
4554
4995
  function addComponent(options) {
4555
4996
  const { file_path, game_object_name, component_type } = options;
4556
4997
  const project_path = options.project_path || find_unity_project_root(path5.dirname(file_path)) || undefined;
@@ -4576,11 +5017,19 @@ function addComponent(options) {
4576
5017
  };
4577
5018
  }
4578
5019
  const goResult = doc.require_unique_game_object(game_object_name);
5020
+ let gameObjectIdStr;
5021
+ let variantInfo = null;
4579
5022
  if ("error" in goResult) {
4580
- return { success: false, file_path, error: goResult.error };
5023
+ const vResult = find_game_object_in_variant(doc, game_object_name, file_path, project_path);
5024
+ if (!vResult) {
5025
+ return { success: false, file_path, error: goResult.error };
5026
+ }
5027
+ gameObjectIdStr = vResult.stripped_go_id;
5028
+ variantInfo = { source_ref: vResult.source_ref, prefab_instance_id: vResult.prefab_instance_id };
5029
+ } else {
5030
+ gameObjectIdStr = goResult.file_id;
4581
5031
  }
4582
- const gameObjectIdStr = goResult.file_id;
4583
- const gameObjectId = parseInt(gameObjectIdStr, 10);
5032
+ const gameObjectId = gameObjectIdStr;
4584
5033
  const classId = get_class_id(component_type);
4585
5034
  let duplicateWarning;
4586
5035
  const goBlock = doc.find_by_file_id(gameObjectIdStr);
@@ -4595,7 +5044,7 @@ function addComponent(options) {
4595
5044
  }
4596
5045
  }
4597
5046
  const componentIdStr = doc.generate_file_id();
4598
- const componentId = parseInt(componentIdStr, 10);
5047
+ const componentId = componentIdStr;
4599
5048
  let componentYAML;
4600
5049
  let scriptGuid;
4601
5050
  let scriptPath;
@@ -4604,7 +5053,16 @@ function addComponent(options) {
4604
5053
  const componentName = UNITY_CLASS_IDS[classId] || component_type;
4605
5054
  componentYAML = createGenericComponentYAML(componentName, classId, componentId, gameObjectId);
4606
5055
  } else {
4607
- const resolved = resolve_script_with_fields(component_type, project_path);
5056
+ let resolved;
5057
+ try {
5058
+ resolved = resolve_script_with_fields(component_type, project_path);
5059
+ } catch (e) {
5060
+ return {
5061
+ success: false,
5062
+ file_path,
5063
+ error: e instanceof Error ? e.message : String(e)
5064
+ };
5065
+ }
4608
5066
  if (!resolved) {
4609
5067
  const hints = [];
4610
5068
  if (project_path) {
@@ -4631,11 +5089,11 @@ function addComponent(options) {
4631
5089
  error: `"${component_type}" is ${resolved.kind === "enum" ? "an enum" : "an interface"}, not a MonoBehaviour. Cannot add as a component.`
4632
5090
  };
4633
5091
  }
4634
- if (resolved.base_class && !["MonoBehaviour", "NetworkBehaviour", "StateMachineBehaviour"].includes(resolved.base_class)) {
5092
+ if (resolved.base_class && !COMPONENT_BASE_CLASSES.has(resolved.base_class) && !is_valid_component_base(resolved.base_class, project_path)) {
4635
5093
  return {
4636
5094
  success: false,
4637
5095
  file_path,
4638
- error: `"${component_type}" extends ${resolved.base_class}, not MonoBehaviour. Cannot add as a component.`
5096
+ error: `"${component_type}" extends ${resolved.base_class}, which does not inherit from MonoBehaviour. Cannot add as a component. If this is incorrect, ensure the type registry is up to date ("setup" command).`
4639
5097
  };
4640
5098
  }
4641
5099
  let version;
@@ -4644,12 +5102,20 @@ function addComponent(options) {
4644
5102
  version = read_project_version(project_path);
4645
5103
  } catch {}
4646
5104
  }
4647
- componentYAML = createMonoBehaviourYAML(componentId, gameObjectId, resolved.guid, resolved.fields, version);
5105
+ let scriptFileId = 11500000;
5106
+ if (resolved.path?.endsWith(".dll") && resolved.class_name) {
5107
+ scriptFileId = compute_dll_script_file_id(resolved.namespace ?? "", resolved.class_name);
5108
+ }
5109
+ componentYAML = createMonoBehaviourYAML(componentId, gameObjectId, resolved.guid, resolved.fields, version, scriptFileId);
4648
5110
  scriptGuid = resolved.guid;
4649
5111
  scriptPath = resolved.path || undefined;
4650
5112
  extractionError = resolved.extraction_error;
4651
5113
  }
4652
- addComponentToGameObject(doc, gameObjectIdStr, componentIdStr);
5114
+ if (variantInfo) {
5115
+ appendToAddedComponents(doc, variantInfo.prefab_instance_id, variantInfo.source_ref, componentIdStr);
5116
+ } else {
5117
+ addComponentToGameObject(doc, gameObjectIdStr, componentIdStr);
5118
+ }
4653
5119
  doc.append_raw(componentYAML);
4654
5120
  const saveResult = doc.save();
4655
5121
  if (!saveResult.success) {
@@ -4726,6 +5192,14 @@ function copyComponent(options) {
4726
5192
  }
4727
5193
  // src/editor/update.ts
4728
5194
  var import_fs11 = require("fs");
5195
+ function enrich_target_ref(targetRef, blockRaw) {
5196
+ if (/guid:/.test(targetRef))
5197
+ return targetRef;
5198
+ const sourceMatch = blockRaw.match(/m_SourcePrefab:[ \t]*\{[^}]*guid:[ \t]*([a-f0-9]{32})/);
5199
+ if (!sourceMatch)
5200
+ return null;
5201
+ return targetRef.replace(/\}$/, `, guid: ${sourceMatch[1]}, type: 3}`);
5202
+ }
4729
5203
  function eulerToQuaternion(euler) {
4730
5204
  const deg2rad = Math.PI / 180;
4731
5205
  const x = euler.x * deg2rad;
@@ -4748,6 +5222,9 @@ function validate_value_type(current_value, new_value) {
4748
5222
  const current = current_value.trim();
4749
5223
  const incoming = new_value.trim();
4750
5224
  if (/^\{fileID:/.test(current)) {
5225
+ if (/^\{fileID:[ \t]*0[ \t]*\}$/.test(current)) {
5226
+ return null;
5227
+ }
4751
5228
  if (!/^\{fileID:/.test(incoming)) {
4752
5229
  return `Expected a reference value ({fileID: ...}), got "${incoming}"`;
4753
5230
  }
@@ -4932,10 +5409,10 @@ function safeUnityYAMLEdit(filePath, objectName, propertyName, newValue, project
4932
5409
  const propertyPattern = new RegExp(`(^\\s*m_${normalizedProperty}:\\s*)([^\\n]*)`, "m");
4933
5410
  let updatedRaw = targetBlock.raw;
4934
5411
  if (propertyPattern.test(updatedRaw)) {
4935
- updatedRaw = updatedRaw.replace(propertyPattern, `$1${newValue}`);
5412
+ updatedRaw = updatedRaw.replace(propertyPattern, (_m, prefix) => prefix + newValue);
4936
5413
  } else {
4937
- updatedRaw = updatedRaw.replace(/(\n)(--- !u!|$)/, `
4938
- m_${normalizedProperty}: ${newValue}$1$2`);
5414
+ updatedRaw = updatedRaw.replace(/(\n)(--- !u!|$)/, (_m, g1, g2) => `
5415
+ m_${normalizedProperty}: ${newValue}${g1}${g2}`);
4939
5416
  }
4940
5417
  targetBlock.replace_raw(updatedRaw);
4941
5418
  const saveResult = doc.save();
@@ -5021,7 +5498,9 @@ function editComponentByFileId(options) {
5021
5498
  };
5022
5499
  }
5023
5500
  }
5024
- const currentValue = targetBlock.get_property(exactProperty) ?? (exactProperty !== prefixedProperty ? targetBlock.get_property(prefixedProperty) : null);
5501
+ const exactValue = targetBlock.get_property(exactProperty);
5502
+ const resolvedProperty = exactValue !== null ? exactProperty : exactProperty !== prefixedProperty && targetBlock.get_property(prefixedProperty) !== null ? prefixedProperty : null;
5503
+ const currentValue = exactValue ?? (resolvedProperty === prefixedProperty ? targetBlock.get_property(prefixedProperty) : null);
5025
5504
  if (currentValue !== null) {
5026
5505
  const typeError = validate_value_type(currentValue, new_value);
5027
5506
  if (typeError) {
@@ -5032,9 +5511,14 @@ function editComponentByFileId(options) {
5032
5511
  };
5033
5512
  }
5034
5513
  }
5035
- let modified = targetBlock.set_property(exactProperty, new_value, "{fileID: 0}");
5036
- if (!modified && exactProperty !== prefixedProperty) {
5037
- modified = targetBlock.set_property(prefixedProperty, new_value, "{fileID: 0}");
5514
+ let modified;
5515
+ if (resolvedProperty) {
5516
+ modified = targetBlock.set_property(resolvedProperty, new_value, "{fileID: 0}");
5517
+ } else {
5518
+ modified = targetBlock.set_property(exactProperty, new_value, "{fileID: 0}");
5519
+ if (!modified && exactProperty !== prefixedProperty) {
5520
+ modified = targetBlock.set_property(prefixedProperty, new_value, "{fileID: 0}");
5521
+ }
5038
5522
  }
5039
5523
  if (!modified) {
5040
5524
  if (currentValue !== null) {
@@ -5071,8 +5555,10 @@ function editComponentByFileId(options) {
5071
5555
  };
5072
5556
  }
5073
5557
  function editPrefabOverride(options) {
5074
- const { file_path, prefab_instance, property_path, new_value, object_reference, target } = options;
5075
- const objRef = object_reference ?? "{fileID: 0}";
5558
+ const { file_path, prefab_instance, new_value, object_reference, target, managed_reference } = options;
5559
+ const property_path = /[\[\]]/.test(options.property_path) && !options.property_path.startsWith("'") ? `'${options.property_path}'` : options.property_path;
5560
+ const effectiveValue = managed_reference ?? new_value;
5561
+ const objRef = managed_reference ? "{fileID: 0}" : object_reference ?? "{fileID: 0}";
5076
5562
  if (!import_fs11.existsSync(file_path)) {
5077
5563
  return { success: false, file_path, error: `File not found: ${file_path}` };
5078
5564
  }
@@ -5086,11 +5572,16 @@ function editPrefabOverride(options) {
5086
5572
  if (!targetBlock) {
5087
5573
  return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
5088
5574
  }
5089
- const escapedPath = property_path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5090
- const entryPattern = new RegExp(`(- target:\\s*\\{[^}]+\\}\\s*\\n\\s*propertyPath:\\s*)${escapedPath}(\\s*\\n\\s*value:\\s*)(.*)(\\s*\\n\\s*objectReference:\\s*)(.*)`, "m");
5575
+ const unquotedPath = property_path.replace(/^'|'$/g, "");
5576
+ const quotedPath = `'${unquotedPath}'`;
5577
+ const escapedUnquoted = unquotedPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5578
+ const escapedQuoted = quotedPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5579
+ const pathAlternation = escapedUnquoted === escapedQuoted ? escapedUnquoted : `(?:${escapedQuoted}|${escapedUnquoted})`;
5580
+ const entryPattern = new RegExp(`(- target:\\s*\\{[^}]+\\}\\s*\\n\\s*propertyPath:\\s*)${pathAlternation}(\\s*\\n\\s*value:\\s*)(.*)(\\s*\\n\\s*objectReference:\\s*)(.*)`, "m");
5091
5581
  const entryMatch = targetBlock.raw.match(entryPattern);
5092
5582
  if (entryMatch) {
5093
- const updatedText2 = targetBlock.raw.replace(entryPattern, `$1${property_path}$2${new_value}$4${objRef}`);
5583
+ const quotedValue = yaml_quote_if_needed(effectiveValue);
5584
+ const updatedText2 = targetBlock.raw.replace(entryPattern, (_m, g1, g2, _g3, g4) => g1 + property_path + g2 + quotedValue + g4 + objRef);
5094
5585
  targetBlock.replace_raw(updatedText2);
5095
5586
  const saveResult2 = doc.save();
5096
5587
  if (!saveResult2.success) {
@@ -5127,9 +5618,13 @@ function editPrefabOverride(options) {
5127
5618
  error: `Invalid target reference "${targetRef}". Expected format: {fileID: N, guid: ..., type: T}`
5128
5619
  };
5129
5620
  }
5621
+ const enriched = enrich_target_ref(targetRef, targetBlock.raw);
5622
+ if (enriched)
5623
+ targetRef = enriched;
5624
+ const quotedNewValue = yaml_quote_if_needed(effectiveValue);
5130
5625
  const newEntry = ` - target: ${targetRef}
5131
5626
  propertyPath: ${property_path}
5132
- value: ${new_value}
5627
+ value: ${quotedNewValue}
5133
5628
  objectReference: ${objRef}`;
5134
5629
  const removedPattern = /(\n\s*m_RemovedComponents:)/m;
5135
5630
  const removedMatch = targetBlock.raw.match(removedPattern);
@@ -5272,10 +5767,10 @@ function batchEditProperties(filePath, edits) {
5272
5767
  const propertyPattern = new RegExp(`(^\\s*m_${normalizedProperty}:\\s*)([^\\n]*)`, "m");
5273
5768
  let updatedRaw = targetBlock.raw;
5274
5769
  if (propertyPattern.test(updatedRaw)) {
5275
- updatedRaw = updatedRaw.replace(propertyPattern, `$1${edit.new_value}`);
5770
+ updatedRaw = updatedRaw.replace(propertyPattern, (_m, prefix) => prefix + edit.new_value);
5276
5771
  } else {
5277
- updatedRaw = updatedRaw.replace(/(\n)(--- !u!|$)/, `
5278
- m_${normalizedProperty}: ${edit.new_value}$1$2`);
5772
+ updatedRaw = updatedRaw.replace(/(\n)(--- !u!|$)/, (_m, g1, g2) => `
5773
+ m_${normalizedProperty}: ${edit.new_value}${g1}${g2}`);
5279
5774
  }
5280
5775
  targetBlock.replace_raw(updatedRaw);
5281
5776
  }
@@ -5380,9 +5875,9 @@ function reparentGameObject(options) {
5380
5875
  return {
5381
5876
  success: true,
5382
5877
  file_path,
5383
- child_transform_id: parseInt(childTransformId, 10),
5384
- old_parent_transform_id: parseInt(oldParentTransformId, 10),
5385
- new_parent_transform_id: parseInt(newParentTransformId, 10)
5878
+ child_transform_id: childTransformId,
5879
+ old_parent_transform_id: oldParentTransformId,
5880
+ new_parent_transform_id: newParentTransformId
5386
5881
  };
5387
5882
  }
5388
5883
  function findPrefabInstanceBlock(doc, identifier) {
@@ -5751,9 +6246,9 @@ function removeRemovedGameObject(options) {
5751
6246
  if (!doc.validate()) {
5752
6247
  return { success: false, file_path, error: "Validation failed after removing GameObject" };
5753
6248
  }
5754
- const saveResult = doc.save();
5755
- if (!saveResult.success) {
5756
- return { success: false, file_path, error: saveResult.error };
6249
+ const restoreGoSaveResult = doc.save();
6250
+ if (!restoreGoSaveResult.success) {
6251
+ return { success: false, file_path, error: restoreGoSaveResult.error };
5757
6252
  }
5758
6253
  return {
5759
6254
  success: true,
@@ -5793,7 +6288,8 @@ function removeComponent(options) {
5793
6288
  const parentGoId = goMatch[1];
5794
6289
  const goBlock = doc.find_by_file_id(parentGoId);
5795
6290
  if (goBlock) {
5796
- const compLinePattern = new RegExp(`\\s*- component: \\{fileID: ${file_id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\}\\n?`);
6291
+ const escaped = file_id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6292
+ const compLinePattern = new RegExp(`^[ \\t]*- component: \\{fileID: ${escaped}\\}[ \\t]*\\n`, "m");
5797
6293
  const modifiedRaw = goBlock.raw.replace(compLinePattern, "");
5798
6294
  goBlock.replace_raw(modifiedRaw);
5799
6295
  }