@alpsckr/unitycli 0.4.3 → 0.4.5

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.
Files changed (425) hide show
  1. package/README.md +5 -5
  2. package/contracts/command-contract.json +109 -217
  3. package/contracts/package-files.json +374 -294
  4. package/contracts/stable-session/direct-call-semantics.json +8 -2
  5. package/contracts/tool-completion-contract.json +148 -672
  6. package/dist/bridge/admission.d.ts +17 -1
  7. package/dist/bridge/admission.js +2 -0
  8. package/dist/bridge/admission.js.map +1 -1
  9. package/dist/bridge/domains/asset.js +3 -1
  10. package/dist/bridge/domains/asset.js.map +1 -1
  11. package/dist/bridge/domains/camera.js +39 -7
  12. package/dist/bridge/domains/camera.js.map +1 -1
  13. package/dist/bridge/domains/console.js +2 -1
  14. package/dist/bridge/domains/console.js.map +1 -1
  15. package/dist/bridge/domains/doctor.d.ts +3 -0
  16. package/dist/bridge/domains/doctor.js +8 -0
  17. package/dist/bridge/domains/doctor.js.map +1 -0
  18. package/dist/bridge/domains/editor.js +0 -4
  19. package/dist/bridge/domains/editor.js.map +1 -1
  20. package/dist/bridge/domains/game-object.js +4 -12
  21. package/dist/bridge/domains/game-object.js.map +1 -1
  22. package/dist/bridge/domains/graphics.js +1 -16
  23. package/dist/bridge/domains/graphics.js.map +1 -1
  24. package/dist/bridge/domains/object.js +3 -0
  25. package/dist/bridge/domains/object.js.map +1 -1
  26. package/dist/bridge/domains/physics.js +1 -2
  27. package/dist/bridge/domains/physics.js.map +1 -1
  28. package/dist/bridge/domains/profiler.js +18 -7
  29. package/dist/bridge/domains/profiler.js.map +1 -1
  30. package/dist/bridge/domains/project-tool.js +4 -0
  31. package/dist/bridge/domains/project-tool.js.map +1 -1
  32. package/dist/bridge/domains/reflection.js +10 -2
  33. package/dist/bridge/domains/reflection.js.map +1 -1
  34. package/dist/bridge/domains/runtime-tool.js +3 -0
  35. package/dist/bridge/domains/runtime-tool.js.map +1 -1
  36. package/dist/bridge/domains/scene.js +3 -8
  37. package/dist/bridge/domains/scene.js.map +1 -1
  38. package/dist/bridge/domains/screenshot.js +30 -6
  39. package/dist/bridge/domains/screenshot.js.map +1 -1
  40. package/dist/bridge/domains/ui.js +3 -1
  41. package/dist/bridge/domains/ui.js.map +1 -1
  42. package/dist/bridge/index.d.ts +5 -5
  43. package/dist/bridge/index.js +6 -9
  44. package/dist/bridge/index.js.map +1 -1
  45. package/dist/bridge/registry.js +3 -5
  46. package/dist/bridge/registry.js.map +1 -1
  47. package/dist/bridge/session-supervisor.d.ts +6 -3
  48. package/dist/bridge/session-supervisor.js +145 -39
  49. package/dist/bridge/session-supervisor.js.map +1 -1
  50. package/dist/bridge/start-diagnostics.d.ts +19 -0
  51. package/dist/bridge/start-diagnostics.js +96 -0
  52. package/dist/bridge/start-diagnostics.js.map +1 -0
  53. package/dist/bridge/types.d.ts +6 -5
  54. package/dist/bridge/types.js +16 -2
  55. package/dist/bridge/types.js.map +1 -1
  56. package/dist/catalog-protocol.d.ts +64 -0
  57. package/dist/catalog-protocol.js +460 -0
  58. package/dist/catalog-protocol.js.map +1 -0
  59. package/dist/cli-commands/command.js +84 -61
  60. package/dist/cli-commands/command.js.map +1 -1
  61. package/dist/cli-commands/extensions.js +70 -50
  62. package/dist/cli-commands/extensions.js.map +1 -1
  63. package/dist/cli-commands/instances.js +1 -1
  64. package/dist/cli-commands/instances.js.map +1 -1
  65. package/dist/cli-commands/package.js +10 -2
  66. package/dist/cli-commands/package.js.map +1 -1
  67. package/dist/cli-process-owners.d.ts +6 -0
  68. package/dist/cli-process-owners.js +27 -0
  69. package/dist/cli-process-owners.js.map +1 -0
  70. package/dist/cli.d.ts +1 -1
  71. package/dist/cli.js +20 -5
  72. package/dist/cli.js.map +1 -1
  73. package/dist/contracts/command-contract.js +14 -4
  74. package/dist/contracts/command-contract.js.map +1 -1
  75. package/dist/doctor.d.ts +4 -6
  76. package/dist/doctor.js +451 -398
  77. package/dist/doctor.js.map +1 -1
  78. package/dist/editor-blocker-observer.d.ts +2 -1
  79. package/dist/editor-blocker-observer.js +40 -13
  80. package/dist/editor-blocker-observer.js.map +1 -1
  81. package/dist/extension-availability.d.ts +27 -9
  82. package/dist/extension-availability.js +96 -56
  83. package/dist/extension-availability.js.map +1 -1
  84. package/dist/extensions/descriptor-loader.d.ts +2 -2
  85. package/dist/extensions/descriptor-loader.js +3 -4
  86. package/dist/extensions/descriptor-loader.js.map +1 -1
  87. package/dist/extensions/index.d.ts +1 -1
  88. package/dist/extensions/index.js +1 -1
  89. package/dist/extensions/index.js.map +1 -1
  90. package/dist/extensions/installer.d.ts +57 -5
  91. package/dist/extensions/installer.js +263 -64
  92. package/dist/extensions/installer.js.map +1 -1
  93. package/dist/extensions/registry.js +3 -5
  94. package/dist/extensions/registry.js.map +1 -1
  95. package/dist/extensions/state-contract.d.ts +11 -0
  96. package/dist/extensions/state-contract.js +50 -0
  97. package/dist/extensions/state-contract.js.map +1 -0
  98. package/dist/instances.d.ts +19 -1
  99. package/dist/instances.js +39 -0
  100. package/dist/instances.js.map +1 -1
  101. package/dist/package-manager.d.ts +0 -24
  102. package/dist/package-manager.js +5 -58
  103. package/dist/package-manager.js.map +1 -1
  104. package/dist/project-tool-discovery.d.ts +5 -2
  105. package/dist/project-tool-discovery.js +22 -18
  106. package/dist/project-tool-discovery.js.map +1 -1
  107. package/dist/runtime-code.js +1 -1
  108. package/dist/runtime-code.js.map +1 -1
  109. package/dist/tool-name.d.ts +4 -0
  110. package/dist/tool-name.js +15 -0
  111. package/dist/tool-name.js.map +1 -0
  112. package/dist/tools/.generated-completion-registry.d.ts +2 -0
  113. package/dist/tools/.generated-completion-registry.js +763 -325
  114. package/dist/tools/.generated-completion-registry.js.map +1 -1
  115. package/dist/tools/.generated-registry.d.ts +6 -11
  116. package/dist/tools/.generated-registry.js +6 -11
  117. package/dist/tools/.generated-registry.js.map +1 -1
  118. package/dist/tools/.generated-schemas.js +11 -16
  119. package/dist/tools/.generated-schemas.js.map +1 -1
  120. package/dist/tools/asset/refresh-status.d.ts +3 -0
  121. package/dist/tools/asset/refresh-status.js +12 -0
  122. package/dist/tools/asset/refresh-status.js.map +1 -0
  123. package/dist/tools/asset/refresh.js +7 -3
  124. package/dist/tools/asset/refresh.js.map +1 -1
  125. package/dist/tools/build/player-status.d.ts +3 -0
  126. package/dist/tools/build/player-status.js +12 -0
  127. package/dist/tools/build/player-status.js.map +1 -0
  128. package/dist/tools/build/run.js +1 -1
  129. package/dist/tools/build/run.js.map +1 -1
  130. package/dist/tools/catalog-control.d.ts +22 -0
  131. package/dist/tools/catalog-control.js +335 -0
  132. package/dist/tools/catalog-control.js.map +1 -0
  133. package/dist/tools/catalog.d.ts +7 -9
  134. package/dist/tools/catalog.js +66 -45
  135. package/dist/tools/catalog.js.map +1 -1
  136. package/dist/tools/commands.d.ts +1 -1
  137. package/dist/tools/commands.js +44 -11
  138. package/dist/tools/commands.js.map +1 -1
  139. package/dist/tools/define.js +3 -2
  140. package/dist/tools/define.js.map +1 -1
  141. package/dist/tools/dispatcher.d.ts +53 -6
  142. package/dist/tools/dispatcher.js +277 -108
  143. package/dist/tools/dispatcher.js.map +1 -1
  144. package/dist/tools/editor/compile.js +1 -1
  145. package/dist/tools/editor/compile.js.map +1 -1
  146. package/dist/tools/editor/play-mode-status.d.ts +3 -0
  147. package/dist/tools/editor/play-mode-status.js +12 -0
  148. package/dist/tools/editor/play-mode-status.js.map +1 -0
  149. package/dist/tools/editor/readiness.d.ts +0 -1
  150. package/dist/tools/editor/readiness.js +0 -1
  151. package/dist/tools/editor/readiness.js.map +1 -1
  152. package/dist/tools/editor/refresh.d.ts +7 -3
  153. package/dist/tools/editor/refresh.js +20 -15
  154. package/dist/tools/editor/refresh.js.map +1 -1
  155. package/dist/tools/editor/state.d.ts +2 -2
  156. package/dist/tools/editor/state.js +13 -12
  157. package/dist/tools/editor/state.js.map +1 -1
  158. package/dist/tools/metadata/derived.js +1 -7
  159. package/dist/tools/metadata/derived.js.map +1 -1
  160. package/dist/tools/metadata/status.js +1 -12
  161. package/dist/tools/metadata/status.js.map +1 -1
  162. package/dist/tools/metadata/types.d.ts +0 -2
  163. package/dist/tools/navmesh/live.js +1 -1
  164. package/dist/tools/navmesh/live.js.map +1 -1
  165. package/dist/tools/occlusion/live.js +1 -1
  166. package/dist/tools/occlusion/live.js.map +1 -1
  167. package/dist/tools/package/deploy.js +2 -2
  168. package/dist/tools/package/deploy.js.map +1 -1
  169. package/dist/tools/package/embed.js +1 -1
  170. package/dist/tools/package/embed.js.map +1 -1
  171. package/dist/tools/package/install-status.d.ts +3 -0
  172. package/dist/tools/package/install-status.js +12 -0
  173. package/dist/tools/package/install-status.js.map +1 -0
  174. package/dist/tools/package/install.js +3 -3
  175. package/dist/tools/package/install.js.map +1 -1
  176. package/dist/tools/package/live-resolve.js +45 -12
  177. package/dist/tools/package/live-resolve.js.map +1 -1
  178. package/dist/tools/package/registry-clear.js.map +1 -1
  179. package/dist/tools/package/registry-set.js.map +1 -1
  180. package/dist/tools/package/remove-status.d.ts +3 -0
  181. package/dist/tools/package/remove-status.js +12 -0
  182. package/dist/tools/package/remove-status.js.map +1 -0
  183. package/dist/tools/package/remove.js +3 -3
  184. package/dist/tools/package/remove.js.map +1 -1
  185. package/dist/tools/package/resolve.js +3 -3
  186. package/dist/tools/package/resolve.js.map +1 -1
  187. package/dist/tools/package/restore.js +2 -2
  188. package/dist/tools/package/restore.js.map +1 -1
  189. package/dist/tools/package/scoped-registry-add.js.map +1 -1
  190. package/dist/tools/status/support.d.ts +13 -0
  191. package/dist/tools/status/support.js +28 -0
  192. package/dist/tools/status/support.js.map +1 -0
  193. package/dist/tools/test/cancel.js +2 -2
  194. package/dist/tools/test/cancel.js.map +1 -1
  195. package/dist/tools/test/run-status.d.ts +3 -0
  196. package/dist/tools/test/run-status.js +12 -0
  197. package/dist/tools/test/run-status.js.map +1 -0
  198. package/dist/tools/test/run.js +7 -4
  199. package/dist/tools/test/run.js.map +1 -1
  200. package/dist/tools/tools/group-summaries.js +10 -14
  201. package/dist/tools/tools/group-summaries.js.map +1 -1
  202. package/docs/architecture.md +4 -4
  203. package/docs/availability-differences.md +4 -4
  204. package/docs/command-execution.md +4 -3
  205. package/docs/error-codes.md +13 -5
  206. package/docs/extensions.md +1 -7
  207. package/docs/risk-and-side-effects.md +4 -3
  208. package/docs/safety-boundaries.md +3 -1
  209. package/docs/target-and-instance.md +1 -1
  210. package/docs/tool-discovery.md +17 -10
  211. package/docs/tool-execution.md +18 -7
  212. package/extensions/cinemachine/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  213. package/extensions/hdrp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  214. package/extensions/probuilder/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  215. package/extensions/urp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  216. package/extensions/vfx-graph/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  217. package/package.json +2 -4
  218. package/skills/unitycli/SKILL.md +11 -2
  219. package/skills/unitycli/manifest.json +1 -1
  220. package/skills/unitycli/references/operation-protocol.md +3 -3
  221. package/unitypkg/Editor/AssemblyInfo.cs +5 -0
  222. package/unitypkg/Editor/Bridge/BridgeCommandRegistry.cs +49 -14
  223. package/unitypkg/Editor/Bridge/BridgeHttpServer.cs +9 -35
  224. package/unitypkg/Editor/Bridge/BridgeQueueState.cs +672 -0
  225. package/unitypkg/Editor/Bridge/BridgeWorkQueue.cs +970 -120
  226. package/unitypkg/Editor/BridgeStartupStateMachine.cs +59 -1
  227. package/unitypkg/Editor/Commands/ProjectToolCommands.cs +4 -0
  228. package/unitypkg/Editor/Commands/RuntimeToolCommands.cs +3 -0
  229. package/unitypkg/Editor/Generated/UnityCliToolCompletionRegistry.g.cs +379 -14
  230. package/unitypkg/Editor/Infrastructure/CodeExecSupport.cs +28 -13
  231. package/unitypkg/Editor/Infrastructure/RegistryOwnershipResolver.cs +132 -105
  232. package/unitypkg/Editor/Infrastructure/UnityCliActiveOperation.cs +138 -29
  233. package/unitypkg/Editor/Infrastructure/UnityCliAdmission.cs +1856 -237
  234. package/unitypkg/Editor/Infrastructure/UnityCliDoctorDiagnostics.cs +220 -0
  235. package/unitypkg/Editor/Infrastructure/UnityCliDurableStatusCoordinator.cs +118 -0
  236. package/unitypkg/Editor/Infrastructure/UnityCliEditorStateSnapshot.cs +2 -1
  237. package/unitypkg/Editor/Infrastructure/UnityCliHierarchyPager.cs +203 -0
  238. package/unitypkg/Editor/Infrastructure/UnityCliInstanceCleanup.cs +2 -1
  239. package/unitypkg/Editor/Infrastructure/UnityCliInstanceRegistry.cs +201 -63
  240. package/unitypkg/Editor/Infrastructure/UnityCliJson.cs +2 -3
  241. package/unitypkg/Editor/Infrastructure/UnityCliLiveBuildCoordinator.cs +289 -46
  242. package/unitypkg/Editor/Infrastructure/UnityCliLiveRefreshCoordinator.cs +87 -53
  243. package/unitypkg/Editor/Infrastructure/UnityCliNavMeshBakeCoordinator.cs +4 -4
  244. package/unitypkg/Editor/Infrastructure/UnityCliObjectRef.cs +128 -25
  245. package/unitypkg/Editor/Infrastructure/UnityCliObjectSerializer.cs +435 -327
  246. package/unitypkg/Editor/Infrastructure/UnityCliOcclusionBakeCoordinator.cs +4 -4
  247. package/unitypkg/Editor/Infrastructure/UnityCliOperationCoordinator.cs +351 -53
  248. package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveCoordinator.cs +118 -18
  249. package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveState.cs +20 -1
  250. package/unitypkg/Editor/Infrastructure/UnityCliReadGeneration.cs +97 -0
  251. package/unitypkg/Editor/Infrastructure/UnityCliReflection.cs +644 -133
  252. package/unitypkg/Editor/Infrastructure/UnityCliReloadOperation.cs +128 -41
  253. package/unitypkg/Editor/Infrastructure/UnityCliRuntimeExecutor.cs +14 -84
  254. package/unitypkg/Editor/Infrastructure/UnityCliSerializer.cs +220 -0
  255. package/unitypkg/Editor/Infrastructure/UnityCliSessionMutationLease.cs +69 -29
  256. package/unitypkg/Editor/Infrastructure/UnityCliTestRunCoordinator.cs +440 -54
  257. package/unitypkg/Editor/Infrastructure/UnityCliWaiter.cs +128 -35
  258. package/unitypkg/Editor/ProjectTools/IUnityCliTool.cs +9 -1
  259. package/unitypkg/Editor/ProjectTools/OfficialAttributeToolRegistry.cs +32 -31
  260. package/unitypkg/Editor/ProjectTools/ProjectToolRegistry.cs +607 -458
  261. package/unitypkg/Editor/ProjectTools/ProjectToolSchemaValidator.cs +35 -7
  262. package/unitypkg/Editor/ProjectTools/RegisteredTool.cs +501 -60
  263. package/unitypkg/Editor/ProjectTools/UnityCliProjectToolContext.cs +21 -0
  264. package/unitypkg/Editor/ProjectTools/UnityCliSchemaDeriver.cs +36 -7
  265. package/unitypkg/Editor/ProjectTools/UnityCliToolName.cs +30 -0
  266. package/unitypkg/Editor/Properties/AssemblyInfo.cs +3 -0
  267. package/unitypkg/Editor/Tools/asset/get-data.cs +16 -7
  268. package/unitypkg/Editor/Tools/build/BuildSceneStateTool.cs +23 -3
  269. package/unitypkg/Editor/Tools/build/BuildToolDtos.cs +2 -1
  270. package/unitypkg/Editor/Tools/build/scene/add.cs +6 -3
  271. package/unitypkg/Editor/Tools/build/scene/disable.cs +1 -1
  272. package/unitypkg/Editor/Tools/build/scene/enable.cs +1 -1
  273. package/unitypkg/Editor/Tools/build/scene/list.cs +1 -1
  274. package/unitypkg/Editor/Tools/build/scene/remove.cs +13 -3
  275. package/unitypkg/Editor/Tools/camera/CameraToolSupport.cs +38 -38
  276. package/unitypkg/Editor/Tools/camera/multiview.cs +13 -6
  277. package/unitypkg/Editor/Tools/camera/screenshot.cs +14 -7
  278. package/unitypkg/Editor/Tools/component/get.cs +16 -9
  279. package/unitypkg/Editor/Tools/component/modify.cs +6 -4
  280. package/unitypkg/Editor/Tools/console/clear.cs +7 -6
  281. package/unitypkg/Editor/Tools/console/get.cs +48 -16
  282. package/unitypkg/Editor/Tools/editor/EditorToolSupport.cs +143 -28
  283. package/unitypkg/Editor/Tools/editor/menu-items.cs +1 -1
  284. package/unitypkg/Editor/Tools/editor/play.cs +2 -11
  285. package/unitypkg/Editor/Tools/editor/selection.cs +1 -1
  286. package/unitypkg/Editor/Tools/editor/stop.cs +2 -11
  287. package/unitypkg/Editor/Tools/editor/windows.cs +1 -1
  288. package/unitypkg/Editor/Tools/go/GameObjectToolSupport.cs +54 -4
  289. package/unitypkg/Editor/Tools/go/delete.cs +1 -1
  290. package/unitypkg/Editor/Tools/go/find.cs +167 -70
  291. package/unitypkg/Editor/Tools/graphics/GraphicsToolSupport.cs +25 -0
  292. package/unitypkg/Editor/Tools/graphics/light-probe-group.cs +9 -3
  293. package/unitypkg/Editor/Tools/graphics/pipeline-info.cs +1 -1
  294. package/unitypkg/Editor/Tools/graphics/pipeline-settings.cs +1 -1
  295. package/unitypkg/Editor/Tools/graphics/quality.cs +2 -2
  296. package/unitypkg/Editor/Tools/graphics/reflection-probe.cs +3 -3
  297. package/unitypkg/Editor/Tools/graphics/stats.cs +1 -1
  298. package/unitypkg/Editor/Tools/object/ObjectToolSupport.cs +4 -4
  299. package/unitypkg/Editor/Tools/object/get-data.cs +9 -7
  300. package/unitypkg/Editor/Tools/object/modify.cs +7 -5
  301. package/unitypkg/Editor/Tools/physics/raycast.cs +24 -6
  302. package/unitypkg/Editor/Tools/prefab/stage.cs +1 -1
  303. package/unitypkg/Editor/Tools/profiler/ProfilerFrameDebuggerSupport.cs +94 -21
  304. package/unitypkg/Editor/Tools/profiler/ProfilerMemorySnapshotSupport.cs +397 -10
  305. package/unitypkg/Editor/Tools/profiler/ProfilerToolSupport.cs +18 -4
  306. package/unitypkg/Editor/Tools/profiler/frame-debugger/enable.cs +1 -1
  307. package/unitypkg/Editor/Tools/profiler/frame-debugger/events.cs +88 -87
  308. package/unitypkg/Editor/Tools/profiler/hierarchy.cs +278 -112
  309. package/unitypkg/Editor/Tools/profiler/memory-snapshot/compare.cs +9 -5
  310. package/unitypkg/Editor/Tools/profiler/memory-snapshot/list.cs +37 -35
  311. package/unitypkg/Editor/Tools/profiler/memory-snapshot/take.cs +99 -97
  312. package/unitypkg/Editor/Tools/project/info.cs +1 -1
  313. package/unitypkg/Editor/Tools/project/layers.cs +1 -1
  314. package/unitypkg/Editor/Tools/project/tags.cs +1 -1
  315. package/unitypkg/Editor/Tools/reflection/method/call.cs +9 -7
  316. package/unitypkg/Editor/Tools/reflection/method/find.cs +27 -37
  317. package/unitypkg/Editor/Tools/reflection/type/schema.cs +24 -28
  318. package/unitypkg/Editor/Tools/scene/gameobject/component.cs +8 -5
  319. package/unitypkg/Editor/Tools/scene/gameobject/components.cs +10 -7
  320. package/unitypkg/Editor/Tools/scene/gameobject-support.cs +104 -8
  321. package/unitypkg/Editor/Tools/scene/gameobject.cs +37 -16
  322. package/unitypkg/Editor/Tools/scene/hierarchy.cs +146 -142
  323. package/unitypkg/Editor/Tools/screenshot/EditorElementCaptureToolSupport.cs +77 -25
  324. package/unitypkg/Editor/Tools/screenshot/ScreenshotToolSupport.cs +315 -95
  325. package/unitypkg/Editor/Tools/screenshot/editor-element.cs +7 -5
  326. package/unitypkg/Editor/Tools/screenshot/game-view.cs +35 -21
  327. package/unitypkg/Editor/Tools/screenshot/isolated-object.cs +29 -23
  328. package/unitypkg/Editor/Tools/screenshot/orbit.cs +37 -25
  329. package/unitypkg/Editor/Tools/screenshot/scene-view.cs +25 -18
  330. package/unitypkg/Editor/Tools/screenshot/screen.cs +177 -17
  331. package/unitypkg/Editor/Tools/screenshot/surround.cs +83 -79
  332. package/unitypkg/Editor/Tools/ui/render-preview.cs +16 -11
  333. package/unitypkg/Editor/UnityCliBridge.cs +97 -34
  334. package/unitypkg/Runtime/Infrastructure/UnityCliRuntimeBridge.cs +753 -122
  335. package/unitypkg/Runtime/Properties/AssemblyInfo.cs +5 -0
  336. package/unitypkg/Runtime/UnityCli.Runtime.asmdef +1 -1
  337. package/unitypkg/Shared/AssemblyInfo.cs +4 -0
  338. package/unitypkg/Shared/UnityCliAdmissionPool.cs +265 -75
  339. package/unitypkg/Shared/UnityCliBridgeQueuePolicy.cs +410 -0
  340. package/unitypkg/Shared/UnityCliBridgeWorkItemState.cs +47 -0
  341. package/unitypkg/Shared/UnityCliExceptionProjection.cs +151 -0
  342. package/unitypkg/Shared/UnityCliQueuePublication.cs +30 -0
  343. package/unitypkg/Shared/UnityCliRequestBudget.cs +352 -0
  344. package/unitypkg/Shared/UnityCliRuntimeRequestPublication.cs +107 -0
  345. package/unitypkg/Tests/Editor/UnityCli.Editor.Tests.asmdef +2 -2
  346. package/unitypkg/Tests/Editor/UnityCliAdmissionCoordinatorIntegrationTests.cs +1329 -0
  347. package/unitypkg/Tests/Editor/UnityCliAdmissionPoolTests.cs +311 -114
  348. package/unitypkg/Tests/Editor/UnityCliAdmissionRepairStateTests.cs +94 -0
  349. package/unitypkg/Tests/Editor/UnityCliBridgeAdmissionIntegrationTests.cs +1229 -0
  350. package/unitypkg/Tests/Editor/UnityCliBridgeQueuePolicyTests.cs +412 -0
  351. package/unitypkg/Tests/Editor/UnityCliContinuationIntegrationTests.cs +292 -0
  352. package/unitypkg/Tests/Editor/UnityCliDiagnosticBoundaryTests.cs +120 -0
  353. package/unitypkg/Tests/Editor/UnityCliDurableStatusTests.cs +84 -0
  354. package/unitypkg/Tests/Editor/UnityCliExceptionProjectionTests.cs +72 -0
  355. package/unitypkg/Tests/Editor/UnityCliInstancePublicationRetryTests.cs +180 -97
  356. package/unitypkg/Tests/Editor/UnityCliMemoryReflectionIntegrationTests.cs +434 -0
  357. package/unitypkg/Tests/Editor/UnityCliRequestBudgetTests.cs +615 -0
  358. package/unitypkg/Tests/Editor/UnityCliRuntimeBridgeIntegrationTests.cs +520 -0
  359. package/unitypkg/Tests/Editor/UnityCliToolCatalogTests.cs +562 -0
  360. package/unitypkg/Tests/Runtime/UnityCli.Runtime.Tests.asmdef +23 -0
  361. package/unitypkg/Tests/Runtime/UnityCliRuntimeCatalogValidationTests.cs +29 -0
  362. package/unitypkg/package.json +1 -1
  363. package/contracts/static-capability-catalog.json +0 -48226
  364. package/dist/bridge/domains/diagnose.d.ts +0 -2
  365. package/dist/bridge/domains/diagnose.js +0 -11
  366. package/dist/bridge/domains/diagnose.js.map +0 -1
  367. package/dist/bridge/domains/extension.d.ts +0 -8
  368. package/dist/bridge/domains/extension.js +0 -20
  369. package/dist/bridge/domains/extension.js.map +0 -1
  370. package/dist/capabilities.d.ts +0 -5
  371. package/dist/capabilities.js +0 -84
  372. package/dist/capabilities.js.map +0 -1
  373. package/dist/static-capability-catalog.d.ts +0 -13
  374. package/dist/static-capability-catalog.js +0 -41
  375. package/dist/static-capability-catalog.js.map +0 -1
  376. package/dist/tools/diagnose/capabilities.d.ts +0 -13
  377. package/dist/tools/diagnose/capabilities.js +0 -34
  378. package/dist/tools/diagnose/capabilities.js.map +0 -1
  379. package/dist/tools/package/info.d.ts +0 -19
  380. package/dist/tools/package/info.js +0 -27
  381. package/dist/tools/package/info.js.map +0 -1
  382. package/dist/tools/package/list.d.ts +0 -27
  383. package/dist/tools/package/list.js +0 -21
  384. package/dist/tools/package/list.js.map +0 -1
  385. package/dist/tools/package/registry-get.d.ts +0 -10
  386. package/dist/tools/package/registry-get.js +0 -22
  387. package/dist/tools/package/registry-get.js.map +0 -1
  388. package/dist/tools/package/scoped-registry-list.d.ts +0 -14
  389. package/dist/tools/package/scoped-registry-list.js +0 -22
  390. package/dist/tools/package/scoped-registry-list.js.map +0 -1
  391. package/dist/tools/package/search.d.ts +0 -17
  392. package/dist/tools/package/search.js +0 -39
  393. package/dist/tools/package/search.js.map +0 -1
  394. package/dist/tools/package/status.d.ts +0 -21
  395. package/dist/tools/package/status.js +0 -37
  396. package/dist/tools/package/status.js.map +0 -1
  397. package/dist/tools/tools/describe.d.ts +0 -27
  398. package/dist/tools/tools/describe.js +0 -35
  399. package/dist/tools/tools/describe.js.map +0 -1
  400. package/dist/tools/tools/group-list.d.ts +0 -16
  401. package/dist/tools/tools/group-list.js +0 -38
  402. package/dist/tools/tools/group-list.js.map +0 -1
  403. package/dist/tools/tools/group.d.ts +0 -13
  404. package/dist/tools/tools/group.js +0 -34
  405. package/dist/tools/tools/group.js.map +0 -1
  406. package/dist/tools/tools/pagination.d.ts +0 -17
  407. package/dist/tools/tools/pagination.js +0 -28
  408. package/dist/tools/tools/pagination.js.map +0 -1
  409. package/dist/tools/tools/search.d.ts +0 -15
  410. package/dist/tools/tools/search.js +0 -42
  411. package/dist/tools/tools/search.js.map +0 -1
  412. package/unitypkg/Editor/Tools/diagnose/preflight.cs +0 -269
  413. package/unitypkg/Editor/Tools/editor/console.cs +0 -26
  414. package/unitypkg/Editor/Tools/extension/status.cs +0 -87
  415. package/unitypkg/Editor/Tools/go/inspect.cs +0 -49
  416. package/unitypkg/Editor/Tools/graphics/pipeline-quality.cs +0 -24
  417. package/unitypkg/Editor/Tools/graphics/probe-positions.cs +0 -58
  418. package/unitypkg/Editor/Tools/graphics/settings.cs +0 -104
  419. package/unitypkg/Editor/Tools/graphics/stats-counters.cs +0 -28
  420. package/unitypkg/Editor/Tools/graphics/stats-memory.cs +0 -21
  421. package/unitypkg/Editor/Tools/instances/list.cs +0 -71
  422. package/unitypkg/Editor/Tools/physics/raycast-all.cs +0 -67
  423. package/unitypkg/Editor/Tools/scene/build-settings.cs +0 -69
  424. package/unitypkg/Editor/Tools/scene/data.cs +0 -125
  425. package/unitypkg/Editor/Tools/tests/list.cs +0 -98
