@alpsckr/unitycli 0.4.2 → 0.4.4

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 (420) 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 +122 -34
  49. package/dist/bridge/session-supervisor.js.map +1 -1
  50. package/dist/bridge/types.d.ts +6 -5
  51. package/dist/bridge/types.js +16 -2
  52. package/dist/bridge/types.js.map +1 -1
  53. package/dist/catalog-protocol.d.ts +64 -0
  54. package/dist/catalog-protocol.js +460 -0
  55. package/dist/catalog-protocol.js.map +1 -0
  56. package/dist/cli-commands/command.js +84 -61
  57. package/dist/cli-commands/command.js.map +1 -1
  58. package/dist/cli-commands/extensions.js +70 -50
  59. package/dist/cli-commands/extensions.js.map +1 -1
  60. package/dist/cli-commands/instances.js +1 -1
  61. package/dist/cli-commands/instances.js.map +1 -1
  62. package/dist/cli-process-owners.d.ts +6 -0
  63. package/dist/cli-process-owners.js +27 -0
  64. package/dist/cli-process-owners.js.map +1 -0
  65. package/dist/cli.d.ts +1 -1
  66. package/dist/cli.js +20 -5
  67. package/dist/cli.js.map +1 -1
  68. package/dist/contracts/command-contract.js +14 -4
  69. package/dist/contracts/command-contract.js.map +1 -1
  70. package/dist/doctor.d.ts +4 -6
  71. package/dist/doctor.js +429 -398
  72. package/dist/doctor.js.map +1 -1
  73. package/dist/editor-blocker-observer.d.ts +2 -1
  74. package/dist/editor-blocker-observer.js +40 -13
  75. package/dist/editor-blocker-observer.js.map +1 -1
  76. package/dist/extension-availability.d.ts +27 -9
  77. package/dist/extension-availability.js +96 -56
  78. package/dist/extension-availability.js.map +1 -1
  79. package/dist/extensions/descriptor-loader.d.ts +2 -2
  80. package/dist/extensions/descriptor-loader.js +3 -4
  81. package/dist/extensions/descriptor-loader.js.map +1 -1
  82. package/dist/extensions/index.d.ts +1 -1
  83. package/dist/extensions/index.js +1 -1
  84. package/dist/extensions/index.js.map +1 -1
  85. package/dist/extensions/installer.d.ts +24 -7
  86. package/dist/extensions/installer.js +142 -66
  87. package/dist/extensions/installer.js.map +1 -1
  88. package/dist/extensions/registry.js +3 -5
  89. package/dist/extensions/registry.js.map +1 -1
  90. package/dist/extensions/state-contract.d.ts +11 -0
  91. package/dist/extensions/state-contract.js +50 -0
  92. package/dist/extensions/state-contract.js.map +1 -0
  93. package/dist/instances.d.ts +13 -1
  94. package/dist/instances.js +26 -0
  95. package/dist/instances.js.map +1 -1
  96. package/dist/package-manager.d.ts +0 -24
  97. package/dist/package-manager.js +5 -58
  98. package/dist/package-manager.js.map +1 -1
  99. package/dist/project-tool-discovery.d.ts +5 -2
  100. package/dist/project-tool-discovery.js +22 -18
  101. package/dist/project-tool-discovery.js.map +1 -1
  102. package/dist/runtime-code.js +1 -1
  103. package/dist/runtime-code.js.map +1 -1
  104. package/dist/tool-name.d.ts +4 -0
  105. package/dist/tool-name.js +15 -0
  106. package/dist/tool-name.js.map +1 -0
  107. package/dist/tools/.generated-completion-registry.d.ts +2 -0
  108. package/dist/tools/.generated-completion-registry.js +763 -325
  109. package/dist/tools/.generated-completion-registry.js.map +1 -1
  110. package/dist/tools/.generated-registry.d.ts +6 -11
  111. package/dist/tools/.generated-registry.js +6 -11
  112. package/dist/tools/.generated-registry.js.map +1 -1
  113. package/dist/tools/.generated-schemas.js +11 -16
  114. package/dist/tools/.generated-schemas.js.map +1 -1
  115. package/dist/tools/asset/refresh-status.d.ts +3 -0
  116. package/dist/tools/asset/refresh-status.js +12 -0
  117. package/dist/tools/asset/refresh-status.js.map +1 -0
  118. package/dist/tools/asset/refresh.js +7 -3
  119. package/dist/tools/asset/refresh.js.map +1 -1
  120. package/dist/tools/build/player-status.d.ts +3 -0
  121. package/dist/tools/build/player-status.js +12 -0
  122. package/dist/tools/build/player-status.js.map +1 -0
  123. package/dist/tools/build/run.js +1 -1
  124. package/dist/tools/build/run.js.map +1 -1
  125. package/dist/tools/catalog-control.d.ts +22 -0
  126. package/dist/tools/catalog-control.js +335 -0
  127. package/dist/tools/catalog-control.js.map +1 -0
  128. package/dist/tools/catalog.d.ts +7 -9
  129. package/dist/tools/catalog.js +66 -45
  130. package/dist/tools/catalog.js.map +1 -1
  131. package/dist/tools/commands.d.ts +1 -1
  132. package/dist/tools/commands.js +44 -11
  133. package/dist/tools/commands.js.map +1 -1
  134. package/dist/tools/define.js +3 -2
  135. package/dist/tools/define.js.map +1 -1
  136. package/dist/tools/dispatcher.d.ts +53 -6
  137. package/dist/tools/dispatcher.js +277 -108
  138. package/dist/tools/dispatcher.js.map +1 -1
  139. package/dist/tools/editor/compile.js +1 -1
  140. package/dist/tools/editor/compile.js.map +1 -1
  141. package/dist/tools/editor/play-mode-status.d.ts +3 -0
  142. package/dist/tools/editor/play-mode-status.js +12 -0
  143. package/dist/tools/editor/play-mode-status.js.map +1 -0
  144. package/dist/tools/editor/readiness.d.ts +0 -1
  145. package/dist/tools/editor/readiness.js +0 -1
  146. package/dist/tools/editor/readiness.js.map +1 -1
  147. package/dist/tools/editor/refresh.d.ts +7 -3
  148. package/dist/tools/editor/refresh.js +20 -15
  149. package/dist/tools/editor/refresh.js.map +1 -1
  150. package/dist/tools/editor/state.d.ts +2 -2
  151. package/dist/tools/editor/state.js +13 -12
  152. package/dist/tools/editor/state.js.map +1 -1
  153. package/dist/tools/metadata/derived.js +1 -7
  154. package/dist/tools/metadata/derived.js.map +1 -1
  155. package/dist/tools/metadata/status.js +1 -12
  156. package/dist/tools/metadata/status.js.map +1 -1
  157. package/dist/tools/metadata/types.d.ts +0 -2
  158. package/dist/tools/navmesh/live.js +1 -1
  159. package/dist/tools/navmesh/live.js.map +1 -1
  160. package/dist/tools/occlusion/live.js +1 -1
  161. package/dist/tools/occlusion/live.js.map +1 -1
  162. package/dist/tools/package/deploy.js +2 -2
  163. package/dist/tools/package/deploy.js.map +1 -1
  164. package/dist/tools/package/embed.js +1 -1
  165. package/dist/tools/package/embed.js.map +1 -1
  166. package/dist/tools/package/install-status.d.ts +3 -0
  167. package/dist/tools/package/install-status.js +12 -0
  168. package/dist/tools/package/install-status.js.map +1 -0
  169. package/dist/tools/package/install.js +3 -3
  170. package/dist/tools/package/install.js.map +1 -1
  171. package/dist/tools/package/live-resolve.js +45 -12
  172. package/dist/tools/package/live-resolve.js.map +1 -1
  173. package/dist/tools/package/registry-clear.js.map +1 -1
  174. package/dist/tools/package/registry-set.js.map +1 -1
  175. package/dist/tools/package/remove-status.d.ts +3 -0
  176. package/dist/tools/package/remove-status.js +12 -0
  177. package/dist/tools/package/remove-status.js.map +1 -0
  178. package/dist/tools/package/remove.js +3 -3
  179. package/dist/tools/package/remove.js.map +1 -1
  180. package/dist/tools/package/resolve.js +3 -3
  181. package/dist/tools/package/resolve.js.map +1 -1
  182. package/dist/tools/package/restore.js +2 -2
  183. package/dist/tools/package/restore.js.map +1 -1
  184. package/dist/tools/package/scoped-registry-add.js.map +1 -1
  185. package/dist/tools/status/support.d.ts +13 -0
  186. package/dist/tools/status/support.js +28 -0
  187. package/dist/tools/status/support.js.map +1 -0
  188. package/dist/tools/test/cancel.js +2 -2
  189. package/dist/tools/test/cancel.js.map +1 -1
  190. package/dist/tools/test/run-status.d.ts +3 -0
  191. package/dist/tools/test/run-status.js +12 -0
  192. package/dist/tools/test/run-status.js.map +1 -0
  193. package/dist/tools/test/run.js +7 -4
  194. package/dist/tools/test/run.js.map +1 -1
  195. package/dist/tools/tools/group-summaries.js +10 -14
  196. package/dist/tools/tools/group-summaries.js.map +1 -1
  197. package/docs/architecture.md +4 -4
  198. package/docs/availability-differences.md +4 -4
  199. package/docs/command-execution.md +4 -3
  200. package/docs/error-codes.md +13 -5
  201. package/docs/extensions.md +1 -7
  202. package/docs/risk-and-side-effects.md +4 -3
  203. package/docs/safety-boundaries.md +3 -1
  204. package/docs/target-and-instance.md +1 -1
  205. package/docs/tool-discovery.md +17 -10
  206. package/docs/tool-execution.md +18 -7
  207. package/extensions/cinemachine/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  208. package/extensions/hdrp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  209. package/extensions/probuilder/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  210. package/extensions/urp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  211. package/extensions/vfx-graph/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  212. package/package.json +2 -4
  213. package/skills/unitycli/SKILL.md +35 -42
  214. package/skills/unitycli/manifest.json +1 -1
  215. package/skills/unitycli/references/operation-protocol.md +3 -3
  216. package/unitypkg/Editor/AssemblyInfo.cs +5 -0
  217. package/unitypkg/Editor/Bridge/BridgeCommandRegistry.cs +49 -14
  218. package/unitypkg/Editor/Bridge/BridgeHttpServer.cs +9 -35
  219. package/unitypkg/Editor/Bridge/BridgeQueueState.cs +672 -0
  220. package/unitypkg/Editor/Bridge/BridgeWorkQueue.cs +970 -120
  221. package/unitypkg/Editor/BridgeStartupStateMachine.cs +59 -1
  222. package/unitypkg/Editor/Commands/ProjectToolCommands.cs +4 -0
  223. package/unitypkg/Editor/Commands/RuntimeToolCommands.cs +3 -0
  224. package/unitypkg/Editor/Generated/UnityCliToolCompletionRegistry.g.cs +379 -14
  225. package/unitypkg/Editor/Infrastructure/CodeExecSupport.cs +28 -13
  226. package/unitypkg/Editor/Infrastructure/RegistryOwnershipResolver.cs +132 -105
  227. package/unitypkg/Editor/Infrastructure/UnityCliActiveOperation.cs +138 -29
  228. package/unitypkg/Editor/Infrastructure/UnityCliAdmission.cs +1856 -237
  229. package/unitypkg/Editor/Infrastructure/UnityCliDoctorDiagnostics.cs +220 -0
  230. package/unitypkg/Editor/Infrastructure/UnityCliDurableStatusCoordinator.cs +118 -0
  231. package/unitypkg/Editor/Infrastructure/UnityCliEditorStateSnapshot.cs +2 -1
  232. package/unitypkg/Editor/Infrastructure/UnityCliHierarchyPager.cs +203 -0
  233. package/unitypkg/Editor/Infrastructure/UnityCliInstanceCleanup.cs +2 -1
  234. package/unitypkg/Editor/Infrastructure/UnityCliInstanceRegistry.cs +201 -63
  235. package/unitypkg/Editor/Infrastructure/UnityCliJson.cs +2 -3
  236. package/unitypkg/Editor/Infrastructure/UnityCliLiveBuildCoordinator.cs +289 -46
  237. package/unitypkg/Editor/Infrastructure/UnityCliLiveRefreshCoordinator.cs +87 -53
  238. package/unitypkg/Editor/Infrastructure/UnityCliNavMeshBakeCoordinator.cs +4 -4
  239. package/unitypkg/Editor/Infrastructure/UnityCliObjectRef.cs +128 -25
  240. package/unitypkg/Editor/Infrastructure/UnityCliObjectSerializer.cs +435 -327
  241. package/unitypkg/Editor/Infrastructure/UnityCliOcclusionBakeCoordinator.cs +4 -4
  242. package/unitypkg/Editor/Infrastructure/UnityCliOperationCoordinator.cs +351 -53
  243. package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveCoordinator.cs +118 -18
  244. package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveState.cs +20 -1
  245. package/unitypkg/Editor/Infrastructure/UnityCliReadGeneration.cs +97 -0
  246. package/unitypkg/Editor/Infrastructure/UnityCliReflection.cs +644 -133
  247. package/unitypkg/Editor/Infrastructure/UnityCliReloadOperation.cs +128 -41
  248. package/unitypkg/Editor/Infrastructure/UnityCliRuntimeExecutor.cs +14 -84
  249. package/unitypkg/Editor/Infrastructure/UnityCliSerializer.cs +220 -0
  250. package/unitypkg/Editor/Infrastructure/UnityCliSessionMutationLease.cs +69 -29
  251. package/unitypkg/Editor/Infrastructure/UnityCliTestRunCoordinator.cs +440 -54
  252. package/unitypkg/Editor/Infrastructure/UnityCliWaiter.cs +128 -35
  253. package/unitypkg/Editor/ProjectTools/IUnityCliTool.cs +9 -1
  254. package/unitypkg/Editor/ProjectTools/OfficialAttributeToolRegistry.cs +32 -31
  255. package/unitypkg/Editor/ProjectTools/ProjectToolRegistry.cs +607 -458
  256. package/unitypkg/Editor/ProjectTools/ProjectToolSchemaValidator.cs +35 -7
  257. package/unitypkg/Editor/ProjectTools/RegisteredTool.cs +501 -60
  258. package/unitypkg/Editor/ProjectTools/UnityCliProjectToolContext.cs +21 -0
  259. package/unitypkg/Editor/ProjectTools/UnityCliSchemaDeriver.cs +36 -7
  260. package/unitypkg/Editor/ProjectTools/UnityCliToolName.cs +30 -0
  261. package/unitypkg/Editor/Properties/AssemblyInfo.cs +3 -0
  262. package/unitypkg/Editor/Tools/asset/get-data.cs +16 -7
  263. package/unitypkg/Editor/Tools/build/BuildSceneStateTool.cs +23 -3
  264. package/unitypkg/Editor/Tools/build/BuildToolDtos.cs +2 -1
  265. package/unitypkg/Editor/Tools/build/scene/add.cs +6 -3
  266. package/unitypkg/Editor/Tools/build/scene/disable.cs +1 -1
  267. package/unitypkg/Editor/Tools/build/scene/enable.cs +1 -1
  268. package/unitypkg/Editor/Tools/build/scene/list.cs +1 -1
  269. package/unitypkg/Editor/Tools/build/scene/remove.cs +13 -3
  270. package/unitypkg/Editor/Tools/camera/CameraToolSupport.cs +38 -38
  271. package/unitypkg/Editor/Tools/camera/multiview.cs +13 -6
  272. package/unitypkg/Editor/Tools/camera/screenshot.cs +14 -7
  273. package/unitypkg/Editor/Tools/component/get.cs +16 -9
  274. package/unitypkg/Editor/Tools/component/modify.cs +6 -4
  275. package/unitypkg/Editor/Tools/console/clear.cs +7 -6
  276. package/unitypkg/Editor/Tools/console/get.cs +48 -16
  277. package/unitypkg/Editor/Tools/editor/EditorToolSupport.cs +143 -28
  278. package/unitypkg/Editor/Tools/editor/menu-items.cs +1 -1
  279. package/unitypkg/Editor/Tools/editor/play.cs +2 -11
  280. package/unitypkg/Editor/Tools/editor/selection.cs +1 -1
  281. package/unitypkg/Editor/Tools/editor/stop.cs +2 -11
  282. package/unitypkg/Editor/Tools/editor/windows.cs +1 -1
  283. package/unitypkg/Editor/Tools/go/GameObjectToolSupport.cs +54 -4
  284. package/unitypkg/Editor/Tools/go/delete.cs +1 -1
  285. package/unitypkg/Editor/Tools/go/find.cs +167 -70
  286. package/unitypkg/Editor/Tools/graphics/GraphicsToolSupport.cs +25 -0
  287. package/unitypkg/Editor/Tools/graphics/light-probe-group.cs +9 -3
  288. package/unitypkg/Editor/Tools/graphics/pipeline-info.cs +1 -1
  289. package/unitypkg/Editor/Tools/graphics/pipeline-settings.cs +1 -1
  290. package/unitypkg/Editor/Tools/graphics/quality.cs +2 -2
  291. package/unitypkg/Editor/Tools/graphics/reflection-probe.cs +3 -3
  292. package/unitypkg/Editor/Tools/graphics/stats.cs +1 -1
  293. package/unitypkg/Editor/Tools/object/ObjectToolSupport.cs +4 -4
  294. package/unitypkg/Editor/Tools/object/get-data.cs +9 -7
  295. package/unitypkg/Editor/Tools/object/modify.cs +7 -5
  296. package/unitypkg/Editor/Tools/physics/raycast.cs +24 -6
  297. package/unitypkg/Editor/Tools/prefab/stage.cs +1 -1
  298. package/unitypkg/Editor/Tools/profiler/ProfilerFrameDebuggerSupport.cs +94 -21
  299. package/unitypkg/Editor/Tools/profiler/ProfilerMemorySnapshotSupport.cs +397 -10
  300. package/unitypkg/Editor/Tools/profiler/ProfilerToolSupport.cs +18 -4
  301. package/unitypkg/Editor/Tools/profiler/frame-debugger/enable.cs +1 -1
  302. package/unitypkg/Editor/Tools/profiler/frame-debugger/events.cs +88 -87
  303. package/unitypkg/Editor/Tools/profiler/hierarchy.cs +278 -112
  304. package/unitypkg/Editor/Tools/profiler/memory-snapshot/compare.cs +9 -5
  305. package/unitypkg/Editor/Tools/profiler/memory-snapshot/list.cs +37 -35
  306. package/unitypkg/Editor/Tools/profiler/memory-snapshot/take.cs +99 -97
  307. package/unitypkg/Editor/Tools/project/info.cs +1 -1
  308. package/unitypkg/Editor/Tools/project/layers.cs +1 -1
  309. package/unitypkg/Editor/Tools/project/tags.cs +1 -1
  310. package/unitypkg/Editor/Tools/reflection/method/call.cs +9 -7
  311. package/unitypkg/Editor/Tools/reflection/method/find.cs +27 -37
  312. package/unitypkg/Editor/Tools/reflection/type/schema.cs +24 -28
  313. package/unitypkg/Editor/Tools/scene/gameobject/component.cs +8 -5
  314. package/unitypkg/Editor/Tools/scene/gameobject/components.cs +10 -7
  315. package/unitypkg/Editor/Tools/scene/gameobject-support.cs +104 -8
  316. package/unitypkg/Editor/Tools/scene/gameobject.cs +37 -16
  317. package/unitypkg/Editor/Tools/scene/hierarchy.cs +146 -142
  318. package/unitypkg/Editor/Tools/screenshot/EditorElementCaptureToolSupport.cs +77 -25
  319. package/unitypkg/Editor/Tools/screenshot/ScreenshotToolSupport.cs +315 -95
  320. package/unitypkg/Editor/Tools/screenshot/editor-element.cs +7 -5
  321. package/unitypkg/Editor/Tools/screenshot/game-view.cs +35 -21
  322. package/unitypkg/Editor/Tools/screenshot/isolated-object.cs +29 -23
  323. package/unitypkg/Editor/Tools/screenshot/orbit.cs +37 -25
  324. package/unitypkg/Editor/Tools/screenshot/scene-view.cs +25 -18
  325. package/unitypkg/Editor/Tools/screenshot/screen.cs +177 -17
  326. package/unitypkg/Editor/Tools/screenshot/surround.cs +83 -79
  327. package/unitypkg/Editor/Tools/ui/render-preview.cs +16 -11
  328. package/unitypkg/Editor/UnityCliBridge.cs +97 -34
  329. package/unitypkg/Runtime/Infrastructure/UnityCliRuntimeBridge.cs +753 -122
  330. package/unitypkg/Runtime/Properties/AssemblyInfo.cs +5 -0
  331. package/unitypkg/Runtime/UnityCli.Runtime.asmdef +1 -1
  332. package/unitypkg/Shared/AssemblyInfo.cs +4 -0
  333. package/unitypkg/Shared/UnityCliAdmissionPool.cs +265 -75
  334. package/unitypkg/Shared/UnityCliBridgeQueuePolicy.cs +410 -0
  335. package/unitypkg/Shared/UnityCliBridgeWorkItemState.cs +47 -0
  336. package/unitypkg/Shared/UnityCliExceptionProjection.cs +151 -0
  337. package/unitypkg/Shared/UnityCliQueuePublication.cs +30 -0
  338. package/unitypkg/Shared/UnityCliRequestBudget.cs +352 -0
  339. package/unitypkg/Shared/UnityCliRuntimeRequestPublication.cs +107 -0
  340. package/unitypkg/Tests/Editor/UnityCli.Editor.Tests.asmdef +2 -2
  341. package/unitypkg/Tests/Editor/UnityCliAdmissionCoordinatorIntegrationTests.cs +1329 -0
  342. package/unitypkg/Tests/Editor/UnityCliAdmissionPoolTests.cs +311 -114
  343. package/unitypkg/Tests/Editor/UnityCliAdmissionRepairStateTests.cs +94 -0
  344. package/unitypkg/Tests/Editor/UnityCliBridgeAdmissionIntegrationTests.cs +1229 -0
  345. package/unitypkg/Tests/Editor/UnityCliBridgeQueuePolicyTests.cs +412 -0
  346. package/unitypkg/Tests/Editor/UnityCliContinuationIntegrationTests.cs +292 -0
  347. package/unitypkg/Tests/Editor/UnityCliDiagnosticBoundaryTests.cs +120 -0
  348. package/unitypkg/Tests/Editor/UnityCliDurableStatusTests.cs +84 -0
  349. package/unitypkg/Tests/Editor/UnityCliExceptionProjectionTests.cs +72 -0
  350. package/unitypkg/Tests/Editor/UnityCliInstancePublicationRetryTests.cs +180 -97
  351. package/unitypkg/Tests/Editor/UnityCliMemoryReflectionIntegrationTests.cs +434 -0
  352. package/unitypkg/Tests/Editor/UnityCliRequestBudgetTests.cs +615 -0
  353. package/unitypkg/Tests/Editor/UnityCliRuntimeBridgeIntegrationTests.cs +520 -0
  354. package/unitypkg/Tests/Editor/UnityCliToolCatalogTests.cs +562 -0
  355. package/unitypkg/Tests/Runtime/UnityCli.Runtime.Tests.asmdef +23 -0
  356. package/unitypkg/Tests/Runtime/UnityCliRuntimeCatalogValidationTests.cs +29 -0
  357. package/unitypkg/package.json +1 -1
  358. package/contracts/static-capability-catalog.json +0 -48226
  359. package/dist/bridge/domains/diagnose.d.ts +0 -2
  360. package/dist/bridge/domains/diagnose.js +0 -11
  361. package/dist/bridge/domains/diagnose.js.map +0 -1
  362. package/dist/bridge/domains/extension.d.ts +0 -8
  363. package/dist/bridge/domains/extension.js +0 -20
  364. package/dist/bridge/domains/extension.js.map +0 -1
  365. package/dist/capabilities.d.ts +0 -5
  366. package/dist/capabilities.js +0 -84
  367. package/dist/capabilities.js.map +0 -1
  368. package/dist/static-capability-catalog.d.ts +0 -13
  369. package/dist/static-capability-catalog.js +0 -41
  370. package/dist/static-capability-catalog.js.map +0 -1
  371. package/dist/tools/diagnose/capabilities.d.ts +0 -13
  372. package/dist/tools/diagnose/capabilities.js +0 -34
  373. package/dist/tools/diagnose/capabilities.js.map +0 -1
  374. package/dist/tools/package/info.d.ts +0 -19
  375. package/dist/tools/package/info.js +0 -27
  376. package/dist/tools/package/info.js.map +0 -1
  377. package/dist/tools/package/list.d.ts +0 -27
  378. package/dist/tools/package/list.js +0 -21
  379. package/dist/tools/package/list.js.map +0 -1
  380. package/dist/tools/package/registry-get.d.ts +0 -10
  381. package/dist/tools/package/registry-get.js +0 -22
  382. package/dist/tools/package/registry-get.js.map +0 -1
  383. package/dist/tools/package/scoped-registry-list.d.ts +0 -14
  384. package/dist/tools/package/scoped-registry-list.js +0 -22
  385. package/dist/tools/package/scoped-registry-list.js.map +0 -1
  386. package/dist/tools/package/search.d.ts +0 -17
  387. package/dist/tools/package/search.js +0 -39
  388. package/dist/tools/package/search.js.map +0 -1
  389. package/dist/tools/package/status.d.ts +0 -21
  390. package/dist/tools/package/status.js +0 -37
  391. package/dist/tools/package/status.js.map +0 -1
  392. package/dist/tools/tools/describe.d.ts +0 -27
  393. package/dist/tools/tools/describe.js +0 -35
  394. package/dist/tools/tools/describe.js.map +0 -1
  395. package/dist/tools/tools/group-list.d.ts +0 -16
  396. package/dist/tools/tools/group-list.js +0 -38
  397. package/dist/tools/tools/group-list.js.map +0 -1
  398. package/dist/tools/tools/group.d.ts +0 -13
  399. package/dist/tools/tools/group.js +0 -34
  400. package/dist/tools/tools/group.js.map +0 -1
  401. package/dist/tools/tools/pagination.d.ts +0 -17
  402. package/dist/tools/tools/pagination.js +0 -28
  403. package/dist/tools/tools/pagination.js.map +0 -1
  404. package/dist/tools/tools/search.d.ts +0 -15
  405. package/dist/tools/tools/search.js +0 -42
  406. package/dist/tools/tools/search.js.map +0 -1
  407. package/unitypkg/Editor/Tools/diagnose/preflight.cs +0 -269
  408. package/unitypkg/Editor/Tools/editor/console.cs +0 -26
  409. package/unitypkg/Editor/Tools/extension/status.cs +0 -87
  410. package/unitypkg/Editor/Tools/go/inspect.cs +0 -49
  411. package/unitypkg/Editor/Tools/graphics/pipeline-quality.cs +0 -24
  412. package/unitypkg/Editor/Tools/graphics/probe-positions.cs +0 -58
  413. package/unitypkg/Editor/Tools/graphics/settings.cs +0 -104
  414. package/unitypkg/Editor/Tools/graphics/stats-counters.cs +0 -28
  415. package/unitypkg/Editor/Tools/graphics/stats-memory.cs +0 -21
  416. package/unitypkg/Editor/Tools/instances/list.cs +0 -71
  417. package/unitypkg/Editor/Tools/physics/raycast-all.cs +0 -67
  418. package/unitypkg/Editor/Tools/scene/build-settings.cs +0 -69
  419. package/unitypkg/Editor/Tools/scene/data.cs +0 -125
  420. package/unitypkg/Editor/Tools/tests/list.cs +0 -98
