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/cli.js CHANGED
@@ -2299,7 +2299,7 @@ var require_package = __commonJS((exports2, module2) => {
2299
2299
  module2.exports = {
2300
2300
  name: "unity-agentic-tools",
2301
2301
  packageManager: "bun@latest",
2302
- version: "0.4.1",
2302
+ version: "0.4.2",
2303
2303
  description: "Fast, token-efficient Unity YAML parser for AI agents",
2304
2304
  exports: {
2305
2305
  ".": {
@@ -2619,6 +2619,9 @@ function find_unity_project_root(startDir) {
2619
2619
  }
2620
2620
  return null;
2621
2621
  }
2622
+ function resolve_project_path(project_path) {
2623
+ return import_path4.resolve(project_path || process.cwd());
2624
+ }
2622
2625
  function ensure_parent_dir(file_path) {
2623
2626
  const dir = import_path4.dirname(file_path);
2624
2627
  if (dir && dir !== "." && !import_fs4.existsSync(dir)) {
@@ -3286,6 +3289,24 @@ function resolveScriptGuid(script, projectPath) {
3286
3289
  }
3287
3290
  } catch {}
3288
3291
  }
3292
+ try {
3293
+ const assetsDir = path.join(projectPath, "Assets");
3294
+ if (import_fs6.existsSync(assetsDir)) {
3295
+ const entries = import_fs6.readdirSync(assetsDir, { recursive: true, withFileTypes: false });
3296
+ for (const entry of entries) {
3297
+ if (!entry.endsWith(".cs"))
3298
+ continue;
3299
+ const fileName = path.basename(entry, ".cs").toLowerCase();
3300
+ if (fileName === scriptNameLower || classNameOnly && fileName === classNameOnly) {
3301
+ const fullPath = path.join(assetsDir, entry);
3302
+ const guid = extractGuidFromMeta(fullPath + ".meta");
3303
+ if (guid) {
3304
+ return { guid, path: path.join("Assets", entry) };
3305
+ }
3306
+ }
3307
+ }
3308
+ }
3309
+ } catch {}
3289
3310
  }
3290
3311
  return null;
3291
3312
  }
@@ -3440,8 +3461,8 @@ function build_type_lookup(project_path) {
3440
3461
  const assetsDir = path.join(project_path, "Assets");
3441
3462
  const csIndex = new Map;
3442
3463
  try {
3443
- const { readdirSync: readdirSync3 } = require("fs");
3444
- const entries = readdirSync3(assetsDir, { recursive: true, withFileTypes: false });
3464
+ const { readdirSync: readdirSync4 } = require("fs");
3465
+ const entries = readdirSync4(assetsDir, { recursive: true, withFileTypes: false });
3445
3466
  for (const entry of entries) {
3446
3467
  if (typeof entry === "string" && entry.endsWith(".cs")) {
3447
3468
  const basename3 = entry.substring(entry.lastIndexOf("/") + 1).replace(/\.cs$/, "");
@@ -5223,48 +5244,320 @@ function find_game_object_in_variant(doc, name, file_path, project_path) {
5223
5244
  }
5224
5245
  return null;
5225
5246
  }
5226
- function appendToAddedGameObjects(doc, piId, targetSourceRef, newGoId) {
5227
- const piBlock = doc.find_by_file_id(piId);
5228
- if (!piBlock)
5229
- return;
5230
- const entry = `
5231
- - targetCorrespondingSourceObject: ${targetSourceRef}
5232
- insertIndex: -1
5233
- addedObject: {fileID: ${newGoId}}`;
5234
- let raw = piBlock.raw;
5235
- const emptyPattern = /m_AddedGameObjects:[ \t]*\[\]/;
5236
- if (emptyPattern.test(raw)) {
5237
- raw = raw.replace(emptyPattern, `m_AddedGameObjects:${entry}`);
5238
- piBlock.replace_raw(raw);
5239
- return;
5247
+ function parsePrefabRef(refText) {
5248
+ const fileIdMatch = refText.match(/fileID:[ \t]*(-?\d+)/i);
5249
+ if (!fileIdMatch)
5250
+ return null;
5251
+ const guidMatch = refText.match(/guid:[ \t]*([a-f0-9]{32})/i);
5252
+ const typeMatch = refText.match(/type:[ \t]*(-?\d+)/i);
5253
+ return {
5254
+ file_id: fileIdMatch[1],
5255
+ guid: guidMatch ? guidMatch[1].toLowerCase() : undefined,
5256
+ type: typeMatch ? typeMatch[1] : undefined
5257
+ };
5258
+ }
5259
+ function extractPrefabSourceGuid(prefabInstanceRaw) {
5260
+ const sourceMatch = prefabInstanceRaw.match(/m_SourcePrefab:[ \t]*\{[^}]*guid:[ \t]*([a-f0-9]{32})/i);
5261
+ return sourceMatch ? sourceMatch[1].toLowerCase() : null;
5262
+ }
5263
+ function normalizePrefabRef(refText, sourceGuid) {
5264
+ const parsed = parsePrefabRef(refText);
5265
+ if (!parsed) {
5266
+ return { error: `Invalid reference "${refText}". Expected format: {fileID: N, guid: ..., type: T}` };
5267
+ }
5268
+ const guid = parsed.guid ?? sourceGuid;
5269
+ if (!guid) {
5270
+ return { error: `Reference "${refText}" is missing guid and source prefab guid could not be inferred.` };
5240
5271
  }
5241
- const existingPattern = /(m_AddedGameObjects:[ \t]*\n(?:[ \t]+-[\s\S]*?(?=\n[ \t]+m_|$))*)/;
5242
- if (existingPattern.test(raw)) {
5243
- raw = raw.replace(existingPattern, `$1${entry}`);
5244
- piBlock.replace_raw(raw);
5272
+ if (!/^[a-f0-9]{32}$/i.test(guid)) {
5273
+ return { error: `Invalid guid in reference "${refText}". Expected 32-character hex GUID.` };
5245
5274
  }
5275
+ const type = parsed.type ?? "3";
5276
+ if (!/^-?\d+$/.test(type)) {
5277
+ return { error: `Invalid type in reference "${refText}". Expected an integer type value.` };
5278
+ }
5279
+ return {
5280
+ ref: {
5281
+ file_id: parsed.file_id,
5282
+ guid,
5283
+ type
5284
+ }
5285
+ };
5246
5286
  }
5247
- function appendToAddedComponents(doc, piId, targetSourceRef, newComponentId) {
5248
- const piBlock = doc.find_by_file_id(piId);
5249
- if (!piBlock)
5250
- return;
5251
- const entry = `
5252
- - targetCorrespondingSourceObject: ${targetSourceRef}
5253
- insertIndex: -1
5254
- addedObject: {fileID: ${newComponentId}}`;
5255
- let raw = piBlock.raw;
5256
- const emptyPattern = /m_AddedComponents:[ \t]*\[\]/;
5257
- if (emptyPattern.test(raw)) {
5258
- raw = raw.replace(emptyPattern, `m_AddedComponents:${entry}`);
5259
- piBlock.replace_raw(raw);
5260
- return;
5287
+ function serializePrefabRef(ref) {
5288
+ return `{fileID: ${ref.file_id}, guid: ${ref.guid}, type: ${ref.type}}`;
5289
+ }
5290
+ function ensurePrefabModificationSerializedVersion(prefabInstanceBlock) {
5291
+ const raw = prefabInstanceBlock.raw;
5292
+ const updated = raw.replace(/(^([ \t]*)m_Modification:[ \t]*\n)(?!\2[ \t]+serializedVersion:[ \t]*\d+)/m, (_match, prefix, indent) => `${prefix}${indent} serializedVersion: 3
5293
+ `);
5294
+ if (updated === raw) {
5295
+ return false;
5261
5296
  }
5262
- const existingPattern = /(m_AddedComponents:[ \t]*\n(?:[ \t]+-[\s\S]*?(?=\n[ \t]+m_|$))*)/;
5263
- if (existingPattern.test(raw)) {
5264
- raw = raw.replace(existingPattern, `$1${entry}`);
5265
- piBlock.replace_raw(raw);
5297
+ prefabInstanceBlock.replace_raw(updated);
5298
+ return true;
5299
+ }
5300
+ function addedOverrideEntryKey(entry) {
5301
+ return `${entry.target.file_id}|${entry.target.guid}|${entry.target.type}|${entry.insert_index}|${entry.added_object_file_id}`;
5302
+ }
5303
+ function splitPrefabArraySection(rawText, key) {
5304
+ const lines = rawText.split(`
5305
+ `);
5306
+ const keyPattern = new RegExp(`^([ ]*)${key}:[ ]*(.*)$`);
5307
+ let keyIndex = -1;
5308
+ let indent = "";
5309
+ for (let i = 0;i < lines.length; i++) {
5310
+ const match = lines[i].match(keyPattern);
5311
+ if (match) {
5312
+ keyIndex = i;
5313
+ indent = match[1];
5314
+ break;
5315
+ }
5316
+ }
5317
+ if (keyIndex === -1) {
5318
+ return null;
5319
+ }
5320
+ const keyIndentLen = indent.length;
5321
+ let sectionEnd = keyIndex + 1;
5322
+ for (let i = keyIndex + 1;i < lines.length; i++) {
5323
+ const line = lines[i];
5324
+ const trimmed = line.trim();
5325
+ if (trimmed.length === 0) {
5326
+ sectionEnd = i + 1;
5327
+ continue;
5328
+ }
5329
+ const indentLen = (line.match(/^[ \t]*/) || [""])[0].length;
5330
+ if (indentLen < keyIndentLen) {
5331
+ break;
5332
+ }
5333
+ if (indentLen === keyIndentLen && !trimmed.startsWith("-") && /^[A-Za-z_][A-Za-z0-9_]*:/.test(trimmed)) {
5334
+ break;
5335
+ }
5336
+ sectionEnd = i + 1;
5337
+ }
5338
+ return {
5339
+ lines,
5340
+ key_index: keyIndex,
5341
+ section_end: sectionEnd,
5342
+ indent
5343
+ };
5344
+ }
5345
+ function collectAddedOverrideEntries(lines, key_index, section_end, sourceGuid) {
5346
+ const sectionLines = lines.slice(key_index + 1, section_end);
5347
+ const dedup = new Map;
5348
+ let i = 0;
5349
+ while (i < sectionLines.length) {
5350
+ const line = sectionLines[i];
5351
+ const trimmed = line.trimStart();
5352
+ if (!trimmed.startsWith("-")) {
5353
+ i++;
5354
+ continue;
5355
+ }
5356
+ const entryIndent = (line.match(/^[ \t]*/) || [""])[0].length;
5357
+ const entryLines = [line];
5358
+ i++;
5359
+ while (i < sectionLines.length) {
5360
+ const nextLine = sectionLines[i];
5361
+ const nextTrimmed = nextLine.trim();
5362
+ const nextIndent = (nextLine.match(/^[ \t]*/) || [""])[0].length;
5363
+ if (nextTrimmed.length > 0 && nextIndent === entryIndent && nextTrimmed.startsWith("-")) {
5364
+ break;
5365
+ }
5366
+ entryLines.push(nextLine);
5367
+ i++;
5368
+ }
5369
+ const entryText = entryLines.join(`
5370
+ `);
5371
+ const targetMatch = entryText.match(/targetCorrespondingSourceObject:[ \t]*(\{[^}]+\})/);
5372
+ const insertMatch = entryText.match(/insertIndex:[ \t]*(-?\d+)/);
5373
+ const addedObjectMatch = entryText.match(/addedObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)/);
5374
+ if (!targetMatch || !addedObjectMatch) {
5375
+ continue;
5376
+ }
5377
+ const normalizedTarget = normalizePrefabRef(targetMatch[1], sourceGuid).ref;
5378
+ if (!normalizedTarget) {
5379
+ continue;
5380
+ }
5381
+ const insertIndex = insertMatch ? Number.parseInt(insertMatch[1], 10) : -1;
5382
+ const entry = {
5383
+ target: normalizedTarget,
5384
+ insert_index: Number.isNaN(insertIndex) ? -1 : insertIndex,
5385
+ added_object_file_id: addedObjectMatch[1]
5386
+ };
5387
+ const dedupKey = addedOverrideEntryKey(entry);
5388
+ if (!dedup.has(dedupKey)) {
5389
+ dedup.set(dedupKey, entry);
5390
+ }
5391
+ }
5392
+ return [...dedup.values()];
5393
+ }
5394
+ function appendToAddedOverrideArray(doc, piId, key, targetSourceRef, newObjectId) {
5395
+ const piBlock = doc.find_by_file_id(piId);
5396
+ if (!piBlock) {
5397
+ return { success: false, error: `PrefabInstance with fileID ${piId} not found` };
5398
+ }
5399
+ ensurePrefabModificationSerializedVersion(piBlock);
5400
+ const split = splitPrefabArraySection(piBlock.raw, key);
5401
+ if (!split) {
5402
+ return { success: false, error: `${key} property not found in PrefabInstance ${piId}` };
5403
+ }
5404
+ const sourceGuid = extractPrefabSourceGuid(piBlock.raw);
5405
+ const normalizedTarget = normalizePrefabRef(targetSourceRef, sourceGuid);
5406
+ if (!normalizedTarget.ref) {
5407
+ return { success: false, error: normalizedTarget.error };
5408
+ }
5409
+ const candidate = {
5410
+ target: normalizedTarget.ref,
5411
+ insert_index: -1,
5412
+ added_object_file_id: newObjectId
5413
+ };
5414
+ const candidateKey = addedOverrideEntryKey(candidate);
5415
+ const existing = collectAddedOverrideEntries(split.lines, split.key_index, split.section_end, sourceGuid);
5416
+ if (existing.some((entry) => addedOverrideEntryKey(entry) === candidateKey)) {
5417
+ return { success: true, changed: false };
5418
+ }
5419
+ const entryLines = [
5420
+ `${split.indent}- targetCorrespondingSourceObject: ${serializePrefabRef(candidate.target)}`,
5421
+ `${split.indent} insertIndex: ${candidate.insert_index}`,
5422
+ `${split.indent} addedObject: {fileID: ${candidate.added_object_file_id}}`
5423
+ ];
5424
+ const keyLine = split.lines[split.key_index].trim();
5425
+ let insertIndex = split.section_end;
5426
+ while (insertIndex > split.key_index + 1 && split.lines[insertIndex - 1].trim().length === 0) {
5427
+ insertIndex--;
5428
+ }
5429
+ let updatedLines;
5430
+ if (keyLine === `${key}: []`) {
5431
+ const replacement = [`${split.indent}${key}:`, ...entryLines];
5432
+ updatedLines = [
5433
+ ...split.lines.slice(0, split.key_index),
5434
+ ...replacement,
5435
+ ...split.lines.slice(split.section_end)
5436
+ ];
5437
+ } else {
5438
+ updatedLines = [
5439
+ ...split.lines.slice(0, insertIndex),
5440
+ ...entryLines,
5441
+ ...split.lines.slice(insertIndex)
5442
+ ];
5266
5443
  }
5444
+ piBlock.replace_raw(updatedLines.join(`
5445
+ `));
5446
+ return { success: true, changed: true };
5447
+ }
5448
+ function appendToAddedGameObjects(doc, piId, targetSourceRef, newGoId) {
5449
+ return appendToAddedOverrideArray(doc, piId, "m_AddedGameObjects", targetSourceRef, newGoId);
5450
+ }
5451
+ function appendToAddedComponents(doc, piId, targetSourceRef, newComponentId) {
5452
+ return appendToAddedOverrideArray(doc, piId, "m_AddedComponents", targetSourceRef, newComponentId);
5267
5453
  }
5454
+ var COMPONENT_FULL_TEMPLATES = {
5455
+ 65: (gameObjectId) => ` m_GameObject: {fileID: ${gameObjectId}}
5456
+ m_Material: {fileID: 0}
5457
+ m_IncludeLayers:
5458
+ serializedVersion: 2
5459
+ m_Bits: 0
5460
+ m_ExcludeLayers:
5461
+ serializedVersion: 2
5462
+ m_Bits: 0
5463
+ m_LayerOverridePriority: 0
5464
+ m_IsTrigger: 0
5465
+ m_ProvidesContacts: 0
5466
+ m_Enabled: 1
5467
+ serializedVersion: 3
5468
+ m_Size: {x: 1, y: 1, z: 1}
5469
+ m_Center: {x: 0, y: 0, z: 0}`,
5470
+ 82: (gameObjectId) => ` m_GameObject: {fileID: ${gameObjectId}}
5471
+ m_Enabled: 1
5472
+ serializedVersion: 4
5473
+ OutputAudioMixerGroup: {fileID: 0}
5474
+ m_audioClip: {fileID: 0}
5475
+ m_PlayOnAwake: 1
5476
+ m_Volume: 1
5477
+ m_Pitch: 1
5478
+ Loop: 0
5479
+ Mute: 0
5480
+ Spatialize: 0
5481
+ SpatializePostEffects: 0
5482
+ Priority: 128
5483
+ DopplerLevel: 1
5484
+ MinDistance: 1
5485
+ MaxDistance: 500
5486
+ Pan2D: 0
5487
+ rolloffMode: 0
5488
+ BypassEffects: 0
5489
+ BypassListenerEffects: 0
5490
+ BypassReverbZones: 0
5491
+ rolloffCustomCurve:
5492
+ serializedVersion: 2
5493
+ m_Curve:
5494
+ - serializedVersion: 3
5495
+ time: 0
5496
+ value: 1
5497
+ inSlope: 0
5498
+ outSlope: 0
5499
+ tangentMode: 0
5500
+ weightedMode: 0
5501
+ inWeight: 0.33333334
5502
+ outWeight: 0.33333334
5503
+ - serializedVersion: 3
5504
+ time: 1
5505
+ value: 0
5506
+ inSlope: 0
5507
+ outSlope: 0
5508
+ tangentMode: 0
5509
+ weightedMode: 0
5510
+ inWeight: 0.33333334
5511
+ outWeight: 0.33333334
5512
+ m_PreInfinity: 2
5513
+ m_PostInfinity: 2
5514
+ m_RotationOrder: 4
5515
+ panLevelCustomCurve:
5516
+ serializedVersion: 2
5517
+ m_Curve:
5518
+ - serializedVersion: 3
5519
+ time: 0
5520
+ value: 0
5521
+ inSlope: 0
5522
+ outSlope: 0
5523
+ tangentMode: 0
5524
+ weightedMode: 0
5525
+ inWeight: 0.33333334
5526
+ outWeight: 0.33333334
5527
+ m_PreInfinity: 2
5528
+ m_PostInfinity: 2
5529
+ m_RotationOrder: 4
5530
+ spreadCustomCurve:
5531
+ serializedVersion: 2
5532
+ m_Curve:
5533
+ - serializedVersion: 3
5534
+ time: 0
5535
+ value: 0
5536
+ inSlope: 0
5537
+ outSlope: 0
5538
+ tangentMode: 0
5539
+ weightedMode: 0
5540
+ inWeight: 0.33333334
5541
+ outWeight: 0.33333334
5542
+ m_PreInfinity: 2
5543
+ m_PostInfinity: 2
5544
+ m_RotationOrder: 4
5545
+ reverbZoneMixCustomCurve:
5546
+ serializedVersion: 2
5547
+ m_Curve:
5548
+ - serializedVersion: 3
5549
+ time: 0
5550
+ value: 1
5551
+ inSlope: 0
5552
+ outSlope: 0
5553
+ tangentMode: 0
5554
+ weightedMode: 0
5555
+ inWeight: 0.33333334
5556
+ outWeight: 0.33333334
5557
+ m_PreInfinity: 2
5558
+ m_PostInfinity: 2
5559
+ m_RotationOrder: 4`
5560
+ };
5268
5561
  var COMPONENT_DEFAULTS = {
5269
5562
  20: ` serializedVersion: 2
5270
5563
  m_ClearFlags: 1
@@ -5391,6 +5684,17 @@ var COMPONENT_DEFAULTS = {
5391
5684
  m_IgnoreParentGroups: 0`
5392
5685
  };
5393
5686
  function createGenericComponentYAML(componentName, classId, componentId, gameObjectId) {
5687
+ const fullTemplate = COMPONENT_FULL_TEMPLATES[classId];
5688
+ if (fullTemplate) {
5689
+ return `--- !u!${classId} &${componentId}
5690
+ ${componentName}:
5691
+ m_ObjectHideFlags: 0
5692
+ m_CorrespondingSourceObject: {fileID: 0}
5693
+ m_PrefabInstance: {fileID: 0}
5694
+ m_PrefabAsset: {fileID: 0}
5695
+ ${fullTemplate(gameObjectId)}
5696
+ `;
5697
+ }
5394
5698
  const defaults = COMPONENT_DEFAULTS[classId] ? `
5395
5699
  ` + COMPONENT_DEFAULTS[classId] + `
5396
5700
  ` : "";
@@ -5543,7 +5847,14 @@ function createGameObject(options) {
5543
5847
  doc.add_child_to_parent(parentTransformIdStr, transformIdStr);
5544
5848
  }
5545
5849
  if (variantPiId && strippedSourceRef) {
5546
- appendToAddedGameObjects(doc, variantPiId, strippedSourceRef, gameObjectIdStr);
5850
+ const appendResult = appendToAddedGameObjects(doc, variantPiId, strippedSourceRef, gameObjectIdStr);
5851
+ if (!appendResult.success) {
5852
+ return {
5853
+ success: false,
5854
+ file_path,
5855
+ error: appendResult.error || `Failed to update m_AddedGameObjects for PrefabInstance ${variantPiId}`
5856
+ };
5857
+ }
5547
5858
  }
5548
5859
  const saveResult = doc.save();
5549
5860
  if (!saveResult.success) {
@@ -6026,6 +6337,7 @@ PrefabInstance:
6026
6337
  m_ObjectHideFlags: 0
6027
6338
  serializedVersion: 2
6028
6339
  m_Modification:
6340
+ serializedVersion: 3
6029
6341
  m_TransformParent: {fileID: 0}
6030
6342
  m_Modifications:
6031
6343
  - target: {fileID: ${rootInfo.game_object.file_id}, guid: ${sourceGuid}, type: 3}
@@ -6172,6 +6484,7 @@ PrefabInstance:
6172
6484
  m_ObjectHideFlags: 0
6173
6485
  serializedVersion: 2
6174
6486
  m_Modification:
6487
+ serializedVersion: 3
6175
6488
  m_TransformParent: {fileID: ${parentTransformIdStr}}
6176
6489
  m_Modifications:
6177
6490
  - target: {fileID: ${rootGoFileId}, guid: ${sourceGuid}, type: 3}
@@ -6476,6 +6789,8 @@ function is_valid_component_base(base_class, project_path, depth = 0) {
6476
6789
  }
6477
6790
  if (!fullPath.endsWith(".cs") || !import_fs8.existsSync(fullPath)) {
6478
6791
  if (match.kind === "class") {
6792
+ if (fullPath.includes("Assembly-CSharp.dll"))
6793
+ return true;
6479
6794
  return is_likely_component_base_name(base_class);
6480
6795
  }
6481
6796
  }
@@ -6620,11 +6935,11 @@ function addComponent(options) {
6620
6935
  };
6621
6936
  }
6622
6937
  if (resolved.base_class && !COMPONENT_BASE_CLASSES.has(resolved.base_class) && !is_valid_component_base(resolved.base_class, project_path)) {
6623
- return {
6624
- success: false,
6625
- file_path,
6626
- 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).`
6627
- };
6938
+ 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).`;
6939
+ if (!extractionError)
6940
+ extractionError = warn;
6941
+ else
6942
+ extractionError += " " + warn;
6628
6943
  }
6629
6944
  let version;
6630
6945
  if (project_path) {
@@ -6640,10 +6955,22 @@ function addComponent(options) {
6640
6955
  componentYAML = createMonoBehaviourYAML(componentId, gameObjectId, resolved.guid, resolved.fields, version, scriptFileId, type_lookup_comp);
6641
6956
  scriptGuid = resolved.guid;
6642
6957
  scriptPath = resolved.path || undefined;
6643
- extractionError = resolved.extraction_error;
6958
+ if (resolved.extraction_error) {
6959
+ if (!extractionError)
6960
+ extractionError = resolved.extraction_error;
6961
+ else
6962
+ extractionError += " " + resolved.extraction_error;
6963
+ }
6644
6964
  }
6645
6965
  if (variantInfo) {
6646
- appendToAddedComponents(doc, variantInfo.prefab_instance_id, variantInfo.source_ref, componentIdStr);
6966
+ const appendResult = appendToAddedComponents(doc, variantInfo.prefab_instance_id, variantInfo.source_ref, componentIdStr);
6967
+ if (!appendResult.success) {
6968
+ return {
6969
+ success: false,
6970
+ file_path,
6971
+ error: appendResult.error || `Failed to update m_AddedComponents for PrefabInstance ${variantInfo.prefab_instance_id}`
6972
+ };
6973
+ }
6647
6974
  } else {
6648
6975
  const added = addComponentToGameObject(doc, gameObjectIdStr, componentIdStr);
6649
6976
  if (!added) {
@@ -7615,7 +7942,7 @@ function editComponentByFileId(options) {
7615
7942
  return {
7616
7943
  success: false,
7617
7944
  file_path,
7618
- error: `Could not resolve "${new_value}" to asset reference. Ensure GUID cache exists (run "setup <project>" first).`
7945
+ error: `Could not resolve "${new_value}" to asset reference. Ensure GUID cache exists (run "setup" first, or "setup -p <path>").`
7619
7946
  };
7620
7947
  }
7621
7948
  if (resolved !== undefined) {
@@ -8192,6 +8519,193 @@ function findPrefabInstanceBlock(doc, identifier) {
8192
8519
  }
8193
8520
  return null;
8194
8521
  }
8522
+ function parsePrefabRef2(refText) {
8523
+ const fileIdMatch = refText.match(/fileID:[ \t]*(-?\d+)/i);
8524
+ if (!fileIdMatch)
8525
+ return null;
8526
+ const guidMatch = refText.match(/guid:[ \t]*([a-f0-9]{32})/i);
8527
+ const typeMatch = refText.match(/type:[ \t]*(-?\d+)/i);
8528
+ return {
8529
+ file_id: fileIdMatch[1],
8530
+ guid: guidMatch ? guidMatch[1].toLowerCase() : undefined,
8531
+ type: typeMatch ? typeMatch[1] : undefined
8532
+ };
8533
+ }
8534
+ function extractPrefabSourceGuid2(prefabInstanceRaw) {
8535
+ const sourceMatch = prefabInstanceRaw.match(/m_SourcePrefab:[ \t]*\{[^}]*guid:[ \t]*([a-f0-9]{32})/i);
8536
+ return sourceMatch ? sourceMatch[1].toLowerCase() : null;
8537
+ }
8538
+ function normalizePrefabRef2(refText, sourceGuid) {
8539
+ const parsed = parsePrefabRef2(refText);
8540
+ if (!parsed) {
8541
+ return { error: `Invalid reference "${refText}". Expected format: {fileID: N, guid: ..., type: T}` };
8542
+ }
8543
+ const guid = parsed.guid ?? sourceGuid;
8544
+ if (!guid) {
8545
+ return { error: `Reference "${refText}" is missing guid and source prefab guid could not be inferred.` };
8546
+ }
8547
+ if (!/^[a-f0-9]{32}$/i.test(guid)) {
8548
+ return { error: `Invalid guid in reference "${refText}". Expected 32-character hex GUID.` };
8549
+ }
8550
+ const type = parsed.type ?? "3";
8551
+ if (!/^-?\d+$/.test(type)) {
8552
+ return { error: `Invalid type in reference "${refText}". Expected an integer type value.` };
8553
+ }
8554
+ return {
8555
+ ref: {
8556
+ file_id: parsed.file_id,
8557
+ guid,
8558
+ type
8559
+ }
8560
+ };
8561
+ }
8562
+ function parsePrefabRefMatcher(refText) {
8563
+ const parsed = parsePrefabRef2(refText);
8564
+ if (!parsed) {
8565
+ return { error: `Invalid reference "${refText}". Expected format: {fileID: N, guid: ..., type: T}` };
8566
+ }
8567
+ if (parsed.guid && !/^[a-f0-9]{32}$/i.test(parsed.guid)) {
8568
+ return { error: `Invalid guid in reference "${refText}". Expected 32-character hex GUID.` };
8569
+ }
8570
+ if (parsed.type && !/^-?\d+$/.test(parsed.type)) {
8571
+ return { error: `Invalid type in reference "${refText}". Expected an integer type value.` };
8572
+ }
8573
+ return { matcher: parsed };
8574
+ }
8575
+ function prefabRefKey(ref) {
8576
+ return `${ref.file_id}|${ref.guid}|${ref.type}`;
8577
+ }
8578
+ function serializePrefabRef2(ref) {
8579
+ return `{fileID: ${ref.file_id}, guid: ${ref.guid}, type: ${ref.type}}`;
8580
+ }
8581
+ function splitPrefabArraySection2(rawText, key) {
8582
+ const lines = rawText.split(`
8583
+ `);
8584
+ const keyPattern = new RegExp(`^([ ]*)${key}:[ ]*(.*)$`);
8585
+ let keyIndex = -1;
8586
+ let indent = "";
8587
+ for (let i = 0;i < lines.length; i++) {
8588
+ const match = lines[i].match(keyPattern);
8589
+ if (match) {
8590
+ keyIndex = i;
8591
+ indent = match[1];
8592
+ break;
8593
+ }
8594
+ }
8595
+ if (keyIndex === -1) {
8596
+ return null;
8597
+ }
8598
+ const keyIndentLen = indent.length;
8599
+ let sectionEnd = keyIndex + 1;
8600
+ for (let i = keyIndex + 1;i < lines.length; i++) {
8601
+ const line = lines[i];
8602
+ const trimmed = line.trim();
8603
+ if (trimmed.length === 0) {
8604
+ sectionEnd = i + 1;
8605
+ continue;
8606
+ }
8607
+ const indentLen = (line.match(/^[ \t]*/) || [""])[0].length;
8608
+ if (indentLen < keyIndentLen) {
8609
+ break;
8610
+ }
8611
+ if (indentLen === keyIndentLen && !trimmed.startsWith("-") && /^[A-Za-z_][A-Za-z0-9_]*:/.test(trimmed)) {
8612
+ break;
8613
+ }
8614
+ sectionEnd = i + 1;
8615
+ }
8616
+ return {
8617
+ lines,
8618
+ key_index: keyIndex,
8619
+ section_end: sectionEnd,
8620
+ indent
8621
+ };
8622
+ }
8623
+ function collectNormalizedRefsFromSection(lines, key_index, section_end, sourceGuid) {
8624
+ const sectionText = lines.slice(key_index, section_end).join(`
8625
+ `);
8626
+ const refMatches = sectionText.match(/\{[^}]*\}/g) || [];
8627
+ const dedup = new Map;
8628
+ for (const token of refMatches) {
8629
+ const parsed = parsePrefabRef2(token);
8630
+ if (!parsed)
8631
+ continue;
8632
+ const guid = parsed.guid ?? sourceGuid;
8633
+ if (!guid || !/^[a-f0-9]{32}$/i.test(guid))
8634
+ continue;
8635
+ const type = parsed.type ?? "3";
8636
+ if (!/^-?\d+$/.test(type))
8637
+ continue;
8638
+ const normalized = {
8639
+ file_id: parsed.file_id,
8640
+ guid: guid.toLowerCase(),
8641
+ type
8642
+ };
8643
+ const key = prefabRefKey(normalized);
8644
+ if (!dedup.has(key)) {
8645
+ dedup.set(key, normalized);
8646
+ }
8647
+ }
8648
+ return [...dedup.values()];
8649
+ }
8650
+ function mutatePrefabReferenceList(options) {
8651
+ const { target_block, key, ref_text, action, item_label } = options;
8652
+ const split = splitPrefabArraySection2(target_block.raw, key);
8653
+ if (!split) {
8654
+ return { success: false, error: `${key} property not found in PrefabInstance` };
8655
+ }
8656
+ const sourceGuid = extractPrefabSourceGuid2(target_block.raw);
8657
+ const existing = collectNormalizedRefsFromSection(split.lines, split.key_index, split.section_end, sourceGuid);
8658
+ let next = existing;
8659
+ if (action === "add") {
8660
+ const normalized = normalizePrefabRef2(ref_text, sourceGuid);
8661
+ if (!normalized.ref) {
8662
+ return { success: false, error: normalized.error };
8663
+ }
8664
+ const keyText = prefabRefKey(normalized.ref);
8665
+ if (existing.some((entry) => prefabRefKey(entry) === keyText)) {
8666
+ return { success: true, changed: false };
8667
+ }
8668
+ next = [...existing, normalized.ref];
8669
+ } else {
8670
+ const matcherResult = parsePrefabRefMatcher(ref_text);
8671
+ const matcher = matcherResult.matcher;
8672
+ if (!matcher) {
8673
+ return { success: false, error: matcherResult.error };
8674
+ }
8675
+ next = existing.filter((entry) => {
8676
+ if (entry.file_id !== matcher.file_id)
8677
+ return true;
8678
+ if (matcher.guid && entry.guid !== matcher.guid.toLowerCase())
8679
+ return true;
8680
+ if (matcher.type && entry.type !== matcher.type)
8681
+ return true;
8682
+ return false;
8683
+ });
8684
+ if (next.length === existing.length) {
8685
+ return {
8686
+ success: false,
8687
+ error: `${item_label} reference "${ref_text}" not found in ${key}`
8688
+ };
8689
+ }
8690
+ }
8691
+ const replacement = [];
8692
+ if (next.length === 0) {
8693
+ replacement.push(`${split.indent}${key}: []`);
8694
+ } else {
8695
+ replacement.push(`${split.indent}${key}:`);
8696
+ for (const entry of next) {
8697
+ replacement.push(`${split.indent}- ${serializePrefabRef2(entry)}`);
8698
+ }
8699
+ }
8700
+ const updatedLines = [
8701
+ ...split.lines.slice(0, split.key_index),
8702
+ ...replacement,
8703
+ ...split.lines.slice(split.section_end)
8704
+ ];
8705
+ target_block.replace_raw(updatedLines.join(`
8706
+ `));
8707
+ return { success: true, changed: true };
8708
+ }
8195
8709
  function editArray(options) {
8196
8710
  const { file_path, file_id, array_property, action, value, index } = options;
8197
8711
  if (!import_fs10.existsSync(file_path)) {
@@ -8402,9 +8916,6 @@ function addRemovedComponent(options) {
8402
8916
  if (!import_fs10.existsSync(file_path)) {
8403
8917
  return { success: false, file_path, error: `File not found: ${file_path}` };
8404
8918
  }
8405
- if (!/^\{fileID:\s*-?\d+/.test(component_ref)) {
8406
- return { success: false, file_path, error: `Invalid component reference "${component_ref}". Expected format: {fileID: N, guid: ..., type: T}` };
8407
- }
8408
8919
  let doc;
8409
8920
  try {
8410
8921
  doc = UnityDocument.from_file(file_path);
@@ -8415,19 +8926,16 @@ function addRemovedComponent(options) {
8415
8926
  if (!targetBlock) {
8416
8927
  return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
8417
8928
  }
8418
- let rawText = targetBlock.raw;
8419
- if (/m_RemovedComponents:\s*\[\]/.test(rawText)) {
8420
- rawText = rawText.replace(/m_RemovedComponents:\s*\[\]/, "m_RemovedComponents:");
8421
- }
8422
- const removedPattern = /m_RemovedComponents:\s*\n/;
8423
- if (removedPattern.test(rawText)) {
8424
- rawText = rawText.replace(removedPattern, `m_RemovedComponents:
8425
- - ${component_ref}
8426
- `);
8427
- } else {
8428
- return { success: false, file_path, error: "m_RemovedComponents property not found in PrefabInstance" };
8929
+ const mutation = mutatePrefabReferenceList({
8930
+ target_block: targetBlock,
8931
+ key: "m_RemovedComponents",
8932
+ ref_text: component_ref,
8933
+ action: "add",
8934
+ item_label: "Component"
8935
+ });
8936
+ if (!mutation.success) {
8937
+ return { success: false, file_path, error: mutation.error };
8429
8938
  }
8430
- targetBlock.replace_raw(rawText);
8431
8939
  if (!doc.validate()) {
8432
8940
  return { success: false, file_path, error: "Validation failed after adding removed component" };
8433
8941
  }
@@ -8456,13 +8964,16 @@ function removeRemovedComponent(options) {
8456
8964
  if (!targetBlock) {
8457
8965
  return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
8458
8966
  }
8459
- const escapedRef = component_ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8460
- const refPattern = new RegExp(`\\s*- ${escapedRef}\\n?`, "m");
8461
- if (!refPattern.test(targetBlock.raw)) {
8462
- return { success: false, file_path, error: `Component reference "${component_ref}" not found in m_RemovedComponents` };
8967
+ const mutation = mutatePrefabReferenceList({
8968
+ target_block: targetBlock,
8969
+ key: "m_RemovedComponents",
8970
+ ref_text: component_ref,
8971
+ action: "remove",
8972
+ item_label: "Component"
8973
+ });
8974
+ if (!mutation.success) {
8975
+ return { success: false, file_path, error: mutation.error };
8463
8976
  }
8464
- const updatedRaw = targetBlock.raw.replace(refPattern, "");
8465
- targetBlock.replace_raw(updatedRaw);
8466
8977
  if (!doc.validate()) {
8467
8978
  return { success: false, file_path, error: "Validation failed after removing component" };
8468
8979
  }
@@ -8481,9 +8992,6 @@ function addRemovedGameObject(options) {
8481
8992
  if (!import_fs10.existsSync(file_path)) {
8482
8993
  return { success: false, file_path, error: `File not found: ${file_path}` };
8483
8994
  }
8484
- if (!/^\{fileID:\s*-?\d+/.test(component_ref)) {
8485
- return { success: false, file_path, error: `Invalid GameObject reference "${component_ref}". Expected format: {fileID: N, guid: ..., type: T}` };
8486
- }
8487
8995
  let doc;
8488
8996
  try {
8489
8997
  doc = UnityDocument.from_file(file_path);
@@ -8494,19 +9002,16 @@ function addRemovedGameObject(options) {
8494
9002
  if (!targetBlock) {
8495
9003
  return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
8496
9004
  }
8497
- let rawText = targetBlock.raw;
8498
- if (/m_RemovedGameObjects:\s*\[\]/.test(rawText)) {
8499
- rawText = rawText.replace(/m_RemovedGameObjects:\s*\[\]/, "m_RemovedGameObjects:");
8500
- }
8501
- const removedPattern = /m_RemovedGameObjects:\s*\n/;
8502
- if (removedPattern.test(rawText)) {
8503
- rawText = rawText.replace(removedPattern, `m_RemovedGameObjects:
8504
- - ${component_ref}
8505
- `);
8506
- } else {
8507
- return { success: false, file_path, error: "m_RemovedGameObjects property not found in PrefabInstance" };
9005
+ const mutation = mutatePrefabReferenceList({
9006
+ target_block: targetBlock,
9007
+ key: "m_RemovedGameObjects",
9008
+ ref_text: component_ref,
9009
+ action: "add",
9010
+ item_label: "GameObject"
9011
+ });
9012
+ if (!mutation.success) {
9013
+ return { success: false, file_path, error: mutation.error };
8508
9014
  }
8509
- targetBlock.replace_raw(rawText);
8510
9015
  if (!doc.validate()) {
8511
9016
  return { success: false, file_path, error: "Validation failed after adding removed GameObject" };
8512
9017
  }
@@ -8535,13 +9040,16 @@ function removeRemovedGameObject(options) {
8535
9040
  if (!targetBlock) {
8536
9041
  return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
8537
9042
  }
8538
- const escapedRef = component_ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8539
- const refPattern = new RegExp(`\\s*- ${escapedRef}\\n?`, "m");
8540
- if (!refPattern.test(targetBlock.raw)) {
8541
- return { success: false, file_path, error: `GameObject reference "${component_ref}" not found in m_RemovedGameObjects` };
9043
+ const mutation = mutatePrefabReferenceList({
9044
+ target_block: targetBlock,
9045
+ key: "m_RemovedGameObjects",
9046
+ ref_text: component_ref,
9047
+ action: "remove",
9048
+ item_label: "GameObject"
9049
+ });
9050
+ if (!mutation.success) {
9051
+ return { success: false, file_path, error: mutation.error };
8542
9052
  }
8543
- const updatedRaw = targetBlock.raw.replace(refPattern, "");
8544
- targetBlock.replace_raw(updatedRaw);
8545
9053
  if (!doc.validate()) {
8546
9054
  return { success: false, file_path, error: "Validation failed after removing GameObject" };
8547
9055
  }
@@ -9398,6 +9906,7 @@ function removeComponent(options) {
9398
9906
  return { success: false, file_path, error: "Cannot remove a Transform with remove-component. Use delete to remove the entire GameObject." };
9399
9907
  }
9400
9908
  const resolved_file_id = found.file_id;
9909
+ let removed_added_component_overrides = 0;
9401
9910
  const goMatch = found.raw.match(/m_GameObject:[ \t]*\{fileID:[ \t]*(-?\d+)\}/);
9402
9911
  if (goMatch) {
9403
9912
  const parentGoId = goMatch[1];
@@ -9408,6 +9917,19 @@ function removeComponent(options) {
9408
9917
  const modifiedRaw = goBlock.raw.replace(compLinePattern, "");
9409
9918
  if (modifiedRaw === goBlock.raw) {}
9410
9919
  goBlock.replace_raw(modifiedRaw);
9920
+ if (goBlock.is_stripped) {
9921
+ const piMatch = goBlock.raw.match(/m_PrefabInstance:[ \t]*\{fileID:[ \t]*(-?\d+)\}/);
9922
+ if (piMatch) {
9923
+ const piBlock = doc.find_by_file_id(piMatch[1]);
9924
+ if (piBlock && piBlock.class_id === 1001) {
9925
+ const updated = removeAddedComponentOverrideEntries(piBlock.raw, resolved_file_id);
9926
+ if (updated.removed_count > 0) {
9927
+ piBlock.replace_raw(updated.updated_raw);
9928
+ removed_added_component_overrides += updated.removed_count;
9929
+ }
9930
+ }
9931
+ }
9932
+ }
9411
9933
  }
9412
9934
  }
9413
9935
  const dangling = [];
@@ -9431,7 +9953,7 @@ function removeComponent(options) {
9431
9953
  file_path,
9432
9954
  removed_file_id: resolved_file_id,
9433
9955
  removed_class_id: found.class_id,
9434
- warning: dangling.length > 0 ? `Dangling references to deleted component found in blocks: ${dangling.join(", ")}` : undefined
9956
+ 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
9435
9957
  };
9436
9958
  }
9437
9959
  function removeComponentBatch(options) {
@@ -9549,6 +10071,124 @@ function findPrefabInstanceBlock2(doc, identifier) {
9549
10071
  }
9550
10072
  return null;
9551
10073
  }
10074
+ function splitPrefabArraySection3(rawText, key) {
10075
+ const lines = rawText.split(`
10076
+ `);
10077
+ const keyPattern = new RegExp(`^[ ]*${key}:[ ]*(.*)$`);
10078
+ let keyIndex = -1;
10079
+ let keyIndentLen = 0;
10080
+ for (let i = 0;i < lines.length; i++) {
10081
+ const match = lines[i].match(keyPattern);
10082
+ if (match) {
10083
+ keyIndex = i;
10084
+ keyIndentLen = (lines[i].match(/^[ \t]*/) || [""])[0].length;
10085
+ break;
10086
+ }
10087
+ }
10088
+ if (keyIndex === -1) {
10089
+ return null;
10090
+ }
10091
+ let sectionEnd = keyIndex + 1;
10092
+ for (let i = keyIndex + 1;i < lines.length; i++) {
10093
+ const line = lines[i];
10094
+ const trimmed = line.trim();
10095
+ if (trimmed.length === 0) {
10096
+ sectionEnd = i + 1;
10097
+ continue;
10098
+ }
10099
+ const indentLen = (line.match(/^[ \t]*/) || [""])[0].length;
10100
+ if (indentLen < keyIndentLen) {
10101
+ break;
10102
+ }
10103
+ if (indentLen === keyIndentLen && !trimmed.startsWith("-") && /^[A-Za-z_][A-Za-z0-9_]*:/.test(trimmed)) {
10104
+ break;
10105
+ }
10106
+ sectionEnd = i + 1;
10107
+ }
10108
+ return {
10109
+ lines,
10110
+ key_index: keyIndex,
10111
+ section_end: sectionEnd
10112
+ };
10113
+ }
10114
+ function collectAddedObjectFileIdsFromArray(rawText, key) {
10115
+ const section = splitPrefabArraySection3(rawText, key);
10116
+ if (!section)
10117
+ return [];
10118
+ const keyLine = section.lines[section.key_index].trim();
10119
+ if (new RegExp(`^${key}:[ ]*[]$`).test(keyLine)) {
10120
+ return [];
10121
+ }
10122
+ const sectionText = section.lines.slice(section.key_index, section.section_end).join(`
10123
+ `);
10124
+ const fileIds = new Set;
10125
+ const addedObjectPattern = /addedObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)/g;
10126
+ let match;
10127
+ while ((match = addedObjectPattern.exec(sectionText)) !== null) {
10128
+ fileIds.add(match[1]);
10129
+ }
10130
+ if (fileIds.size === 0) {
10131
+ const inlineEntryPattern = /^[ \t]*-[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)/gm;
10132
+ while ((match = inlineEntryPattern.exec(sectionText)) !== null) {
10133
+ fileIds.add(match[1]);
10134
+ }
10135
+ }
10136
+ return [...fileIds];
10137
+ }
10138
+ function removeAddedComponentOverrideEntries(rawText, componentId) {
10139
+ const section = splitPrefabArraySection3(rawText, "m_AddedComponents");
10140
+ if (!section)
10141
+ return { updated_raw: rawText, removed_count: 0 };
10142
+ const keyLine = section.lines[section.key_index].trim();
10143
+ if (/^m_AddedComponents:[ \t]*\[\]$/.test(keyLine)) {
10144
+ return { updated_raw: rawText, removed_count: 0 };
10145
+ }
10146
+ const bodyLines = section.lines.slice(section.key_index + 1, section.section_end);
10147
+ const keptEntries = [];
10148
+ let removed = 0;
10149
+ let i = 0;
10150
+ while (i < bodyLines.length) {
10151
+ const line = bodyLines[i];
10152
+ const trimmed = line.trimStart();
10153
+ if (!trimmed.startsWith("-")) {
10154
+ i++;
10155
+ continue;
10156
+ }
10157
+ const entryIndent = (line.match(/^[ \t]*/) || [""])[0].length;
10158
+ const entryLines = [line];
10159
+ i++;
10160
+ while (i < bodyLines.length) {
10161
+ const nextLine = bodyLines[i];
10162
+ const nextTrimmed = nextLine.trim();
10163
+ const nextIndent = (nextLine.match(/^[ \t]*/) || [""])[0].length;
10164
+ if (nextTrimmed.length > 0 && nextIndent === entryIndent && nextTrimmed.startsWith("-")) {
10165
+ break;
10166
+ }
10167
+ entryLines.push(nextLine);
10168
+ i++;
10169
+ }
10170
+ const entryText = entryLines.join(`
10171
+ `);
10172
+ const addedObjectMatch = entryText.match(/addedObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)/);
10173
+ if (addedObjectMatch && addedObjectMatch[1] === componentId) {
10174
+ removed++;
10175
+ continue;
10176
+ }
10177
+ keptEntries.push(entryLines);
10178
+ }
10179
+ if (removed === 0) {
10180
+ return { updated_raw: rawText, removed_count: 0 };
10181
+ }
10182
+ const keyIndent = (section.lines[section.key_index].match(/^[ \t]*/) || [""])[0];
10183
+ const replacement = keptEntries.length === 0 ? [`${keyIndent}m_AddedComponents: []`] : [`${keyIndent}m_AddedComponents:`, ...keptEntries.flat()];
10184
+ const updatedLines = [
10185
+ ...section.lines.slice(0, section.key_index),
10186
+ ...replacement,
10187
+ ...section.lines.slice(section.section_end)
10188
+ ];
10189
+ return { updated_raw: updatedLines.join(`
10190
+ `), removed_count: removed };
10191
+ }
9552
10192
  function deletePrefabInstance(options) {
9553
10193
  const { file_path, prefab_instance } = options;
9554
10194
  const pathError = validate_file_path(file_path, "write");
@@ -9576,36 +10216,23 @@ function deletePrefabInstance(options) {
9576
10216
  allToRemove.add(block.file_id);
9577
10217
  }
9578
10218
  }
9579
- const addedGOPattern = /m_AddedGameObjects:\s*\n((?:\s*- \{fileID: \d+\}\n)*)/m;
9580
- const addedGOMatch = piBlock.raw.match(addedGOPattern);
9581
- if (addedGOMatch) {
9582
- const addedGOSection = addedGOMatch[1];
9583
- const goMatches = addedGOSection.matchAll(/fileID:[ \t]*(\d+)/g);
9584
- for (const match of goMatches) {
9585
- const goId = match[1];
9586
- allToRemove.add(goId);
9587
- const goBlock = doc.find_by_file_id(goId);
9588
- if (goBlock) {
9589
- const compMatch = goBlock.raw.match(/component:[ \t]*\{fileID:[ \t]*(\d+)\}/);
9590
- if (compMatch) {
9591
- const transformId = compMatch[1];
9592
- allToRemove.add(transformId);
9593
- const descendants = doc.collect_hierarchy(transformId);
9594
- for (const id of descendants) {
9595
- allToRemove.add(id);
9596
- }
10219
+ for (const goId of collectAddedObjectFileIdsFromArray(piBlock.raw, "m_AddedGameObjects")) {
10220
+ allToRemove.add(goId);
10221
+ const goBlock = doc.find_by_file_id(goId);
10222
+ if (goBlock) {
10223
+ const compMatch = goBlock.raw.match(/component:[ \t]*\{fileID:[ \t]*(\d+)\}/);
10224
+ if (compMatch) {
10225
+ const transformId = compMatch[1];
10226
+ allToRemove.add(transformId);
10227
+ const descendants = doc.collect_hierarchy(transformId);
10228
+ for (const id of descendants) {
10229
+ allToRemove.add(id);
9597
10230
  }
9598
10231
  }
9599
10232
  }
9600
10233
  }
9601
- const addedCompPattern = /m_AddedComponents:\s*\n((?:\s*- \{fileID: \d+\}\n)*)/m;
9602
- const addedCompMatch = piBlock.raw.match(addedCompPattern);
9603
- if (addedCompMatch) {
9604
- const addedCompSection = addedCompMatch[1];
9605
- const compMatches = addedCompSection.matchAll(/fileID:[ \t]*(\d+)/g);
9606
- for (const match of compMatches) {
9607
- allToRemove.add(match[1]);
9608
- }
10234
+ for (const componentId of collectAddedObjectFileIdsFromArray(piBlock.raw, "m_AddedComponents")) {
10235
+ allToRemove.add(componentId);
9609
10236
  }
9610
10237
  const parentMatch = piBlock.raw.match(/m_TransformParent:[ \t]*\{fileID:[ \t]*(\d+)\}/);
9611
10238
  const parentTransformId = parentMatch ? parentMatch[1] : "0";
@@ -10615,10 +11242,11 @@ function build_create_command() {
10615
11242
  if (!result.success)
10616
11243
  process.exitCode = 1;
10617
11244
  });
10618
- cmd.command("build <project_path> <scene_path>").description("Add a scene to build settings").option("--index <n>", "Insert at position (0-based)").option("-j, --json", "Output as JSON").action((project_path, scene_path, options) => {
11245
+ cmd.command("build <scene_path>").description("Add a scene to build settings").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("--index <n>", "Insert at position (0-based)").option("-j, --json", "Output as JSON").action((scene_path, options) => {
10619
11246
  try {
11247
+ const resolvedProjectPath = resolve_project_path(options.project);
10620
11248
  const position = options.index !== undefined ? parseInt(options.index, 10) : undefined;
10621
- const result = add_scene(project_path, scene_path, { position });
11249
+ const result = add_scene(resolvedProjectPath, scene_path, { position });
10622
11250
  console.log(JSON.stringify(result, null, 2));
10623
11251
  if (!result.success)
10624
11252
  process.exitCode = 1;
@@ -10726,9 +11354,10 @@ NativeFormatImporter:
10726
11354
  shader_guid
10727
11355
  }, null, 2));
10728
11356
  });
10729
- cmd.command("package <project_path> <name> <version>").description("Add a package to Packages/manifest.json").option("-j, --json", "Output as JSON").action((project_path, name, version, _options) => {
11357
+ cmd.command("package <name> <version>").description("Add a package to Packages/manifest.json").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-j, --json", "Output as JSON").action((name, version, options) => {
10730
11358
  try {
10731
- const result = add_package(project_path, name, version);
11359
+ const resolvedProjectPath = resolve_project_path(options.project);
11360
+ const result = add_package(resolvedProjectPath, name, version);
10732
11361
  if ("error" in result) {
10733
11362
  console.log(JSON.stringify({ success: false, error: result.error }, null, 2));
10734
11363
  process.exitCode = 1;
@@ -11032,6 +11661,116 @@ init_scanner();
11032
11661
  var import_fs18 = require("fs");
11033
11662
  var import_path8 = require("path");
11034
11663
  var import_os = require("os");
11664
+ function parse_unity_ref(ref_text) {
11665
+ const file_id_match = ref_text.match(/fileID:[ \t]*(-?\d+)/i);
11666
+ if (!file_id_match)
11667
+ return null;
11668
+ const guid_match = ref_text.match(/guid:[ \t]*([a-f0-9]{32})/i);
11669
+ const type_match = ref_text.match(/type:[ \t]*(-?\d+)/i);
11670
+ const parsed = { file_id: file_id_match[1] };
11671
+ if (guid_match)
11672
+ parsed.guid = guid_match[1].toLowerCase();
11673
+ if (type_match)
11674
+ parsed.type = parseInt(type_match[1], 10);
11675
+ return parsed;
11676
+ }
11677
+ function extract_prefab_list_section(raw, key) {
11678
+ const lines = raw.split(`
11679
+ `);
11680
+ const key_pattern = new RegExp(`^(\\s*)${key}:[ \\t]*(.*)$`);
11681
+ let key_index = -1;
11682
+ let key_indent = 0;
11683
+ for (let i = 0;i < lines.length; i++) {
11684
+ const match = lines[i].match(key_pattern);
11685
+ if (match) {
11686
+ key_index = i;
11687
+ key_indent = match[1].length;
11688
+ break;
11689
+ }
11690
+ }
11691
+ if (key_index === -1)
11692
+ return null;
11693
+ const section_lines = [lines[key_index]];
11694
+ for (let i = key_index + 1;i < lines.length; i++) {
11695
+ const line = lines[i];
11696
+ const trimmed = line.trim();
11697
+ if (trimmed.length === 0) {
11698
+ section_lines.push(line);
11699
+ continue;
11700
+ }
11701
+ const indent = (line.match(/^\s*/) || [""])[0].length;
11702
+ if (indent < key_indent)
11703
+ break;
11704
+ if (indent === key_indent && !trimmed.startsWith("-") && /^[A-Za-z_][A-Za-z0-9_]*:/.test(trimmed)) {
11705
+ break;
11706
+ }
11707
+ section_lines.push(line);
11708
+ }
11709
+ return { key_indent, section_lines };
11710
+ }
11711
+ function parse_removed_list(raw, key) {
11712
+ const section = extract_prefab_list_section(raw, key);
11713
+ if (!section)
11714
+ return [];
11715
+ const key_line = section.section_lines[0].trim();
11716
+ if (key_line.endsWith("[]"))
11717
+ return [];
11718
+ const refs = section.section_lines.join(`
11719
+ `).match(/\{[^}]*fileID:[^}]*\}/g) || [];
11720
+ const dedup = new Map;
11721
+ for (const ref of refs) {
11722
+ const parsed = parse_unity_ref(ref);
11723
+ if (!parsed)
11724
+ continue;
11725
+ const key_text = `${parsed.file_id}|${parsed.guid ?? ""}|${parsed.type ?? ""}`;
11726
+ if (!dedup.has(key_text))
11727
+ dedup.set(key_text, parsed);
11728
+ }
11729
+ return [...dedup.values()];
11730
+ }
11731
+ function parse_added_list(raw, key) {
11732
+ const section = extract_prefab_list_section(raw, key);
11733
+ if (!section)
11734
+ return [];
11735
+ const key_line = section.section_lines[0].trim();
11736
+ if (key_line.endsWith("[]"))
11737
+ return [];
11738
+ const lines = section.section_lines.slice(1);
11739
+ const entries = [];
11740
+ let i = 0;
11741
+ while (i < lines.length) {
11742
+ const line = lines[i];
11743
+ const trimmed = line.trimStart();
11744
+ if (!trimmed.startsWith("-")) {
11745
+ i++;
11746
+ continue;
11747
+ }
11748
+ const entry_indent = (line.match(/^\s*/) || [""])[0].length;
11749
+ const entry_lines = [line];
11750
+ i++;
11751
+ while (i < lines.length) {
11752
+ const next = lines[i];
11753
+ const next_trimmed = next.trim();
11754
+ const next_indent = (next.match(/^\s*/) || [""])[0].length;
11755
+ if (next_trimmed.length > 0 && next_indent === entry_indent && next_trimmed.startsWith("-")) {
11756
+ break;
11757
+ }
11758
+ entry_lines.push(next);
11759
+ i++;
11760
+ }
11761
+ const entry_text = entry_lines.join(`
11762
+ `);
11763
+ const target_match = entry_text.match(/targetCorrespondingSourceObject:[ \t]*(\{[^}]+\})/);
11764
+ const insert_match = entry_text.match(/insertIndex:[ \t]*(-?\d+)/);
11765
+ const added_match = entry_text.match(/addedObject:[ \t]*(\{[^}]+\})/);
11766
+ entries.push({
11767
+ target_corresponding_source_object: target_match ? parse_unity_ref(target_match[1]) : null,
11768
+ insert_index: insert_match ? parseInt(insert_match[1], 10) : null,
11769
+ added_object: added_match ? parse_unity_ref(added_match[1]) : null
11770
+ });
11771
+ }
11772
+ return entries;
11773
+ }
11035
11774
  function parse_material_yaml(content) {
11036
11775
  const lines = content.replace(/\r/g, "").split(`
11037
11776
  `);
@@ -11735,9 +12474,16 @@ function build_read_command(getScanner) {
11735
12474
  }
11736
12475
  if (result.total === 0 && !result.error) {
11737
12476
  const prefabInstances = result.prefabInstances;
12477
+ let variantDetected = false;
12478
+ let variantResolvedFromSource = false;
11738
12479
  if (prefabInstances && prefabInstances.length > 0) {
12480
+ variantDetected = true;
11739
12481
  try {
11740
12482
  const doc = UnityDocument.from_file(file);
12483
+ const strippedGoCount = doc.blocks.filter((b) => b.class_id === 1 && b.is_stripped).length;
12484
+ if (strippedGoCount > 0) {
12485
+ variantDetected = true;
12486
+ }
11741
12487
  const projectPath = find_unity_project_root(import_path8.dirname(file));
11742
12488
  const resolved = resolve_source_prefab(doc, file, projectPath ?? undefined);
11743
12489
  if (resolved) {
@@ -11772,6 +12518,7 @@ function build_read_command(getScanner) {
11772
12518
  resultRecord.resolvedFromSource = true;
11773
12519
  resultRecord.sourcePrefab = resolved.source_path;
11774
12520
  resultRecord.sourceGuid = resolved.source_guid;
12521
+ variantResolvedFromSource = true;
11775
12522
  const variantNote = `PrefabVariant resolved from source prefab: ${resolved.source_path}`;
11776
12523
  result.warning = result.warning ? `${result.warning}; ${variantNote}` : variantNote;
11777
12524
  }
@@ -11782,8 +12529,13 @@ function build_read_command(getScanner) {
11782
12529
  try {
11783
12530
  const fileSize = import_fs18.statSync(file).size;
11784
12531
  if (fileSize > 100) {
11785
- const corruptWarning = "File has valid Unity YAML header but contains no parseable GameObjects -- file may be corrupt or malformed";
11786
- result.warning = result.warning ? `${result.warning}; ${corruptWarning}` : corruptWarning;
12532
+ if (variantDetected && !variantResolvedFromSource) {
12533
+ const variantWarning = "PrefabVariant detected but source prefab hierarchy could not be resolved (missing project context or GUID cache). Use --project or run setup to resolve source hierarchy.";
12534
+ result.warning = result.warning ? `${result.warning}; ${variantWarning}` : variantWarning;
12535
+ } else {
12536
+ const corruptWarning = "File has valid Unity YAML header but contains no parseable GameObjects -- file may be corrupt or malformed";
12537
+ result.warning = result.warning ? `${result.warning}; ${corruptWarning}` : corruptWarning;
12538
+ }
11787
12539
  }
11788
12540
  } catch {}
11789
12541
  }
@@ -12056,38 +12808,41 @@ function build_read_command(getScanner) {
12056
12808
  by_type: options.unresolved ? undefined : byType
12057
12809
  };
12058
12810
  if (!cache) {
12059
- output._hint = "Run 'setup <project>' to resolve GUID paths";
12811
+ output._hint = "Run 'setup' (or 'setup -p <path>') to resolve GUID paths";
12060
12812
  }
12061
12813
  console.log(JSON.stringify(output, null, 2));
12062
12814
  });
12063
- cmd.command("settings <project_path>").description("Read Unity project settings (TagManager, DynamicsManager, QualitySettings, TimeManager, etc.)").option("-s, --setting <name>", "Setting name or alias (tags, physics, quality, time)", "TagManager").option("-j, --json", "Output as JSON").action((project_path, options) => {
12815
+ cmd.command("settings").description("Read Unity project settings (TagManager, DynamicsManager, QualitySettings, TimeManager, etc.)").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-s, --setting <name>", "Setting name or alias (tags, physics, quality, time)", "TagManager").option("-j, --json", "Output as JSON").action((options) => {
12816
+ const resolvedProjectPath = resolve_project_path(options.project);
12064
12817
  const result = read_settings({
12065
- project_path,
12818
+ project_path: resolvedProjectPath,
12066
12819
  setting: options.setting
12067
12820
  });
12068
12821
  console.log(JSON.stringify(result, null, 2));
12069
12822
  if (!result.success)
12070
12823
  process.exit(1);
12071
12824
  });
12072
- cmd.command("build <project_path>").description("Read build settings (scene list, build profiles)").option("-j, --json", "Output as JSON").action((project_path, _options) => {
12825
+ cmd.command("build").description("Read build settings (scene list, build profiles)").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-j, --json", "Output as JSON").action((options) => {
12826
+ const resolvedProjectPath = resolve_project_path(options.project);
12073
12827
  try {
12074
- const result = get_build_settings(project_path);
12828
+ const result = get_build_settings(resolvedProjectPath);
12075
12829
  console.log(JSON.stringify(result, null, 2));
12076
12830
  } catch (err) {
12077
12831
  console.log(JSON.stringify({ success: false, error: err instanceof Error ? err.message : String(err) }, null, 2));
12078
12832
  process.exitCode = 1;
12079
12833
  }
12080
12834
  });
12081
- cmd.command("scenes <project_path>").description('Read build scenes (alias for "read build")').option("-j, --json", "Output as JSON").action((project_path, _options) => {
12835
+ cmd.command("scenes").description('Read build scenes (alias for "read build")').option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-j, --json", "Output as JSON").action((options) => {
12836
+ const resolvedProjectPath = resolve_project_path(options.project);
12082
12837
  try {
12083
- const result = get_build_settings(project_path);
12838
+ const result = get_build_settings(resolvedProjectPath);
12084
12839
  console.log(JSON.stringify(result, null, 2));
12085
12840
  } catch (err) {
12086
12841
  console.log(JSON.stringify({ success: false, error: err instanceof Error ? err.message : String(err) }, null, 2));
12087
12842
  process.exitCode = 1;
12088
12843
  }
12089
12844
  });
12090
- cmd.command("overrides <file> <prefab_instance>").description("Read PrefabInstance override modifications").option("--flat", "Output simplified list").option("-j, --json", "Output as JSON").action((file, prefab_instance, options) => {
12845
+ cmd.command("overrides <file> <prefab_instance>").description("Read PrefabInstance overrides (modifications + removed/added state)").option("--flat", "Output simplified list").option("-j, --json", "Output as JSON").action((file, prefab_instance, options) => {
12091
12846
  try {
12092
12847
  const doc = UnityDocument.from_file(file);
12093
12848
  let block = null;
@@ -12150,6 +12905,10 @@ function build_read_command(getScanner) {
12150
12905
  i++;
12151
12906
  }
12152
12907
  }
12908
+ const removed_components = parse_removed_list(block.raw, "m_RemovedComponents");
12909
+ const removed_gameobjects = parse_removed_list(block.raw, "m_RemovedGameObjects");
12910
+ const added_gameobjects = parse_added_list(block.raw, "m_AddedGameObjects");
12911
+ const added_components = parse_added_list(block.raw, "m_AddedComponents");
12153
12912
  if (options.flat) {
12154
12913
  const flat = modifications.map((m) => ({
12155
12914
  property_path: m.property_path,
@@ -12158,7 +12917,14 @@ function build_read_command(getScanner) {
12158
12917
  }));
12159
12918
  console.log(JSON.stringify(flat, null, 2));
12160
12919
  } else {
12161
- console.log(JSON.stringify(modifications, null, 2));
12920
+ console.log(JSON.stringify({
12921
+ prefab_instance_id: block.file_id,
12922
+ modifications,
12923
+ removed_components,
12924
+ removed_gameobjects,
12925
+ added_gameobjects,
12926
+ added_components
12927
+ }, null, 2));
12162
12928
  }
12163
12929
  } catch (err) {
12164
12930
  console.log(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }, null, 2));
@@ -12418,9 +13184,7 @@ function build_read_command(getScanner) {
12418
13184
  types: displayed
12419
13185
  }, null, 2));
12420
13186
  });
12421
- cmd.command("log [project-path]").description("Read and filter the Unity Editor.log").option("--path <file>", "Path to Editor.log (auto-detected if omitted)").option("--project <path>", "Filter to log entries from a specific Unity project session").option("--tail <n>", "Show last N lines (default 50)", "50").option("--errors", "Show only error entries").option("--warnings", "Show only warning entries").option("--compile-errors", "Show only C# compilation errors").option("--import-errors", "Show only asset import errors").option("--since <timestamp>", "Filter entries after this timestamp (YYYY-MM-DD or HH:MM:SS)").option("--search <pattern>", "Regex filter on log content").option("-j, --json", "Output as JSON").action((projectPath, options) => {
12422
- if (projectPath && !options.project)
12423
- options.project = projectPath;
13187
+ cmd.command("log").description("Read and filter the Unity Editor.log").option("--path <file>", "Path to Editor.log (auto-detected if omitted)").option("--project <path>", "Filter to log entries from a specific Unity project session").option("--tail <n>", "Show last N lines (default 50)", "50").option("--errors", "Show only error entries").option("--warnings", "Show only warning entries").option("--compile-errors", "Show only C# compilation errors").option("--import-errors", "Show only asset import errors").option("--since <timestamp>", "Filter entries after this timestamp (YYYY-MM-DD or HH:MM:SS)").option("--search <pattern>", "Regex filter on log content").option("-j, --json", "Output as JSON").action((options) => {
12424
13188
  const logPath = options.path || get_editor_log_path();
12425
13189
  if (!logPath || !import_fs18.existsSync(logPath)) {
12426
13190
  console.log(JSON.stringify({ error: `Editor.log not found${logPath ? `: ${logPath}` : ". Could not detect platform log path."}` }, null, 2));
@@ -12429,9 +13193,9 @@ function build_read_command(getScanner) {
12429
13193
  const content = import_fs18.readFileSync(logPath, "utf-8");
12430
13194
  let lines = content.split(/\r?\n/);
12431
13195
  if (options.project) {
12432
- const projectPath2 = import_path8.resolve(options.project);
12433
- const projectName = import_path8.basename(projectPath2);
12434
- const escapedPath = projectPath2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13196
+ const projectPath = import_path8.resolve(options.project);
13197
+ const projectName = import_path8.basename(projectPath);
13198
+ const escapedPath = projectPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
12435
13199
  const escapedName = projectName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
12436
13200
  const session_markers = [
12437
13201
  new RegExp(`Loading project at '${escapedPath}'`),
@@ -12876,7 +13640,7 @@ function build_read_command(getScanner) {
12876
13640
  }
12877
13641
  const states_out = { file, states_by_layer: by_layer };
12878
13642
  if (needs_setup_hint)
12879
- states_out._hint = "Run 'setup <project>' to resolve motion paths";
13643
+ states_out._hint = "Run 'setup' (or 'setup -p <path>') to resolve motion paths";
12880
13644
  console.log(JSON.stringify(states_out, null, 2));
12881
13645
  return;
12882
13646
  }
@@ -12925,14 +13689,15 @@ function build_read_command(getScanner) {
12925
13689
  }))
12926
13690
  };
12927
13691
  if (needs_setup_hint)
12928
- default_out._hint = "Run 'setup <project>' to resolve motion paths";
13692
+ default_out._hint = "Run 'setup' (or 'setup -p <path>') to resolve motion paths";
12929
13693
  console.log(JSON.stringify(default_out, null, 2));
12930
13694
  });
12931
- cmd.command("dependents <project_path> <guid>").description("Find which files reference a given GUID (reverse dependency lookup)").option("--type <type>", "Filter to specific file types (scene, prefab, mat, etc.)").option("-j, --json", "Output as JSON").action((project_path, guid, options) => {
13695
+ cmd.command("dependents <guid>").description("Find which files reference a given GUID (reverse dependency lookup)").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("--type <type>", "Filter to specific file types (scene, prefab, mat, etc.)").option("-j, --json", "Output as JSON").action((guid, options) => {
12932
13696
  if (!/^[0-9a-f]{32}$/i.test(guid)) {
12933
13697
  console.log(JSON.stringify({ error: "GUID must be a 32-character hexadecimal string" }, null, 2));
12934
13698
  process.exit(1);
12935
13699
  }
13700
+ const project_path = resolve_project_path(options.project);
12936
13701
  const assetsDir = import_path8.join(import_path8.resolve(project_path), "Assets");
12937
13702
  if (!import_fs18.existsSync(assetsDir)) {
12938
13703
  console.log(JSON.stringify({ error: `Assets directory not found in "${project_path}"` }, null, 2));
@@ -12990,11 +13755,11 @@ function build_read_command(getScanner) {
12990
13755
  by_type
12991
13756
  }, null, 2));
12992
13757
  });
12993
- cmd.command("unused <project_path>").description("Find potentially unused assets (zero inbound GUID references)").option("--type <type>", "Filter to specific asset types").option("--ignore <glob>", "Exclude paths matching this pattern").option("--max <n>", "Maximum results to return (default 200)", "200").option("-j, --json", "Output as JSON").action((project_path, options) => {
12994
- const resolvedProject = import_path8.resolve(project_path);
13758
+ cmd.command("unused").description("Find potentially unused assets (zero inbound GUID references)").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("--type <type>", "Filter to specific asset types").option("--ignore <glob>", "Exclude paths matching this pattern").option("--max <n>", "Maximum results to return (default 200)", "200").option("-j, --json", "Output as JSON").action((options) => {
13759
+ const resolvedProject = resolve_project_path(options.project);
12995
13760
  const assetsDir = import_path8.join(resolvedProject, "Assets");
12996
13761
  if (!import_fs18.existsSync(assetsDir)) {
12997
- console.log(JSON.stringify({ error: `Assets directory not found in "${project_path}"` }, null, 2));
13762
+ console.log(JSON.stringify({ error: `Assets directory not found in "${resolvedProject}"` }, null, 2));
12998
13763
  process.exit(1);
12999
13764
  }
13000
13765
  const guidCacheObj = load_guid_cache(resolvedProject);
@@ -13085,7 +13850,8 @@ function build_read_command(getScanner) {
13085
13850
  by_type
13086
13851
  }, null, 2));
13087
13852
  });
13088
- cmd.command("manifest <project_path>").description("List packages from Packages/manifest.json").option("--search <pattern>", "Filter packages by name pattern").option("-j, --json", "Output as JSON").action((project_path, options) => {
13853
+ cmd.command("manifest").description("List packages from Packages/manifest.json").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("--search <pattern>", "Filter packages by name pattern").option("-j, --json", "Output as JSON").action((options) => {
13854
+ const project_path = resolve_project_path(options.project);
13089
13855
  const result = list_packages(project_path, options.search);
13090
13856
  if ("error" in result) {
13091
13857
  console.log(JSON.stringify({ success: false, error: result.error }, null, 2));
@@ -13404,13 +14170,14 @@ function build_update_command(getScanner) {
13404
14170
  if (!result.success)
13405
14171
  process.exitCode = 1;
13406
14172
  });
13407
- cmd.command("settings <project_path>").description("Edit a property in any ProjectSettings/*.asset file").option("-s, --setting <name>", "Setting name or alias").option("--property <name>", "Property name to edit").option("--value <value>", "New value").option("-j, --json", "Output as JSON").action((project_path, options) => {
14173
+ cmd.command("settings").description("Edit a property in any ProjectSettings/*.asset file").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-s, --setting <name>", "Setting name or alias").option("--property <name>", "Property name to edit").option("--value <value>", "New value").option("-j, --json", "Output as JSON").action((options) => {
13408
14174
  if (!options.setting || !options.property || !options.value) {
13409
14175
  console.log(JSON.stringify({ success: false, error: "Required: --setting, --property, --value" }, null, 2));
13410
14176
  process.exit(1);
13411
14177
  }
14178
+ const resolvedProjectPath = resolve_project_path(options.project);
13412
14179
  const result = edit_settings({
13413
- project_path,
14180
+ project_path: resolvedProjectPath,
13414
14181
  setting: options.setting,
13415
14182
  property: options.property,
13416
14183
  value: options.value
@@ -13419,13 +14186,14 @@ function build_update_command(getScanner) {
13419
14186
  if (!result.success)
13420
14187
  process.exitCode = 1;
13421
14188
  });
13422
- cmd.command("tag <project_path> <action> <tag>").description("Add or remove a tag in the TagManager").option("-j, --json", "Output as JSON").action((project_path, action, tag, _options) => {
14189
+ cmd.command("tag <action> <tag>").description("Add or remove a tag in the TagManager").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-j, --json", "Output as JSON").action((action, tag, options) => {
13423
14190
  if (action !== "add" && action !== "remove") {
13424
14191
  console.log(JSON.stringify({ success: false, error: 'Action must be "add" or "remove"' }, null, 2));
13425
14192
  process.exit(1);
13426
14193
  }
14194
+ const resolvedProjectPath = resolve_project_path(options.project);
13427
14195
  const result = edit_tag({
13428
- project_path,
14196
+ project_path: resolvedProjectPath,
13429
14197
  action,
13430
14198
  tag
13431
14199
  });
@@ -13433,9 +14201,10 @@ function build_update_command(getScanner) {
13433
14201
  if (!result.success)
13434
14202
  process.exitCode = 1;
13435
14203
  });
13436
- cmd.command("layer <project_path> <index> <name>").description("Set a named layer at a specific index (3-31)").option("-j, --json", "Output as JSON").action((project_path, index, name, _options) => {
14204
+ cmd.command("layer <index> <name>").description("Set a named layer at a specific index (3-31)").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-j, --json", "Output as JSON").action((index, name, options) => {
14205
+ const resolvedProjectPath = resolve_project_path(options.project);
13437
14206
  const result = edit_layer({
13438
- project_path,
14207
+ project_path: resolvedProjectPath,
13439
14208
  index: parseInt(index, 10),
13440
14209
  name
13441
14210
  });
@@ -13443,13 +14212,14 @@ function build_update_command(getScanner) {
13443
14212
  if (!result.success)
13444
14213
  process.exitCode = 1;
13445
14214
  });
13446
- cmd.command("sorting-layer <project_path> <action> <name>").description("Add or remove a sorting layer").option("-j, --json", "Output as JSON").action((project_path, action, name, _options) => {
14215
+ cmd.command("sorting-layer <action> <name>").description("Add or remove a sorting layer").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-j, --json", "Output as JSON").action((action, name, options) => {
13447
14216
  if (action !== "add" && action !== "remove") {
13448
14217
  console.log(JSON.stringify({ success: false, error: 'Action must be "add" or "remove"' }, null, 2));
13449
14218
  process.exit(1);
13450
14219
  }
14220
+ const resolvedProjectPath = resolve_project_path(options.project);
13451
14221
  const result = edit_sorting_layer({
13452
- project_path,
14222
+ project_path: resolvedProjectPath,
13453
14223
  action,
13454
14224
  name
13455
14225
  });
@@ -13549,20 +14319,21 @@ function build_update_command(getScanner) {
13549
14319
  if (!result.success)
13550
14320
  process.exitCode = 1;
13551
14321
  });
13552
- cmd.command("build <project_path> <scene_path>").description("Enable, disable, or move a scene in build settings").option("--enable", "Enable the scene").option("--disable", "Disable the scene").option("--move <index>", "Move scene to position").option("-j, --json", "Output as JSON").action((project_path, scene_path, options) => {
14322
+ cmd.command("build <scene_path>").description("Enable, disable, or move a scene in build settings").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("--enable", "Enable the scene").option("--disable", "Disable the scene").option("--move <index>", "Move scene to position").option("-j, --json", "Output as JSON").action((scene_path, options) => {
13553
14323
  try {
14324
+ const resolvedProjectPath = resolve_project_path(options.project);
13554
14325
  if (options.move !== undefined) {
13555
- const result = move_scene(project_path, scene_path, parseInt(options.move, 10));
14326
+ const result = move_scene(resolvedProjectPath, scene_path, parseInt(options.move, 10));
13556
14327
  console.log(JSON.stringify(result, null, 2));
13557
14328
  if (!result.success)
13558
14329
  process.exitCode = 1;
13559
14330
  } else if (options.enable) {
13560
- const result = enable_scene(project_path, scene_path);
14331
+ const result = enable_scene(resolvedProjectPath, scene_path);
13561
14332
  console.log(JSON.stringify(result, null, 2));
13562
14333
  if (!result.success)
13563
14334
  process.exitCode = 1;
13564
14335
  } else if (options.disable) {
13565
- const result = disable_scene(project_path, scene_path);
14336
+ const result = disable_scene(resolvedProjectPath, scene_path);
13566
14337
  console.log(JSON.stringify(result, null, 2));
13567
14338
  if (!result.success)
13568
14339
  process.exitCode = 1;
@@ -15256,9 +16027,10 @@ function build_delete_command() {
15256
16027
  process.exitCode = 1;
15257
16028
  }
15258
16029
  });
15259
- cmd.command("build <project_path> <scene_path>").description("Remove a scene from build settings").option("-j, --json", "Output as JSON").action((project_path, scene_path, _options) => {
16030
+ cmd.command("build <scene_path>").description("Remove a scene from build settings").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-j, --json", "Output as JSON").action((scene_path, options) => {
16031
+ const resolvedProjectPath = resolve_project_path(options.project);
15260
16032
  try {
15261
- const result = remove_scene(project_path, scene_path);
16033
+ const result = remove_scene(resolvedProjectPath, scene_path);
15262
16034
  console.log(JSON.stringify(result, null, 2));
15263
16035
  if (!result.success)
15264
16036
  process.exitCode = 1;
@@ -15276,9 +16048,10 @@ function build_delete_command() {
15276
16048
  if (!result.success)
15277
16049
  process.exitCode = 1;
15278
16050
  });
15279
- cmd.command("package <project_path> <name>").description("Remove a package from Packages/manifest.json").option("-j, --json", "Output as JSON").action((project_path, name, _options) => {
16051
+ cmd.command("package <name>").description("Remove a package from Packages/manifest.json").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-j, --json", "Output as JSON").action((name, options) => {
15280
16052
  try {
15281
- const result = remove_package(project_path, name);
16053
+ const resolvedProjectPath = resolve_project_path(options.project);
16054
+ const result = remove_package(resolvedProjectPath, name);
15282
16055
  if ("error" in result) {
15283
16056
  console.log(JSON.stringify({ success: false, error: result.error }, null, 2));
15284
16057
  process.exitCode = 1;
@@ -15565,7 +16338,7 @@ function generate_id() {
15565
16338
  var BRIDGE_PACKAGE_NAME = "com.unity-agentic-tools.editor-bridge";
15566
16339
  var BRIDGE_PACKAGE_VERSION = "https://github.com/taconotsandwich/unity-agentic-tools.git?path=unity-package";
15567
16340
  function get_common_options(cmd) {
15568
- let current = cmd.parent;
16341
+ let current = cmd;
15569
16342
  let project;
15570
16343
  let timeout_str;
15571
16344
  let port_str;
@@ -15757,9 +16530,9 @@ function build_editor_command() {
15757
16530
  params.mode = options.mode;
15758
16531
  await handle_rpc(this, "editor.tests.run", params);
15759
16532
  });
15760
- cmd.command("install <project_path>").description("Install the editor bridge package into a Unity project").action((_project_path) => {
15761
- const resolved_path = import_path11.resolve(_project_path);
15762
- const result = add_package(resolved_path, BRIDGE_PACKAGE_NAME, BRIDGE_PACKAGE_VERSION);
16533
+ cmd.command("install").description("Install the editor bridge package into a Unity project").option("-p, --project <path>", "Path to Unity project (defaults to cwd)").action(function() {
16534
+ const { project_path } = get_common_options(this);
16535
+ const result = add_package(project_path, BRIDGE_PACKAGE_NAME, BRIDGE_PACKAGE_VERSION);
15763
16536
  if ("error" in result) {
15764
16537
  console.log(JSON.stringify({ success: false, error: result.error }, null, 2));
15765
16538
  process.exitCode = 1;
@@ -15767,9 +16540,9 @@ function build_editor_command() {
15767
16540
  }
15768
16541
  console.log(JSON.stringify(result, null, 2));
15769
16542
  });
15770
- cmd.command("uninstall <project_path>").description("Remove the editor bridge package from a Unity project").action((_project_path) => {
15771
- const resolved_path = import_path11.resolve(_project_path);
15772
- const result = remove_package(resolved_path, BRIDGE_PACKAGE_NAME);
16543
+ cmd.command("uninstall").description("Remove the editor bridge package from a Unity project").option("-p, --project <path>", "Path to Unity project (defaults to cwd)").action(function() {
16544
+ const { project_path } = get_common_options(this);
16545
+ const result = remove_package(project_path, BRIDGE_PACKAGE_NAME);
15773
16546
  if ("error" in result) {
15774
16547
  console.log(JSON.stringify({ success: false, error: result.error }, null, 2));
15775
16548
  process.exitCode = 1;
@@ -16100,11 +16873,12 @@ program.command("search <path> [pattern]").description("Search for GameObjects.
16100
16873
  console.log(JSON.stringify(result, null, 2));
16101
16874
  }
16102
16875
  });
16103
- program.command("grep <project_path> <pattern>").description("Search for a regex pattern across project files").option("--type <type>", "File type filter: cs, yaml, unity, prefab, asset, all", "all").option("-m, --max <n>", "Max results (default: 100)", "100").option("-C, --context <n>", "Context lines around matches", "0").option("-j, --json", "Output as JSON").action((project_path, pattern, options) => {
16876
+ program.command("grep <pattern>").description("Search for a regex pattern across project files").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("--type <type>", "File type filter: cs, yaml, unity, prefab, asset, all", "all").option("-m, --max <n>", "Max results (default: 100)", "100").option("-C, --context <n>", "Context lines around matches", "0").option("-j, --json", "Output as JSON").action((pattern, options) => {
16104
16877
  if (!pattern || pattern.trim() === "") {
16105
16878
  console.log(JSON.stringify({ success: false, error: "Pattern must not be empty" }, null, 2));
16106
16879
  process.exit(1);
16107
16880
  }
16881
+ let project_path = resolve_project_path(options.project);
16108
16882
  const abs_path = path9.resolve(project_path);
16109
16883
  const project_root = find_unity_project_root(abs_path);
16110
16884
  if (project_root) {
@@ -16132,8 +16906,9 @@ program.command("grep <project_path> <pattern>").description("Search for a regex
16132
16906
  if (!result.success)
16133
16907
  process.exitCode = 1;
16134
16908
  });
16135
- program.command("version <project_path>").description("Read Unity project version").option("-j, --json", "Output as JSON").action((project_path, _options) => {
16909
+ program.command("version").description("Read Unity project version").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-j, --json", "Output as JSON").action((options) => {
16136
16910
  try {
16911
+ const project_path = resolve_project_path(options.project);
16137
16912
  const version = read_project_version(project_path);
16138
16913
  console.log(JSON.stringify(version, null, 2));
16139
16914
  } catch (err) {