unity-agentic-tools 0.4.1 → 0.4.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
@@ -205,7 +205,7 @@ var require_package = __commonJS((exports2, module2) => {
205
205
  module2.exports = {
206
206
  name: "unity-agentic-tools",
207
207
  packageManager: "bun@latest",
208
- version: "0.4.1",
208
+ version: "0.4.2",
209
209
  description: "Fast, token-efficient Unity YAML parser for AI agents",
210
210
  exports: {
211
211
  ".": {
@@ -2336,6 +2336,24 @@ function resolveScriptGuid(script, projectPath) {
2336
2336
  }
2337
2337
  } catch {}
2338
2338
  }
2339
+ try {
2340
+ const assetsDir = path3.join(projectPath, "Assets");
2341
+ if (import_fs8.existsSync(assetsDir)) {
2342
+ const entries = import_fs8.readdirSync(assetsDir, { recursive: true, withFileTypes: false });
2343
+ for (const entry of entries) {
2344
+ if (!entry.endsWith(".cs"))
2345
+ continue;
2346
+ const fileName = path3.basename(entry, ".cs").toLowerCase();
2347
+ if (fileName === scriptNameLower || classNameOnly && fileName === classNameOnly) {
2348
+ const fullPath = path3.join(assetsDir, entry);
2349
+ const guid = extractGuidFromMeta(fullPath + ".meta");
2350
+ if (guid) {
2351
+ return { guid, path: path3.join("Assets", entry) };
2352
+ }
2353
+ }
2354
+ }
2355
+ }
2356
+ } catch {}
2339
2357
  }
2340
2358
  return null;
2341
2359
  }
@@ -2490,8 +2508,8 @@ function build_type_lookup(project_path) {
2490
2508
  const assetsDir = path3.join(project_path, "Assets");
2491
2509
  const csIndex = new Map;
2492
2510
  try {
2493
- const { readdirSync: readdirSync4 } = require("fs");
2494
- const entries = readdirSync4(assetsDir, { recursive: true, withFileTypes: false });
2511
+ const { readdirSync: readdirSync5 } = require("fs");
2512
+ const entries = readdirSync5(assetsDir, { recursive: true, withFileTypes: false });
2495
2513
  for (const entry of entries) {
2496
2514
  if (typeof entry === "string" && entry.endsWith(".cs")) {
2497
2515
  const basename4 = entry.substring(entry.lastIndexOf("/") + 1).replace(/\.cs$/, "");
@@ -4252,48 +4270,320 @@ function find_game_object_in_variant(doc, name, file_path, project_path) {
4252
4270
  }
4253
4271
  return null;
4254
4272
  }
4255
- function appendToAddedGameObjects(doc, piId, targetSourceRef, newGoId) {
4256
- const piBlock = doc.find_by_file_id(piId);
4257
- if (!piBlock)
4258
- return;
4259
- const entry = `
4260
- - targetCorrespondingSourceObject: ${targetSourceRef}
4261
- insertIndex: -1
4262
- addedObject: {fileID: ${newGoId}}`;
4263
- let raw = piBlock.raw;
4264
- const emptyPattern = /m_AddedGameObjects:[ \t]*\[\]/;
4265
- if (emptyPattern.test(raw)) {
4266
- raw = raw.replace(emptyPattern, `m_AddedGameObjects:${entry}`);
4267
- piBlock.replace_raw(raw);
4268
- return;
4273
+ function parsePrefabRef(refText) {
4274
+ const fileIdMatch = refText.match(/fileID:[ \t]*(-?\d+)/i);
4275
+ if (!fileIdMatch)
4276
+ return null;
4277
+ const guidMatch = refText.match(/guid:[ \t]*([a-f0-9]{32})/i);
4278
+ const typeMatch = refText.match(/type:[ \t]*(-?\d+)/i);
4279
+ return {
4280
+ file_id: fileIdMatch[1],
4281
+ guid: guidMatch ? guidMatch[1].toLowerCase() : undefined,
4282
+ type: typeMatch ? typeMatch[1] : undefined
4283
+ };
4284
+ }
4285
+ function extractPrefabSourceGuid(prefabInstanceRaw) {
4286
+ const sourceMatch = prefabInstanceRaw.match(/m_SourcePrefab:[ \t]*\{[^}]*guid:[ \t]*([a-f0-9]{32})/i);
4287
+ return sourceMatch ? sourceMatch[1].toLowerCase() : null;
4288
+ }
4289
+ function normalizePrefabRef(refText, sourceGuid) {
4290
+ const parsed = parsePrefabRef(refText);
4291
+ if (!parsed) {
4292
+ return { error: `Invalid reference "${refText}". Expected format: {fileID: N, guid: ..., type: T}` };
4293
+ }
4294
+ const guid = parsed.guid ?? sourceGuid;
4295
+ if (!guid) {
4296
+ return { error: `Reference "${refText}" is missing guid and source prefab guid could not be inferred.` };
4269
4297
  }
4270
- const existingPattern = /(m_AddedGameObjects:[ \t]*\n(?:[ \t]+-[\s\S]*?(?=\n[ \t]+m_|$))*)/;
4271
- if (existingPattern.test(raw)) {
4272
- raw = raw.replace(existingPattern, `$1${entry}`);
4273
- piBlock.replace_raw(raw);
4298
+ if (!/^[a-f0-9]{32}$/i.test(guid)) {
4299
+ return { error: `Invalid guid in reference "${refText}". Expected 32-character hex GUID.` };
4274
4300
  }
4301
+ const type = parsed.type ?? "3";
4302
+ if (!/^-?\d+$/.test(type)) {
4303
+ return { error: `Invalid type in reference "${refText}". Expected an integer type value.` };
4304
+ }
4305
+ return {
4306
+ ref: {
4307
+ file_id: parsed.file_id,
4308
+ guid,
4309
+ type
4310
+ }
4311
+ };
4275
4312
  }
4276
- function appendToAddedComponents(doc, piId, targetSourceRef, newComponentId) {
4277
- const piBlock = doc.find_by_file_id(piId);
4278
- if (!piBlock)
4279
- return;
4280
- const entry = `
4281
- - targetCorrespondingSourceObject: ${targetSourceRef}
4282
- insertIndex: -1
4283
- addedObject: {fileID: ${newComponentId}}`;
4284
- let raw = piBlock.raw;
4285
- const emptyPattern = /m_AddedComponents:[ \t]*\[\]/;
4286
- if (emptyPattern.test(raw)) {
4287
- raw = raw.replace(emptyPattern, `m_AddedComponents:${entry}`);
4288
- piBlock.replace_raw(raw);
4289
- return;
4313
+ function serializePrefabRef(ref) {
4314
+ return `{fileID: ${ref.file_id}, guid: ${ref.guid}, type: ${ref.type}}`;
4315
+ }
4316
+ function ensurePrefabModificationSerializedVersion(prefabInstanceBlock) {
4317
+ const raw = prefabInstanceBlock.raw;
4318
+ const updated = raw.replace(/(^([ \t]*)m_Modification:[ \t]*\n)(?!\2[ \t]+serializedVersion:[ \t]*\d+)/m, (_match, prefix, indent) => `${prefix}${indent} serializedVersion: 3
4319
+ `);
4320
+ if (updated === raw) {
4321
+ return false;
4290
4322
  }
4291
- const existingPattern = /(m_AddedComponents:[ \t]*\n(?:[ \t]+-[\s\S]*?(?=\n[ \t]+m_|$))*)/;
4292
- if (existingPattern.test(raw)) {
4293
- raw = raw.replace(existingPattern, `$1${entry}`);
4294
- piBlock.replace_raw(raw);
4323
+ prefabInstanceBlock.replace_raw(updated);
4324
+ return true;
4325
+ }
4326
+ function addedOverrideEntryKey(entry) {
4327
+ return `${entry.target.file_id}|${entry.target.guid}|${entry.target.type}|${entry.insert_index}|${entry.added_object_file_id}`;
4328
+ }
4329
+ function splitPrefabArraySection(rawText, key) {
4330
+ const lines = rawText.split(`
4331
+ `);
4332
+ const keyPattern = new RegExp(`^([ ]*)${key}:[ ]*(.*)$`);
4333
+ let keyIndex = -1;
4334
+ let indent = "";
4335
+ for (let i = 0;i < lines.length; i++) {
4336
+ const match = lines[i].match(keyPattern);
4337
+ if (match) {
4338
+ keyIndex = i;
4339
+ indent = match[1];
4340
+ break;
4341
+ }
4295
4342
  }
4343
+ if (keyIndex === -1) {
4344
+ return null;
4345
+ }
4346
+ const keyIndentLen = indent.length;
4347
+ let sectionEnd = keyIndex + 1;
4348
+ for (let i = keyIndex + 1;i < lines.length; i++) {
4349
+ const line = lines[i];
4350
+ const trimmed = line.trim();
4351
+ if (trimmed.length === 0) {
4352
+ sectionEnd = i + 1;
4353
+ continue;
4354
+ }
4355
+ const indentLen = (line.match(/^[ \t]*/) || [""])[0].length;
4356
+ if (indentLen < keyIndentLen) {
4357
+ break;
4358
+ }
4359
+ if (indentLen === keyIndentLen && !trimmed.startsWith("-") && /^[A-Za-z_][A-Za-z0-9_]*:/.test(trimmed)) {
4360
+ break;
4361
+ }
4362
+ sectionEnd = i + 1;
4363
+ }
4364
+ return {
4365
+ lines,
4366
+ key_index: keyIndex,
4367
+ section_end: sectionEnd,
4368
+ indent
4369
+ };
4296
4370
  }
4371
+ function collectAddedOverrideEntries(lines, key_index, section_end, sourceGuid) {
4372
+ const sectionLines = lines.slice(key_index + 1, section_end);
4373
+ const dedup = new Map;
4374
+ let i = 0;
4375
+ while (i < sectionLines.length) {
4376
+ const line = sectionLines[i];
4377
+ const trimmed = line.trimStart();
4378
+ if (!trimmed.startsWith("-")) {
4379
+ i++;
4380
+ continue;
4381
+ }
4382
+ const entryIndent = (line.match(/^[ \t]*/) || [""])[0].length;
4383
+ const entryLines = [line];
4384
+ i++;
4385
+ while (i < sectionLines.length) {
4386
+ const nextLine = sectionLines[i];
4387
+ const nextTrimmed = nextLine.trim();
4388
+ const nextIndent = (nextLine.match(/^[ \t]*/) || [""])[0].length;
4389
+ if (nextTrimmed.length > 0 && nextIndent === entryIndent && nextTrimmed.startsWith("-")) {
4390
+ break;
4391
+ }
4392
+ entryLines.push(nextLine);
4393
+ i++;
4394
+ }
4395
+ const entryText = entryLines.join(`
4396
+ `);
4397
+ const targetMatch = entryText.match(/targetCorrespondingSourceObject:[ \t]*(\{[^}]+\})/);
4398
+ const insertMatch = entryText.match(/insertIndex:[ \t]*(-?\d+)/);
4399
+ const addedObjectMatch = entryText.match(/addedObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)/);
4400
+ if (!targetMatch || !addedObjectMatch) {
4401
+ continue;
4402
+ }
4403
+ const normalizedTarget = normalizePrefabRef(targetMatch[1], sourceGuid).ref;
4404
+ if (!normalizedTarget) {
4405
+ continue;
4406
+ }
4407
+ const insertIndex = insertMatch ? Number.parseInt(insertMatch[1], 10) : -1;
4408
+ const entry = {
4409
+ target: normalizedTarget,
4410
+ insert_index: Number.isNaN(insertIndex) ? -1 : insertIndex,
4411
+ added_object_file_id: addedObjectMatch[1]
4412
+ };
4413
+ const dedupKey = addedOverrideEntryKey(entry);
4414
+ if (!dedup.has(dedupKey)) {
4415
+ dedup.set(dedupKey, entry);
4416
+ }
4417
+ }
4418
+ return [...dedup.values()];
4419
+ }
4420
+ function appendToAddedOverrideArray(doc, piId, key, targetSourceRef, newObjectId) {
4421
+ const piBlock = doc.find_by_file_id(piId);
4422
+ if (!piBlock) {
4423
+ return { success: false, error: `PrefabInstance with fileID ${piId} not found` };
4424
+ }
4425
+ ensurePrefabModificationSerializedVersion(piBlock);
4426
+ const split = splitPrefabArraySection(piBlock.raw, key);
4427
+ if (!split) {
4428
+ return { success: false, error: `${key} property not found in PrefabInstance ${piId}` };
4429
+ }
4430
+ const sourceGuid = extractPrefabSourceGuid(piBlock.raw);
4431
+ const normalizedTarget = normalizePrefabRef(targetSourceRef, sourceGuid);
4432
+ if (!normalizedTarget.ref) {
4433
+ return { success: false, error: normalizedTarget.error };
4434
+ }
4435
+ const candidate = {
4436
+ target: normalizedTarget.ref,
4437
+ insert_index: -1,
4438
+ added_object_file_id: newObjectId
4439
+ };
4440
+ const candidateKey = addedOverrideEntryKey(candidate);
4441
+ const existing = collectAddedOverrideEntries(split.lines, split.key_index, split.section_end, sourceGuid);
4442
+ if (existing.some((entry) => addedOverrideEntryKey(entry) === candidateKey)) {
4443
+ return { success: true, changed: false };
4444
+ }
4445
+ const entryLines = [
4446
+ `${split.indent}- targetCorrespondingSourceObject: ${serializePrefabRef(candidate.target)}`,
4447
+ `${split.indent} insertIndex: ${candidate.insert_index}`,
4448
+ `${split.indent} addedObject: {fileID: ${candidate.added_object_file_id}}`
4449
+ ];
4450
+ const keyLine = split.lines[split.key_index].trim();
4451
+ let insertIndex = split.section_end;
4452
+ while (insertIndex > split.key_index + 1 && split.lines[insertIndex - 1].trim().length === 0) {
4453
+ insertIndex--;
4454
+ }
4455
+ let updatedLines;
4456
+ if (keyLine === `${key}: []`) {
4457
+ const replacement = [`${split.indent}${key}:`, ...entryLines];
4458
+ updatedLines = [
4459
+ ...split.lines.slice(0, split.key_index),
4460
+ ...replacement,
4461
+ ...split.lines.slice(split.section_end)
4462
+ ];
4463
+ } else {
4464
+ updatedLines = [
4465
+ ...split.lines.slice(0, insertIndex),
4466
+ ...entryLines,
4467
+ ...split.lines.slice(insertIndex)
4468
+ ];
4469
+ }
4470
+ piBlock.replace_raw(updatedLines.join(`
4471
+ `));
4472
+ return { success: true, changed: true };
4473
+ }
4474
+ function appendToAddedGameObjects(doc, piId, targetSourceRef, newGoId) {
4475
+ return appendToAddedOverrideArray(doc, piId, "m_AddedGameObjects", targetSourceRef, newGoId);
4476
+ }
4477
+ function appendToAddedComponents(doc, piId, targetSourceRef, newComponentId) {
4478
+ return appendToAddedOverrideArray(doc, piId, "m_AddedComponents", targetSourceRef, newComponentId);
4479
+ }
4480
+ var COMPONENT_FULL_TEMPLATES = {
4481
+ 65: (gameObjectId) => ` m_GameObject: {fileID: ${gameObjectId}}
4482
+ m_Material: {fileID: 0}
4483
+ m_IncludeLayers:
4484
+ serializedVersion: 2
4485
+ m_Bits: 0
4486
+ m_ExcludeLayers:
4487
+ serializedVersion: 2
4488
+ m_Bits: 0
4489
+ m_LayerOverridePriority: 0
4490
+ m_IsTrigger: 0
4491
+ m_ProvidesContacts: 0
4492
+ m_Enabled: 1
4493
+ serializedVersion: 3
4494
+ m_Size: {x: 1, y: 1, z: 1}
4495
+ m_Center: {x: 0, y: 0, z: 0}`,
4496
+ 82: (gameObjectId) => ` m_GameObject: {fileID: ${gameObjectId}}
4497
+ m_Enabled: 1
4498
+ serializedVersion: 4
4499
+ OutputAudioMixerGroup: {fileID: 0}
4500
+ m_audioClip: {fileID: 0}
4501
+ m_PlayOnAwake: 1
4502
+ m_Volume: 1
4503
+ m_Pitch: 1
4504
+ Loop: 0
4505
+ Mute: 0
4506
+ Spatialize: 0
4507
+ SpatializePostEffects: 0
4508
+ Priority: 128
4509
+ DopplerLevel: 1
4510
+ MinDistance: 1
4511
+ MaxDistance: 500
4512
+ Pan2D: 0
4513
+ rolloffMode: 0
4514
+ BypassEffects: 0
4515
+ BypassListenerEffects: 0
4516
+ BypassReverbZones: 0
4517
+ rolloffCustomCurve:
4518
+ serializedVersion: 2
4519
+ m_Curve:
4520
+ - serializedVersion: 3
4521
+ time: 0
4522
+ value: 1
4523
+ inSlope: 0
4524
+ outSlope: 0
4525
+ tangentMode: 0
4526
+ weightedMode: 0
4527
+ inWeight: 0.33333334
4528
+ outWeight: 0.33333334
4529
+ - serializedVersion: 3
4530
+ time: 1
4531
+ value: 0
4532
+ inSlope: 0
4533
+ outSlope: 0
4534
+ tangentMode: 0
4535
+ weightedMode: 0
4536
+ inWeight: 0.33333334
4537
+ outWeight: 0.33333334
4538
+ m_PreInfinity: 2
4539
+ m_PostInfinity: 2
4540
+ m_RotationOrder: 4
4541
+ panLevelCustomCurve:
4542
+ serializedVersion: 2
4543
+ m_Curve:
4544
+ - serializedVersion: 3
4545
+ time: 0
4546
+ value: 0
4547
+ inSlope: 0
4548
+ outSlope: 0
4549
+ tangentMode: 0
4550
+ weightedMode: 0
4551
+ inWeight: 0.33333334
4552
+ outWeight: 0.33333334
4553
+ m_PreInfinity: 2
4554
+ m_PostInfinity: 2
4555
+ m_RotationOrder: 4
4556
+ spreadCustomCurve:
4557
+ serializedVersion: 2
4558
+ m_Curve:
4559
+ - serializedVersion: 3
4560
+ time: 0
4561
+ value: 0
4562
+ inSlope: 0
4563
+ outSlope: 0
4564
+ tangentMode: 0
4565
+ weightedMode: 0
4566
+ inWeight: 0.33333334
4567
+ outWeight: 0.33333334
4568
+ m_PreInfinity: 2
4569
+ m_PostInfinity: 2
4570
+ m_RotationOrder: 4
4571
+ reverbZoneMixCustomCurve:
4572
+ serializedVersion: 2
4573
+ m_Curve:
4574
+ - serializedVersion: 3
4575
+ time: 0
4576
+ value: 1
4577
+ inSlope: 0
4578
+ outSlope: 0
4579
+ tangentMode: 0
4580
+ weightedMode: 0
4581
+ inWeight: 0.33333334
4582
+ outWeight: 0.33333334
4583
+ m_PreInfinity: 2
4584
+ m_PostInfinity: 2
4585
+ m_RotationOrder: 4`
4586
+ };
4297
4587
  var COMPONENT_DEFAULTS = {
4298
4588
  20: ` serializedVersion: 2
4299
4589
  m_ClearFlags: 1
@@ -4420,6 +4710,17 @@ var COMPONENT_DEFAULTS = {
4420
4710
  m_IgnoreParentGroups: 0`
4421
4711
  };
4422
4712
  function createGenericComponentYAML(componentName, classId, componentId, gameObjectId) {
4713
+ const fullTemplate = COMPONENT_FULL_TEMPLATES[classId];
4714
+ if (fullTemplate) {
4715
+ return `--- !u!${classId} &${componentId}
4716
+ ${componentName}:
4717
+ m_ObjectHideFlags: 0
4718
+ m_CorrespondingSourceObject: {fileID: 0}
4719
+ m_PrefabInstance: {fileID: 0}
4720
+ m_PrefabAsset: {fileID: 0}
4721
+ ${fullTemplate(gameObjectId)}
4722
+ `;
4723
+ }
4423
4724
  const defaults = COMPONENT_DEFAULTS[classId] ? `
4424
4725
  ` + COMPONENT_DEFAULTS[classId] + `
4425
4726
  ` : "";
@@ -4572,7 +4873,14 @@ function createGameObject(options) {
4572
4873
  doc.add_child_to_parent(parentTransformIdStr, transformIdStr);
4573
4874
  }
4574
4875
  if (variantPiId && strippedSourceRef) {
4575
- appendToAddedGameObjects(doc, variantPiId, strippedSourceRef, gameObjectIdStr);
4876
+ const appendResult = appendToAddedGameObjects(doc, variantPiId, strippedSourceRef, gameObjectIdStr);
4877
+ if (!appendResult.success) {
4878
+ return {
4879
+ success: false,
4880
+ file_path,
4881
+ error: appendResult.error || `Failed to update m_AddedGameObjects for PrefabInstance ${variantPiId}`
4882
+ };
4883
+ }
4576
4884
  }
4577
4885
  const saveResult = doc.save();
4578
4886
  if (!saveResult.success) {
@@ -5055,6 +5363,7 @@ PrefabInstance:
5055
5363
  m_ObjectHideFlags: 0
5056
5364
  serializedVersion: 2
5057
5365
  m_Modification:
5366
+ serializedVersion: 3
5058
5367
  m_TransformParent: {fileID: 0}
5059
5368
  m_Modifications:
5060
5369
  - target: {fileID: ${rootInfo.game_object.file_id}, guid: ${sourceGuid}, type: 3}
@@ -5341,6 +5650,8 @@ function is_valid_component_base(base_class, project_path, depth = 0) {
5341
5650
  }
5342
5651
  if (!fullPath.endsWith(".cs") || !import_fs10.existsSync(fullPath)) {
5343
5652
  if (match.kind === "class") {
5653
+ if (fullPath.includes("Assembly-CSharp.dll"))
5654
+ return true;
5344
5655
  return is_likely_component_base_name(base_class);
5345
5656
  }
5346
5657
  }
@@ -5485,11 +5796,11 @@ function addComponent(options) {
5485
5796
  };
5486
5797
  }
5487
5798
  if (resolved.base_class && !COMPONENT_BASE_CLASSES.has(resolved.base_class) && !is_valid_component_base(resolved.base_class, project_path)) {
5488
- return {
5489
- success: false,
5490
- file_path,
5491
- 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).`
5492
- };
5799
+ const warn = `"${component_type}" extends ${resolved.base_class}, which does not inherit from MonoBehaviour. Adding anyway, but ensure the script is a valid component. (Re-run "setup" if the inheritance graph is stale).`;
5800
+ if (!extractionError)
5801
+ extractionError = warn;
5802
+ else
5803
+ extractionError += " " + warn;
5493
5804
  }
5494
5805
  let version;
5495
5806
  if (project_path) {
@@ -5505,10 +5816,22 @@ function addComponent(options) {
5505
5816
  componentYAML = createMonoBehaviourYAML(componentId, gameObjectId, resolved.guid, resolved.fields, version, scriptFileId, type_lookup_comp);
5506
5817
  scriptGuid = resolved.guid;
5507
5818
  scriptPath = resolved.path || undefined;
5508
- extractionError = resolved.extraction_error;
5819
+ if (resolved.extraction_error) {
5820
+ if (!extractionError)
5821
+ extractionError = resolved.extraction_error;
5822
+ else
5823
+ extractionError += " " + resolved.extraction_error;
5824
+ }
5509
5825
  }
5510
5826
  if (variantInfo) {
5511
- appendToAddedComponents(doc, variantInfo.prefab_instance_id, variantInfo.source_ref, componentIdStr);
5827
+ const appendResult = appendToAddedComponents(doc, variantInfo.prefab_instance_id, variantInfo.source_ref, componentIdStr);
5828
+ if (!appendResult.success) {
5829
+ return {
5830
+ success: false,
5831
+ file_path,
5832
+ error: appendResult.error || `Failed to update m_AddedComponents for PrefabInstance ${variantInfo.prefab_instance_id}`
5833
+ };
5834
+ }
5512
5835
  } else {
5513
5836
  const added = addComponentToGameObject(doc, gameObjectIdStr, componentIdStr);
5514
5837
  if (!added) {
@@ -5873,7 +6196,7 @@ function editComponentByFileId(options) {
5873
6196
  return {
5874
6197
  success: false,
5875
6198
  file_path,
5876
- error: `Could not resolve "${new_value}" to asset reference. Ensure GUID cache exists (run "setup <project>" first).`
6199
+ error: `Could not resolve "${new_value}" to asset reference. Ensure GUID cache exists (run "setup" first, or "setup -p <path>").`
5877
6200
  };
5878
6201
  }
5879
6202
  if (resolved !== undefined) {
@@ -6338,6 +6661,193 @@ function findPrefabInstanceBlock(doc, identifier) {
6338
6661
  }
6339
6662
  return null;
6340
6663
  }
6664
+ function parsePrefabRef2(refText) {
6665
+ const fileIdMatch = refText.match(/fileID:[ \t]*(-?\d+)/i);
6666
+ if (!fileIdMatch)
6667
+ return null;
6668
+ const guidMatch = refText.match(/guid:[ \t]*([a-f0-9]{32})/i);
6669
+ const typeMatch = refText.match(/type:[ \t]*(-?\d+)/i);
6670
+ return {
6671
+ file_id: fileIdMatch[1],
6672
+ guid: guidMatch ? guidMatch[1].toLowerCase() : undefined,
6673
+ type: typeMatch ? typeMatch[1] : undefined
6674
+ };
6675
+ }
6676
+ function extractPrefabSourceGuid2(prefabInstanceRaw) {
6677
+ const sourceMatch = prefabInstanceRaw.match(/m_SourcePrefab:[ \t]*\{[^}]*guid:[ \t]*([a-f0-9]{32})/i);
6678
+ return sourceMatch ? sourceMatch[1].toLowerCase() : null;
6679
+ }
6680
+ function normalizePrefabRef2(refText, sourceGuid) {
6681
+ const parsed = parsePrefabRef2(refText);
6682
+ if (!parsed) {
6683
+ return { error: `Invalid reference "${refText}". Expected format: {fileID: N, guid: ..., type: T}` };
6684
+ }
6685
+ const guid = parsed.guid ?? sourceGuid;
6686
+ if (!guid) {
6687
+ return { error: `Reference "${refText}" is missing guid and source prefab guid could not be inferred.` };
6688
+ }
6689
+ if (!/^[a-f0-9]{32}$/i.test(guid)) {
6690
+ return { error: `Invalid guid in reference "${refText}". Expected 32-character hex GUID.` };
6691
+ }
6692
+ const type = parsed.type ?? "3";
6693
+ if (!/^-?\d+$/.test(type)) {
6694
+ return { error: `Invalid type in reference "${refText}". Expected an integer type value.` };
6695
+ }
6696
+ return {
6697
+ ref: {
6698
+ file_id: parsed.file_id,
6699
+ guid,
6700
+ type
6701
+ }
6702
+ };
6703
+ }
6704
+ function parsePrefabRefMatcher(refText) {
6705
+ const parsed = parsePrefabRef2(refText);
6706
+ if (!parsed) {
6707
+ return { error: `Invalid reference "${refText}". Expected format: {fileID: N, guid: ..., type: T}` };
6708
+ }
6709
+ if (parsed.guid && !/^[a-f0-9]{32}$/i.test(parsed.guid)) {
6710
+ return { error: `Invalid guid in reference "${refText}". Expected 32-character hex GUID.` };
6711
+ }
6712
+ if (parsed.type && !/^-?\d+$/.test(parsed.type)) {
6713
+ return { error: `Invalid type in reference "${refText}". Expected an integer type value.` };
6714
+ }
6715
+ return { matcher: parsed };
6716
+ }
6717
+ function prefabRefKey(ref) {
6718
+ return `${ref.file_id}|${ref.guid}|${ref.type}`;
6719
+ }
6720
+ function serializePrefabRef2(ref) {
6721
+ return `{fileID: ${ref.file_id}, guid: ${ref.guid}, type: ${ref.type}}`;
6722
+ }
6723
+ function splitPrefabArraySection2(rawText, key) {
6724
+ const lines = rawText.split(`
6725
+ `);
6726
+ const keyPattern = new RegExp(`^([ ]*)${key}:[ ]*(.*)$`);
6727
+ let keyIndex = -1;
6728
+ let indent = "";
6729
+ for (let i = 0;i < lines.length; i++) {
6730
+ const match = lines[i].match(keyPattern);
6731
+ if (match) {
6732
+ keyIndex = i;
6733
+ indent = match[1];
6734
+ break;
6735
+ }
6736
+ }
6737
+ if (keyIndex === -1) {
6738
+ return null;
6739
+ }
6740
+ const keyIndentLen = indent.length;
6741
+ let sectionEnd = keyIndex + 1;
6742
+ for (let i = keyIndex + 1;i < lines.length; i++) {
6743
+ const line = lines[i];
6744
+ const trimmed = line.trim();
6745
+ if (trimmed.length === 0) {
6746
+ sectionEnd = i + 1;
6747
+ continue;
6748
+ }
6749
+ const indentLen = (line.match(/^[ \t]*/) || [""])[0].length;
6750
+ if (indentLen < keyIndentLen) {
6751
+ break;
6752
+ }
6753
+ if (indentLen === keyIndentLen && !trimmed.startsWith("-") && /^[A-Za-z_][A-Za-z0-9_]*:/.test(trimmed)) {
6754
+ break;
6755
+ }
6756
+ sectionEnd = i + 1;
6757
+ }
6758
+ return {
6759
+ lines,
6760
+ key_index: keyIndex,
6761
+ section_end: sectionEnd,
6762
+ indent
6763
+ };
6764
+ }
6765
+ function collectNormalizedRefsFromSection(lines, key_index, section_end, sourceGuid) {
6766
+ const sectionText = lines.slice(key_index, section_end).join(`
6767
+ `);
6768
+ const refMatches = sectionText.match(/\{[^}]*\}/g) || [];
6769
+ const dedup = new Map;
6770
+ for (const token of refMatches) {
6771
+ const parsed = parsePrefabRef2(token);
6772
+ if (!parsed)
6773
+ continue;
6774
+ const guid = parsed.guid ?? sourceGuid;
6775
+ if (!guid || !/^[a-f0-9]{32}$/i.test(guid))
6776
+ continue;
6777
+ const type = parsed.type ?? "3";
6778
+ if (!/^-?\d+$/.test(type))
6779
+ continue;
6780
+ const normalized = {
6781
+ file_id: parsed.file_id,
6782
+ guid: guid.toLowerCase(),
6783
+ type
6784
+ };
6785
+ const key = prefabRefKey(normalized);
6786
+ if (!dedup.has(key)) {
6787
+ dedup.set(key, normalized);
6788
+ }
6789
+ }
6790
+ return [...dedup.values()];
6791
+ }
6792
+ function mutatePrefabReferenceList(options) {
6793
+ const { target_block, key, ref_text, action, item_label } = options;
6794
+ const split = splitPrefabArraySection2(target_block.raw, key);
6795
+ if (!split) {
6796
+ return { success: false, error: `${key} property not found in PrefabInstance` };
6797
+ }
6798
+ const sourceGuid = extractPrefabSourceGuid2(target_block.raw);
6799
+ const existing = collectNormalizedRefsFromSection(split.lines, split.key_index, split.section_end, sourceGuid);
6800
+ let next = existing;
6801
+ if (action === "add") {
6802
+ const normalized = normalizePrefabRef2(ref_text, sourceGuid);
6803
+ if (!normalized.ref) {
6804
+ return { success: false, error: normalized.error };
6805
+ }
6806
+ const keyText = prefabRefKey(normalized.ref);
6807
+ if (existing.some((entry) => prefabRefKey(entry) === keyText)) {
6808
+ return { success: true, changed: false };
6809
+ }
6810
+ next = [...existing, normalized.ref];
6811
+ } else {
6812
+ const matcherResult = parsePrefabRefMatcher(ref_text);
6813
+ const matcher = matcherResult.matcher;
6814
+ if (!matcher) {
6815
+ return { success: false, error: matcherResult.error };
6816
+ }
6817
+ next = existing.filter((entry) => {
6818
+ if (entry.file_id !== matcher.file_id)
6819
+ return true;
6820
+ if (matcher.guid && entry.guid !== matcher.guid.toLowerCase())
6821
+ return true;
6822
+ if (matcher.type && entry.type !== matcher.type)
6823
+ return true;
6824
+ return false;
6825
+ });
6826
+ if (next.length === existing.length) {
6827
+ return {
6828
+ success: false,
6829
+ error: `${item_label} reference "${ref_text}" not found in ${key}`
6830
+ };
6831
+ }
6832
+ }
6833
+ const replacement = [];
6834
+ if (next.length === 0) {
6835
+ replacement.push(`${split.indent}${key}: []`);
6836
+ } else {
6837
+ replacement.push(`${split.indent}${key}:`);
6838
+ for (const entry of next) {
6839
+ replacement.push(`${split.indent}- ${serializePrefabRef2(entry)}`);
6840
+ }
6841
+ }
6842
+ const updatedLines = [
6843
+ ...split.lines.slice(0, split.key_index),
6844
+ ...replacement,
6845
+ ...split.lines.slice(split.section_end)
6846
+ ];
6847
+ target_block.replace_raw(updatedLines.join(`
6848
+ `));
6849
+ return { success: true, changed: true };
6850
+ }
6341
6851
  function editArray(options) {
6342
6852
  const { file_path, file_id, array_property, action, value, index } = options;
6343
6853
  if (!import_fs11.existsSync(file_path)) {
@@ -6548,9 +7058,6 @@ function addRemovedComponent(options) {
6548
7058
  if (!import_fs11.existsSync(file_path)) {
6549
7059
  return { success: false, file_path, error: `File not found: ${file_path}` };
6550
7060
  }
6551
- if (!/^\{fileID:\s*-?\d+/.test(component_ref)) {
6552
- return { success: false, file_path, error: `Invalid component reference "${component_ref}". Expected format: {fileID: N, guid: ..., type: T}` };
6553
- }
6554
7061
  let doc;
6555
7062
  try {
6556
7063
  doc = UnityDocument.from_file(file_path);
@@ -6561,19 +7068,16 @@ function addRemovedComponent(options) {
6561
7068
  if (!targetBlock) {
6562
7069
  return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
6563
7070
  }
6564
- let rawText = targetBlock.raw;
6565
- if (/m_RemovedComponents:\s*\[\]/.test(rawText)) {
6566
- rawText = rawText.replace(/m_RemovedComponents:\s*\[\]/, "m_RemovedComponents:");
6567
- }
6568
- const removedPattern = /m_RemovedComponents:\s*\n/;
6569
- if (removedPattern.test(rawText)) {
6570
- rawText = rawText.replace(removedPattern, `m_RemovedComponents:
6571
- - ${component_ref}
6572
- `);
6573
- } else {
6574
- return { success: false, file_path, error: "m_RemovedComponents property not found in PrefabInstance" };
7071
+ const mutation = mutatePrefabReferenceList({
7072
+ target_block: targetBlock,
7073
+ key: "m_RemovedComponents",
7074
+ ref_text: component_ref,
7075
+ action: "add",
7076
+ item_label: "Component"
7077
+ });
7078
+ if (!mutation.success) {
7079
+ return { success: false, file_path, error: mutation.error };
6575
7080
  }
6576
- targetBlock.replace_raw(rawText);
6577
7081
  if (!doc.validate()) {
6578
7082
  return { success: false, file_path, error: "Validation failed after adding removed component" };
6579
7083
  }
@@ -6602,13 +7106,16 @@ function removeRemovedComponent(options) {
6602
7106
  if (!targetBlock) {
6603
7107
  return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
6604
7108
  }
6605
- const escapedRef = component_ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6606
- const refPattern = new RegExp(`\\s*- ${escapedRef}\\n?`, "m");
6607
- if (!refPattern.test(targetBlock.raw)) {
6608
- return { success: false, file_path, error: `Component reference "${component_ref}" not found in m_RemovedComponents` };
7109
+ const mutation = mutatePrefabReferenceList({
7110
+ target_block: targetBlock,
7111
+ key: "m_RemovedComponents",
7112
+ ref_text: component_ref,
7113
+ action: "remove",
7114
+ item_label: "Component"
7115
+ });
7116
+ if (!mutation.success) {
7117
+ return { success: false, file_path, error: mutation.error };
6609
7118
  }
6610
- const updatedRaw = targetBlock.raw.replace(refPattern, "");
6611
- targetBlock.replace_raw(updatedRaw);
6612
7119
  if (!doc.validate()) {
6613
7120
  return { success: false, file_path, error: "Validation failed after removing component" };
6614
7121
  }
@@ -6627,9 +7134,6 @@ function addRemovedGameObject(options) {
6627
7134
  if (!import_fs11.existsSync(file_path)) {
6628
7135
  return { success: false, file_path, error: `File not found: ${file_path}` };
6629
7136
  }
6630
- if (!/^\{fileID:\s*-?\d+/.test(component_ref)) {
6631
- return { success: false, file_path, error: `Invalid GameObject reference "${component_ref}". Expected format: {fileID: N, guid: ..., type: T}` };
6632
- }
6633
7137
  let doc;
6634
7138
  try {
6635
7139
  doc = UnityDocument.from_file(file_path);
@@ -6640,19 +7144,16 @@ function addRemovedGameObject(options) {
6640
7144
  if (!targetBlock) {
6641
7145
  return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
6642
7146
  }
6643
- let rawText = targetBlock.raw;
6644
- if (/m_RemovedGameObjects:\s*\[\]/.test(rawText)) {
6645
- rawText = rawText.replace(/m_RemovedGameObjects:\s*\[\]/, "m_RemovedGameObjects:");
6646
- }
6647
- const removedPattern = /m_RemovedGameObjects:\s*\n/;
6648
- if (removedPattern.test(rawText)) {
6649
- rawText = rawText.replace(removedPattern, `m_RemovedGameObjects:
6650
- - ${component_ref}
6651
- `);
6652
- } else {
6653
- return { success: false, file_path, error: "m_RemovedGameObjects property not found in PrefabInstance" };
7147
+ const mutation = mutatePrefabReferenceList({
7148
+ target_block: targetBlock,
7149
+ key: "m_RemovedGameObjects",
7150
+ ref_text: component_ref,
7151
+ action: "add",
7152
+ item_label: "GameObject"
7153
+ });
7154
+ if (!mutation.success) {
7155
+ return { success: false, file_path, error: mutation.error };
6654
7156
  }
6655
- targetBlock.replace_raw(rawText);
6656
7157
  if (!doc.validate()) {
6657
7158
  return { success: false, file_path, error: "Validation failed after adding removed GameObject" };
6658
7159
  }
@@ -6681,13 +7182,16 @@ function removeRemovedGameObject(options) {
6681
7182
  if (!targetBlock) {
6682
7183
  return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
6683
7184
  }
6684
- const escapedRef = component_ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6685
- const refPattern = new RegExp(`\\s*- ${escapedRef}\\n?`, "m");
6686
- if (!refPattern.test(targetBlock.raw)) {
6687
- return { success: false, file_path, error: `GameObject reference "${component_ref}" not found in m_RemovedGameObjects` };
7185
+ const mutation = mutatePrefabReferenceList({
7186
+ target_block: targetBlock,
7187
+ key: "m_RemovedGameObjects",
7188
+ ref_text: component_ref,
7189
+ action: "remove",
7190
+ item_label: "GameObject"
7191
+ });
7192
+ if (!mutation.success) {
7193
+ return { success: false, file_path, error: mutation.error };
6688
7194
  }
6689
- const updatedRaw = targetBlock.raw.replace(refPattern, "");
6690
- targetBlock.replace_raw(updatedRaw);
6691
7195
  if (!doc.validate()) {
6692
7196
  return { success: false, file_path, error: "Validation failed after removing GameObject" };
6693
7197
  }
@@ -6786,6 +7290,7 @@ function removeComponent(options) {
6786
7290
  return { success: false, file_path, error: "Cannot remove a Transform with remove-component. Use delete to remove the entire GameObject." };
6787
7291
  }
6788
7292
  const resolved_file_id = found.file_id;
7293
+ let removed_added_component_overrides = 0;
6789
7294
  const goMatch = found.raw.match(/m_GameObject:[ \t]*\{fileID:[ \t]*(-?\d+)\}/);
6790
7295
  if (goMatch) {
6791
7296
  const parentGoId = goMatch[1];
@@ -6796,6 +7301,19 @@ function removeComponent(options) {
6796
7301
  const modifiedRaw = goBlock.raw.replace(compLinePattern, "");
6797
7302
  if (modifiedRaw === goBlock.raw) {}
6798
7303
  goBlock.replace_raw(modifiedRaw);
7304
+ if (goBlock.is_stripped) {
7305
+ const piMatch = goBlock.raw.match(/m_PrefabInstance:[ \t]*\{fileID:[ \t]*(-?\d+)\}/);
7306
+ if (piMatch) {
7307
+ const piBlock = doc.find_by_file_id(piMatch[1]);
7308
+ if (piBlock && piBlock.class_id === 1001) {
7309
+ const updated = removeAddedComponentOverrideEntries(piBlock.raw, resolved_file_id);
7310
+ if (updated.removed_count > 0) {
7311
+ piBlock.replace_raw(updated.updated_raw);
7312
+ removed_added_component_overrides += updated.removed_count;
7313
+ }
7314
+ }
7315
+ }
7316
+ }
6799
7317
  }
6800
7318
  }
6801
7319
  const dangling = [];
@@ -6819,7 +7337,7 @@ function removeComponent(options) {
6819
7337
  file_path,
6820
7338
  removed_file_id: resolved_file_id,
6821
7339
  removed_class_id: found.class_id,
6822
- warning: dangling.length > 0 ? `Dangling references to deleted component found in blocks: ${dangling.join(", ")}` : undefined
7340
+ warning: dangling.length > 0 ? `Dangling references to deleted component found in blocks: ${dangling.join(", ")}${removed_added_component_overrides > 0 ? ` (removed ${removed_added_component_overrides} m_AddedComponents override entr${removed_added_component_overrides === 1 ? "y" : "ies"})` : ""}` : undefined
6823
7341
  };
6824
7342
  }
6825
7343
  function removeComponentBatch(options) {
@@ -6937,6 +7455,124 @@ function findPrefabInstanceBlock2(doc, identifier) {
6937
7455
  }
6938
7456
  return null;
6939
7457
  }
7458
+ function splitPrefabArraySection3(rawText, key) {
7459
+ const lines = rawText.split(`
7460
+ `);
7461
+ const keyPattern = new RegExp(`^[ ]*${key}:[ ]*(.*)$`);
7462
+ let keyIndex = -1;
7463
+ let keyIndentLen = 0;
7464
+ for (let i = 0;i < lines.length; i++) {
7465
+ const match = lines[i].match(keyPattern);
7466
+ if (match) {
7467
+ keyIndex = i;
7468
+ keyIndentLen = (lines[i].match(/^[ \t]*/) || [""])[0].length;
7469
+ break;
7470
+ }
7471
+ }
7472
+ if (keyIndex === -1) {
7473
+ return null;
7474
+ }
7475
+ let sectionEnd = keyIndex + 1;
7476
+ for (let i = keyIndex + 1;i < lines.length; i++) {
7477
+ const line = lines[i];
7478
+ const trimmed = line.trim();
7479
+ if (trimmed.length === 0) {
7480
+ sectionEnd = i + 1;
7481
+ continue;
7482
+ }
7483
+ const indentLen = (line.match(/^[ \t]*/) || [""])[0].length;
7484
+ if (indentLen < keyIndentLen) {
7485
+ break;
7486
+ }
7487
+ if (indentLen === keyIndentLen && !trimmed.startsWith("-") && /^[A-Za-z_][A-Za-z0-9_]*:/.test(trimmed)) {
7488
+ break;
7489
+ }
7490
+ sectionEnd = i + 1;
7491
+ }
7492
+ return {
7493
+ lines,
7494
+ key_index: keyIndex,
7495
+ section_end: sectionEnd
7496
+ };
7497
+ }
7498
+ function collectAddedObjectFileIdsFromArray(rawText, key) {
7499
+ const section = splitPrefabArraySection3(rawText, key);
7500
+ if (!section)
7501
+ return [];
7502
+ const keyLine = section.lines[section.key_index].trim();
7503
+ if (new RegExp(`^${key}:[ ]*[]$`).test(keyLine)) {
7504
+ return [];
7505
+ }
7506
+ const sectionText = section.lines.slice(section.key_index, section.section_end).join(`
7507
+ `);
7508
+ const fileIds = new Set;
7509
+ const addedObjectPattern = /addedObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)/g;
7510
+ let match;
7511
+ while ((match = addedObjectPattern.exec(sectionText)) !== null) {
7512
+ fileIds.add(match[1]);
7513
+ }
7514
+ if (fileIds.size === 0) {
7515
+ const inlineEntryPattern = /^[ \t]*-[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)/gm;
7516
+ while ((match = inlineEntryPattern.exec(sectionText)) !== null) {
7517
+ fileIds.add(match[1]);
7518
+ }
7519
+ }
7520
+ return [...fileIds];
7521
+ }
7522
+ function removeAddedComponentOverrideEntries(rawText, componentId) {
7523
+ const section = splitPrefabArraySection3(rawText, "m_AddedComponents");
7524
+ if (!section)
7525
+ return { updated_raw: rawText, removed_count: 0 };
7526
+ const keyLine = section.lines[section.key_index].trim();
7527
+ if (/^m_AddedComponents:[ \t]*\[\]$/.test(keyLine)) {
7528
+ return { updated_raw: rawText, removed_count: 0 };
7529
+ }
7530
+ const bodyLines = section.lines.slice(section.key_index + 1, section.section_end);
7531
+ const keptEntries = [];
7532
+ let removed = 0;
7533
+ let i = 0;
7534
+ while (i < bodyLines.length) {
7535
+ const line = bodyLines[i];
7536
+ const trimmed = line.trimStart();
7537
+ if (!trimmed.startsWith("-")) {
7538
+ i++;
7539
+ continue;
7540
+ }
7541
+ const entryIndent = (line.match(/^[ \t]*/) || [""])[0].length;
7542
+ const entryLines = [line];
7543
+ i++;
7544
+ while (i < bodyLines.length) {
7545
+ const nextLine = bodyLines[i];
7546
+ const nextTrimmed = nextLine.trim();
7547
+ const nextIndent = (nextLine.match(/^[ \t]*/) || [""])[0].length;
7548
+ if (nextTrimmed.length > 0 && nextIndent === entryIndent && nextTrimmed.startsWith("-")) {
7549
+ break;
7550
+ }
7551
+ entryLines.push(nextLine);
7552
+ i++;
7553
+ }
7554
+ const entryText = entryLines.join(`
7555
+ `);
7556
+ const addedObjectMatch = entryText.match(/addedObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)/);
7557
+ if (addedObjectMatch && addedObjectMatch[1] === componentId) {
7558
+ removed++;
7559
+ continue;
7560
+ }
7561
+ keptEntries.push(entryLines);
7562
+ }
7563
+ if (removed === 0) {
7564
+ return { updated_raw: rawText, removed_count: 0 };
7565
+ }
7566
+ const keyIndent = (section.lines[section.key_index].match(/^[ \t]*/) || [""])[0];
7567
+ const replacement = keptEntries.length === 0 ? [`${keyIndent}m_AddedComponents: []`] : [`${keyIndent}m_AddedComponents:`, ...keptEntries.flat()];
7568
+ const updatedLines = [
7569
+ ...section.lines.slice(0, section.key_index),
7570
+ ...replacement,
7571
+ ...section.lines.slice(section.section_end)
7572
+ ];
7573
+ return { updated_raw: updatedLines.join(`
7574
+ `), removed_count: removed };
7575
+ }
6940
7576
  function deletePrefabInstance(options) {
6941
7577
  const { file_path, prefab_instance } = options;
6942
7578
  const pathError = validate_file_path(file_path, "write");
@@ -6964,36 +7600,23 @@ function deletePrefabInstance(options) {
6964
7600
  allToRemove.add(block.file_id);
6965
7601
  }
6966
7602
  }
6967
- const addedGOPattern = /m_AddedGameObjects:\s*\n((?:\s*- \{fileID: \d+\}\n)*)/m;
6968
- const addedGOMatch = piBlock.raw.match(addedGOPattern);
6969
- if (addedGOMatch) {
6970
- const addedGOSection = addedGOMatch[1];
6971
- const goMatches = addedGOSection.matchAll(/fileID:[ \t]*(\d+)/g);
6972
- for (const match of goMatches) {
6973
- const goId = match[1];
6974
- allToRemove.add(goId);
6975
- const goBlock = doc.find_by_file_id(goId);
6976
- if (goBlock) {
6977
- const compMatch = goBlock.raw.match(/component:[ \t]*\{fileID:[ \t]*(\d+)\}/);
6978
- if (compMatch) {
6979
- const transformId = compMatch[1];
6980
- allToRemove.add(transformId);
6981
- const descendants = doc.collect_hierarchy(transformId);
6982
- for (const id of descendants) {
6983
- allToRemove.add(id);
6984
- }
7603
+ for (const goId of collectAddedObjectFileIdsFromArray(piBlock.raw, "m_AddedGameObjects")) {
7604
+ allToRemove.add(goId);
7605
+ const goBlock = doc.find_by_file_id(goId);
7606
+ if (goBlock) {
7607
+ const compMatch = goBlock.raw.match(/component:[ \t]*\{fileID:[ \t]*(\d+)\}/);
7608
+ if (compMatch) {
7609
+ const transformId = compMatch[1];
7610
+ allToRemove.add(transformId);
7611
+ const descendants = doc.collect_hierarchy(transformId);
7612
+ for (const id of descendants) {
7613
+ allToRemove.add(id);
6985
7614
  }
6986
7615
  }
6987
7616
  }
6988
7617
  }
6989
- const addedCompPattern = /m_AddedComponents:\s*\n((?:\s*- \{fileID: \d+\}\n)*)/m;
6990
- const addedCompMatch = piBlock.raw.match(addedCompPattern);
6991
- if (addedCompMatch) {
6992
- const addedCompSection = addedCompMatch[1];
6993
- const compMatches = addedCompSection.matchAll(/fileID:[ \t]*(\d+)/g);
6994
- for (const match of compMatches) {
6995
- allToRemove.add(match[1]);
6996
- }
7618
+ for (const componentId of collectAddedObjectFileIdsFromArray(piBlock.raw, "m_AddedComponents")) {
7619
+ allToRemove.add(componentId);
6997
7620
  }
6998
7621
  const parentMatch = piBlock.raw.match(/m_TransformParent:[ \t]*\{fileID:[ \t]*(\d+)\}/);
6999
7622
  const parentTransformId = parentMatch ? parentMatch[1] : "0";