package/dist/doctor.js CHANGED
@@ -1,349 +1,377 @@
1
1
  /**
2
- * CLI command: doctor
2
+ * CLI control command: doctor.
3
3
  *
4
- * Diagnoses CLI UnityPkg connectivity, instance health,
5
- * live Bridge preflight, configuration integrity, and capability discovery.
6
- * Transport endpoint and listener identity remain internal to instance selection.
4
+ * Registry/Health identity, main-thread pulse, admitted business work, catalog
5
+ * integrity, compilation, and extension installation are independent owners.
7
6
  */
8
7
  import { getString } from "./args.js";
8
+ import { Buffer } from "node:buffer";
9
9
  import { access, stat } from "node:fs/promises";
10
- import { discoverProjectTools } from "./project-tool-discovery.js";
11
- import { instanceSelectionFailure, obsoleteLocalStatePaths, readInstances, readInstancesWithRegistryDiagnostics, selectInstanceForFlags } from "./instances.js";
12
- import { executeBridgePayload } from "./bridge/index.js";
10
+ import { inspectInstanceHealth, instanceSelectionFailure, obsoleteLocalStatePaths, readInstancesWithRegistryDiagnostics, selectInstanceForFlags, } from "./instances.js";
11
+ import { executeBridgePayload, SessionSupervisor, SessionSupervisorError, } from "./bridge/index.js";
13
12
  import { EDITOR_STATE_OBSERVATION_BUDGET_MS } from "./bridge/session-supervisor.js";
