nccgs 2.1.1 → 3.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (705) hide show
  1. package/CHANGELOG.md +76 -235
  2. package/LICENSE +1 -1
  3. package/README.md +87 -206
  4. package/THIRD_PARTY_NOTICES.md +6 -34
  5. package/bin/nccgs.mjs +96 -0
  6. package/cli/doctor.mjs +82 -0
  7. package/cli/feedback.mjs +13 -0
  8. package/cli/install.mjs +111 -0
  9. package/cli/release.mjs +59 -0
  10. package/cli/run.mjs +35 -0
  11. package/cli/spawn.mjs +20 -0
  12. package/cli/uninstall.mjs +43 -0
  13. package/cli/update-check.mjs +66 -0
  14. package/docs/EVIDENCE-VERIFICATION.md +22 -0
  15. package/package.json +23 -30
  16. package/runtime/core/diag.mjs +43 -0
  17. package/runtime/core/feedback-policy.mjs +54 -0
  18. package/runtime/core/fs.mjs +80 -0
  19. package/runtime/core/job-object.mjs +73 -0
  20. package/runtime/core/modes.mjs +87 -0
  21. package/runtime/core/process-tree.mjs +109 -0
  22. package/runtime/core/project.mjs +113 -0
  23. package/runtime/core/snapshot.mjs +114 -0
  24. package/runtime/hooks/common.mjs +22 -0
  25. package/runtime/hooks/done-guard.mjs +15 -0
  26. package/runtime/hooks/guard-mcp.mjs +8 -0
  27. package/runtime/hooks/guard-shell.mjs +16 -0
  28. package/runtime/hooks/guard-write.mjs +25 -0
  29. package/runtime/hooks/session-log.mjs +7 -0
  30. package/runtime/hooks/session-start.mjs +40 -0
  31. package/runtime/hooks/unity-transport.mjs +12 -0
  32. package/runtime/modes.json +39 -0
  33. package/runtime/task/cost.mjs +103 -0
  34. package/runtime/task/criteria.mjs +128 -0
  35. package/runtime/task/feedback.mjs +136 -0
  36. package/runtime/task/ledger.mjs +109 -0
  37. package/runtime/task/plan.mjs +69 -0
  38. package/runtime/task/project.mjs +79 -0
  39. package/runtime/task/report.mjs +252 -0
  40. package/runtime/task/task.mjs +238 -0
  41. package/runtime/unity/assets.mjs +51 -0
  42. package/runtime/unity/playtest.mjs +145 -0
  43. package/runtime/unity/unity.mjs +350 -0
  44. package/scripts/check-job-object.mjs +18 -0
  45. package/scripts/sync-unity-skills.mjs +35 -119
  46. package/template/CLAUDE.md +39 -0
  47. package/template/agents/fast-fix.md +9 -0
  48. package/template/agents/implementer.md +19 -0
  49. package/template/agents/playtester.md +15 -0
  50. package/template/agents/reviewer.md +19 -0
  51. package/template/agents/scout.md +9 -0
  52. package/template/ponytail/README.md +8 -0
  53. package/{.claude/nccgs/packages → template}/ponytail/engineering.md +10 -0
  54. package/template/protocols/evidence.md +52 -0
  55. package/template/protocols/playtest.md +45 -0
  56. package/template/protocols/review.md +34 -0
  57. package/template/protocols/unity-cli-notes.md +56 -0
  58. package/template/settings.fragment.json +33 -0
  59. package/template/skills/art-bible/SKILL.md +19 -0
  60. package/template/skills/asset-spec/SKILL.md +19 -0
  61. package/template/skills/brainstorm/SKILL.md +17 -0
  62. package/template/skills/feedback/SKILL.md +16 -0
  63. package/template/skills/map-systems/SKILL.md +25 -0
  64. package/template/skills/playtest/SKILL.md +17 -0
  65. package/template/skills/status/SKILL.md +18 -0
  66. package/template/skills/test/SKILL.md +34 -0
  67. package/template/skills/unity-foundation/SKILL.md +17 -0
  68. package/template/skills/vertical-slice/SKILL.md +17 -0
  69. package/template/skills/work/SKILL.md +65 -0
  70. package/tools/feedback-worker/README.md +12 -0
  71. package/tools/feedback-worker/worker.js +39 -0
  72. package/tools/feedback-worker/wrangler.toml +12 -0
  73. package/unity-skills/2d-pixel-perfect/SKILL.md +208 -0
  74. package/unity-skills/2d-pixel-perfect/references/api-reference.md +81 -0
  75. package/unity-skills/2d-pixel-perfect/references/camera-setup-builtin.cs +40 -0
  76. package/unity-skills/2d-pixel-perfect/references/camera-setup-urp.cs +49 -0
  77. package/unity-skills/2d-pixel-perfect/references/pipeline-detection.cs +50 -0
  78. package/unity-skills/2d-pixel-perfect/references/sprite-settings.cs +37 -0
  79. package/unity-skills/asset-transformer-toolkit/SECURITY.md +15 -0
  80. package/unity-skills/asset-transformer-toolkit/SKILL.md +39 -0
  81. package/unity-skills/asset-transformer-toolkit/references/create-importer.md +121 -0
  82. package/unity-skills/asset-transformer-toolkit/references/lods.md +61 -0
  83. package/unity-skills/asset-transformer-toolkit/references/rule-api.md +154 -0
  84. package/unity-skills/asset-transformer-toolkit/references/ruleblock-api.md +37 -0
  85. package/unity-skills/asset-transformer-toolkit/references/ruleset-api.md +138 -0
  86. package/unity-skills/asset-transformer-toolkit/references/rulesets-and-actions.md +227 -0
  87. package/unity-skills/audio-setup-mixers/SECURITY.md +16 -0
  88. package/unity-skills/audio-setup-mixers/SKILL.md +127 -0
  89. package/unity-skills/audio-setup-mixers/references/api.md +162 -0
  90. package/unity-skills/build-gtk/SKILL.md +242 -0
  91. package/unity-skills/build-gtk/references/graph-api.md +324 -0
  92. package/unity-skills/build-gtk/references/pitfalls.md +86 -0
  93. package/unity-skills/build-gtk/references/runtime-and-visualization.md +196 -0
  94. package/unity-skills/build-gtk/references/state-machine-api.md +286 -0
  95. package/unity-skills/build-live-game/SKILL.md +255 -0
  96. package/{.claude/skills → unity-skills}/build-live-game/references/achievements.md +132 -154
  97. package/{.claude/skills → unity-skills}/build-live-game/references/apis.md +10 -19
  98. package/{.claude/skills → unity-skills}/build-live-game/references/authentication.md +14 -20
  99. package/{.claude/skills → unity-skills}/build-live-game/references/battlepass.md +269 -225
  100. package/unity-skills/build-live-game/references/blueprints-and-samples.md +75 -0
  101. package/unity-skills/build-live-game/references/cloud-code-cloudbehaviours.md +1111 -0
  102. package/unity-skills/build-live-game/references/cloud-code-modules.md +1276 -0
  103. package/unity-skills/build-live-game/references/cloud-code.md +58 -0
  104. package/{.claude/skills → unity-skills}/build-live-game/references/cloud-save.md +15 -25
  105. package/{.claude/skills → unity-skills}/build-live-game/references/deployment.md +33 -18
  106. package/{.claude/skills → unity-skills}/build-live-game/references/player-account.md +31 -23
  107. package/{.claude/skills → unity-skills}/build-live-game/references/tooling.md +0 -56
  108. package/unity-skills/build-live-game/references/ugs-cli.md +86 -0
  109. package/unity-skills/craft-game-floors/SECURITY.md +23 -0
  110. package/unity-skills/craft-game-floors/SKILL.md +134 -0
  111. package/unity-skills/craft-game-floors/references/art-direction.md +85 -0
  112. package/unity-skills/craft-game-floors/references/camera-rigs.md +51 -0
  113. package/unity-skills/craft-game-floors/references/character-motion.md +35 -0
  114. package/unity-skills/craft-game-floors/references/game-audio.md +47 -0
  115. package/unity-skills/craft-game-floors/references/game-feel.md +68 -0
  116. package/unity-skills/craft-game-floors/references/game-shell.md +45 -0
  117. package/unity-skills/craft-game-floors/references/live-floor.md +35 -0
  118. package/unity-skills/craft-game-floors/references/placeholder-assets.md +35 -0
  119. package/unity-skills/craft-game-floors/references/scene-lighting.md +52 -0
  120. package/unity-skills/craft-game-floors/references/toon-shading.md +34 -0
  121. package/unity-skills/craft-game-floors/references/vfx.md +40 -0
  122. package/unity-skills/craft-game-floors/references/visual-scorecard.md +43 -0
  123. package/unity-skills/create-2d-physics/SECURITY.md +16 -0
  124. package/unity-skills/create-2d-physics/SKILL.md +128 -0
  125. package/unity-skills/create-2d-physics/references/2d-physics-core.md +155 -0
  126. package/unity-skills/create-2d-physics/references/legacy-2d-physics.md +45 -0
  127. package/unity-skills/create-tile-palette-or-rule-tiles/SKILL.md +12 -0
  128. package/unity-skills/create-tile-palette-or-rule-tiles/references/hexagonalruletile.md +71 -0
  129. package/unity-skills/create-tile-palette-or-rule-tiles/references/palette-create.md +38 -0
  130. package/unity-skills/create-tile-palette-or-rule-tiles/references/ruletile-createempty.md +35 -0
  131. package/unity-skills/create-tile-palette-or-rule-tiles/references/ruletile-createfromsegment.md +212 -0
  132. package/unity-skills/create-tile-palette-or-rule-tiles/references/ruletile.md +90 -0
  133. package/unity-skills/create-tile-palette-or-rule-tiles/scripts/CreatePaletteTemplate.cs +148 -0
  134. package/unity-skills/create-tile-palette-or-rule-tiles/scripts/ManualWorkflowExample.cs +63 -0
  135. package/unity-skills/create-tile-palette-or-rule-tiles/scripts/RuleTileGenerator.cs +61 -0
  136. package/unity-skills/create-tile-palette-or-rule-tiles/scripts/TilemapRuleTileCreateFromSegment.cs +225 -0
  137. package/unity-skills/generate-editor-search-query/SECURITY.md +17 -0
  138. package/unity-skills/generate-editor-search-query/SKILL.md +117 -0
  139. package/unity-skills/generate-editor-search-query/references/open-search-window.md +70 -0
  140. package/unity-skills/generate-editor-search-query/references/query-patterns.md +124 -0
  141. package/{.claude/skills → unity-skills}/implement-in-app-purchases/SKILL.md +1 -2
  142. package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/api-notes.md +25 -28
  143. package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/path-convert-native-google-billing.md +1 -1
  144. package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/path-implement-iap-d2c.md +40 -5
  145. package/{.claude/skills → unity-skills}/initialize-ai-navigation/SKILL.md +4 -4
  146. package/{.claude/skills → unity-skills}/initialize-ai-navigation/references/navigation-system.md +3 -3
  147. package/{.claude/skills → unity-skills}/levelplay-unity-integration/CHANGELOG.md +12 -0
  148. package/{.claude/skills → unity-skills}/levelplay-unity-integration/README.md +9 -5
  149. package/unity-skills/levelplay-unity-integration/SKILL.md +442 -0
  150. package/{.claude/skills → unity-skills}/levelplay-unity-integration/references/banner-api.md +15 -8
  151. package/{.claude/skills → unity-skills}/levelplay-unity-integration/references/best-practices.md +604 -536
  152. package/unity-skills/levelplay-unity-integration/references/dependency-resolution.md +94 -0
  153. package/{.claude/skills → unity-skills}/levelplay-unity-integration/references/ilrd-api.md +2 -2
  154. package/{.claude/skills → unity-skills}/levelplay-unity-integration/references/initialization-api.md +221 -0
  155. package/{.claude/skills → unity-skills}/levelplay-unity-integration/references/interstitial-api.md +11 -3
  156. package/{.claude/skills → unity-skills}/levelplay-unity-integration/references/ios-setup.md +6 -34
  157. package/{.claude/skills → unity-skills}/levelplay-unity-integration/references/migration-sdk-9.md +1 -1
  158. package/{.claude/skills → unity-skills}/levelplay-unity-integration/references/privacy-settings.md +14 -29
  159. package/{.claude/skills → unity-skills}/levelplay-unity-integration/references/rewarded-api.md +20 -4
  160. package/unity-skills/levelplay-unity-integration/references/testing-and-validation.md +222 -0
  161. package/unity-skills/levelplay-unity-integration/references/troubleshooting.md +99 -0
  162. package/unity-skills/localization/SKILL.md +347 -0
  163. package/{.claude/skills → unity-skills}/localization/references/api-notes.md +4 -0
  164. package/unity-skills/localization/resources/L10nBatchProcessor.cs +191 -0
  165. package/unity-skills/manage-sprite-atlas/SKILL.md +351 -0
  166. package/unity-skills/manage-sprite-atlas/references/addressables-delivery.md +58 -0
  167. package/unity-skills/manage-sprite-atlas/references/api.md +382 -0
  168. package/unity-skills/manage-sprite-atlas/references/best-practices.md +183 -0
  169. package/unity-skills/manage-sprite-atlas/references/common-errors.md +123 -0
  170. package/unity-skills/manage-sprite-atlas/references/custom-packing.md +147 -0
  171. package/unity-skills/manage-sprite-atlas/references/manual-authoring.md +39 -0
  172. package/unity-skills/manage-sprite-atlas/resources/authoringvsruntime.cs +176 -0
  173. package/unity-skills/manage-sprite-atlas/resources/buildaddressablespostprocess.cs +59 -0
  174. package/unity-skills/manage-sprite-atlas/resources/cacheloadedatlases.cs +145 -0
  175. package/unity-skills/manage-sprite-atlas/resources/cleanupbatchoperations.cs +119 -0
  176. package/unity-skills/manage-sprite-atlas/resources/createandconfiguremasteratlas.cs +57 -0
  177. package/unity-skills/manage-sprite-atlas/resources/custompackersetup.cs +88 -0
  178. package/unity-skills/manage-sprite-atlas/resources/deprecatedmethods.cs +142 -0
  179. package/unity-skills/manage-sprite-atlas/resources/dontmodifywithoutsaving.cs +120 -0
  180. package/unity-skills/manage-sprite-atlas/resources/dontpackinruntimebuilds.cs +190 -0
  181. package/unity-skills/manage-sprite-atlas/resources/dontscriptspriteatlasineditor.cs +114 -0
  182. package/unity-skills/manage-sprite-atlas/resources/editorconfiguration.cs +95 -0
  183. package/unity-skills/manage-sprite-atlas/resources/enablespritepacking.cs +172 -0
  184. package/unity-skills/manage-sprite-atlas/resources/gridspritepacker.cs +50 -0
  185. package/unity-skills/manage-sprite-atlas/resources/handlelatebinding.cs +147 -0
  186. package/unity-skills/manage-sprite-atlas/resources/handlenullcases.cs +141 -0
  187. package/unity-skills/manage-sprite-atlas/resources/migratev1tov2.cs +335 -0
  188. package/unity-skills/manage-sprite-atlas/resources/multipagepacker.cs +80 -0
  189. package/unity-skills/manage-sprite-atlas/resources/optimizetexturesettings.cs +191 -0
  190. package/unity-skills/manage-sprite-atlas/resources/packerdebugging.cs +158 -0
  191. package/unity-skills/manage-sprite-atlas/resources/packtransform.cs +51 -0
  192. package/unity-skills/manage-sprite-atlas/resources/platformnames.cs +127 -0
  193. package/unity-skills/manage-sprite-atlas/resources/queryspritesefficiently.cs +222 -0
  194. package/unity-skills/manage-sprite-atlas/resources/registercallbacks.cs +152 -0
  195. package/unity-skills/manage-sprite-atlas/resources/runtimeatlasloader.cs +15 -0
  196. package/unity-skills/manage-sprite-atlas/resources/runtimespriteaccess.cs +19 -0
  197. package/unity-skills/manage-sprite-atlas/resources/savespriteatlasasset.cs +107 -0
  198. package/unity-skills/manage-sprite-atlas/resources/scriptablepacker.cs +81 -0
  199. package/unity-skills/manage-sprite-atlas/resources/setincludeinbuild.cs +164 -0
  200. package/unity-skills/manage-sprite-atlas/resources/sizeoptimizedpacker.cs +88 -0
  201. package/unity-skills/manage-sprite-atlas/resources/spriteatlaslatebinding.cs +123 -0
  202. package/unity-skills/manage-sprite-atlas/resources/spriteatlasprebuildgenerator.cs +219 -0
  203. package/unity-skills/manage-sprite-atlas/resources/spritedata.cs +35 -0
  204. package/unity-skills/manage-sprite-atlas/resources/spritepack.cs +39 -0
  205. package/unity-skills/manage-sprite-atlas/resources/texturedata.cs +15 -0
  206. package/unity-skills/manage-sprite-atlas/resources/updatebuildscripts.cs +357 -0
  207. package/unity-skills/manage-sprite-atlas/resources/updatesettingsconfiguration.cs +135 -0
  208. package/unity-skills/manage-sprite-atlas/resources/usevariantswisely.cs +179 -0
  209. package/unity-skills/manage-sprite-atlas/resources/variantatlas.cs +39 -0
  210. package/unity-skills/manifest.json +1155 -0
  211. package/unity-skills/migrate-birp-to-urp/SECURITY.md +16 -0
  212. package/unity-skills/migrate-birp-to-urp/SKILL.md +543 -0
  213. package/unity-skills/migrate-birp-to-urp/references/capturing-the-editor.md +66 -0
  214. package/unity-skills/migrate-birp-to-urp/references/complex-shader-situations.md +272 -0
  215. package/unity-skills/migrate-birp-to-urp/references/custom-shader-triage.md +123 -0
  216. package/unity-skills/migrate-birp-to-urp/references/implementation-patterns.md +529 -0
  217. package/unity-skills/migrate-birp-to-urp/references/migration-workflow.md +265 -0
  218. package/unity-skills/migrate-birp-to-urp/references/quality-settings-map.md +69 -0
  219. package/unity-skills/new-unity-project/SECURITY.md +26 -0
  220. package/unity-skills/new-unity-project/SKILL.md +386 -0
  221. package/unity-skills/new-unity-project/references/skill-setup.md +66 -0
  222. package/unity-skills/optimize-audio/SECURITY.md +16 -0
  223. package/{.claude/skills → unity-skills}/optimize-audio/SKILL.md +17 -17
  224. package/{.claude/skills → unity-skills}/optimize-audio/resources/audio-import-api.md +8 -5
  225. package/{.claude/skills → unity-skills}/optimize-text-mesh-pro/SKILL.md +1 -11
  226. package/unity-skills/optimize-web/SECURITY.md +16 -0
  227. package/{.claude/skills → unity-skills}/optimize-web/SKILL.md +164 -27
  228. package/unity-skills/optimize-web/scripts/WebOptimizer.cs +46 -0
  229. package/unity-skills/optimize-web/scripts/toktx-examples.sh +11 -0
  230. package/{.claude/skills → unity-skills}/physics-3d-collision/SKILL.md +2 -2
  231. package/unity-skills/physics-3d-collision/references/troubleshooting.md +8 -0
  232. package/unity-skills/project-auditor-fixes/SKILL.md +64 -0
  233. package/{.claude/skills → unity-skills}/setup-multiplayer-services/SKILL.md +1 -4
  234. package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/SKILL.md +1 -1
  235. package/{.claude/skills → unity-skills}/shader-graph-create-custom-node/SKILL.md +1 -1
  236. package/unity-skills/sprite-editor/SECURITY.md +16 -0
  237. package/{.claude/skills → unity-skills}/sprite-editor/SKILL.md +9 -9
  238. package/{.claude/skills → unity-skills}/sprite-editor/references/templates.md +1 -1
  239. package/unity-skills/sprite-segment-3x3grid/SKILL.md +136 -0
  240. package/unity-skills/sprite-segment-3x3grid/scripts/SpriteGridAnalysis.cs +119 -0
  241. package/{.claude/skills → unity-skills}/ui/SKILL.md +1 -1
  242. package/{.claude/skills → unity-skills}/ui-imgui/SKILL.md +1 -7
  243. package/{.claude/skills → unity-skills}/ui-ugui/SKILL.md +3 -9
  244. package/{.claude/skills → unity-skills}/ui-uitk/SKILL.md +8 -14
  245. package/{.claude/skills → unity-skills}/ui-uitk/references/custom-elements.md +1 -1
  246. package/unity-skills/unity-cli/CHANGELOG.md +515 -0
  247. package/unity-skills/unity-cli/SECURITY.md +68 -0
  248. package/unity-skills/unity-cli/SKILL.md +723 -0
  249. package/unity-skills/unity-cli/references/auth-license-cloud.md +318 -0
  250. package/unity-skills/unity-cli/references/build-run-test.md +555 -0
  251. package/unity-skills/unity-cli/references/cloud-automation.md +736 -0
  252. package/{.claude/skills → unity-skills}/unity-cli/references/collaboration.md +166 -105
  253. package/unity-skills/unity-cli/references/config-hub.md +239 -0
  254. package/unity-skills/unity-cli/references/diagnostics-maintenance.md +720 -0
  255. package/unity-skills/unity-cli/references/docs-workflow.md +179 -0
  256. package/unity-skills/unity-cli/references/editors-install.md +476 -0
  257. package/unity-skills/unity-cli/references/integration-advanced.md +1130 -0
  258. package/unity-skills/unity-cli/references/playmode-verification-loop.md +279 -0
  259. package/unity-skills/unity-cli/references/projects-templates.md +996 -0
  260. package/unity-skills/unity-cli/references/version-control.md +612 -0
  261. package/{.claude/skills → unity-skills}/unity-package-management/SKILL.md +60 -12
  262. package/{.claude/skills → unity-skills}/unity-package-management/references/select-packages.md +39 -21
  263. package/unity-skills/unity-pipeline/SECURITY.md +17 -0
  264. package/unity-skills/unity-pipeline/SKILL.md +335 -0
  265. package/unity-skills/urp-postprocessing/SECURITY.md +16 -0
  266. package/{.claude/skills → unity-skills}/urp-postprocessing/SKILL.md +12 -12
  267. package/{.claude/skills → unity-skills}/urp-postprocessing/references/code-templates.md +39 -8
  268. package/{.claude/skills → unity-skills}/validate-urp-render-graph-renderer-feature/SKILL.md +1 -1
  269. package/.claude/agents/nccgs-adversarial-reviewer.md +0 -43
  270. package/.claude/agents/nccgs-engine-programmer.md +0 -27
  271. package/.claude/agents/nccgs-fast-implementer.md +0 -30
  272. package/.claude/agents/nccgs-game-designer.md +0 -26
  273. package/.claude/agents/nccgs-gameplay-programmer.md +0 -26
  274. package/.claude/agents/nccgs-qa-engineer.md +0 -27
  275. package/.claude/agents/nccgs-task-scout.md +0 -24
  276. package/.claude/agents/nccgs-ui-programmer.md +0 -27
  277. package/.claude/agents/nccgs-unity-build-specialist.md +0 -27
  278. package/.claude/agents/nccgs-unity-implementer.md +0 -28
  279. package/.claude/agents/nccgs-unity-systems-specialist.md +0 -27
  280. package/.claude/agents/nccgs-unity-ui-specialist.md +0 -27
  281. package/.claude/agents/nccgs-verification-engineer.md +0 -27
  282. package/.claude/nccgs/MEMORY.md +0 -66
  283. package/.claude/nccgs/THIRD_PARTY_NOTICES.md +0 -29
  284. package/.claude/nccgs/VERSION +0 -1
  285. package/.claude/nccgs/agent-library/nccgs-accessibility-specialist.md +0 -25
  286. package/.claude/nccgs/agent-library/nccgs-adversarial-reviewer.md +0 -41
  287. package/.claude/nccgs/agent-library/nccgs-ai-programmer.md +0 -25
  288. package/.claude/nccgs/agent-library/nccgs-analytics-engineer.md +0 -25
  289. package/.claude/nccgs/agent-library/nccgs-art-direction-lead.md +0 -25
  290. package/.claude/nccgs/agent-library/nccgs-audio-direction-lead.md +0 -25
  291. package/.claude/nccgs/agent-library/nccgs-creative-director.md +0 -25
  292. package/.claude/nccgs/agent-library/nccgs-documentation-manager.md +0 -25
  293. package/.claude/nccgs/agent-library/nccgs-economy-designer.md +0 -25
  294. package/.claude/nccgs/agent-library/nccgs-engine-programmer.md +0 -25
  295. package/.claude/nccgs/agent-library/nccgs-fast-implementer.md +0 -28
  296. package/.claude/nccgs/agent-library/nccgs-game-design-lead.md +0 -25
  297. package/.claude/nccgs/agent-library/nccgs-game-designer.md +0 -24
  298. package/.claude/nccgs/agent-library/nccgs-gameplay-programmer.md +0 -24
  299. package/.claude/nccgs/agent-library/nccgs-level-designer.md +0 -25
  300. package/.claude/nccgs/agent-library/nccgs-live-ops-designer.md +0 -25
  301. package/.claude/nccgs/agent-library/nccgs-localization-lead.md +0 -25
  302. package/.claude/nccgs/agent-library/nccgs-narrative-lead.md +0 -25
  303. package/.claude/nccgs/agent-library/nccgs-network-programmer.md +0 -25
  304. package/.claude/nccgs/agent-library/nccgs-performance-analyst.md +0 -25
  305. package/.claude/nccgs/agent-library/nccgs-producer.md +0 -25
  306. package/.claude/nccgs/agent-library/nccgs-production-coordinator.md +0 -22
  307. package/.claude/nccgs/agent-library/nccgs-programming-lead.md +0 -25
  308. package/.claude/nccgs/agent-library/nccgs-prototyper.md +0 -25
  309. package/.claude/nccgs/agent-library/nccgs-qa-engineer.md +0 -25
  310. package/.claude/nccgs/agent-library/nccgs-qa-lead.md +0 -25
  311. package/.claude/nccgs/agent-library/nccgs-release-engineer.md +0 -25
  312. package/.claude/nccgs/agent-library/nccgs-release-lead.md +0 -25
  313. package/.claude/nccgs/agent-library/nccgs-security-engineer.md +0 -25
  314. package/.claude/nccgs/agent-library/nccgs-sound-designer.md +0 -25
  315. package/.claude/nccgs/agent-library/nccgs-systems-designer.md +0 -25
  316. package/.claude/nccgs/agent-library/nccgs-task-scout.md +0 -22
  317. package/.claude/nccgs/agent-library/nccgs-technical-architect.md +0 -25
  318. package/.claude/nccgs/agent-library/nccgs-technical-artist.md +0 -25
  319. package/.claude/nccgs/agent-library/nccgs-technical-director.md +0 -25
  320. package/.claude/nccgs/agent-library/nccgs-tools-programmer.md +0 -25
  321. package/.claude/nccgs/agent-library/nccgs-ui-programmer.md +0 -25
  322. package/.claude/nccgs/agent-library/nccgs-unity-build-specialist.md +0 -25
  323. package/.claude/nccgs/agent-library/nccgs-unity-content-specialist.md +0 -25
  324. package/.claude/nccgs/agent-library/nccgs-unity-implementer.md +0 -26
  325. package/.claude/nccgs/agent-library/nccgs-unity-rendering-specialist.md +0 -25
  326. package/.claude/nccgs/agent-library/nccgs-unity-systems-specialist.md +0 -25
  327. package/.claude/nccgs/agent-library/nccgs-unity-ui-specialist.md +0 -25
  328. package/.claude/nccgs/agent-library/nccgs-ux-designer.md +0 -25
  329. package/.claude/nccgs/agent-library/nccgs-verification-engineer.md +0 -25
  330. package/.claude/nccgs/agent-library/nccgs-world-builder.md +0 -25
  331. package/.claude/nccgs/agent-library/nccgs-writer.md +0 -25
  332. package/.claude/nccgs/constitution.md +0 -187
  333. package/.claude/nccgs/hooks/agent-audit.mjs +0 -16
  334. package/.claude/nccgs/hooks/closure-guard.mjs +0 -59
  335. package/.claude/nccgs/hooks/common.mjs +0 -42
  336. package/.claude/nccgs/hooks/post-compact.mjs +0 -1
  337. package/.claude/nccgs/hooks/pre-compact.mjs +0 -8
  338. package/.claude/nccgs/hooks/protect-git.mjs +0 -22
  339. package/.claude/nccgs/hooks/protect-write.mjs +0 -17
  340. package/.claude/nccgs/hooks/runtime-observation.mjs +0 -11
  341. package/.claude/nccgs/hooks/session-start.mjs +0 -29
  342. package/.claude/nccgs/hooks/session-stop.mjs +0 -7
  343. package/.claude/nccgs/hooks/unity-transport-guard.mjs +0 -11
  344. package/.claude/nccgs/packages/ponytail/README.md +0 -46
  345. package/.claude/nccgs/packages/ponytail/manifest.json +0 -24
  346. package/.claude/nccgs/pipeline.json +0 -260
  347. package/.claude/nccgs/protocols/agent-contract.md +0 -101
  348. package/.claude/nccgs/protocols/astra-claude.md +0 -111
  349. package/.claude/nccgs/protocols/bounded-repair.md +0 -246
  350. package/.claude/nccgs/protocols/context-packets.md +0 -23
  351. package/.claude/nccgs/protocols/delivery-acceptance.md +0 -105
  352. package/.claude/nccgs/protocols/design-lifecycle.md +0 -139
  353. package/.claude/nccgs/protocols/editor-session-evidence.md +0 -52
  354. package/.claude/nccgs/protocols/evidence.md +0 -31
  355. package/.claude/nccgs/protocols/model-routing.md +0 -46
  356. package/.claude/nccgs/protocols/orchestration.md +0 -74
  357. package/.claude/nccgs/protocols/review-handoff.md +0 -101
  358. package/.claude/nccgs/protocols/shared-workflow-context.md +0 -33
  359. package/.claude/nccgs/protocols/standalone-gates.md +0 -329
  360. package/.claude/nccgs/protocols/standalone-pilot.md +0 -144
  361. package/.claude/nccgs/protocols/standalone.md +0 -207
  362. package/.claude/nccgs/protocols/task-autonomy.md +0 -55
  363. package/.claude/nccgs/protocols/task-gates.md +0 -144
  364. package/.claude/nccgs/protocols/task-slices.md +0 -106
  365. package/.claude/nccgs/protocols/unity-boundary.md +0 -31
  366. package/.claude/nccgs/routing.generated.md +0 -49
  367. package/.claude/nccgs/settings.fragment.json +0 -213
  368. package/.claude/nccgs/studio.json +0 -472
  369. package/.claude/nccgs/tools/approve-overrides.mjs +0 -81
  370. package/.claude/nccgs/tools/autonomy.mjs +0 -40
  371. package/.claude/nccgs/tools/command-process.mjs +0 -50
  372. package/.claude/nccgs/tools/command-tokens.mjs +0 -22
  373. package/.claude/nccgs/tools/compile-policy.mjs +0 -121
  374. package/.claude/nccgs/tools/configure-models.mjs +0 -96
  375. package/.claude/nccgs/tools/criteria.mjs +0 -121
  376. package/.claude/nccgs/tools/delivery-budget.mjs +0 -52
  377. package/.claude/nccgs/tools/design-readiness.mjs +0 -158
  378. package/.claude/nccgs/tools/editor-session-evidence.mjs +0 -66
  379. package/.claude/nccgs/tools/evidence.mjs +0 -153
  380. package/.claude/nccgs/tools/gates.mjs +0 -297
  381. package/.claude/nccgs/tools/handoff.mjs +0 -51
  382. package/.claude/nccgs/tools/hook-transport.mjs +0 -34
  383. package/.claude/nccgs/tools/inputs.mjs +0 -108
  384. package/.claude/nccgs/tools/installation-guard.mjs +0 -55
  385. package/.claude/nccgs/tools/integrity.mjs +0 -69
  386. package/.claude/nccgs/tools/lifecycle.mjs +0 -542
  387. package/.claude/nccgs/tools/native-completion.mjs +0 -30
  388. package/.claude/nccgs/tools/orchestration.mjs +0 -680
  389. package/.claude/nccgs/tools/paths.mjs +0 -60
  390. package/.claude/nccgs/tools/policy.mjs +0 -107
  391. package/.claude/nccgs/tools/process-lock.mjs +0 -74
  392. package/.claude/nccgs/tools/repair-packet.mjs +0 -106
  393. package/.claude/nccgs/tools/returned-handoff.mjs +0 -47
  394. package/.claude/nccgs/tools/review-budget.mjs +0 -171
  395. package/.claude/nccgs/tools/review-handoff.mjs +0 -66
  396. package/.claude/nccgs/tools/runtime.mjs +0 -71
  397. package/.claude/nccgs/tools/session-budget.mjs +0 -67
  398. package/.claude/nccgs/tools/shared-context-guard.mjs +0 -60
  399. package/.claude/nccgs/tools/shared-context.mjs +0 -25
  400. package/.claude/nccgs/tools/task-slices.mjs +0 -147
  401. package/.claude/nccgs/tools/task.mjs +0 -145
  402. package/.claude/nccgs/tools/tool-guard.mjs +0 -120
  403. package/.claude/nccgs/tools/unity-repair.mjs +0 -128
  404. package/.claude/nccgs/tools/unity-transport.mjs +0 -170
  405. package/.claude/nccgs/tools/verification-jobs.mjs +0 -171
  406. package/.claude/nccgs/tools/verification-worker.mjs +0 -71
  407. package/.claude/nccgs/tools/windows-compiler-server.cs +0 -163
  408. package/.claude/nccgs/tools/windows-job.cs +0 -103
  409. package/.claude/nccgs/unity-skills-manifest.json +0 -727
  410. package/.claude/nccgs/workflow-catalog.json +0 -654
  411. package/.claude/rules/nccgs-canonical-docs.md +0 -19
  412. package/.claude/rules/nccgs-editor-tools.md +0 -12
  413. package/.claude/rules/nccgs-localization.md +0 -12
  414. package/.claude/rules/nccgs-networking.md +0 -13
  415. package/.claude/rules/nccgs-performance.md +0 -14
  416. package/.claude/rules/nccgs-rendering.md +0 -16
  417. package/.claude/rules/nccgs-security.md +0 -13
  418. package/.claude/rules/nccgs-tests.md +0 -18
  419. package/.claude/rules/nccgs-ui.md +0 -14
  420. package/.claude/rules/nccgs-unity-assets.md +0 -21
  421. package/.claude/rules/nccgs-unity-code.md +0 -22
  422. package/.claude/skills/accessibility-review/SKILL.md +0 -22
  423. package/.claude/skills/accessibility-review/references/astra.md +0 -17
  424. package/.claude/skills/accessibility-review/references/legacy.md +0 -7
  425. package/.claude/skills/architecture-decision/SKILL.md +0 -22
  426. package/.claude/skills/architecture-decision/references/astra.md +0 -12
  427. package/.claude/skills/architecture-decision/references/legacy.md +0 -7
  428. package/.claude/skills/asset-audit/SKILL.md +0 -22
  429. package/.claude/skills/asset-audit/references/astra.md +0 -17
  430. package/.claude/skills/asset-audit/references/legacy.md +0 -7
  431. package/.claude/skills/audit/SKILL.md +0 -22
  432. package/.claude/skills/audit/references/astra.md +0 -17
  433. package/.claude/skills/audit/references/dimensions.md +0 -46
  434. package/.claude/skills/audit/references/legacy.md +0 -9
  435. package/.claude/skills/balance-review/SKILL.md +0 -22
  436. package/.claude/skills/balance-review/references/astra.md +0 -17
  437. package/.claude/skills/balance-review/references/legacy.md +0 -7
  438. package/.claude/skills/bug-triage/SKILL.md +0 -22
  439. package/.claude/skills/bug-triage/references/astra.md +0 -27
  440. package/.claude/skills/bug-triage/references/legacy.md +0 -7
  441. package/.claude/skills/build-live-game/SKILL.md +0 -317
  442. package/.claude/skills/build-live-game/references/cloud-code.md +0 -563
  443. package/.claude/skills/closure/SKILL.md +0 -22
  444. package/.claude/skills/closure/references/astra.md +0 -15
  445. package/.claude/skills/closure/references/legacy.md +0 -7
  446. package/.claude/skills/compatibility-review/SKILL.md +0 -22
  447. package/.claude/skills/compatibility-review/references/astra.md +0 -17
  448. package/.claude/skills/compatibility-review/references/legacy.md +0 -7
  449. package/.claude/skills/context-pack/SKILL.md +0 -22
  450. package/.claude/skills/context-pack/references/astra.md +0 -12
  451. package/.claude/skills/context-pack/references/legacy.md +0 -7
  452. package/.claude/skills/dependency-review/SKILL.md +0 -22
  453. package/.claude/skills/dependency-review/references/astra.md +0 -17
  454. package/.claude/skills/dependency-review/references/legacy.md +0 -7
  455. package/.claude/skills/design/SKILL.md +0 -24
  456. package/.claude/skills/design/references/astra.md +0 -12
  457. package/.claude/skills/design/references/legacy.md +0 -13
  458. package/.claude/skills/design-review/SKILL.md +0 -24
  459. package/.claude/skills/design-review/references/astra.md +0 -17
  460. package/.claude/skills/design-review/references/legacy.md +0 -7
  461. package/.claude/skills/evidence-review/SKILL.md +0 -22
  462. package/.claude/skills/evidence-review/references/astra.md +0 -17
  463. package/.claude/skills/evidence-review/references/legacy.md +0 -7
  464. package/.claude/skills/hotfix/SKILL.md +0 -22
  465. package/.claude/skills/hotfix/references/astra.md +0 -30
  466. package/.claude/skills/hotfix/references/legacy.md +0 -7
  467. package/.claude/skills/incident-recovery/SKILL.md +0 -22
  468. package/.claude/skills/incident-recovery/references/astra.md +0 -27
  469. package/.claude/skills/incident-recovery/references/legacy.md +0 -7
  470. package/.claude/skills/levelplay-unity-integration/SKILL.md +0 -1126
  471. package/.claude/skills/localization/SKILL.md +0 -135
  472. package/.claude/skills/localization/resources/L10nBatchProcessor.cs +0 -69
  473. package/.claude/skills/localize-game/SKILL.md +0 -22
  474. package/.claude/skills/localize-game/references/astra.md +0 -27
  475. package/.claude/skills/localize-game/references/legacy.md +0 -7
  476. package/.claude/skills/migrate-project/SKILL.md +0 -22
  477. package/.claude/skills/migrate-project/references/astra.md +0 -30
  478. package/.claude/skills/migrate-project/references/legacy.md +0 -19
  479. package/.claude/skills/migrate-project/references/procedure.md +0 -96
  480. package/.claude/skills/milestone-review/SKILL.md +0 -22
  481. package/.claude/skills/milestone-review/references/astra.md +0 -12
  482. package/.claude/skills/milestone-review/references/legacy.md +0 -7
  483. package/.claude/skills/nccgs-code-review/SKILL.md +0 -22
  484. package/.claude/skills/nccgs-code-review/references/astra.md +0 -21
  485. package/.claude/skills/nccgs-code-review/references/legacy.md +0 -7
  486. package/.claude/skills/new-unity-project/SKILL.md +0 -179
  487. package/.claude/skills/performance-audit/SKILL.md +0 -22
  488. package/.claude/skills/performance-audit/references/astra.md +0 -17
  489. package/.claude/skills/performance-audit/references/legacy.md +0 -7
  490. package/.claude/skills/physics-3d-collision/references/troubleshooting.md +0 -41
  491. package/.claude/skills/plan-feature/SKILL.md +0 -22
  492. package/.claude/skills/plan-feature/references/astra.md +0 -12
  493. package/.claude/skills/plan-feature/references/legacy.md +0 -7
  494. package/.claude/skills/playtest/SKILL.md +0 -22
  495. package/.claude/skills/playtest/references/astra.md +0 -31
  496. package/.claude/skills/playtest/references/legacy.md +0 -7
  497. package/.claude/skills/project-stage/SKILL.md +0 -22
  498. package/.claude/skills/project-stage/references/astra.md +0 -12
  499. package/.claude/skills/project-stage/references/legacy.md +0 -7
  500. package/.claude/skills/prototype-feature/SKILL.md +0 -22
  501. package/.claude/skills/prototype-feature/references/astra.md +0 -31
  502. package/.claude/skills/prototype-feature/references/legacy.md +0 -7
  503. package/.claude/skills/qa-plan/SKILL.md +0 -22
  504. package/.claude/skills/qa-plan/references/astra.md +0 -12
  505. package/.claude/skills/qa-plan/references/legacy.md +0 -7
  506. package/.claude/skills/release/SKILL.md +0 -22
  507. package/.claude/skills/release/references/astra.md +0 -30
  508. package/.claude/skills/release/references/legacy.md +0 -11
  509. package/.claude/skills/release-readiness/SKILL.md +0 -22
  510. package/.claude/skills/release-readiness/references/astra.md +0 -17
  511. package/.claude/skills/release-readiness/references/legacy.md +0 -7
  512. package/.claude/skills/retrospective/SKILL.md +0 -22
  513. package/.claude/skills/retrospective/references/astra.md +0 -12
  514. package/.claude/skills/retrospective/references/legacy.md +0 -7
  515. package/.claude/skills/review/SKILL.md +0 -22
  516. package/.claude/skills/review/references/astra.md +0 -21
  517. package/.claude/skills/review/references/legacy.md +0 -9
  518. package/.claude/skills/security-audit/SKILL.md +0 -22
  519. package/.claude/skills/security-audit/references/astra.md +0 -17
  520. package/.claude/skills/security-audit/references/legacy.md +0 -7
  521. package/.claude/skills/sprint-plan/SKILL.md +0 -22
  522. package/.claude/skills/sprint-plan/references/astra.md +0 -12
  523. package/.claude/skills/sprint-plan/references/legacy.md +0 -7
  524. package/.claude/skills/status/SKILL.md +0 -22
  525. package/.claude/skills/status/references/astra.md +0 -19
  526. package/.claude/skills/status/references/legacy.md +0 -9
  527. package/.claude/skills/story-readiness/SKILL.md +0 -24
  528. package/.claude/skills/story-readiness/references/astra.md +0 -17
  529. package/.claude/skills/story-readiness/references/legacy.md +0 -7
  530. package/.claude/skills/test/SKILL.md +0 -22
  531. package/.claude/skills/test/references/astra.md +0 -27
  532. package/.claude/skills/test/references/legacy.md +0 -9
  533. package/.claude/skills/ui-review/SKILL.md +0 -22
  534. package/.claude/skills/ui-review/references/astra.md +0 -17
  535. package/.claude/skills/ui-review/references/legacy.md +0 -7
  536. package/.claude/skills/unity-cli/CHANGELOG.md +0 -233
  537. package/.claude/skills/unity-cli/SECURITY.md +0 -22
  538. package/.claude/skills/unity-cli/SKILL.md +0 -414
  539. package/.claude/skills/unity-cli/references/auth-license-cloud.md +0 -146
  540. package/.claude/skills/unity-cli/references/build-run-test.md +0 -349
  541. package/.claude/skills/unity-cli/references/config-hub.md +0 -103
  542. package/.claude/skills/unity-cli/references/diagnostics-maintenance.md +0 -326
  543. package/.claude/skills/unity-cli/references/editors-install.md +0 -327
  544. package/.claude/skills/unity-cli/references/integration-advanced.md +0 -472
  545. package/.claude/skills/unity-cli/references/projects-templates.md +0 -574
  546. package/.claude/skills/work/SKILL.md +0 -22
  547. package/.claude/skills/work/references/astra.md +0 -32
  548. package/.claude/skills/work/references/classification.md +0 -41
  549. package/.claude/skills/work/references/closure.md +0 -60
  550. package/.claude/skills/work/references/feature-contracts.md +0 -36
  551. package/.claude/skills/work/references/legacy.md +0 -29
  552. package/.claude/skills/work/references/verification.md +0 -26
  553. package/CLAUDE.md +0 -10
  554. package/MEMORY.md +0 -109
  555. package/UPGRADING.md +0 -130
  556. package/VERSION +0 -1
  557. package/docs/ARCHITECTURE.md +0 -131
  558. package/docs/ASSET-PRODUCTION-GRAPHIFY-PLAN.md +0 -230
  559. package/docs/BOUNDED-REPAIR.md +0 -80
  560. package/docs/EDITOR-SESSION-EVIDENCE.md +0 -52
  561. package/docs/HANDOFF-AUTOMATION.md +0 -144
  562. package/docs/HUONG-DAN-MIGRATE-VA-SU-DUNG.md +0 -117
  563. package/docs/MIGRATION-MATRIX.md +0 -23
  564. package/docs/NCCGS-1.3.md +0 -13
  565. package/docs/NCCGS-1.4.1.md +0 -149
  566. package/docs/NCCGS-1.4.md +0 -184
  567. package/docs/NCCGS-1.5-IMPLEMENTATION-PLAN.md +0 -378
  568. package/docs/NCCGS-1.5-PILOT.md +0 -130
  569. package/docs/NCCGS-1.5.md +0 -237
  570. package/docs/PHASE-1-OPERATIONS.md +0 -278
  571. package/docs/PHASE-2-OPERATIONS.md +0 -183
  572. package/docs/PROJECT-POLICY.md +0 -94
  573. package/docs/RELEASE-STATUS.md +0 -150
  574. package/docs/TASK-SLICING.md +0 -57
  575. package/docs/UNINSTALL.md +0 -134
  576. package/docs/UPGRADE-QUALIFICATION.md +0 -45
  577. package/docs/WFK17-HOOK-REPAIR.md +0 -71
  578. package/docs/WORKFLOWS.md +0 -85
  579. package/package-assets/nccgs-project.gitignore +0 -2
  580. package/package-assets/setup-vivox-voice-chat-evals.gitignore +0 -7
  581. package/scaffold/.nccgs/autonomy.json +0 -4
  582. package/scaffold/.nccgs/bugs/.gitkeep +0 -1
  583. package/scaffold/.nccgs/closures/.gitkeep +0 -1
  584. package/scaffold/.nccgs/context/.gitkeep +0 -1
  585. package/scaffold/.nccgs/decisions/.gitkeep +0 -1
  586. package/scaffold/.nccgs/evidence/.gitkeep +0 -1
  587. package/scaffold/.nccgs/execution.json +0 -10
  588. package/scaffold/.nccgs/features/.gitkeep +0 -1
  589. package/scaffold/.nccgs/gates.json +0 -7
  590. package/scaffold/.nccgs/inputs.json +0 -6
  591. package/scaffold/.nccgs/migrations/.gitkeep +0 -1
  592. package/scaffold/.nccgs/playtests/.gitkeep +0 -1
  593. package/scaffold/.nccgs/project.yaml +0 -109
  594. package/scaffold/.nccgs/requirements.yaml +0 -10
  595. package/scaffold/.nccgs/reviews/.gitkeep +0 -1
  596. package/scaffold/.nccgs/state.md +0 -40
  597. package/scaffold/.nccgs/templates/agent-handoff.md +0 -37
  598. package/scaffold/.nccgs/templates/architecture-decision.md +0 -27
  599. package/scaffold/.nccgs/templates/closure-record.md +0 -62
  600. package/scaffold/.nccgs/templates/context-packet.yaml +0 -17
  601. package/scaffold/.nccgs/templates/design-contract.json +0 -34
  602. package/scaffold/.nccgs/templates/evidence-record.md +0 -23
  603. package/scaffold/.nccgs/templates/feature-contract.md +0 -60
  604. package/scaffold/.nccgs/templates/implementation-report.md +0 -52
  605. package/scaffold/.nccgs/templates/migration-plan.md +0 -40
  606. package/scaffold/.nccgs/templates/repair-packet.json +0 -14
  607. package/scaffold/.nccgs/templates/repair-unity-code.json +0 -18
  608. package/scaffold/.nccgs/templates/repair-unity-editor.json +0 -21
  609. package/scaffold/.nccgs/templates/task-slices.json +0 -28
  610. package/scaffold/.nccgs/templates/waiver.md +0 -11
  611. package/scripts/benchmark-snapshot.mjs +0 -205
  612. package/scripts/benchmark-v15.mjs +0 -173
  613. package/scripts/cli.mjs +0 -205
  614. package/scripts/doctor.mjs +0 -261
  615. package/scripts/install.mjs +0 -530
  616. package/scripts/recover-session.mjs +0 -89
  617. package/scripts/run.mjs +0 -86
  618. package/scripts/supervise.mjs +0 -152
  619. package/scripts/uninstall.mjs +0 -292
  620. package/scripts/validate.mjs +0 -285
  621. package/scripts/work-handoff.mjs +0 -38
  622. package/tests/autonomy.test.mjs +0 -126
  623. package/tests/benchmark-v15.test.mjs +0 -65
  624. package/tests/design-readiness.test.mjs +0 -200
  625. package/tests/editor-session-evidence.test.mjs +0 -45
  626. package/tests/evidence.test.mjs +0 -186
  627. package/tests/fixtures/compiler-shutdown.cs +0 -67
  628. package/tests/fixtures/native-provider.mjs +0 -61
  629. package/tests/framework.test.mjs +0 -740
  630. package/tests/gates.test.mjs +0 -479
  631. package/tests/hook-repair.test.mjs +0 -106
  632. package/tests/lifecycle-v15.test.mjs +0 -346
  633. package/tests/native-completion-notices.test.mjs +0 -181
  634. package/tests/orchestration-v15.test.mjs +0 -596
  635. package/tests/phase1.test.mjs +0 -427
  636. package/tests/phase2.test.mjs +0 -250
  637. package/tests/policy-v15.test.mjs +0 -234
  638. package/tests/ponytail-package.test.mjs +0 -75
  639. package/tests/returned-handoff-notices.test.mjs +0 -170
  640. package/tests/review-budget.test.mjs +0 -177
  641. package/tests/runtime-v15.test.mjs +0 -1198
  642. package/tests/session-budget.test.mjs +0 -34
  643. package/tests/task-slices.test.mjs +0 -285
  644. package/tests/uninstall.test.mjs +0 -477
  645. package/tests/unity-generated.test.mjs +0 -140
  646. package/tests/unity-transport.test.mjs +0 -348
  647. /package/{.claude/nccgs/packages → template}/ponytail/LICENSE +0 -0
  648. /package/{.claude/nccgs/packages → template}/ponytail/NOTICE.md +0 -0
  649. /package/{.claude/nccgs/packages → template}/ponytail/complexity-review.md +0 -0
  650. /package/{.claude/skills → unity-skills}/build-live-game/references/remote-config.md +0 -0
  651. /package/{.claude/skills → unity-skills}/implement-in-app-purchases/README.md +0 -0
  652. /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/codeless-catalog.md +0 -0
  653. /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/convert-adapty.md +0 -0
  654. /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/convert-essentialkit.md +0 -0
  655. /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/convert-revenuecat.md +0 -0
  656. /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/convert-unipay.md +0 -0
  657. /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/migration-v4-to-v5.md +0 -0
  658. /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/path-add-iap-to-new-project.md +0 -0
  659. /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/path-convert-native-storekit.md +0 -0
  660. /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/platform-notes.md +0 -0
  661. /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/pre-check.md +0 -0
  662. /package/{.claude/skills → unity-skills}/localization/resources/LocalizedFontAsset.cs +0 -0
  663. /package/{.claude/skills → unity-skills}/optimize-audio/resources/platform-settings.md +0 -0
  664. /package/{.claude/skills → unity-skills}/optimize-web/resources/WebOptimizer.cs +0 -0
  665. /package/{.claude/skills → unity-skills}/optimize-web/resources/toktx-examples.sh +0 -0
  666. /package/{.claude/skills → unity-skills}/physics-3d-collision/resources/CollisionDebugger.cs +0 -0
  667. /package/{.claude/skills → unity-skills}/setup-multiplayer-services/references/dgs-entrypoint.md +0 -0
  668. /package/{.claude/skills → unity-skills}/setup-multiplayer-services/references/entrypoints.md +0 -0
  669. /package/{.claude/skills → unity-skills}/setup-multiplayer-services/references/examples.md +0 -0
  670. /package/{.claude/skills → unity-skills}/setup-multiplayer-services/references/implementation-fit.md +0 -0
  671. /package/{.claude/skills → unity-skills}/setup-multiplayer-services/references/underlying-services.md +0 -0
  672. /package/{.claude/skills → unity-skills}/setup-multiplayer-services/references/workflows-prerequisites.md +0 -0
  673. /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/evals/.env.example +0 -0
  674. /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/evals/README.md +0 -0
  675. /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/evals/promptfooconfig.yaml +0 -0
  676. /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/evals/tests/init-and-login.yaml +0 -0
  677. /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/evals/tests/text-chat.yaml +0 -0
  678. /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/evals/tests/voice-channels.yaml +0 -0
  679. /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/references/events-and-participants.md +0 -0
  680. /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/references/init-and-login.md +0 -0
  681. /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/references/text-chat.md +0 -0
  682. /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/references/troubleshooting.md +0 -0
  683. /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/references/voice-channels.md +0 -0
  684. /package/{.claude/skills → unity-skills}/shader-graph-create-custom-node/resources/all_hints.hlsl +0 -0
  685. /package/{.claude/skills → unity-skills}/sprite-editor/references/api_reference.md +0 -0
  686. /package/{.claude/skills → unity-skills}/sprite-editor/references/background.md +0 -0
  687. /package/{.claude/skills → unity-skills}/sprite-editor/scripts/AutomaticSliceTexture.cs +0 -0
  688. /package/{.claude/skills → unity-skills}/sprite-editor/scripts/GenerateNewSpriteRects.cs +0 -0
  689. /package/{.claude/skills → unity-skills}/sprite-editor/scripts/GetTextureSourceImageSize.cs +0 -0
  690. /package/{.claude/skills → unity-skills}/sprite-editor/scripts/GetTextureToSlice.cs +0 -0
  691. /package/{.claude/skills → unity-skills}/sprite-editor/scripts/GridSliceTexture.cs +0 -0
  692. /package/{.claude/skills → unity-skills}/sprite-editor/scripts/IsometricSliceTexture.cs +0 -0
  693. /package/{.claude/skills → unity-skills}/sprite-editor/scripts/README.md +0 -0
  694. /package/{.claude/skills → unity-skills}/sprite-editor/scripts/SetPivotExample.cs +0 -0
  695. /package/{.claude/skills → unity-skills}/sprite-editor/scripts/SpriteToPng.cs +0 -0
  696. /package/{.claude/skills → unity-skills}/ui-imgui/references/gui-elements.md +0 -0
  697. /package/{.claude/skills → unity-skills}/ui-imgui/references/templates.md +0 -0
  698. /package/{.claude/skills → unity-skills}/ui-ugui/references/scrollview-setup.md +0 -0
  699. /package/{.claude/skills → unity-skills}/ui-uitk/references/common-issues.md +0 -0
  700. /package/{.claude/skills → unity-skills}/ui-uitk/references/painter2d.md +0 -0
  701. /package/{.claude/skills → unity-skills}/ui-uitk/references/pointermanipulator-guide.md +0 -0
  702. /package/{.claude/skills → unity-skills}/ui-uitk/references/svg-icons.md +0 -0
  703. /package/{.claude/skills → unity-skills}/ui-uitk/references/ui-runtime-binding.md +0 -0
  704. /package/{.claude/skills → unity-skills}/ui-uitk/references/uss-guide.md +0 -0
  705. /package/{.claude/skills → unity-skills}/urp-postprocessing/references/effect-reference.md +0 -0