@@ -32,6 +32,7 @@ export interface ExtensionStatusCheck {
32
32
  extensionId: ExtensionId;
33
33
  name: string;
34
34
  status: ExtensionInstallStatus;
35
+ integrity: "shallow" | "target" | "full";
35
36
  dependencyInstalled: boolean;
36
37
  installed: boolean;
37
38
  outdated: boolean;
@@ -42,10 +43,25 @@ export interface ExtensionStatusCheck {
42
43
  missingPackageIds: string[];
43
44
  nextActions: string[];
44
45
  }
46
+ /** 一次公开调用冻结的 package/install 输入,后续检查不得重新读取。 */
47
+ export interface ExtensionStateSnapshot {
48
+ projectPath: string;
49
+ installedPackages: ReadonlyMap<string, string>;
50
+ installManifest: ExtensionInstallManifest;
51
+ }
52
+ export interface ExtensionCheckOptions {
53
+ snapshot?: ExtensionStateSnapshot;
54
+ absoluteDeadlineMs?: number;
55
+ }
56
+ export type ExtensionFilesystemOperation = "snapshot" | "target-proof" | "managed-file-hash" | "source-proof" | "source-file-hash" | "source-tree-entry" | "unknown-tree-entry";
45
57
  /** 仅测试使用:在 source projection 复制后强制制造交错。 */
46
58
  export declare function setAfterProjectionCopyForTest(hook: (() => Promise<void>) | undefined): void;
47
59
  /** 仅测试使用:把可变 source 夹具隔离到临时目录。 */
48
60
  export declare function setExtensionSourceRootForTest(resolver: ((extensionId: ExtensionId) => string) | undefined): void;
61
+ /** 仅测试使用:计数 discovery 的语义文件系统操作,不暴露到公开结果。 */
62
+ export declare function setExtensionFilesystemOperationObserverForTest(observer: ((operation: ExtensionFilesystemOperation, extensionId?: ExtensionId) => void) | undefined): void;
63
+ /** 仅测试使用:以确定时钟证明大目录扫描在deadline内中止。 */
64
+ export declare function setExtensionFilesystemClockForTest(clock: (() => number) | undefined): void;
49
65
  /** 返回 extension 源码目录。 */
50
66
  export declare function extensionSourceRoot(extensionId: ExtensionId): string;
51
67
  /** 返回 extension Unity 安装投影使用的默认相对路径。 */
@@ -65,13 +81,49 @@ export declare function resolveInstallAvailability(manifest: ExtensionManifest,
65
81
  export declare function installExtensionBundleUnchecked(projectPath: string, manifest: ExtensionManifest, installedStatus?: "installed" | "already_installed"): Promise<ExtensionInstallResult>;
66
82
  /** 按 manifest 删除一个 extension 目录复制过的文件。 */
67
83
  export declare function removeExtensionBundle(projectPath: string, extensionId: ExtensionId): Promise<ExtensionInstallResult>;
68
- /** 检查所有 extension 的物理安装状态。 */
69
- export declare function checkExtensionBundles(projectPath: string): Promise<ExtensionStatusCheck[]>;
84
+ /** package lock 与安装 manifest 每次 invocation 只读取一次。 */
85
+ export declare function readExtensionStateSnapshot(projectPath: string): Promise<ExtensionStateSnapshot>;
86
+ /** list 使用 manifest/version/root 的浅状态,不做 source 或 managed-file hash。 */
87
+ export declare function listExtensionBundles(projectPath: string, snapshot?: ExtensionStateSnapshot): Promise<ExtensionStatusCheck[]>;
88
+ /** 普通 discovery/执行只证明一个 selected extension 的 target 投影,不读取 source tree。 */
89
+ export declare function checkSelectedExtensionBundle(projectPath: string, extensionId: ExtensionId, options?: ExtensionCheckOptions): Promise<ExtensionStatusCheck | undefined>;
90
+ /** extensions doctor 独占全 extension source/managed-file 深 hash。 */
91
+ export declare function checkExtensionBundles(projectPath: string, options?: ExtensionCheckOptions): Promise<ExtensionStatusCheck[]>;
92
+ export interface ExtensionProjectionRepairEntry {
93
+ extensionId: ExtensionId;
94
+ fileCount: number;
95
+ sourceHash?: string;
96
+ }
97
+ export interface ExtensionProjectionRepairFailure {
98
+ extensionId: ExtensionId;
99
+ code: string;
100
+ message: string;
101
+ nextActions: string[];
102
+ }
103
+ export interface ExtensionProjectionRepairResult {
104
+ repaired: ExtensionProjectionRepairEntry[];
105
+ upToDate: ExtensionId[];
106
+ failures: ExtensionProjectionRepairFailure[];
107
+ }
108
+ /**
109
+ * init 包发布后的扩展升级自愈:把已装扩展投影对齐到当前 CLI 内置 bundle。
110
+ * Unity 增量编译只看源文件变化,包升级后若不重铺投影,扩展程序集不会重编译,
111
+ * 新协议要求的精确 UnityCliExtensionAssembly 标记就不会出现,bridge 启动校验会失败。
112
+ * 逐项收集失败结果并原样透出,任何单项失败都不中断 init 包发布。
113
+ */
114
+ export declare function refreshInstalledExtensionProjections(projectPath: string, absoluteDeadlineMs?: number): Promise<ExtensionProjectionRepairResult>;
115
+ export interface CompiledExtensionMarkerCheck {
116
+ extensionId: ExtensionId;
117
+ assemblyName: string;
118
+ compiledAssemblyFound: boolean;
119
+ markerPresent?: boolean;
120
+ }
70
121
  /**
71
- * 返回目标项目的唯一 extension physical install state 视图。
72
- * 控制面和 live capability gate 均必须使用此入口,不能用 package dependency 替代安装状态。
122
+ * 只读检查扩展编译产物是否包含当前协议要求的精确扩展标记。
123
+ * 投影重铺后 Unity 可能尚未重编译;此时 Library/ScriptAssemblies 的旧 dll 不含标记,
124
+ * bridge 启动校验仍会失败,doctor 必须把这种状态显式暴露为 warn 而不是 pass。
73
125
  */
74
- export declare function readExtensionPhysicalStates(projectPath: string): Promise<Map<ExtensionId, ExtensionStatusCheck>>;
126
+ export declare function inspectCompiledExtensionMarkers(projectPath: string, extensionIds: readonly ExtensionId[], absoluteDeadlineMs?: number): Promise<CompiledExtensionMarkerCheck[]>;
75
127
  export declare class ExtensionInstallerError extends Error {
76
128
  readonly code: string;
77
129
  readonly nextActions: string[];
@@ -5,6 +5,7 @@ import { basename, dirname, join, relative, resolve, sep } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { readInstalledUnityPackages } from "./project-resolver.js";
7
7
  import { ExtensionRegistry, resolveExtensionAvailability } from "./registry.js";
8
+ import { EXTENSION_STATE_MAX_ENTRIES, ExtensionStateContractError, isValidExtensionId, readBoundedExtensionStateFile, } from "./state-contract.js";
8
9
  import { stripBom } from "../util.js";
9
10
  const packageName = "com.alpsckr.unitycli";
10
11
  const manifestFileName = "unitycli.extensions.manifest.json";
@@ -12,6 +13,8 @@ const manifestLockTimeoutMs = 15_000;
12
13
  const processStartIdentity = new Date(Date.now() - Math.floor(process.uptime() * 1_000)).toISOString();
13
14
  let afterProjectionCopyForTest;
14
15
  let sourceRootForTest;
16
+ let filesystemOperationObserverForTest;
17
+ let extensionFilesystemNow = Date.now;
15
18
  /** 仅测试使用:在 source projection 复制后强制制造交错。 */
16
19
  export function setAfterProjectionCopyForTest(hook) {
17
20
  afterProjectionCopyForTest = hook;
@@ -20,6 +23,14 @@ export function setAfterProjectionCopyForTest(hook) {
20
23
  export function setExtensionSourceRootForTest(resolver) {
21
24
  sourceRootForTest = resolver;
22
25
  }
26
+ /** 仅测试使用:计数 discovery 的语义文件系统操作,不暴露到公开结果。 */
27
+ export function setExtensionFilesystemOperationObserverForTest(observer) {
28
+ filesystemOperationObserverForTest = observer;
29
+ }
30
+ /** 仅测试使用:以确定时钟证明大目录扫描在deadline内中止。 */
31
+ export function setExtensionFilesystemClockForTest(clock) {
32
+ extensionFilesystemNow = clock ?? Date.now;
33
+ }
23
34
  /** 返回 extension 源码目录。 */
24
35
  export function extensionSourceRoot(extensionId) {
25
36
  return resolve(fileURLToPath(new URL(`../../extensions/${extensionId}`, import.meta.url)));
@@ -38,20 +49,23 @@ export function extensionInstallManifestPath(projectPath) {
38
49
  }
39
50
  /** 读取 extension 安装 manifest;仅缺失文件代表空状态,损坏状态必须阻断变更。 */
40
51
  export async function readExtensionInstallManifest(projectPath) {
41
- const path = extensionInstallManifestPath(projectPath);
42
52
  let content;
43
53
  try {
44
- content = await readFile(path, "utf8");
54
+ content = await readBoundedExtensionStateFile(extensionInstallManifestPath(projectPath));
45
55
  }
46
56
  catch (error) {
47
- if (error.code === "ENOENT")
48
- return emptyManifest();
57
+ if (error instanceof ExtensionStateContractError)
58
+ throw invalidInstallManifest(projectPath, error.message);
49
59
  throw error;
50
60
  }
61
+ if (content === undefined)
62
+ return emptyManifest();
51
63
  try {
52
64
  const parsed = JSON.parse(stripBom(content));
53
- if (parsed.version !== 1 || !Array.isArray(parsed.extensions) || !parsed.extensions.every(isInstalledEntry)) {
54
- throw invalidInstallManifest(projectPath, "Expected version 1 and valid extension entries.");
65
+ if (parsed.version !== 1 || !Array.isArray(parsed.extensions)
66
+ || parsed.extensions.length > EXTENSION_STATE_MAX_ENTRIES
67
+ || !parsed.extensions.every(isInstalledEntry)) {
68
+ throw invalidInstallManifest(projectPath, `Expected version 1 and at most ${EXTENSION_STATE_MAX_ENTRIES} valid extension entries.`);
55
69
  }
56
70
  if (new Set(parsed.extensions.map((entry) => entry.extensionId)).size !== parsed.extensions.length) {
57
71
  throw invalidInstallManifest(projectPath, "Extension entries must have unique extensionId values.");
@@ -66,6 +80,11 @@ export async function readExtensionInstallManifest(projectPath) {
66
80
  }
67
81
  /** 原子写入 extension 安装 manifest,读取方不会观察到半写文件。 */
68
82
  export async function writeExtensionInstallManifest(projectPath, manifest) {
83
+ if (manifest.extensions.length > EXTENSION_STATE_MAX_ENTRIES
84
+ || !manifest.extensions.every(isInstalledEntry)
85
+ || new Set(manifest.extensions.map((entry) => entry.extensionId)).size !== manifest.extensions.length) {
86
+ throw invalidInstallManifest(projectPath, `Expected at most ${EXTENSION_STATE_MAX_ENTRIES} unique valid extension entries.`);
87
+ }
69
88
  const manifestPath = extensionInstallManifestPath(projectPath);
70
89
  const temporaryPath = `${manifestPath}.${process.pid}.${randomUUID()}.tmp`;
71
90
  await mkdir(dirname(manifestPath), { recursive: true });
@@ -199,50 +218,217 @@ export async function removeExtensionBundle(projectPath, extensionId) {
199
218
  };
200
219
  });
201
220
  }
202
- /** 检查所有 extension 的物理安装状态。 */
203
- export async function checkExtensionBundles(projectPath) {
221
+ /** package lock 与安装 manifest 每次 invocation 只读取一次。 */
222
+ export async function readExtensionStateSnapshot(projectPath) {
223
+ filesystemOperationObserverForTest?.("snapshot");
224
+ const canonicalProjectPath = resolve(projectPath);
225
+ const [installedPackages, installManifest] = await Promise.all([
226
+ readInstalledUnityPackages(canonicalProjectPath),
227
+ readExtensionInstallManifest(canonicalProjectPath),
228
+ ]);
229
+ return { projectPath: canonicalProjectPath, installedPackages, installManifest };
230
+ }
231
+ /** list 使用 manifest/version/root 的浅状态,不做 source 或 managed-file hash。 */
232
+ export async function listExtensionBundles(projectPath, snapshot) {
233
+ const frozen = snapshot ?? await readExtensionStateSnapshot(projectPath);
204
234
  const registry = new ExtensionRegistry();
205
- const installedPackages = await readInstalledUnityPackages(projectPath);
206
- const installManifest = await readExtensionInstallManifest(projectPath);
235
+ return registry.extensionIds().map((extensionId) => {
236
+ const extension = registry.getExtension(extensionId);
237
+ const availability = resolveInstallAvailability(extension.manifest, frozen.installedPackages);
238
+ const entry = frozen.installManifest.extensions.find((item) => item.extensionId === extensionId);
239
+ const targetPresent = existsSync(extensionInstallRoot(frozen.projectPath, extensionId));
240
+ const outdated = Boolean(entry && (entry.version !== extension.manifest.version || !targetPresent));
241
+ const status = resolvePhysicalStatus(availability, entry !== undefined, outdated, [], [], []);
242
+ return extensionStatus(extension.manifest, availability, status, "shallow", Boolean(entry && targetPresent && !outdated), outdated, {
243
+ missingFiles: [], changedFiles: [], unknownFiles: [],
244
+ });
245
+ });
246
+ }
247
+ /** 普通 discovery/执行只证明一个 selected extension 的 target 投影,不读取 source tree。 */
248
+ export async function checkSelectedExtensionBundle(projectPath, extensionId, options = {}) {
249
+ const registry = new ExtensionRegistry();
250
+ const extension = registry.getExtension(extensionId);
251
+ if (!extension)
252
+ return undefined;
253
+ const snapshot = options.snapshot ?? await readExtensionStateSnapshot(projectPath);
254
+ const availability = resolveInstallAvailability(extension.manifest, snapshot.installedPackages);
255
+ const entry = snapshot.installManifest.extensions.find((item) => item.extensionId === extensionId);
256
+ const targetRoot = extensionInstallRoot(snapshot.projectPath, extensionId);
257
+ filesystemOperationObserverForTest?.("target-proof", extensionId);
258
+ const fileCheck = entry
259
+ ? await checkManagedFiles(targetRoot, entry.files, options.absoluteDeadlineMs, extensionId)
260
+ : { missingFiles: [], changedFiles: [], unknownFiles: [] };
261
+ const outdated = Boolean(entry && entry.version !== extension.manifest.version);
262
+ const installed = Boolean(entry && !outdated && fileCheck.missingFiles.length === 0 && fileCheck.changedFiles.length === 0 && fileCheck.unknownFiles.length === 0);
263
+ const status = resolvePhysicalStatus(availability, entry !== undefined, outdated, fileCheck.missingFiles, fileCheck.changedFiles, fileCheck.unknownFiles);
264
+ return extensionStatus(extension.manifest, availability, status, "target", installed, outdated, fileCheck);
265
+ }
266
+ /** extensions doctor 独占全 extension source/managed-file 深 hash。 */
267
+ export async function checkExtensionBundles(projectPath, options = {}) {
268
+ const registry = new ExtensionRegistry();
269
+ const snapshot = options.snapshot ?? await readExtensionStateSnapshot(projectPath);
207
270
  const checks = [];
208
271
  for (const extensionId of registry.extensionIds()) {
272
+ requireExtensionScanDeadline(options.absoluteDeadlineMs);
209
273
  const extension = registry.getExtension(extensionId);
210
- const bundleAvailability = resolveInstallAvailability(extension.manifest, installedPackages);
211
- const entry = installManifest.extensions.find((item) => item.extensionId === extensionId);
212
- const targetRoot = extensionInstallRoot(projectPath, extensionId);
274
+ const availability = resolveInstallAvailability(extension.manifest, snapshot.installedPackages);
275
+ const entry = snapshot.installManifest.extensions.find((item) => item.extensionId === extensionId);
276
+ const targetRoot = extensionInstallRoot(snapshot.projectPath, extensionId);
213
277
  const sourceRoot = extensionSourceRoot(extensionId);
214
278
  const includes = extensionUnityInstallIncludes(extension.manifest);
215
279
  const sourceProjectionComplete = existsSync(sourceRoot) && includes.every((include) => existsSync(safeSourcePath(sourceRoot, include)));
216
- const sourceHash = sourceProjectionComplete ? await hashBundleProjection(sourceRoot, includes) : undefined;
217
- const fileCheck = entry ? await checkManagedFiles(targetRoot, entry.files) : { missingFiles: [], changedFiles: [], unknownFiles: await listUnknownFiles(targetRoot, []) };
218
- // version、投影源码 hash 和 include 文件均属于 physical install contract。
280
+ filesystemOperationObserverForTest?.("source-proof", extensionId);
281
+ const sourceHash = sourceProjectionComplete ? await hashBundleProjection(sourceRoot, includes, options.absoluteDeadlineMs, extensionId) : undefined;
282
+ filesystemOperationObserverForTest?.("target-proof", extensionId);
283
+ const fileCheck = entry
284
+ ? await checkManagedFiles(targetRoot, entry.files, options.absoluteDeadlineMs, extensionId)
285
+ : { missingFiles: [], changedFiles: [], unknownFiles: await listUnknownFiles(targetRoot, [], options.absoluteDeadlineMs, extensionId) };
219
286
  const outdated = Boolean(entry && (!sourceProjectionComplete || !sourceHash || entry.sourceHash !== sourceHash || entry.version !== extension.manifest.version));
220
- const hasUnknownFiles = fileCheck.unknownFiles.length > 0;
221
- const installed = Boolean(entry && fileCheck.missingFiles.length === 0 && fileCheck.changedFiles.length === 0 && !outdated && !hasUnknownFiles);
222
- const status = resolvePhysicalStatus(bundleAvailability, entry !== undefined, outdated, fileCheck.missingFiles, fileCheck.changedFiles, fileCheck.unknownFiles);
223
- checks.push({
224
- extensionId,
225
- name: extension.manifest.name,
226
- status,
227
- dependencyInstalled: bundleAvailability.status === "available",
228
- installed,
229
- outdated,
230
- unknownFiles: fileCheck.unknownFiles,
231
- missingFiles: fileCheck.missingFiles,
232
- changedFiles: fileCheck.changedFiles,
233
- providedTools: extension.manifest.contributions.tools?.map((tool) => tool.name) ?? [],
234
- missingPackageIds: bundleAvailability.missingPackageIds,
235
- nextActions: nextActionsForStatus(extension.manifest, bundleAvailability, status)
236
- });
287
+ const installed = Boolean(entry && fileCheck.missingFiles.length === 0 && fileCheck.changedFiles.length === 0 && !outdated && fileCheck.unknownFiles.length === 0);
288
+ const status = resolvePhysicalStatus(availability, entry !== undefined, outdated, fileCheck.missingFiles, fileCheck.changedFiles, fileCheck.unknownFiles);
289
+ checks.push(extensionStatus(extension.manifest, availability, status, "full", installed, outdated, fileCheck));
237
290
  }
238
291
  return checks;
239
292
  }
240
293
  /**
241
- * 返回目标项目的唯一 extension physical install state 视图。
242
- * 控制面和 live capability gate 均必须使用此入口,不能用 package dependency 替代安装状态。
294
+ * init 包发布后的扩展升级自愈:把已装扩展投影对齐到当前 CLI 内置 bundle。
295
+ * Unity 增量编译只看源文件变化,包升级后若不重铺投影,扩展程序集不会重编译,
296
+ * 新协议要求的精确 UnityCliExtensionAssembly 标记就不会出现,bridge 启动校验会失败。
297
+ * 逐项收集失败结果并原样透出,任何单项失败都不中断 init 包发布。
298
+ */
299
+ export async function refreshInstalledExtensionProjections(projectPath, absoluteDeadlineMs) {
300
+ const result = { repaired: [], upToDate: [], failures: [] };
301
+ const registry = new ExtensionRegistry();
302
+ let installed;
303
+ try {
304
+ installed = (await readExtensionInstallManifest(projectPath)).extensions;
305
+ }
306
+ catch (error) {
307
+ result.failures.push({
308
+ extensionId: "unknown",
309
+ code: "E_EXTENSION_REPAIR_FAILED",
310
+ message: `Installed extension manifest could not be read, so installed extension projections were not refreshed: ${error instanceof Error ? error.message : String(error)}`,
311
+ nextActions: [`Run \`unitycli extensions status --project "${projectPath}"\` to inspect the broken extension install manifest.`],
312
+ });
313
+ return result;
314
+ }
315
+ for (const entry of installed) {
316
+ if (absoluteDeadlineMs !== undefined && Date.now() >= absoluteDeadlineMs) {
317
+ result.failures.push({
318
+ extensionId: entry.extensionId,
319
+ code: "E_EXTENSION_REPAIR_DEADLINE",
320
+ message: `The immutable init deadline elapsed before the ${entry.extensionId} extension projection could be refreshed.`,
321
+ nextActions: ["Rerun `unitycli init package --project <UnityProject>` to finish refreshing extension projections."],
322
+ });
323
+ continue;
324
+ }
325
+ const extension = registry.getExtension(entry.extensionId);
326
+ if (!extension) {
327
+ result.failures.push({
328
+ extensionId: entry.extensionId,
329
+ code: "E_EXTENSION_UNKNOWN_INSTALLED",
330
+ message: `Installed extension ${entry.extensionId} is not provided by this CLI build, so its projection cannot be refreshed automatically.`,
331
+ nextActions: [`Run \`unitycli extensions remove ${entry.extensionId} --project "${projectPath}"\` if the extension is no longer needed.`],
332
+ });
333
+ continue;
334
+ }
335
+ const sourceRoot = extensionSourceRoot(entry.extensionId);
336
+ const includes = extensionUnityInstallIncludes(extension.manifest);
337
+ if (!existsSync(sourceRoot) || !includes.every((include) => existsSync(safeSourcePath(sourceRoot, include)))) {
338
+ result.failures.push({
339
+ extensionId: entry.extensionId,
340
+ code: "E_EXTENSION_BUNDLE_MISSING",
341
+ message: `The ${entry.extensionId} extension bundle shipped with this CLI build is incomplete, so its installed projection was left unchanged.`,
342
+ nextActions: ["Reinstall the @alpsckr/unitycli package, then run `unitycli init package` again."],
343
+ });
344
+ continue;
345
+ }
346
+ const currentHash = await hashBundleProjection(sourceRoot, includes);
347
+ if (entry.sourceHash === currentHash && entry.version === extension.manifest.version) {
348
+ result.upToDate.push(entry.extensionId);
349
+ continue;
350
+ }
351
+ try {
352
+ const repair = await installExtensionBundleUnchecked(projectPath, extension.manifest, "installed");
353
+ result.repaired.push({ extensionId: entry.extensionId, fileCount: repair.fileCount, sourceHash: repair.sourceHash });
354
+ }
355
+ catch (error) {
356
+ result.failures.push({
357
+ extensionId: entry.extensionId,
358
+ code: error instanceof ExtensionInstallerError ? error.code : "E_EXTENSION_REPAIR_FAILED",
359
+ message: `Extension projection refresh failed for ${entry.extensionId}: ${error instanceof Error ? error.message : String(error)}`,
360
+ nextActions: error instanceof ExtensionInstallerError && error.nextActions.length > 0
361
+ ? error.nextActions
362
+ : [`Reinstall manually: \`unitycli extensions remove ${entry.extensionId}\` followed by \`unitycli extensions install ${entry.extensionId} --project "${projectPath}"\`.`],
363
+ });
364
+ }
365
+ }
366
+ return result;
367
+ }
368
+ const compiledMarkerNeedle = Buffer.from("UnityCliExtensionAssembly", "utf8");
369
+ const maxCompiledAssemblyReadBytes = 32 * 1024 * 1024;
370
+ /**
371
+ * 只读检查扩展编译产物是否包含当前协议要求的精确扩展标记。
372
+ * 投影重铺后 Unity 可能尚未重编译;此时 Library/ScriptAssemblies 的旧 dll 不含标记,
373
+ * bridge 启动校验仍会失败,doctor 必须把这种状态显式暴露为 warn 而不是 pass。
243
374
  */
244
- export async function readExtensionPhysicalStates(projectPath) {
245
- return new Map((await checkExtensionBundles(projectPath)).map((check) => [check.extensionId, check]));
375
+ export async function inspectCompiledExtensionMarkers(projectPath, extensionIds, absoluteDeadlineMs) {
376
+ const checks = [];
377
+ for (const extensionId of extensionIds) {
378
+ if (absoluteDeadlineMs !== undefined && Date.now() >= absoluteDeadlineMs)
379
+ break;
380
+ // 投影根目录即 Unity 包根,asmdef 与 Editor/ 都在 Extensions/<id>/ 顶层。
381
+ const unityPackageRoot = extensionInstallRoot(projectPath, extensionId);
382
+ let assemblyName;
383
+ try {
384
+ const entries = await readdir(unityPackageRoot);
385
+ const asmdefFile = entries.find((name) => name.endsWith(".asmdef"));
386
+ if (!asmdefFile)
387
+ continue;
388
+ const parsed = JSON.parse(stripBom(await readFile(join(unityPackageRoot, asmdefFile), "utf8")));
389
+ if (typeof parsed.name !== "string" || parsed.name.length === 0)
390
+ continue;
391
+ assemblyName = parsed.name;
392
+ }
393
+ catch {
394
+ continue; // 投影缺失或不可读时无从检查编译产物
395
+ }
396
+ const assemblyPath = join(projectPath, "Library", "ScriptAssemblies", `${assemblyName}.dll`);
397
+ try {
398
+ const info = await stat(assemblyPath);
399
+ if (!info.isFile() || info.size > maxCompiledAssemblyReadBytes) {
400
+ checks.push({ extensionId, assemblyName, compiledAssemblyFound: false });
401
+ continue;
402
+ }
403
+ const bytes = await readFile(assemblyPath);
404
+ checks.push({ extensionId, assemblyName, compiledAssemblyFound: true, markerPresent: bytes.includes(compiledMarkerNeedle) });
405
+ }
406
+ catch (error) {
407
+ if (error.code === "ENOENT") {
408
+ checks.push({ extensionId, assemblyName, compiledAssemblyFound: false });
409
+ continue;
410
+ }
411
+ throw error;
412
+ }
413
+ }
414
+ return checks;
415
+ }
416
+ function extensionStatus(manifest, availability, status, integrity, installed, outdated, files) {
417
+ return {
418
+ extensionId: manifest.id,
419
+ name: manifest.name,
420
+ status,
421
+ integrity,
422
+ dependencyInstalled: availability.status === "available",
423
+ installed,
424
+ outdated,
425
+ unknownFiles: files.unknownFiles,
426
+ missingFiles: files.missingFiles,
427
+ changedFiles: files.changedFiles,
428
+ providedTools: manifest.contributions.tools?.map((tool) => tool.name) ?? [],
429
+ missingPackageIds: availability.missingPackageIds,
430
+ nextActions: nextActionsForStatus(manifest, availability, status),
431
+ };
246
432
  }
247
433
  /** 跨进程序列化同一项目的 extension 文件与 manifest 变更;活 owner 与损坏锁一律不按时间抢占。 */
248
434
  async function withExtensionManifestLock(projectPath, action) {
@@ -386,46 +572,48 @@ async function removeManagedFiles(targetRoot, files) {
386
572
  await rm(safeManagedPath(targetRoot, file.path), { force: true });
387
573
  }
388
574
  }
389
- async function checkManagedFiles(targetRoot, files) {
575
+ async function checkManagedFiles(targetRoot, files, absoluteDeadlineMs, extensionId) {
390
576
  const missingFiles = [];
391
577
  const changedFiles = [];
392
578
  const managed = new Set(files.map((file) => file.path));
393
579
  for (const file of files) {
580
+ requireExtensionScanDeadline(absoluteDeadlineMs);
394
581
  const fullPath = safeManagedPath(targetRoot, file.path);
395
582
  if (!existsSync(fullPath)) {
396
583
  missingFiles.push(file.path);
397
584
  continue;
398
585
  }
399
- const actualHash = await hashFile(fullPath);
400
- if (actualHash !== file.sha256) {
586
+ filesystemOperationObserverForTest?.("managed-file-hash", extensionId);
587
+ const actualHash = await hashFile(fullPath, absoluteDeadlineMs);
588
+ if (actualHash !== file.sha256)
401
589
  changedFiles.push(file.path);
402
- }
403
590
  }
404
- const unknownFiles = await listUnknownFiles(targetRoot, managed);
591
+ const unknownFiles = await listUnknownFiles(targetRoot, managed, absoluteDeadlineMs, extensionId);
405
592
  return { missingFiles, changedFiles, unknownFiles };
406
593
  }
407
- async function listUnknownFiles(targetRoot, managed) {
408
- if (!existsSync(targetRoot)) {
594
+ async function listUnknownFiles(targetRoot, managed, absoluteDeadlineMs, extensionId) {
595
+ requireExtensionScanDeadline(absoluteDeadlineMs);
596
+ if (!existsSync(targetRoot))
409
597
  return [];
410
- }
411
598
  const managedSet = new Set(managed);
412
- const allFiles = await listFiles(targetRoot);
599
+ const allFiles = await listFiles(targetRoot, targetRoot, absoluteDeadlineMs, extensionId);
413
600
  return allFiles.filter((file) => !managedSet.has(file) && !isUnityGeneratedMetaFile(file)).sort();
414
601
  }
415
602
  function isUnityGeneratedMetaFile(path) {
416
603
  return path.endsWith(".meta");
417
604
  }
418
- async function listFiles(root, current = root) {
605
+ async function listFiles(root, current = root, absoluteDeadlineMs, extensionId) {
606
+ requireExtensionScanDeadline(absoluteDeadlineMs);
419
607
  const entries = await readdir(current, { withFileTypes: true });
420
608
  const files = [];
421
609
  for (const entry of entries) {
610
+ requireExtensionScanDeadline(absoluteDeadlineMs);
611
+ filesystemOperationObserverForTest?.("unknown-tree-entry", extensionId);
422
612
  const fullPath = join(current, entry.name);
423
- if (entry.isDirectory()) {
424
- files.push(...await listFiles(root, fullPath));
425
- }
426
- else if (entry.isFile()) {
613
+ if (entry.isDirectory())
614
+ files.push(...await listFiles(root, fullPath, absoluteDeadlineMs, extensionId));
615
+ else if (entry.isFile())
427
616
  files.push(normalizePath(relative(root, fullPath)));
428
- }
429
617
  }
430
618
  return files;
431
619
  }
@@ -452,43 +640,54 @@ async function hashManagedProjection(root, files) {
452
640
  }
453
641
  return hash.digest("hex");
454
642
  }
455
- async function hashBundleProjection(sourceRoot, includes) {
643
+ async function hashBundleProjection(sourceRoot, includes, absoluteDeadlineMs, extensionId) {
456
644
  const files = [];
457
645
  for (const includePath of includes.map(normalizePath).sort()) {
646
+ requireExtensionScanDeadline(absoluteDeadlineMs);
458
647
  const sourcePath = safeSourcePath(sourceRoot, includePath);
459
648
  if (!existsSync(sourcePath))
460
649
  continue;
461
650
  const targetRelativeRoot = projectionTargetRoot(includePath);
462
- if ((await stat(sourcePath)).isDirectory()) {
463
- await collectProjectionFiles(sourcePath, sourcePath, targetRelativeRoot, files);
464
- }
465
- else {
651
+ if ((await stat(sourcePath)).isDirectory())
652
+ await collectProjectionFiles(sourcePath, sourcePath, targetRelativeRoot, files, absoluteDeadlineMs, extensionId);
653
+ else
466
654
  files.push({ path: targetRelativeRoot, sourcePath });
467
- }
468
655
  }
469
656
  const hash = createHash("sha256");
470
657
  for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
658
+ requireExtensionScanDeadline(absoluteDeadlineMs);
471
659
  hash.update(`file:${file.path}\n`);
660
+ filesystemOperationObserverForTest?.("source-file-hash", extensionId);
472
661
  hash.update(await readFile(file.sourcePath));
473
662
  hash.update("\n");
474
663
  }
475
664
  return hash.digest("hex");
476
665
  }
477
- async function collectProjectionFiles(dir, root, targetPrefix, files) {
666
+ async function collectProjectionFiles(dir, root, targetPrefix, files, absoluteDeadlineMs, extensionId) {
667
+ requireExtensionScanDeadline(absoluteDeadlineMs);
478
668
  for (const entry of (await readdir(dir, { withFileTypes: true })).sort((left, right) => left.name.localeCompare(right.name))) {
669
+ requireExtensionScanDeadline(absoluteDeadlineMs);
670
+ filesystemOperationObserverForTest?.("source-tree-entry", extensionId);
479
671
  const sourcePath = join(dir, entry.name);
480
- if (entry.isDirectory()) {
481
- await collectProjectionFiles(sourcePath, root, targetPrefix, files);
482
- }
672
+ if (entry.isDirectory())
673
+ await collectProjectionFiles(sourcePath, root, targetPrefix, files, absoluteDeadlineMs, extensionId);
483
674
  else if (entry.isFile()) {
484
675
  const relativePath = normalizePath(relative(root, sourcePath));
485
676
  files.push({ path: normalizePath(targetPrefix ? `${targetPrefix}/${relativePath}` : relativePath), sourcePath });
486
677
  }
487
678
  }
488
679
  }
489
- async function hashFile(path) {
680
+ async function hashFile(path, absoluteDeadlineMs) {
681
+ requireExtensionScanDeadline(absoluteDeadlineMs);
490
682
  return createHash("sha256").update(await readFile(path)).digest("hex");
491
683
  }
684
+ function requireExtensionScanDeadline(absoluteDeadlineMs) {
685
+ if (absoluteDeadlineMs !== undefined && extensionFilesystemNow() >= absoluteDeadlineMs) {
686
+ throw new ExtensionInstallerError("E_EXTENSION_SCAN_TIMEOUT", "Extension filesystem inspection exceeded the immutable command deadline.", [
687
+ "Remove unexpected files or retry extensions doctor with a larger --timeout-ms after filesystem activity settles."
688
+ ]);
689
+ }
690
+ }
492
691
  function resolvePhysicalStatus(availability, hasEntry, outdated, missingFiles, changedFiles, unknownFiles) {
493
692
  if (availability.status !== "available")
494
693
  return "missing_dependency";
@@ -564,7 +763,7 @@ function invalidInstallManifest(projectPath, reason) {
564
763
  }
565
764
  function isInstalledEntry(value) {
566
765
  const entry = value;
567
- return typeof entry?.extensionId === "string" && entry.extensionId.length > 0
766
+ return isValidExtensionId(entry?.extensionId)
568
767
  && typeof entry.version === "string" && entry.version.length > 0
569
768
  && typeof entry.installedAt === "string" && !Number.isNaN(Date.parse(entry.installedAt))
570
769
  && typeof entry.sourceHash === "string" && /^[a-f0-9]{64}$/i.test(entry.sourceHash)