14
- import { builtinToolMetadata } from "./tools/metadata/index.js";
15
- import { builtinExtensionMetadata } from "./extensions/metadata/index.js";
16
- import { ExtensionRegistry, extensionAvailabilitySummary } from "./extensions/index.js";
13
+ import { withObservationSelection } from "./bridge/types.js";
17
14
  import { extensionAvailabilityFlags } from "./extension-availability.js";
18
- import { readInstalledUnityPackages } from "./extensions/project-resolver.js";
19
- import { success } from "./output.js";
15
+ import { readInstalledExtensions } from "./cli-commands/extensions.js";
16
+ import { checkExtensionBundles, readExtensionInstallManifest } from "./extensions/installer.js";
17
+ import { EXTENSION_DIAGNOSTIC_SAMPLE_LIMIT } from "./extensions/state-contract.js";
18
+ import { failure, success } from "./output.js";
19
+ const DOCTOR_BUSINESS_TIMEOUT_MS = 10_000;
20
+ const DOCTOR_BUSINESS_COMPLETION = "doctor-business-read-v1";
21
+ const DOCTOR_CATALOG_IDENTITY = "unity-cli-immutable-domain-catalog";
22
+ const MAX_DOCTOR_CATALOG_TOOLS = 10_000;
23
+ const MAX_DOCTOR_CATALOG_ASSEMBLIES = 100_000;
24
+ const MAX_DOCTOR_CATALOG_RESPONSE_BYTES = 64 * 1024;
20
25
  export async function handleDoctorCommand(flags, runtime, requestId) {
21
26
  const checks = [];
22
- // Check 1: Instance registry
23
- const instanceCheck = await checkInstanceRegistry(runtime.dataDir);
24
- checks.push(instanceCheck);
25
- // Check 2: Reduced v3 instance states
26
- let readyInstances = [];
27
- if (instanceCheck.status !== "fail") {
28
- const instances = await readInstances(runtime.dataDir);
29
- readyInstances = instances.filter((instance) => instance.status === "ready");
30
- checks.push(checkReadyInstances(instances, readyInstances));
31
- }
32
- else {
33
- checks.push({ name: "ready_instances", status: "warn", message: "Skipped: instance registry not available." });
34
- }
35
- // Check 3: Bridge connectivity follows the same strict --instance/CWD selector as business calls.
36
- let bridgeReachable = false;
27
+ checks.push(await checkDataDirectory(runtime.dataDir));
28
+ checks.push(await checkObsoleteLocalState(runtime.dataDir));
29
+ const selected = await inspectSelectedHealth(flags, runtime, requestId);
30
+ checks.push(await checkRegistryHealthIdentity(runtime.dataDir, selected));
31
+ checks.push(checkMainThreadPulse(selected));
32
+ checks.push(await checkBusinessDiagnostic(flags, runtime, requestId, selected));
33
+ checks.push(await checkCatalogIntegrity(flags, runtime, requestId, selected));
34
+ checks.push(await checkCompile(flags, runtime, requestId, selected));
35
+ checks.push(await checkExtensions(flags, runtime));
36
+ const summary = checks.some((check) => check.status === "fail")
37
+ ? "fail"
38
+ : checks.some((check) => check.status === "warn") ? "warn" : "pass";
39
+ return success(requestId, { summary, checks, suggestions: buildSuggestions(checks) });
40
+ }
41
+ /** selector 与严格 Health v3 只执行一次,后续 check 共享脱敏结果但不共享严重性。 */
42
+ async function inspectSelectedHealth(flags, runtime, requestId) {
37
43
  try {
38
44
  const selection = await (runtime.selectInstance ?? selectInstanceForFlags)(flags, runtime.dataDir);
39
- const bridgeCheck = await checkBridgeConnectivity(selection.instance.instanceId, requestId, runtime);
40
- checks.push(bridgeCheck);
41
- bridgeReachable = bridgeCheck.status === "pass";
45
+ return { selection, health: await inspectInstanceHealth(selection.instance) };
42
46
  }
43
47
  catch (error) {
44
- const selectionFailure = instanceSelectionFailure(requestId, error);
45
- const selectionError = selectionFailure && !selectionFailure.ok ? selectionFailure.error : undefined;
46
- checks.push({
47
- name: "bridge_connectivity",
48
- status: "warn",
49
- message: selectionError?.message ?? "No Unity project identity can be selected from --instance or CWD.",
50
- details: selectionError ? { code: selectionError.code, details: selectionError.details } : undefined,
51
- });
52
- }
53
- // Check 4: Data directory
54
- checks.push(await checkDataDirectory(runtime.dataDir));
55
- // Check 5: Configuration integrity and obsolete local state
56
- checks.push(await checkConfigIntegrity(readyInstances));
57
- checks.push(await checkObsoleteLocalState(runtime.dataDir));
58
- const fingerprintCheck = checkBridgeFingerprintStatus(checks);
59
- if (fingerprintCheck) {
60
- checks.push(fingerprintCheck);
61
- }
62
- const compileCheck = checkCompileStatus(checks);
63
- if (compileCheck) {
64
- checks.push(compileCheck);
48
+ const projected = projectDoctorError(`${requestId}_identity`, error);
49
+ return { error: projected };
65
50
  }
66
- // Unified CLI/tools discovery surface
67
- checks.push(await checkUnifiedDiscovery(flags, runtime, requestId, bridgeReachable));
68
- // Extension package availability comes from physical project state.
69
- checks.push(await checkExtensionAvailability(flags, runtime));
70
- checks.push(checkExtensionRegistry());
71
- const allPassed = checks.every((c) => c.status === "pass");
72
- const hasWarnings = checks.some((c) => c.status === "warn");
73
- const hasFailures = checks.some((c) => c.status === "fail");
74
- const summary = hasFailures ? "fail" : hasWarnings ? "warn" : "pass";
75
- const data = {
76
- summary,
77
- checks,
78
- suggestions: buildSuggestions(checks)
79
- };
80
- return success(requestId, data);
81
51
  }