@@ -0,0 +1,51 @@
1
+ // Import-setting check against the art bible: one `script eval` reads each TextureImporter and returns
2
+ // the fields; mismatches are computed here. No .meta parsing, no hand-typed values.
3
+ import { evalCode } from "./unity.mjs";
4
+
5
+ const FIELDS = ["pixelsPerUnit", "filterMode", "textureType", "maxTextureSize", "spritePivot", "mipmapEnabled", "width", "height"];
6
+
7
+ export function assetScript(paths) {
8
+ const list = paths.map(p => JSON.stringify(p)).join(", ");
9
+ return `var sb = new System.Text.StringBuilder(); sb.Append("[");
10
+ var paths = new string[]{ ${list} }; bool first = true;
11
+ foreach (var p in paths) {
12
+ if (!first) sb.Append(","); first = false;
13
+ var imp = UnityEditor.AssetImporter.GetAtPath(p) as UnityEditor.TextureImporter;
14
+ if (imp == null) { sb.Append("{\\"path\\":" + Q(p) + ",\\"missing\\":true}"); continue; }
15
+ var tex = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Texture2D>(p);
16
+ var s = imp.spritePivot;
17
+ sb.Append("{\\"path\\":" + Q(p) + ",\\"pixelsPerUnit\\":" + imp.spritePixelsPerUnit.ToString("R", System.Globalization.CultureInfo.InvariantCulture)
18
+ + ",\\"filterMode\\":" + Q(imp.filterMode.ToString()) + ",\\"textureType\\":" + Q(imp.textureType.ToString()) + ",\\"maxTextureSize\\":" + imp.maxTextureSize
19
+ + ",\\"spritePivot\\":[" + s.x.ToString("R", System.Globalization.CultureInfo.InvariantCulture) + "," + s.y.ToString("R", System.Globalization.CultureInfo.InvariantCulture) + "]"
20
+ + ",\\"mipmapEnabled\\":" + (imp.mipmapEnabled ? "true" : "false") + ",\\"width\\":" + (tex == null ? -1 : tex.width) + ",\\"height\\":" + (tex == null ? -1 : tex.height) + "}");
21
+ }
22
+ sb.Append("]"); return sb.ToString();
23
+ string Q(string v) { return "\\"" + v.Replace("\\\\", "\\\\\\\\").Replace("\\"", "\\\\\\"") + "\\""; }`;
24
+ }
25
+
26
+ export function compare(expected, actual) {
27
+ const mismatches = [];
28
+ for (const e of expected) {
29
+ const a = actual.find(x => x.path === e.path);
30
+ if (!a) { mismatches.push({ path: e.path, field: "path", expected: "present", actual: "not read" }); continue; }
31
+ if (a.missing) { mismatches.push({ path: e.path, field: "importer", expected: "TextureImporter", actual: "missing" }); continue; }
32
+ for (const f of FIELDS) {
33
+ if (!(f in e)) continue;
34
+ const ev = e[f], av = a[f];
35
+ const same = Array.isArray(ev) ? Array.isArray(av) && ev.length === av.length && ev.every((v, i) => Math.abs(v - av[i]) < 1e-4) : typeof ev === "number" ? Math.abs(ev - av) < 1e-4 : String(ev).toLowerCase() === String(av).toLowerCase();
36
+ if (!same) mismatches.push({ path: e.path, field: f, expected: ev, actual: av });
37
+ }
38
+ }
39
+ return mismatches;
40
+ }
41
+
42
+ export async function checkAssets(root, spec) {
43
+ const expected = spec.assets ?? [];
44
+ if (!expected.length) return { ok: false, errors: [{ message: "spec.assets is empty" }], assets: [], mismatches: [] };
45
+ const r = await evalCode(root, assetScript(expected.map(a => a.path)), 60);
46
+ const raw = r.result?.result ?? r.result;
47
+ if (!r.ok || typeof raw !== "string") return { ok: false, errors: r.errors?.length ? r.errors : [{ message: `unexpected eval result: ${String(JSON.stringify(raw)).slice(0, 200)}` }], assets: [], mismatches: [] };
48
+ let actual; try { actual = JSON.parse(raw); } catch (e) { return { ok: false, errors: [{ message: `cannot parse importer data: ${e.message}` }], assets: [], mismatches: [] }; }
49
+ const mismatches = compare(expected, actual);
50
+ return { ok: true, assets: actual, mismatches, errors: [] };
51
+ }
@@ -0,0 +1,145 @@
1
+ // Scripted playtest with telemetry, run entirely inside the Editor by one `script eval`.
2
+ // A spec says what to press when, which game members to sample and how often; the Editor writes a CSV
3
+ // of samples (10 Hz by default) and this module turns it into a measurement artifact with a summary
4
+ // (final values, first change of each boolean, min/max of each number, key presses). The playtester
5
+ // agent reads that artifact to judge feel; criteria can assert on the summary.
6
+ //
7
+ // spec = {
8
+ // name: "space-every-0.9s", durationS: 30, sampleMs: 100, holdS: 1,
9
+ // keys: [{ at: 0.9, key: "Space", holdMs: 60 }, ...] // explicit timeline, or
10
+ // pattern: { key: "Space", everyS: 0.9, fromS: 0.5, holdMs: 60 } // expanded into keys
11
+ // probes: [{ name: "distance", expr: "Runner.Instance.DistanceTravelled" }, { name: "alive", expr: "GameState.IsRunning" }],
12
+ // stopWhen: "!GameState.IsRunning", // optional C# expression, ends the run early
13
+ // reset: "UnityEngine.SceneManagement.SceneManager.LoadScene(UnityEngine.SceneManagement.SceneManager.GetActiveScene().name)",
14
+ // settleS: 0.5, // reset runs at schedule time; the timeline starts settleS later
15
+ // rules: [{ name: "jump-near-hazard", when: "<C# bool expr>", key: "Space", holdMs: 60, cooldownS: 0.3 }]
16
+ // // reactive input: pressed whenever `when` holds (a bot, not a metronome)
17
+ // }
18
+ // The schedule lands 2-3 s after Play (Pipeline round trips), so without `reset` the game is already
19
+ // mid-run; `gameTime` (Time.timeSinceLevelLoad) is sampled beside `t` to make that visible.
20
+ import fs from "node:fs";
21
+ import path from "node:path";
22
+ import { exists } from "../core/fs.mjs";
23
+ import { playStart, playStop, evalCode, waitReady, runInBackground, consoleGet } from "./unity.mjs";
24
+ import { sleep } from "../core/process-tree.mjs";
25
+
26
+ const q = v => JSON.stringify(String(v));
27
+ const INV = "System.Globalization.CultureInfo.InvariantCulture";
28
+
29
+ export function expandKeys(spec) {
30
+ if (Array.isArray(spec.keys) && spec.keys.length) return spec.keys.map(k => ({ at: Number(k.at), key: String(k.key), holdMs: Number(k.holdMs ?? 60) })).sort((a, b) => a.at - b.at);
31
+ const p = spec.pattern;
32
+ if (!p?.key || !(p.everyS > 0)) return [];
33
+ const keys = [];
34
+ for (let t = Number(p.fromS ?? 0.5); t < Number(spec.durationS ?? 30); t += Number(p.everyS)) keys.push({ at: Number(t.toFixed(3)), key: String(p.key), holdMs: Number(p.holdMs ?? 60) });
35
+ return keys;
36
+ }
37
+
38
+ export function playtestScript(spec, { csvFile, doneFile }) {
39
+ const K = "UnityEngine.InputSystem";
40
+ const keys = expandKeys(spec);
41
+ const probes = spec.probes ?? [];
42
+ const sampleS = ((spec.sampleMs ?? 100) / 1000).toFixed(3);
43
+ const duration = Number(spec.durationS ?? 30).toFixed(3);
44
+ // each probe: value or "ERR" (the game may not have the object yet)
45
+ const probeCode = probes.map(p => `try { var v = (object)(${p.expr}); cells.Add(v is float f ? f.ToString("R", ${INV}) : v is double d ? d.ToString("R", ${INV}) : System.Convert.ToString(v, ${INV})); } catch (System.Exception) { cells.Add("ERR"); }`).join(" ");
46
+ const keyTimes = keys.map(k => `new double[]{${k.at.toFixed(3)}, ${(k.at + k.holdMs / 1000).toFixed(3)}}`).join(", ");
47
+ const keyNames = keys.map(k => `${K}.Key.${k.key}`).join(", ");
48
+ const stopWhen = spec.stopWhen ? `try { if (${spec.stopWhen}) stop = true; } catch (System.Exception) {}` : "";
49
+ const rules = (spec.rules ?? []).map((r, i) => `try { if (ruleDown[${i}] < 0 && dt >= ruleNext[${i}] && (${r.when})) { ${K}.InputSystem.QueueStateEvent(${K}.Keyboard.current, new ${K}.LowLevel.KeyboardState(${K}.Key.${r.key ?? "Space"})); ruleDown[${i}] = dt; ruleHits[${i}]++; presses++; } else if (ruleDown[${i}] >= 0 && dt >= ruleDown[${i}] + ${((r.holdMs ?? 60) / 1000).toFixed(3)}) { ${K}.InputSystem.QueueStateEvent(${K}.Keyboard.current, new ${K}.LowLevel.KeyboardState()); ruleDown[${i}] = -1; ruleNext[${i}] = dt + ${Number(r.cooldownS ?? 0.3).toFixed(3)}; } } catch (System.Exception) {}`).join(" ");
50
+ const ruleCount = (spec.rules ?? []).length;
51
+ const reset = spec.reset ? `try { ${spec.reset}; } catch (System.Exception e) { return "reset failed: " + e.Message; }` : "";
52
+ const settle = Number(spec.reset ? (spec.settleS ?? 0.5) : 0).toFixed(3);
53
+ return `var kb = ${K}.Keyboard.current; if (kb == null) return "no keyboard";
54
+ ${reset}
55
+ var header = "t,gameTime,${probes.map(p => p.name).join(",")}";
56
+ var lines = new System.Collections.Generic.List<string>(); lines.Add(header);
57
+ var keyTimes = new double[][]{ ${keyTimes} }; var keyNames = new ${K}.Key[]{ ${keyNames} };
58
+ var keyState = new int[keyTimes.Length]; int presses = 0;
59
+ var ruleDown = new double[${ruleCount}]; var ruleNext = new double[${ruleCount}]; var ruleHits = new int[${ruleCount}]; for (int i = 0; i < ruleDown.Length; i++) ruleDown[i] = -1;
60
+ double tSched = UnityEditor.EditorApplication.timeSinceStartup; double t0 = tSched + ${settle}; double nextSample = 0; bool stop = false; bool finished = false;
61
+ UnityEditor.EditorApplication.CallbackFunction cb = null;
62
+ cb = () => {
63
+ if (finished) return;
64
+ double dt = UnityEditor.EditorApplication.timeSinceStartup - t0;
65
+ if (dt < 0) return;
66
+ for (int i = 0; i < keyTimes.Length; i++) {
67
+ if (keyState[i] == 0 && dt >= keyTimes[i][0]) { ${K}.InputSystem.QueueStateEvent(${K}.Keyboard.current, new ${K}.LowLevel.KeyboardState(keyNames[i])); keyState[i] = 1; presses++; }
68
+ else if (keyState[i] == 1 && dt >= keyTimes[i][1]) { ${K}.InputSystem.QueueStateEvent(${K}.Keyboard.current, new ${K}.LowLevel.KeyboardState()); keyState[i] = 2; }
69
+ }
70
+ ${rules}
71
+ if (dt >= nextSample) { nextSample += ${sampleS}; var cells = new System.Collections.Generic.List<string>(); cells.Add(dt.ToString("F3", ${INV})); cells.Add(UnityEngine.Time.timeSinceLevelLoad.ToString("F3", ${INV})); ${probeCode} lines.Add(string.Join(",", cells)); }
72
+ ${stopWhen}
73
+ if (stop || dt >= ${duration}) {
74
+ { var cells = new System.Collections.Generic.List<string>(); cells.Add(dt.ToString("F3", ${INV})); cells.Add(UnityEngine.Time.timeSinceLevelLoad.ToString("F3", ${INV})); ${probeCode} lines.Add(string.Join(",", cells)); }
75
+ finished = true; UnityEditor.EditorApplication.update -= cb;
76
+ System.IO.File.WriteAllLines(${q(csvFile)}, lines);
77
+ System.IO.File.WriteAllText(${q(doneFile)}, "presses=" + presses + ";stopped=" + stop + ";t=" + dt.ToString("F3", ${INV}) + ";rules=" + string.Join("|", ruleHits));
78
+ }
79
+ };
80
+ UnityEditor.EditorApplication.update += cb; return "scheduled";`;
81
+ }
82
+
83
+ export function parseCsv(text) {
84
+ const [head, ...rows] = text.trim().split(/\r?\n/);
85
+ const names = head.split(",");
86
+ return rows.filter(Boolean).map(r => { const c = r.split(","); const o = {}; names.forEach((n, i) => { const v = c[i]; o[n] = v === "True" ? true : v === "False" ? false : v === "ERR" || v === undefined ? null : Number.isFinite(Number(v)) ? Number(v) : v; }); return o; });
87
+ }
88
+
89
+ export function summarize(samples, probes) {
90
+ const summary = { samples: samples.length, durationS: samples.at(-1)?.t ?? 0, gameTimeStartS: samples[0]?.gameTime ?? null, gameTimeEndS: samples.at(-1)?.gameTime ?? null };
91
+ for (const p of probes) {
92
+ const vals = samples.map(s => s[p.name]).filter(v => v !== null && v !== undefined);
93
+ if (!vals.length) { summary[p.name] = null; continue; }
94
+ const last = vals.at(-1);
95
+ if (typeof last === "boolean") {
96
+ const first = samples.find(s => s[p.name] !== null && s[p.name] !== vals[0]);
97
+ summary[p.name] = { final: last, initial: vals[0], changedAtS: first ? first.t : null };
98
+ } else if (typeof last === "number") {
99
+ summary[p.name] = { final: last, min: Math.min(...vals), max: Math.max(...vals) };
100
+ } else summary[p.name] = { final: last };
101
+ }
102
+ return summary;
103
+ }
104
+
105
+ async function waitFor(file, timeoutMs) {
106
+ const until = Date.now() + timeoutMs;
107
+ while (Date.now() < until) { if (exists(file)) return true; await sleep(300); }
108
+ return exists(file);
109
+ }
110
+
111
+ export async function playtest(root, spec, { outDir, holdMs = 1000 } = {}) {
112
+ const outcome = { name: spec.name ?? "playtest", enteredPlayMode: false, exitedPlayMode: false, keys: expandKeys(spec).length, errors: [], samples: [], summary: null, console: null };
113
+ fs.mkdirSync(outDir, { recursive: true });
114
+ const csvFile = path.resolve(outDir, `${outcome.name}.csv`), doneFile = path.resolve(outDir, `${outcome.name}.done`);
115
+ for (const f of [csvFile, doneFile]) try { fs.rmSync(f, { force: true }); } catch {}
116
+ const rib = await runInBackground(root, true);
117
+ const mark = await consoleGet(root, { tail: 1, level: "log" });
118
+ const since = mark.ok ? { since: mark.result?.cursor, since_session: mark.result?.session } : {};
119
+ const started = await playStart(root);
120
+ if (!started.ok) { outcome.errors.push(started.errors?.[0]?.message ?? "play start failed"); return outcome; }
121
+ outcome.enteredPlayMode = true;
122
+ await sleep(Number(spec.holdS ?? 1) * 1000 || holdMs);
123
+ const r = await evalCode(root, playtestScript(spec, { csvFile, doneFile }), 60);
124
+ const value = r.result?.result ?? r.result;
125
+ if (!r.ok || value !== "scheduled") outcome.errors.push(`playtest not scheduled: ${r.errors?.[0]?.message ?? JSON.stringify(value).slice(0, 300)}`);
126
+ else {
127
+ const ok = await waitFor(doneFile, (Number(spec.durationS ?? 30) + 15) * 1000);
128
+ if (!ok) outcome.errors.push("playtest did not finish in time");
129
+ else {
130
+ const done = Object.fromEntries(fs.readFileSync(doneFile, "utf8").split(";").map(kv => kv.split("=")));
131
+ outcome.presses = Number(done.presses); outcome.stoppedEarly = done.stopped === "True"; outcome.ranS = Number(done.t); outcome.stoppedAtS = done.stopped === "True" ? Number(done.t) : null;
132
+ outcome.rulePresses = (spec.rules ?? []).map((r, i) => ({ name: r.name ?? `rule${i}`, presses: Number((done.rules ?? "").split("|")[i] ?? 0) }));
133
+ outcome.samples = exists(csvFile) ? parseCsv(fs.readFileSync(csvFile, "utf8")) : [];
134
+ outcome.summary = summarize(outcome.samples, spec.probes ?? []);
135
+ outcome.csv = csvFile;
136
+ }
137
+ }
138
+ const con = await consoleGet(root, { tail: 300, level: "log", ...since }); // the game's own log lines ([Runner] jump, dead …) are part of the telemetry
139
+ if (con.ok) outcome.console = { counts: con.result?.counts ?? null, entries: (con.result?.entries ?? []).slice(-100).map(e => ({ time: e.time ?? e.timestamp ?? null, level: e.level ?? null, message: String(e.message ?? e.text ?? "").slice(0, 200) })) };
140
+ const stopped = await playStop(root);
141
+ outcome.exitedPlayMode = stopped.ok;
142
+ await waitReady(root, { timeoutMs: 10000 });
143
+ if (rib.ok) await runInBackground(root, false);
144
+ return outcome;
145
+ }
@@ -0,0 +1,350 @@
1
+ // Unity CLI + Pipeline wrapper. Every call goes through here so the rest of the
2
+ // runtime never spells a Pipeline command name: names are discovered at runtime
3
+ // (`unity command --detail compact`) and cached per package version.
4
+ //
5
+ // Verified on 2026-11-04 against Unity CLI 1.0.0-beta.14 and com.unity.pipeline 0.9.x
6
+ // with Editors 6000.3.17f1 and 6000.6.7f1 (see docs/EVIDENCE-VERIFICATION.md).
7
+ import path from "node:path";
8
+ import fs from "node:fs";
9
+ import { run, sleep } from "../core/process-tree.mjs";
10
+ import { readJson, writeJson, exists } from "../core/fs.mjs";
11
+ import { at, editorVersion, pipelineVersion } from "../core/project.mjs";
12
+ import { diag } from "../core/diag.mjs";
13
+
14
+ export const UNITY_BIN = process.env.NCCGS_UNITY_BIN || "unity";
15
+ // Test seam: NCCGS_UNITY_ARGS prepends fixed arguments (used to point NCCGS_UNITY_BIN=node at a fake CLI).
16
+ const PREFIX = process.env.NCCGS_UNITY_ARGS ? process.env.NCCGS_UNITY_ARGS.split("|") : [];
17
+ export const unityArgv = args => [...PREFIX, ...args];
18
+
19
+ function parseEnvelope(result) {
20
+ const text = (result.stdout || "").replace(/^/, "").trim();
21
+ let json = null;
22
+ try { json = JSON.parse(text); } catch {}
23
+ return { ...result, json, text };
24
+ }
25
+
26
+ // `unity test` / `unity build` start a batch Editor that must not outlive the command (ownTree);
27
+ // `unity open` starts an Editor that must. Everything else is a short-lived CLI call.
28
+ export async function cli(args, { cwd, timeoutMs = 120000 } = {}) {
29
+ const ownTree = ["test", "build"].includes(args[0]);
30
+ const result = await run(UNITY_BIN, unityArgv([...args, "--format", "json"]), { cwd, timeoutMs, ownTree });
31
+ const env = parseEnvelope(result);
32
+ if (result.timedOut || result.error || (env.json && env.json.success === false) || (!env.json && result.code !== 0)) {
33
+ const i = args.indexOf("--project-path");
34
+ const shown = i >= 0 ? [...args.slice(0, i), ...args.slice(i + 2)] : args;
35
+ diag(i >= 0 ? args[i + 1] : (process.env.NCCGS_PROJECT_ROOT ?? process.cwd()), "unity.failed", { args: shown.map(a => String(a).slice(0, 200)), code: result.code, timedOut: result.timedOut, errors: env.json?.errors ?? null, stderr: result.stderr?.slice(-500) ?? null, jobObject: result.jobObject });
36
+ }
37
+ return env;
38
+ }
39
+
40
+ export async function cliVersion() {
41
+ const r = await run(UNITY_BIN, unityArgv(["--version"]), { timeoutMs: 30000 });
42
+ return r.code === 0 ? r.stdout.trim() : null;
43
+ }
44
+
45
+ // ---- Editor instances ------------------------------------------------------
46
+
47
+ export async function status() {
48
+ const r = await cli(["status"], { timeoutMs: 30000 });
49
+ return r.json?.data?.instances ?? [];
50
+ }
51
+
52
+ export async function instanceFor(root) {
53
+ const want = path.resolve(root).toLowerCase();
54
+ return (await status()).find(i => path.resolve(i.projectPath || i.project || "").toLowerCase() === want) ?? null;
55
+ }
56
+
57
+ // Poll until the project's Pipeline server reports `ready`. Entering Play Mode on 6.3
58
+ // reloads the domain and drops the server for ~3 s, so every post-playmode call waits here.
59
+ export async function waitReady(root, { timeoutMs = 15000, intervalMs = 1000 } = {}) {
60
+ const started = Date.now();
61
+ let last = null;
62
+ while (Date.now() - started < timeoutMs) {
63
+ last = await instanceFor(root);
64
+ if (last?.state === "ready") return last;
65
+ await sleep(intervalMs);
66
+ }
67
+ return last;
68
+ }
69
+
70
+ // ---- Pipeline commands -----------------------------------------------------
71
+
72
+ export async function command(root, name, params = {}, { timeoutMs = 120000, retries = 2 } = {}) {
73
+ const args = ["command", ...name.split(" ")];
74
+ for (const [k, v] of Object.entries(params)) { if (v !== undefined && v !== null) args.push(`--${k}`, String(v)); }
75
+ args.push("--project-path", root);
76
+ let r;
77
+ for (let attempt = 0; attempt <= retries; attempt++) {
78
+ r = await cli(args, { timeoutMs });
79
+ const code = r.json?.errors?.[0]?.code;
80
+ if (code !== "EDITOR_NOT_RUNNING" || attempt === retries) break;
81
+ await waitReady(root, { timeoutMs: 8000 });
82
+ }
83
+ const ok = r.json?.success === true;
84
+ return { ok, result: r.json?.data?.result, errors: r.json?.errors ?? [], raw: r.json, code: r.code, stderr: r.stderr };
85
+ }
86
+
87
+ // Discover the command catalog; cached under .nccgs/cache keyed by pipeline version.
88
+ export async function catalog(root, { refresh = false } = {}) {
89
+ const version = pipelineVersion(root) || "unknown";
90
+ const file = path.join(at(root, "cache"), `pipeline-commands-${version.replace(/[^\w.-]/g, "_")}.json`);
91
+ if (!refresh && exists(file)) return readJson(file);
92
+ const r = await cli(["command", "--detail", "compact", "--limit", "500", "--project-path", root], { timeoutMs: 60000 });
93
+ const commands = r.json?.data?.commands ?? [];
94
+ if (!commands.length) return { version, commands: [], discoveredAt: null, error: r.json?.errors?.[0]?.message ?? "no commands" };
95
+ const value = { version, discoveredAt: new Date().toISOString(), commands: commands.map(c => ({ command: c.command, group: c.group, name: c.name, description: c.description })) };
96
+ writeJson(file, value);
97
+ return value;
98
+ }
99
+
100
+ // Resolve a capability to the real command name (handles renames between Pipeline versions).
101
+ const CAPABILITIES = {
102
+ playStart: [/^editor\.playmode start$/, /^editor_play$/],
103
+ playStop: [/^editor\.playmode stop$/, /^editor_stop$/],
104
+ screenshot: [/^capture screenshot$/, /^screenshot$/],
105
+ consoleGet: [/^console get$/, /^get_console$/],
106
+ consoleStatus: [/^console status$/],
107
+ consoleClear: [/^console clear$/],
108
+ eval: [/^script eval$/, /^eval$/],
109
+ testRun: [/^test run$/, /^run_tests$/],
110
+ testStatus: [/^test status$/, /^test_status$/],
111
+ runInBackground: [/^editor\.playmode set_run_in_background$/],
112
+ testList: [/^test list$/],
113
+ simulateKey: [/^runtime simulate_key$/],
114
+ performance: [/^diagnostics get_performance$/],
115
+ hierarchy: [/^scene get_hierarchy$/, /^get_scene_hierarchy$/],
116
+ recompile: [/^script recompile$/, /^recompile$/],
117
+ recompileStatus: [/^script recompile_status$/]
118
+ };
119
+
120
+ export async function resolve(root, capability) {
121
+ const cat = await catalog(root);
122
+ const patterns = CAPABILITIES[capability];
123
+ if (!patterns) throw new Error(`Unknown capability ${capability}`);
124
+ const hit = cat.commands.find(c => patterns.some(p => p.test(c.command)));
125
+ return hit?.command ?? null;
126
+ }
127
+
128
+ async function call(root, capability, params, options) {
129
+ const name = await resolve(root, capability);
130
+ if (!name) return { ok: false, errors: [{ code: "CAPABILITY_MISSING", message: `No Pipeline command for ${capability}` }] };
131
+ return command(root, name, params, options);
132
+ }
133
+
134
+ // ---- High-level operations used by `task verify` -------------------------------
135
+
136
+ export async function playStart(root) {
137
+ const r = await call(root, "playStart", {});
138
+ if (r.ok) await waitReady(root, { timeoutMs: 15000 });
139
+ return r;
140
+ }
141
+ export const playStop = root => call(root, "playStop", {});
142
+ // Play Mode freezes when the Editor loses focus unless Run In Background is on; the Pipeline override
143
+ // restores the project's own value when disabled. Best effort: an older Pipeline without it is fine.
144
+ export const runInBackground = (root, enable) => call(root, "runInBackground", { enable }).catch(() => ({ ok: false }));
145
+ export const simulateKey = (root, key, action = "press") => call(root, "simulateKey", { key, action });
146
+ export const evalCode = (root, code, timeout = 30) => call(root, "eval", { code, timeout });
147
+ export const performance = root => call(root, "performance", {});
148
+ export const hierarchy = root => call(root, "hierarchy", {});
149
+
150
+ export async function screenshot(root, output, { view = "game", width = 1280, height = 720 } = {}) {
151
+ fs.mkdirSync(path.dirname(output), { recursive: true });
152
+ const r = await call(root, "screenshot", { view, output, width, height });
153
+ return { ...r, path: r.ok ? (r.result?.path ?? output) : null, bytes: exists(output) ? fs.statSync(output).size : 0 };
154
+ }
155
+
156
+ export const consoleStatus = root => call(root, "consoleStatus", {});
157
+ export const consoleGet = (root, { tail = 100, level = "warn", since, since_session } = {}) =>
158
+ call(root, "consoleGet", { tail, level, since, since_session });
159
+
160
+ // Tests inside the live Editor. Returns the Pipeline summary normalised to tests.json shape.
161
+ // The command envelope can succeed while the run itself failed (result.success false + result.error);
162
+ // that is a failed run, never "ok with 0 tests".
163
+ export async function testRunLive(root, { mode = "editor", filter, timeout = 600 } = {}) {
164
+ if (mode === "playmode") return testRunLiveAsync(root, { mode, filter, timeout });
165
+ const r = await call(root, "testRun", { mode, filter: filter || undefined, timeout }, { timeoutMs: (timeout + 60) * 1000 });
166
+ if (!r.ok) return { ok: false, errors: r.errors };
167
+ if (r.result?.success === false) return runFailed(mode, r.result.error ?? r.result.message);
168
+ const s = r.result?.Summary ?? {};
169
+ const results = r.result?.Results ?? [];
170
+ return {
171
+ ok: true,
172
+ source: "pipeline",
173
+ mode: r.result?.Mode ?? mode,
174
+ total: s.Total ?? 0, passed: s.Passed ?? 0, failed: s.Failed ?? 0, skipped: s.Skipped ?? 0, inconclusive: s.Inconclusive ?? 0,
175
+ durationSec: r.result?.Duration ?? null,
176
+ failures: results.filter(t => /fail/i.test(t.Result ?? t.Outcome ?? t.Status ?? "")).map(t => ({ name: t.Name ?? t.FullName, message: t.Message ?? "" }))
177
+ };
178
+ }
179
+
180
+ // PlayMode cannot run synchronously over HTTP: entering Play Mode reloads the domain and drops the request.
181
+ // Start it async and poll `test status`. Starting a run deletes the previous status file, so a
182
+ // "completed" status read after a successful start belongs to this run.
183
+ async function testRunLiveAsync(root, { mode, filter, timeout }) {
184
+ const started = await call(root, "testRun", { mode, filter: filter || undefined, async_tests: true, timeout });
185
+ if (!started.ok) return { ok: false, source: "pipeline", mode, errors: started.errors };
186
+ if (started.result?.success === false) return runFailed(mode, started.result.error ?? started.result.message);
187
+ const deadline = Date.now() + (timeout + 60) * 1000;
188
+ let st = null;
189
+ while (Date.now() < deadline) {
190
+ await sleep(2000);
191
+ const r = await call(root, "testStatus", {});
192
+ st = r.ok ? r.result : null; // null while the domain reloads or before the request is registered
193
+ if (st && st.status !== "running") break;
194
+ }
195
+ if (st?.status !== "completed") return runFailed(mode, st?.message ?? `test status: ${st?.status ?? "timed out"}`);
196
+ const s = st.summary ?? {};
197
+ const results = st.results ?? [];
198
+ return {
199
+ ok: true,
200
+ source: "pipeline",
201
+ mode: "PlayMode",
202
+ total: s.total ?? 0, passed: s.passed ?? 0, failed: s.failed ?? 0, skipped: s.skipped ?? 0, inconclusive: s.inconclusive ?? 0,
203
+ durationSec: st.duration ?? null,
204
+ failures: results.filter(t => /fail/i.test(t.Status ?? "")).map(t => ({ name: t.FullName ?? t.Name, message: t.Message ?? "" }))
205
+ };
206
+ }
207
+
208
+ function runFailed(mode, message) {
209
+ return { ok: false, source: "pipeline", mode, errors: [{ code: "TEST_RUN_FAILED", message: message ?? "test run failed" }] };
210
+ }
211
+
212
+ // Tests through a batch Editor (no live Editor on this project). Pins the project's own Editor version.
213
+ export async function testRunBatch(root, { mode = "EditMode", filter, timeout = 900, output } = {}) {
214
+ const version = editorVersion(root);
215
+ const xml = output || path.join(at(root, "measurements"), `${mode.toLowerCase()}-results.xml`);
216
+ const args = ["test", root, "--mode", mode, "--output", xml, "--timeout", String(timeout)];
217
+ if (filter) args.push("--filter", filter);
218
+ if (version) args.push("--editor-version", version);
219
+ const r = await cli(args, { timeoutMs: (timeout + 120) * 1000 });
220
+ const errorCode = r.json?.errors?.[0]?.code ?? null;
221
+ if (!exists(xml)) return { ok: false, source: "batch", exitCode: r.code, errorCode, errors: r.json?.errors ?? [{ message: r.stderr.slice(0, 400) }] };
222
+ return { ok: true, source: "batch", exitCode: r.code, mode, xml, ...parseNUnit(fs.readFileSync(xml, "utf8")) };
223
+ }
224
+
225
+ // Minimal NUnit3 reader: run-level counts plus failed test cases. No XML dependency.
226
+ export function parseNUnit(text) {
227
+ const attr = (tag, name) => Number(text.match(new RegExp(`<${tag}[^>]*\\b${name}="(\\d+)"`))?.[1] ?? 0);
228
+ const failures = [];
229
+ for (const m of text.matchAll(/<test-case\b([^>]*)result="Failed"([^>]*)>([\s\S]*?)<\/test-case>/g)) {
230
+ const head = m[1] + m[2];
231
+ const name = head.match(/fullname="([^"]*)"/)?.[1] ?? head.match(/name="([^"]*)"/)?.[1] ?? "unknown";
232
+ const message = m[3].match(/<message><!\[CDATA\[([\s\S]*?)\]\]><\/message>/)?.[1]?.trim() ?? "";
233
+ failures.push({ name, message: message.slice(0, 500) });
234
+ }
235
+ return { total: attr("test-run", "total"), passed: attr("test-run", "passed"), failed: attr("test-run", "failed"), skipped: attr("test-run", "skipped"), inconclusive: attr("test-run", "inconclusive"), durationSec: Number(text.match(/<test-run[^>]*duration="([\d.]+)"/)?.[1] ?? 0), failures };
236
+ }
237
+
238
+ // Pick live when the project has a ready Editor, batch otherwise. A run that executed no test is not ok:
239
+ // it means a wrong mode/filter or a run that never started, and must not read as a green 0/0.
240
+ export async function testRun(root, options = {}) {
241
+ const live = await instanceFor(root);
242
+ const r = live?.state === "ready"
243
+ ? await testRunLive(root, { mode: options.mode === "PlayMode" ? "playmode" : options.mode === "EditMode" ? "editor" : (options.mode ?? "editor"), filter: options.filter, timeout: options.timeout })
244
+ : await testRunBatch(root, { mode: options.mode === "playmode" ? "PlayMode" : options.mode === "editor" ? "EditMode" : (options.mode ?? "EditMode"), filter: options.filter, timeout: options.timeout, output: options.output });
245
+ if (r.ok && (r.total ?? 0) === 0) return { ...r, ok: false, errors: [...(r.errors ?? []), { code: "NO_TESTS_RAN", message: `no ${r.mode ?? "test"} tests ran${options.filter ? ` for filter '${options.filter}'` : ""}` }] };
246
+ return r;
247
+ }
248
+
249
+ // Observe: enter Play Mode, wait, optionally press keys, capture screenshots, read the console, exit.
250
+ // Keys are pressed once, after the first hold and before frame 1 (so frame 1 shows the reaction and frame 2
251
+ // the settled state); `keysEveryFrame` repeats them before every frame. `matchLog` (regex) counts the
252
+ // Play Mode log entries that match it, so a criterion can assert "one log line per press".
253
+ // Timed mode (`shotsMs` given): one `script eval` schedules everything inside the Editor on
254
+ // EditorApplication.update — key down at t=0 through the Input System's low-level queue, key up after
255
+ // `holdKeyMs`, ScreenCapture.CaptureScreenshot at each offset in `shotsMs`. Every Pipeline round trip costs
256
+ // ~0.9 s, so this is the only way to photograph anything that happens within a second of a key press
257
+ // (a 0.75 s jump landed before the first `capture screenshot` ever returned). Frames are the Game view's
258
+ // own size. Needs com.unity.inputsystem; with no keyboard device the eval returns "no keyboard".
259
+ const q = v => JSON.stringify(String(v));
260
+ function timedScript({ keys, holdKeyMs, shots, timingFile }) {
261
+ const K = "UnityEngine.InputSystem";
262
+ const keyList = keys.map(k => `${K}.Key.${k}`).join(", ");
263
+ const press = keys.length ? `${K}.InputSystem.QueueStateEvent(kb, new ${K}.LowLevel.KeyboardState(${keyList}));` : "";
264
+ const release = keys.length ? `if (!released && dt > ${(holdKeyMs / 1000).toFixed(3)}) { ${K}.InputSystem.QueueStateEvent(${K}.Keyboard.current, new ${K}.LowLevel.KeyboardState()); released = true; }` : "";
265
+ const captures = shots.map((sh, i) => `if (!done[${i}] && dt > ${(sh.ms / 1000).toFixed(3)}) { UnityEngine.ScreenCapture.CaptureScreenshot(${q(sh.path)}); done[${i}] = true; gameTimes[${i}] = UnityEngine.Time.timeSinceLevelLoad; }`).join(" ");
266
+ const last = ((shots.at(-1)?.ms ?? 0) / 1000 + 0.5).toFixed(3);
267
+ const timing = `System.IO.File.WriteAllText(${q(timingFile)}, g0.ToString("R", System.Globalization.CultureInfo.InvariantCulture) + ";" + string.Join(",", System.Array.ConvertAll(gameTimes, x => x.ToString("R", System.Globalization.CultureInfo.InvariantCulture))));`;
268
+ return `var kb = ${K}.Keyboard.current; if (kb == null) return "no keyboard"; ${press} double t0 = UnityEditor.EditorApplication.timeSinceStartup; float g0 = UnityEngine.Time.timeSinceLevelLoad; bool released = false; var done = new bool[${shots.length}]; var gameTimes = new float[${shots.length}]; UnityEditor.EditorApplication.CallbackFunction cb = null; cb = () => { double dt = UnityEditor.EditorApplication.timeSinceStartup - t0; ${release} ${captures} if (dt > ${last}) { UnityEditor.EditorApplication.update -= cb; ${timing} } }; UnityEditor.EditorApplication.update += cb; return "scheduled";`;
269
+ }
270
+
271
+ function parseTiming(text) {
272
+ const [g0, rest = ""] = text.trim().split(";");
273
+ const n = v => { const x = Number(v); return Number.isFinite(x) ? x : null; };
274
+ return { scheduledAtGameTime: n(g0), captureGameTimes: rest.split(",").map(n) };
275
+ }
276
+
277
+ async function waitForFiles(files, timeoutMs) {
278
+ const deadline = Date.now() + timeoutMs;
279
+ const sizes = {};
280
+ while (Date.now() < deadline) {
281
+ let ready = true;
282
+ for (const f of files) {
283
+ const size = exists(f) ? fs.statSync(f).size : 0;
284
+ if (!size || sizes[f] !== size) ready = false;
285
+ sizes[f] = size;
286
+ }
287
+ if (ready) return true;
288
+ await sleep(300);
289
+ }
290
+ return files.every(f => exists(f) && fs.statSync(f).size > 0);
291
+ }
292
+
293
+ export async function observe(root, { screensDir, frames = 2, keys = [], holdMs = 1500, width = 1280, height = 720, keysEveryFrame = false, matchLog = null, shotsMs = null, holdKeyMs = 60 } = {}) {
294
+ const outcome = { enteredPlayMode: false, exitedPlayMode: false, screens: [], keys: [], console: null, performance: null, errors: [], logMatches: null, timed: Array.isArray(shotsMs) && shotsMs.length > 0 };
295
+ const rib = await runInBackground(root, true);
296
+ outcome.runInBackground = rib.ok === true;
297
+ const mark = await consoleGet(root, { tail: 1, level: "log" }); // cursor: only entries after this count
298
+ const since = mark.ok ? { since: mark.result?.cursor, since_session: mark.result?.session } : {};
299
+ const started = await playStart(root);
300
+ if (!started.ok) { outcome.errors.push(started.errors?.[0]?.message ?? "play start failed"); return outcome; }
301
+ const playStartedAt = Date.now();
302
+ outcome.enteredPlayMode = true;
303
+ await sleep(holdMs);
304
+ if (outcome.timed) {
305
+ fs.mkdirSync(screensDir, { recursive: true });
306
+ const shots = shotsMs.map((ms, i) => ({ ms, path: path.resolve(screensDir, `${String(i + 1).padStart(2, "0")}.png`) }));
307
+ for (const sh of shots) { try { fs.rmSync(sh.path, { force: true }); } catch {} }
308
+ const timingFile = path.resolve(screensDir, "timing.json");
309
+ try { fs.rmSync(timingFile, { force: true }); } catch {}
310
+ const scheduledAt = Date.now();
311
+ const r = await evalCode(root, timedScript({ keys, holdKeyMs, shots, timingFile }));
312
+ outcome.scheduleDelayMs = Date.now() - playStartedAt; // wall-clock from Play start to the schedule: frames are atMs after this
313
+ const value = r.result?.result ?? r.result;
314
+ if (!r.ok || value !== "scheduled") outcome.errors.push(`timed observe not scheduled: ${r.errors?.[0]?.message ?? JSON.stringify(value).slice(0, 200)}`);
315
+ else {
316
+ for (const key of keys) outcome.keys.push({ key, ok: true, holdMs: holdKeyMs });
317
+ const ok = await waitForFiles([...shots.map(s => s.path), timingFile], (shots.at(-1).ms) + 8000);
318
+ const timing = exists(timingFile) ? parseTiming(fs.readFileSync(timingFile, "utf8")) : null;
319
+ outcome.scheduledAtGameTime = timing?.scheduledAtGameTime ?? null;
320
+ for (const [i, sh] of shots.entries()) outcome.screens.push({ path: exists(sh.path) ? sh.path : null, bytes: exists(sh.path) ? fs.statSync(sh.path).size : 0, ok: exists(sh.path), atMs: sh.ms, gameTimeS: timing?.captureGameTimes?.[i] ?? null });
321
+ if (!ok) outcome.errors.push("timed observe: not every screenshot was written");
322
+ }
323
+ }
324
+ for (let i = 0; !outcome.timed && i < Math.max(1, frames); i++) {
325
+ if (i === 0 || keysEveryFrame) {
326
+ for (const key of keys) { const k = await simulateKey(root, key); outcome.keys.push({ key, ok: k.ok }); }
327
+ if (keys.length) await sleep(400);
328
+ }
329
+ const file = path.join(screensDir, `${String(i + 1).padStart(2, "0")}.png`);
330
+ const shot = await screenshot(root, file, { width, height });
331
+ outcome.screens.push({ path: shot.path, bytes: shot.bytes, ok: shot.ok });
332
+ if (!shot.ok) outcome.errors.push(shot.errors?.[0]?.message ?? "screenshot failed");
333
+ if (i + 1 < frames) await sleep(holdMs);
334
+ }
335
+ const perf = await performance(root);
336
+ if (perf.ok) outcome.performance = perf.result;
337
+ const con = await consoleGet(root, { tail: 50, level: "warn" });
338
+ if (con.ok) outcome.console = { counts: con.result?.counts ?? null, groundTruth: con.result?.groundTruth ?? null, entries: (con.result?.entries ?? []).slice(-20) };
339
+ if (matchLog) {
340
+ const all = await consoleGet(root, { tail: 500, level: "log", ...since });
341
+ const re = new RegExp(matchLog);
342
+ outcome.logMatches = all.ok ? (all.result?.entries ?? []).filter(e => re.test(e.message ?? e.text ?? "")).length : null;
343
+ }
344
+ const stopped = await playStop(root);
345
+ outcome.exitedPlayMode = stopped.ok;
346
+ if (!stopped.ok) outcome.errors.push(stopped.errors?.[0]?.message ?? "play stop failed");
347
+ await waitReady(root, { timeoutMs: 10000 });
348
+ if (outcome.runInBackground) await runInBackground(root, false);
349
+ return outcome;
350
+ }
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ // Windows only: prove the Job Object kills a re-parented grandchild that taskkill /T would miss.
3
+ // Spawns powershell -> Start-Process ping (detached), terminates the tree, counts ping.exe before/after.
4
+ import { spawnSync } from "node:child_process";
5
+ import { run, terminate } from "../runtime/core/process-tree.mjs";
6
+ import { available } from "../runtime/core/job-object.mjs";
7
+ const count = () => (spawnSync("tasklist", ["/FI", "IMAGENAME eq ping.exe", "/NH"], { encoding: "utf8" }).stdout.match(/ping\.exe/gi) || []).length;
8
+ const before = count();
9
+ const jobAvailable = await available();
10
+ const started = run("powershell", ["-NoProfile", "-Command", "Start-Process ping -ArgumentList '-n','40','127.0.0.1' -WindowStyle Hidden; Start-Sleep 40"], { timeoutMs: 6000 });
11
+ await new Promise(r => setTimeout(r, 3500));
12
+ const during = count();
13
+ const result = await started; // the timeout fires terminate() with the job
14
+ await new Promise(r => setTimeout(r, 1000));
15
+ const after = count();
16
+ const out = { jobAvailable, jobObject: result.jobObject, timedOut: result.timedOut, pingBefore: before, pingDuring: during, pingAfter: after, grandchildKilled: during > before && after <= before };
17
+ console.log(JSON.stringify(out, null, 2));
18
+ process.exit(out.grandchildKilled ? 0 : 1);