82
- async function checkInstanceRegistry(dataDir) {
52
+ async function checkRegistryHealthIdentity(dataDir, selected) {
53
+ let instanceCount = 0;
54
+ let invalidRecordCount = 0;
83
55
  try {
84
- const { instances, invalidRecordCount } = await readInstancesWithRegistryDiagnostics(dataDir);
85
- if (invalidRecordCount > 0) {
86
- return {
87
- name: "instance_registry",
88
- status: "warn",
89
- message: `Instance registry contains ${instances.length} valid instance(s) and ignored ${invalidRecordCount} invalid or unreadable record(s).`,
90
- details: { instanceCount: instances.length, invalidRecordCount },
91
- };
92
- }
93
- if (instances.length === 0) {
94
- return {
95
- name: "instance_registry",
96
- status: "warn",
97
- message: "Instance registry is empty. No Unity instances registered.",
98
- details: { instanceCount: 0 }
99
- };
100
- }
101
- return {
102
- name: "instance_registry",
103
- status: "pass",
104
- message: `Instance registry contains ${instances.length} instance(s).`,
105
- details: { instanceCount: instances.length }
106
- };
56
+ const registry = await readInstancesWithRegistryDiagnostics(dataDir);
57
+ instanceCount = registry.instances.length;
58
+ invalidRecordCount = registry.invalidRecordCount;
107
59
  }
108
60
  catch (error) {
109
61
  return {
110
- name: "instance_registry",
62
+ name: "registry_health_identity",
111
63
  status: "fail",
112
- message: `Failed to read instance registry directory: ${error instanceof Error ? error.message : String(error)}`
64
+ message: `Instance registry could not be read: ${errorMessage(error)}`,
65
+ details: { code: "E_INSTANCE_REGISTRY_INVALID" },
113
66
  };
114
67
  }
115
- }
116
- function checkReadyInstances(instances, ready) {
117
- const conflicts = instances.filter((instance) => instance.status === "conflict");
118
- const unverifiable = instances.filter((instance) => instance.status === "unverifiable");
119
- const blocked = instances.filter((instance) => instance.status === "blocked");
120
- if (conflicts.length > 0) {
68
+ if (!selected.selection) {
69
+ const error = selected.error;
70
+ const status = error?.code === "E_INSTANCE_CONFLICT" || error?.code === "E_INSTANCE_UNVERIFIABLE" ? "fail" : "warn";
121
71
  return {
122
- name: "ready_instances",
123
- status: "fail",
124
- message: `${conflicts.length} project identity conflict(s) block live dispatch.`,
125
- details: { readyCount: ready.length, conflictCount: conflicts.length, blockedCount: blocked.length }
72
+ name: "registry_health_identity",
73
+ status,
74
+ message: error?.message ?? "No exact Editor identity was selected from Registry v3.",
75
+ details: {
76
+ code: error?.code ?? "E_INSTANCE_REQUIRED",
77
+ instanceCount,
78
+ invalidRecordCount,
79
+ transportVerified: false,
80
+ },
126
81
  };
127
82
  }
128
- if (unverifiable.length > 0) {
83
+ const identity = selected.selection.instance;
84
+ if (!selected.health?.transportVerified) {
129
85
  return {
130
- name: "ready_instances",
86
+ name: "registry_health_identity",
131
87
  status: "fail",
132
- message: `${unverifiable.length} project identity observation(s) could not be verified safely.`,
133
- details: { readyCount: ready.length, conflictCount: 0, unverifiableCount: unverifiable.length, blockedCount: blocked.length }
88
+ message: `Registry v3 selected ${identity.instanceId}, but strict Health v3 transport identity was not verified.`,
89
+ details: {
90
+ code: "BRIDGE_CONNECTION_FAILED",
91
+ instanceId: identity.instanceId,
92
+ canonicalProjectPath: identity.canonicalProjectPath,
93
+ registryStatus: identity.status,
94
+ instanceCount,
95
+ invalidRecordCount,
96
+ transportVerified: false,
97
+ },
134
98
  };
135
99
  }
136
- if (ready.length === 0) {
100
+ return {
101
+ name: "registry_health_identity",
102
+ status: invalidRecordCount > 0 ? "warn" : "pass",
103
+ message: invalidRecordCount > 0
104
+ ? `Strict Registry/Health identity is verified; ${invalidRecordCount} unrelated invalid registry record(s) were ignored.`
105
+ : "Strict Registry v3 and Health v3 transport identity are verified.",
106
+ details: {
107
+ instanceId: identity.instanceId,
108
+ canonicalProjectPath: identity.canonicalProjectPath,
109
+ editorSessionId: identity.editorSessionId,
110
+ registryStatus: identity.status,
111
+ instanceCount,
112
+ invalidRecordCount,
113
+ transportVerified: true,
114
+ },
115
+ };
116
+ }
117
+ function checkMainThreadPulse(selected) {
118
+ const pulse = selected.health?.mainThread;
119
+ if (!selected.health?.transportVerified || !pulse) {
137
120
  return {
138
- name: "ready_instances",
121
+ name: "main_thread_pulse",
139
122
  status: "warn",
140
- message: "No ready Unity Editor session found.",
141
- details: {
142
- readyCount: 0,
143
- transitioningCount: instances.filter((instance) => instance.status === "transitioning").length,
144
- unresponsiveCount: instances.filter((instance) => instance.status === "unresponsive").length,
145
- blockedCount: blocked.length,
146
- offlineCount: instances.filter((instance) => instance.status === "offline").length,
147
- }
123
+ message: "Main-thread pulse could not be evaluated independently of transport identity.",
124
+ details: { code: "E_MAIN_THREAD_PULSE_UNAVAILABLE", observed: false, fresh: false },
125
+ };
126
+ }
127
+ if (!pulse.fresh) {
128
+ return {
129
+ name: "main_thread_pulse",
130
+ status: "warn",
131
+ message: `Health v3 transport is reachable, but the Editor main-thread pulse is stale or absent (${pulse.lifecycle}).`,
132
+ details: { code: "E_MAIN_THREAD_UNRESPONSIVE", ...pulse },
148
133
  };
149
134
  }
150
135
  return {
151
- name: "ready_instances",
152
- status: "pass",
153
- message: `${ready.length} ready instance(s) found.`,
154
- details: {
155
- readyCount: ready.length,
156
- instances: ready.map((instance) => ({
157
- instanceId: instance.instanceId,
158
- canonicalProjectPath: instance.canonicalProjectPath,
159
- editorSessionId: instance.editorSessionId,
160
- publishedAt: instance.publishedAt,
161
- }))
162
- }
136
+ name: "main_thread_pulse",
137
+ status: pulse.lifecycle === "ready" ? "pass" : "warn",
138
+ message: pulse.lifecycle === "ready"
139
+ ? "The read-only Editor main-thread pulse is fresh."
140
+ : `The main-thread pulse is fresh while the Editor lifecycle is ${pulse.lifecycle}.`,
141
+ details: { ...pulse },
163
142
  };
164
143
  }
165
- async function checkBridgeConnectivity(instanceId, requestId, runtime) {
144
+ /** check 独占 admission;其失败不得改写 Registry/Health check。 */
145
+ async function checkBusinessDiagnostic(flags, runtime, requestId, selected) {
146
+ if (!selected.selection || !selected.health?.transportVerified || !selected.health.mainThread?.fresh) {
147
+ return skippedLiveCheck("business_diagnostic", "E_BUSINESS_DIAGNOSTIC_SKIPPED", "Business diagnostic was skipped because an exact fresh Editor session was not available.");
148
+ }
149
+ const childRequestId = `${requestId}_doctor_business`;
150
+ const input = {};
151
+ const selectedFlags = { ...flags, instance: selected.selection.instance.instanceId };
152
+ const supervisor = new SessionSupervisor(selectedFlags, runtime);
153
+ let admission;
166
154
  try {
167
- const result = await executeBridgePayload(requestId, "project-tool.run", { name: "diagnose.preflight", input: {} }, { instance: instanceId, "timeout-ms": String(EDITOR_STATE_OBSERVATION_BUDGET_MS) }, runtime, undefined,
168
- // connectivity probe 与 editor.state 共享同一即时观察预算,由 supervisor 将 Bridge POST 限制为 3500ms。
169
- { mode: "observeNow", mutation: false, defaultTimeoutMs: EDITOR_STATE_OBSERVATION_BUDGET_MS });
170
- if (result.ok) {
171
- const wrapperData = result.data;
172
- const bridgeResponse = wrapperData && typeof wrapperData === "object" && !Array.isArray(wrapperData)
173
- && wrapperData.result && typeof wrapperData.result === "object"
174
- ? wrapperData.result
175
- : wrapperData;
176
- return {
177
- name: "bridge_connectivity",
178
- status: "pass",
179
- message: `Bridge identity is reachable for ${instanceId}.`,
180
- details: { instanceId, bridgeResponse }
181
- };
155
+ admission = await supervisor.acquireRootAdmission({
156
+ requestId: childRequestId,
157
+ command: "internal.doctor.business",
158
+ input,
159
+ dryRun: false,
160
+ target: "editor",
161
+ completionContractIdentity: DOCTOR_BUSINESS_COMPLETION,
162
+ timeoutMs: DOCTOR_BUSINESS_TIMEOUT_MS,
163
+ admissionAbsoluteDeadlineMs: Date.now() + DOCTOR_BUSINESS_TIMEOUT_MS,
164
+ });
165
+ const result = await executeBridgePayload(childRequestId, "internal.doctor.business", input, selectedFlags, { ...runtime, rootAdmission: admission }, { mode: "read", mutation: false, absoluteDeadlineMs: admission.executionAbsoluteDeadlineMs });
166
+ if (!result.ok)
167
+ return failedLiveCheck("business_diagnostic", result.error);
168
+ const data = record(result.data);
169
+ if (data?.accepted !== true || typeof data.sceneLoaded !== "boolean" || typeof data.projectPath !== "string") {
170
+ return invalidLiveCheck("business_diagnostic", "Business diagnostic returned an invalid DTO.");
182
171
  }
183
172
  return {
184
- name: "bridge_connectivity",
185
- status: "fail",
186
- message: `Bridge for ${instanceId} returned error: ${result.error?.code ?? "unknown"} — ${result.error?.message ?? "no message"}`,
187
- details: { instanceId, errorCode: result.error?.code, errorMessage: result.error?.message }
173
+ name: "business_diagnostic",
174
+ status: "pass",
175
+ message: "An admission-bound read reached the Editor business layer.",
176
+ details: {
177
+ accepted: true,
178
+ projectPath: data.projectPath,
179
+ sceneLoaded: data.sceneLoaded,
180
+ activeScenePath: typeof data.activeScenePath === "string" ? data.activeScenePath : "",
181
+ playMode: data.playMode === true,
182
+ },
188
183
  };
189
184
  }
190
185
  catch (error) {
191
- return {
192
- name: "bridge_connectivity",
193
- status: "fail",
194
- message: `Cannot reach bridge for ${instanceId}: ${error instanceof Error ? error.message : String(error)}`,
195
- details: { instanceId, error: error instanceof Error ? error.message : String(error) }
196
- };
186
+ return failedLiveCheck("business_diagnostic", projectDoctorError(childRequestId, error));
197
187
  }
198
- }
199
- function checkBridgeFingerprintStatus(checks) {
200
- const bridgeCheck = checks.find((check) => check.name === "bridge_connectivity" && check.status === "pass");
201
- const bridgeResponse = bridgeCheck?.details?.bridgeResponse;
202
- if (!bridgeResponse || typeof bridgeResponse !== "object") {
203
- return undefined;
188
+ finally {
189
+ if (admission)
190
+ await supervisor.completeRootAdmission(admission);
204
191
  }
205
- const customTools = bridgeResponse.customTools;
206
- const fingerprint = customTools && typeof customTools === "object" ? customTools.fingerprint : undefined;
207
- if (!fingerprint || typeof fingerprint !== "object") {
208
- return undefined;
192
+ }
193
+ async function checkCatalogIntegrity(flags, runtime, requestId, selected) {
194
+ const result = await privateDoctorRead("catalog_integrity", "internal.doctor.catalog", flags, runtime, requestId, selected);
195
+ if (isDoctorCheck(result)) {
196
+ return result.details?.code === "E_TOOL_REGISTRY_UNHEALTHY" ? { ...result, status: "fail" } : result;
209
197
  }
210
- const blockedCount = numberProp(fingerprint, "blockedCount") ?? 0;
211
- const toolCount = numberProp(fingerprint, "toolCount") ?? 0;
212
- const status = blockedCount > 0 ? "fail" : "pass";
198
+ const data = doctorCatalogData(result.data);
199
+ if (!data)
200
+ return invalidLiveCheck("catalog_integrity", "Catalog integrity read returned an invalid bounded DTO.");
213
201
  return {
214
- name: "project_tool_fingerprint",
215
- status,
216
- message: status === "pass"
217
- ? `Custom tool fingerprints match for ${toolCount} tool(s).`
218
- : `Custom tool fingerprints are blocked for ${blockedCount} of ${toolCount} tool(s).`,
219
- details: { fingerprint }
202
+ name: "catalog_integrity",
203
+ status: "pass",
204
+ message: `The immutable domain catalog contains ${data.toolCount} registered Tool(s).`,
205
+ details: {
206
+ identity: data.identity,
207
+ toolCount: data.toolCount,
208
+ providerCounts: data.providerCounts,
209
+ publicationDiagnostics: data.publicationDiagnostics,
210
+ },
220
211
  };
221
212
  }
222
- function checkCompileStatus(checks) {
223
- const bridgeCheck = checks.find((check) => check.name === "bridge_connectivity" && check.status === "pass");
224
- const bridgeResponse = bridgeCheck?.details?.bridgeResponse;
225
- if (!bridgeResponse || typeof bridgeResponse !== "object") {
226
- return undefined;
227
- }
228
- const checksArray = bridgeResponse.checks;
229
- if (!Array.isArray(checksArray)) {
230
- return undefined;
213
+ async function checkCompile(flags, runtime, requestId, selected) {
214
+ const result = await privateDoctorRead("compile", "internal.doctor.compile", flags, runtime, requestId, selected);
215
+ if (isDoctorCheck(result))
216
+ return result;
217
+ const data = record(result.data);
218
+ const errors = Array.isArray(data?.compileErrors) && data.compileErrors.every((value) => typeof value === "string")
219
+ ? data.compileErrors : undefined;
220
+ const nextActions = Array.isArray(data?.nextActions) && data.nextActions.every((value) => typeof value === "string")
221
+ ? data.nextActions : undefined;
222
+ if (!data || typeof data.compiling !== "boolean" || typeof data.scanComplete !== "boolean"
223
+ || typeof data.hasCompileErrors !== "boolean" || !isNonNegativeInteger(data.compileErrorCount)
224
+ || typeof data.compileErrorsTruncated !== "boolean" || !errors || errors.length > 20 || !nextActions) {
225
+ return invalidLiveCheck("compile", "Compile diagnostic returned an invalid bounded DTO.");
231
226
  }
232
- const compile = checksArray.find((entry) => typeof entry === "object" && entry !== null && entry.name === "compileStatus");
233
- if (!compile) {
234
- return undefined;
235
- }
236
- const compiling = compile.compiling === true;
237
- const hasErrors = compile.hasCompileErrors === true;
238
- const errors = Array.isArray(compile.compileErrors) ? compile.compileErrors.filter((e) => typeof e === "string") : [];
239
- const status = hasErrors ? "fail" : compiling ? "warn" : "pass";
240
- const message = hasErrors
241
- ? `Unity has compile errors (${errors.length} shown). Often caused by a broken extension under Packages/com.alpsckr.unitycli/Extensions.`
242
- : compiling
243
- ? "Unity is currently compiling scripts."
244
- : "Unity scripts compiled cleanly.";
227
+ const status = data.hasCompileErrors ? "fail" : data.compiling || !data.scanComplete ? "warn" : "pass";
228
+ const code = data.hasCompileErrors ? "E_UNITY_COMPILE_FAILED"
229
+ : data.compiling ? "E_UNITY_COMPILING"
230
+ : !data.scanComplete ? "E_COMPILE_DIAGNOSTICS_INCOMPLETE" : undefined;
245
231
  return {
246
- name: "compile_status",
232
+ name: "compile",
247
233
  status,
248
- message,
249
- details: { compiling, hasCompileErrors: hasErrors, compileErrors: errors.slice(0, 10), nextActions: compile.nextActions }
234
+ message: data.hasCompileErrors
235
+ ? `Unity reports ${data.compileErrorCount} compiler error(s).`
236
+ : data.compiling ? "Unity is compiling scripts."
237
+ : !data.scanComplete ? "Compile diagnostics were not scanned completely."
238
+ : "Unity scripts are not compiling and no compiler errors were found.",
239
+ details: {
240
+ ...(code ? { code } : {}),
241
+ compiling: data.compiling,
242
+ scanComplete: data.scanComplete,
243
+ hasCompileErrors: data.hasCompileErrors,
244
+ compileErrorCount: data.compileErrorCount,
245
+ compileErrorsTruncated: data.compileErrorsTruncated,
246
+ compileErrors: errors,
247
+ nextActions,
248
+ },
250
249
  };
251
250
  }
252
- function numberProp(value, key) {
253
- const raw = value && typeof value === "object" ? value[key] : undefined;
254
- return typeof raw === "number" && Number.isFinite(raw) ? raw : undefined;
255
- }
256
- function stringProp(value, key) {
257
- const raw = value && typeof value === "object" ? value[key] : undefined;
258
- return typeof raw === "string" ? raw : undefined;
251
+ async function privateDoctorRead(checkName, command, flags, runtime, requestId, selected) {
252
+ if (!selected.selection || !selected.health?.transportVerified || !selected.health.mainThread?.fresh) {
253
+ return skippedLiveCheck(checkName, "E_DOCTOR_READ_SKIPPED", `${checkName} was skipped because an exact fresh Editor session was not available.`);
254
+ }
255
+ const result = await executeBridgePayload(`${requestId}_${checkName}`, command, {}, { ...flags, instance: selected.selection.instance.instanceId }, runtime, withObservationSelection({ mode: "observeNow", mutation: false, defaultTimeoutMs: EDITOR_STATE_OBSERVATION_BUDGET_MS }, selected.selection));
256
+ return result.ok ? { data: result.data } : failedLiveCheck(checkName, result.error);
259
257
  }
260
- async function checkDataDirectory(dataDir) {
261
- try {
262
- await access(dataDir);
263
- const stats = await stat(dataDir);
264
- if (!stats.isDirectory()) {
265
- return { name: "data_directory", status: "fail", message: `Data path is not a directory: ${dataDir}` };
266
- }
258
+ async function checkExtensions(flags, runtime) {
259
+ const resolvedFlags = await extensionAvailabilityFlags(flags, runtime);
260
+ const projectPath = getString(resolvedFlags, "project");
261
+ if (!projectPath) {
267
262
  return {
268
- name: "data_directory",
269
- status: "pass",
270
- message: `Data directory exists: ${dataDir}`,
271
- details: { dataDir }
263
+ name: "extensions",
264
+ status: "warn",
265
+ message: "Extension installation state is unknown because no project path was resolved.",
266
+ details: { code: "PROJECT_PATH_REQUIRED", projectResolved: false },
272
267
  };
273
268
  }
274
- catch (error) {
275
- const nodeError = error;
276
- if (nodeError.code === "ENOENT") {
269
+ try {
270
+ const [enabled, manifest, physical] = await Promise.all([
271
+ readInstalledExtensions(projectPath),
272
+ readExtensionInstallManifest(projectPath),
273
+ checkExtensionBundles(projectPath),
274
+ ]);
275
+ const installedEntries = new Set(manifest.extensions.map((entry) => entry.extensionId));
276
+ const knownIds = new Set(physical.map((entry) => entry.extensionId));
277
+ const unknownInstalled = manifest.extensions.map((entry) => entry.extensionId).filter((id) => !knownIds.has(id));
278
+ const unknownEnabled = [...enabled].filter((id) => !knownIds.has(id));
279
+ const broken = physical.filter((entry) => (enabled.has(entry.extensionId) || installedEntries.has(entry.extensionId)) && !entry.installed);
280
+ const optional = physical.filter((entry) => !enabled.has(entry.extensionId) && !installedEntries.has(entry.extensionId));
281
+ const healthyInstalled = physical.filter((entry) => entry.installed).length;
282
+ if (unknownInstalled.length > 0 || unknownEnabled.length > 0 || broken.length > 0) {
283
+ const missingDependency = broken.some((entry) => entry.status === "missing_dependency");
284
+ const code = unknownInstalled.length > 0 ? "E_EXTENSION_UNKNOWN_INSTALLED"
285
+ : unknownEnabled.length > 0 ? "E_EXTENSION_STATE_INVALID"
286
+ : missingDependency ? "E_EXTENSION_DEPENDENCY_MISSING" : "E_EXTENSION_TARGET_PROJECTION_INVALID";
287
+ const nextActions = unknownInstalled.length > 0
288
+ ? ["Inspect unitycli.extensions.manifest.json and the matching extension directories.", "Remove unknown installed entries only after auditing their projected files."]
289
+ : unknownEnabled.length > 0
290
+ ? ["Inspect extensions.json and remove IDs that are not present in the CLI extension registry."]
291
+ : [...new Set(broken.flatMap((entry) => entry.nextActions))];
277
292
  return {
278
- name: "data_directory",
293
+ name: "extensions",
294
+ status: "fail",
295
+ message: `${broken.length + unknownInstalled.length + unknownEnabled.length} installed or enabled extension state(s) are broken.`,
296
+ details: {
297
+ code,
298
+ projectPath,
299
+ healthyInstalled,
300
+ optionalUninstalled: optional.length,
301
+ unknownInstalledCount: unknownInstalled.length,
302
+ unknownInstalled: unknownInstalled.slice(0, EXTENSION_DIAGNOSTIC_SAMPLE_LIMIT),
303
+ unknownInstalledTruncated: unknownInstalled.length > EXTENSION_DIAGNOSTIC_SAMPLE_LIMIT,
304
+ unknownEnabledCount: unknownEnabled.length,
305
+ unknownEnabled: unknownEnabled.slice(0, EXTENSION_DIAGNOSTIC_SAMPLE_LIMIT),
306
+ unknownEnabledTruncated: unknownEnabled.length > EXTENSION_DIAGNOSTIC_SAMPLE_LIMIT,
307
+ brokenCount: broken.length,
308
+ broken: broken.slice(0, EXTENSION_DIAGNOSTIC_SAMPLE_LIMIT).map(extensionDiagnostic),
309
+ brokenTruncated: broken.length > EXTENSION_DIAGNOSTIC_SAMPLE_LIMIT,
310
+ nextActions,
311
+ },
312
+ };
313
+ }
314
+ if (optional.length > 0) {
315
+ return {
316
+ name: "extensions",
279
317
  status: "warn",
280
- message: `Data directory does not exist yet: ${dataDir}. It will be created on first use.`,
281
- details: { dataDir }
318
+ message: `${optional.length} optional extension(s) are not installed; no installed or enabled extension is broken.`,
319
+ details: {
320
+ projectPath,
321
+ healthyInstalled,
322
+ optionalUninstalled: optional.length,
323
+ optional: optional.slice(0, EXTENSION_DIAGNOSTIC_SAMPLE_LIMIT).map((entry) => ({
324
+ extensionId: entry.extensionId,
325
+ status: entry.status,
326
+ missingPackageIds: entry.missingPackageIds,
327
+ })),
328
+ optionalTruncated: optional.length > EXTENSION_DIAGNOSTIC_SAMPLE_LIMIT,
329
+ },
282
330
  };
283
331
  }
284
332
  return {
285
- name: "data_directory",
286
- status: "fail",
287
- message: `Cannot access data directory: ${error instanceof Error ? error.message : String(error)}`
288
- };
289
- }
290
- }
291
- async function checkUnifiedDiscovery(flags, runtime, requestId, bridgeReachable) {
292
- if (!bridgeReachable && runtime.projectTools === undefined) {
293
- return {
294
- name: "unified_tool_discovery",
295
- status: "warn",
296
- message: "Tool discovery is unavailable because no Unity instance is connected.",
297
- details: { mode: "unavailable", toolCount: 0 }
333
+ name: "extensions",
334
+ status: "pass",
335
+ message: `All ${healthyInstalled} installed extension(s) passed physical and dependency checks.`,
336
+ details: { projectPath, healthyInstalled, optionalUninstalled: 0 },
298
337
  };
299
338
  }
300
- const discovery = await discoverProjectTools(flags, runtime, `${requestId}_project_tools`, { requireBridge: true });
301
- if (!discovery.ok) {
339
+ catch (error) {
340
+ const projected = projectDoctorError("doctor_extensions", error);
302
341
  return {
303
- name: "unified_tool_discovery",
304
- status: "warn",
305
- message: `Tool discovery failed: ${discovery.error?.code ?? "unknown"} — ${discovery.error?.message ?? "no message"}`,
306
- details: { mode: "error", toolCount: 0, errorCode: discovery.error?.code, errorMessage: discovery.error?.message }
342
+ name: "extensions",
343
+ status: "fail",
344
+ message: projected.message,
345
+ details: { code: projected.code, projectPath, nextActions: projected.nextActions },
307
346
  };
308
347
  }
309
- const localCount = builtinToolMetadata().length;
348
+ }
349
+ function extensionDiagnostic(entry) {
310
350
  return {
311
- name: "unified_tool_discovery",
312
- status: "pass",
313
- message: `The connected instance exposes ${localCount + discovery.tools.length} Tool descriptor(s).`,
314
- details: { mode: discovery.mode, localCount, liveCount: discovery.tools.length, toolCount: localCount + discovery.tools.length }
351
+ extensionId: entry.extensionId,
352
+ status: entry.status,
353
+ dependencyInstalled: entry.dependencyInstalled,
354
+ missingPackageIds: entry.missingPackageIds,
355
+ missingFileCount: entry.missingFiles.length,
356
+ changedFileCount: entry.changedFiles.length,
357
+ unknownFileCount: entry.unknownFiles.length,
358
+ nextActions: entry.nextActions,
315
359
  };
316
360
  }
317
- async function checkConfigIntegrity(readyInstances) {
318
- const issues = [];
319
- for (const instance of readyInstances) {
320
- if (instance.canonicalizationVersion !== 1)
321
- issues.push(`Instance ${instance.instanceId}: unsupported canonicalizationVersion.`);
322
- if (!/^unity-[0-9a-f]{64}$/.test(instance.instanceId))
323
- issues.push(`Instance ${instance.instanceId}: invalid deterministic instanceId.`);
324
- if (!instance.canonicalProjectPath)
325
- issues.push(`Instance ${instance.instanceId}: missing canonicalProjectPath.`);
326
- if (!instance.editorSessionId)
327
- issues.push(`Instance ${instance.instanceId}: missing editorSessionId.`);
328
- if (!instance.publishedAt)
329
- issues.push(`Instance ${instance.instanceId}: missing publishedAt.`);
361
+ async function checkDataDirectory(dataDir) {
362
+ try {
363
+ await access(dataDir);
364
+ if (!(await stat(dataDir)).isDirectory()) {
365
+ return { name: "data_directory", status: "fail", message: `Data path is not a directory: ${dataDir}`, details: { code: "E_DATA_DIRECTORY_INVALID" } };
366
+ }
367
+ return { name: "data_directory", status: "pass", message: `Data directory exists: ${dataDir}`, details: { dataDir } };
330
368
  }
331
- if (issues.length > 0) {
332
- return {
333
- name: "config_integrity",
334
- status: "warn",
335
- message: `${issues.length} configuration issue(s) found.`,
336
- details: { issues }
337
- };
369
+ catch (error) {
370
+ if (error.code === "ENOENT") {
371
+ return { name: "data_directory", status: "warn", message: `Data directory does not exist yet: ${dataDir}.`, details: { dataDir } };
372
+ }
373
+ return { name: "data_directory", status: "fail", message: `Cannot access data directory: ${errorMessage(error)}`, details: { code: "E_DATA_DIRECTORY_INVALID" } };
338
374
  }
339
- return {
340
- name: "config_integrity",
341
- status: "pass",
342
- message: readyInstances.length > 0
343
- ? `${readyInstances.length} instance(s) passed v3 identity checks.`
344
- : "No ready instances to check.",
345
- details: { checkedInstances: readyInstances.length }
346
- };
347
375
  }
348
376
  /** 旧 active-target/alias/aggregate 文件只报告,不解析、不迁移。 */
349
377
  async function checkObsoleteLocalState(dataDir) {
@@ -357,142 +385,145 @@ async function checkObsoleteLocalState(dataDir) {
357
385
  details: { code: "E_OBSOLETE_LOCAL_STATE", paths },
358
386
  };
359
387
  }
360
- async function checkExtensionAvailability(flags, runtime) {
361
- const resolvedFlags = await extensionAvailabilityFlags(flags, runtime);
362
- const projectPath = getString(resolvedFlags, "project");
363
- if (!projectPath) {
364
- return {
365
- name: "extension_availability",
366
- status: "warn",
367
- message: "No project path provided. Cannot resolve extension availability. Pass --project <path>.",
368
- details: { projectResolved: false }
369
- };
370
- }
371
- const registry = new ExtensionRegistry();
372
- let installedPackages;
373
- try {
374
- installedPackages = await readInstalledUnityPackages(projectPath);
375
- }
376
- catch {
377
- return {
378
- name: "extension_availability",
379
- status: "warn",
380
- message: `Cannot read Unity packages from project: ${projectPath}.`,
381
- details: { projectPath }
382
- };
383
- }
384
- const summary = extensionAvailabilitySummary(registry, installedPackages);
385
- const unavailableExtensions = summary.extensions.filter((e) => e.status === "unavailable");
386
- const unknownExtensions = summary.extensions.filter((e) => e.status === "unknown");
387
- if (unavailableExtensions.length > 0) {
388
- return {
389
- name: "extension_availability",
390
- status: "fail",
391
- message: `${unavailableExtensions.length} extension(s) have missing Unity package dependencies: ${unavailableExtensions.map((e) => e.name + " (missing: " + e.missingPackages.join(", ") + ")").join("; ")}.`,
392
- details: {
393
- projectPath,
394
- projectResolved: true,
395
- total: summary.total,
396
- available: summary.available,
397
- unavailable: summary.unavailable,
398
- unknown: summary.unknown,
399
- unavailableExtensions,
400
- unknownExtensions
401
- }
402
- };
403
- }
404
- if (unknownExtensions.length > 0) {
405
- return {
406
- name: "extension_availability",
407
- status: "warn",
408
- message: `${unknownExtensions.length} extension(s) could not be resolved (no installed packages data): ${unknownExtensions.map((e) => e.name).join(", ")}.`,
409
- details: {
410
- projectPath,
411
- projectResolved: true,
412
- total: summary.total,
413
- available: summary.available,
414
- unavailable: summary.unavailable,
415
- unknown: summary.unknown
416
- }
417
- };
388
+ function projectDoctorError(requestId, error) {
389
+ const selected = instanceSelectionFailure(requestId, error);
390
+ if (selected && !selected.ok)
391
+ return selected.error;
392
+ if (error instanceof SessionSupervisorError) {
393
+ const projected = failure(requestId, error.code, error.message, error.nextActions, undefined, {
394
+ transient: error.transient,
395
+ retryAfterMs: error.retryAfterMs,
396
+ });
397
+ if (!projected.ok)
398
+ return projected.error;
418
399
  }
400
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string"
401
+ ? error.code : "BRIDGE_CONNECTION_FAILED";
402
+ const nextActions = typeof error === "object" && error !== null && "nextActions" in error && Array.isArray(error.nextActions)
403
+ ? error.nextActions : ["Run unitycli instances list and inspect the selected Editor session."];
404
+ const projected = failure(requestId, code, errorMessage(error), nextActions);
405
+ if (!projected.ok)
406
+ return projected.error;
407
+ throw new Error("Doctor error projection unexpectedly succeeded.");
408
+ }
409
+ function failedLiveCheck(name, error) {
419
410
  return {
420
- name: "extension_availability",
421
- status: "pass",
422
- message: `All ${summary.total} extensions resolved: ${summary.available} available, ${summary.unavailable} unavailable, ${summary.unknown} unknown.`,
411
+ name,
412
+ status: error.transient ? "warn" : "fail",
413
+ message: `${error.code}: ${error.message}`,
423
414
  details: {
424
- projectPath,
425
- projectResolved: true,
426
- total: summary.total,
427
- available: summary.available,
428
- unavailable: summary.unavailable,
429
- unknown: summary.unknown,
430
- extensions: summary.extensions
431
- }
415
+ code: error.code,
416
+ transient: error.transient,
417
+ retryAfterMs: error.retryAfterMs,
418
+ nextActions: error.nextActions,
419
+ },
432
420
  };
433
421
  }
434
- function checkExtensionRegistry() {
435
- const extensions = builtinExtensionMetadata();
436
- const installed = extensions.filter((e) => e.status === "installed").length;
437
- const available = extensions.filter((e) => e.status === "available").length;
422
+ function skippedLiveCheck(name, code, message) {
423
+ return { name, status: "warn", message, details: { code } };
424
+ }
425
+ function invalidLiveCheck(name, message) {
426
+ return { name, status: "fail", message, details: { code: "E_BRIDGE_RESPONSE_INVALID" } };
427
+ }
428
+ function isDoctorCheck(value) {
429
+ return "status" in value && "name" in value;
430
+ }
431
+ function isNonNegativeInteger(value) {
432
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
433
+ }
434
+ /** catalog DTO 只接受唯一 published snapshot 的 aggregate,并校验 count parity 与固定 publication 边界。 */
435
+ function doctorCatalogData(value) {
436
+ const data = exactRecord(value, ["identity", "toolCount", "providerCounts", "publicationDiagnostics"]);
437
+ if (!data)
438
+ return undefined;
439
+ const providerCounts = exactRecord(data.providerCounts, ["builtIn", "extension", "projectInterface", "officialAttribute"]);
440
+ const publication = exactRecord(data.publicationDiagnostics, [
441
+ "assemblyCount", "providerAssemblyCount", "elapsedMilliseconds", "discoveryPassCount",
442
+ "providerMetadataReadCount", "aggregateSuccessLogCount",
443
+ ]);
444
+ const serialized = JSON.stringify(data);
445
+ const toolCount = data.toolCount;
446
+ if (!providerCounts || !publication || serialized === undefined
447
+ || Buffer.byteLength(serialized, "utf8") > MAX_DOCTOR_CATALOG_RESPONSE_BYTES
448
+ || data.identity !== DOCTOR_CATALOG_IDENTITY
449
+ || !isNonNegativeInteger(toolCount) || toolCount > MAX_DOCTOR_CATALOG_TOOLS)
450
+ return undefined;
451
+ if (!isNonNegativeInteger(providerCounts.builtIn) || !isNonNegativeInteger(providerCounts.extension)
452
+ || !isNonNegativeInteger(providerCounts.projectInterface) || !isNonNegativeInteger(providerCounts.officialAttribute))
453
+ return undefined;
454
+ const typedProviderCounts = {
455
+ builtIn: providerCounts.builtIn,
456
+ extension: providerCounts.extension,
457
+ projectInterface: providerCounts.projectInterface,
458
+ officialAttribute: providerCounts.officialAttribute,
459
+ };
460
+ if (Object.values(typedProviderCounts).some((count) => count > toolCount)
461
+ || Object.values(typedProviderCounts).reduce((sum, count) => sum + count, 0) !== toolCount
462
+ || !isNonNegativeInteger(publication.assemblyCount) || publication.assemblyCount > MAX_DOCTOR_CATALOG_ASSEMBLIES
463
+ || !isNonNegativeInteger(publication.providerAssemblyCount) || publication.providerAssemblyCount > publication.assemblyCount
464
+ || !isNonNegativeInteger(publication.elapsedMilliseconds)
465
+ || publication.discoveryPassCount !== 1
466
+ || publication.providerMetadataReadCount !== publication.providerAssemblyCount
467
+ || publication.aggregateSuccessLogCount !== 1)
468
+ return undefined;
469
+ const typedPublication = {
470
+ assemblyCount: publication.assemblyCount,
471
+ providerAssemblyCount: publication.providerAssemblyCount,
472
+ elapsedMilliseconds: publication.elapsedMilliseconds,
473
+ discoveryPassCount: publication.discoveryPassCount,
474
+ providerMetadataReadCount: publication.providerMetadataReadCount,
475
+ aggregateSuccessLogCount: publication.aggregateSuccessLogCount,
476
+ };
438
477
  return {
439
- name: "extension_registry",
440
- status: "pass",
441
- message: `Extension registry loaded: ${extensions.length} extension(s) (${installed} installed, ${available} available).`,
442
- details: { total: extensions.length, installed, available }
478
+ identity: DOCTOR_CATALOG_IDENTITY,
479
+ toolCount,
480
+ providerCounts: typedProviderCounts,
481
+ publicationDiagnostics: typedPublication,
443
482
  };
444
483
  }
484
+ function exactRecord(value, fields) {
485
+ const item = record(value);
486
+ if (!item)
487
+ return undefined;
488
+ const allowed = new Set(fields);
489
+ const keys = Object.keys(item);
490
+ return keys.length === fields.length && keys.every((key) => allowed.has(key)) ? item : undefined;
491
+ }
492
+ function record(value) {
493
+ return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
494
+ }
495
+ function errorMessage(error) {
496
+ return error instanceof Error ? error.message : String(error);
497
+ }
445
498
  function buildSuggestions(checks) {
446
499
  const suggestions = [];
447
500
  for (const check of checks) {
448
- if (check.status === "fail" || check.status === "warn") {
449
- switch (check.name) {
450
- case "instance_registry":
451
- if (check.status === "warn") {
452
- suggestions.push("Open a Unity project with the UnityCLI Bridge package installed.");
453
- suggestions.push("Run unitycli init package --project <UnityProject> to set up the Bridge.");
454
- }
455
- break;
456
- case "ready_instances":
457
- suggestions.push("Reopen the Unity Editor for the exact canonical project path if no session is active.");
458
- suggestions.push("Run unitycli instances list to inspect transitioning, blocked, offline, or conflicting claims.");
459
- break;
460
- case "bridge_connectivity":
461
- suggestions.push("Ensure Unity Editor is running with the UnityCLI Bridge package for the selected --instance or CWD project.");
462
- suggestions.push("Run unitycli instances list to inspect identity, session, blocker, and transition state.");
463
- break;
464
- case "project_tool_fingerprint":
465
- suggestions.push("Inspect unitycli doctor fingerprint details, then review project tool descriptor/handler code changes.");
466
- suggestions.push("Apply the reported rePinValue only after accepting the exact Tool change.");
467
- break;
468
- case "data_directory":
469
- suggestions.push("Run any unitycli command to auto-create the data directory.");
470
- break;
471
- case "config_integrity":
472
- suggestions.push("Re-initialize the canonical registry claim by restarting Unity Editor.");
473
- break;
474
- case "obsolete_local_state":
475
- suggestions.push("Delete only the reported obsolete config.json, aliases.json, or aggregate instances.json files after auditing them.");
476
- break;
477
- case "unified_tool_discovery":
478
- suggestions.push("Run unitycli tools group to see tool groups.");
479
- suggestions.push("Run unitycli tools group list <group> with a connected Unity instance to inspect its complete Tool descriptors.");
480
- break;
481
- case "extension_registry":
482
- suggestions.push("Run unitycli extensions list to see available extensions.");
483
- suggestions.push("Run unitycli extensions doctor to check extension status.");
484
- break;
485
- case "extension_availability":
486
- suggestions.push("Run 'unitycli extensions doctor --project <path>' for detailed extension diagnostics.");
487
- suggestions.push("Run 'unitycli extensions list --project <path>' to see all extensions and their status.");
488
- suggestions.push("Install missing Unity packages manually via Unity Package Manager or Packages/manifest.json, then run the matching 'unitycli extensions install <id> --project <path>'.");
489
- break;
490
- }
501
+ if (check.status === "pass")
502
+ continue;
503
+ switch (check.name) {
504
+ case "registry_health_identity":
505
+ suggestions.push("Run unitycli instances list and select the exact canonical project identity.");
506
+ break;
507
+ case "main_thread_pulse":
508
+ suggestions.push("Run unitycli tools run editor.state and wait for a fresh ready Editor pulse.");
509
+ break;
510
+ case "business_diagnostic":
511
+ suggestions.push("Use the business diagnostic error code and nextActions without treating it as a transport failure.");
512
+ break;
513
+ case "catalog_integrity":
514
+ suggestions.push("Fix the catalog publication failure, then let Unity compile and reload the domain.");
515
+ break;
516
+ case "compile":
517
+ suggestions.push("Fix the bounded compiler diagnostics or wait for compilation to finish.");
518
+ break;
519
+ case "extensions":
520
+ suggestions.push("Run unitycli extensions doctor --project <path> for installed extension details.");
521
+ break;
522
+ case "obsolete_local_state":
523
+ suggestions.push("Delete only the explicitly reported obsolete files after auditing them.");
524
+ break;
491
525
  }
492
526
  }
493
- if (suggestions.length === 0) {
494
- suggestions.push("All checks passed. No action needed.");
495
- }
496
- return suggestions;
527
+ return suggestions.length > 0 ? [...new Set(suggestions)] : ["All checks passed. No action needed."];
497
528
  }
498
529
  //# sourceMappingURL=doctor.js.map