@ohos-ports/nx 23.3.0-beta.3 → 24.0.2-beta.0

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 (301) hide show
  1. package/README.md +3 -6
  2. package/dist/bin/nx.js +2 -2
  3. package/dist/bin/post-install.js +5 -3
  4. package/dist/nx-cloud-internals.d.ts +1 -0
  5. package/dist/nx-cloud-internals.js +7 -1
  6. package/dist/src/adapter/ngcli-adapter.js +2 -2
  7. package/dist/src/ai/clone-ai-config-repo.js +49 -12
  8. package/dist/src/command-line/add/add.d.ts +2 -0
  9. package/dist/src/command-line/add/add.js +24 -6
  10. package/dist/src/command-line/affected/affected.js +2 -2
  11. package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +1 -13
  12. package/dist/src/command-line/graph/graph.js +38 -10
  13. package/dist/src/command-line/init/command-object.js +0 -12
  14. package/dist/src/command-line/init/implementation/add-nx-to-nest.js +21 -62
  15. package/dist/src/command-line/init/init-v2.js +1 -3
  16. package/dist/src/command-line/migrate/agentic/close-agent-session.d.ts +1 -1
  17. package/dist/src/command-line/migrate/agentic/close-agent-session.js +4 -3
  18. package/dist/src/command-line/migrate/agentic/definitions.d.ts +1 -3
  19. package/dist/src/command-line/migrate/agentic/definitions.js +5 -97
  20. package/dist/src/command-line/migrate/agentic/handoff-gitignore.d.ts +7 -45
  21. package/dist/src/command-line/migrate/agentic/handoff-gitignore.js +9 -51
  22. package/dist/src/command-line/migrate/agentic/handoff.d.ts +1 -43
  23. package/dist/src/command-line/migrate/agentic/handoff.js +3 -119
  24. package/dist/src/command-line/migrate/agentic/instruction-files.d.ts +0 -21
  25. package/dist/src/command-line/migrate/agentic/instruction-files.js +6 -27
  26. package/dist/src/command-line/migrate/agentic/master/run-master-session.d.ts +19 -1
  27. package/dist/src/command-line/migrate/agentic/master/run-master-session.js +88 -49
  28. package/dist/src/command-line/migrate/agentic/master/spawn-master.d.ts +1 -0
  29. package/dist/src/command-line/migrate/agentic/master/spawn-master.js +86 -24
  30. package/dist/src/command-line/migrate/agentic/print-dropped-agent-context.d.ts +2 -2
  31. package/dist/src/command-line/migrate/agentic/print-dropped-agent-context.js +2 -2
  32. package/dist/src/command-line/migrate/agentic/prompts/fragments.d.ts +1 -12
  33. package/dist/src/command-line/migrate/agentic/prompts/fragments.js +8 -17
  34. package/dist/src/command-line/migrate/agentic/prompts/shared-rendering.d.ts +0 -15
  35. package/dist/src/command-line/migrate/agentic/prompts/shared-rendering.js +3 -100
  36. package/dist/src/command-line/migrate/agentic/select.d.ts +12 -6
  37. package/dist/src/command-line/migrate/agentic/select.js +38 -8
  38. package/dist/src/command-line/migrate/agentic/types.d.ts +9 -68
  39. package/dist/src/command-line/migrate/agentic/types.js +6 -8
  40. package/dist/src/command-line/migrate/command-object.js +6 -10
  41. package/dist/src/command-line/migrate/execute-migration.d.ts +6 -6
  42. package/dist/src/command-line/migrate/execute-migration.js +25 -15
  43. package/dist/src/command-line/migrate/migrate-analytics.d.ts +18 -1
  44. package/dist/src/command-line/migrate/migrate-analytics.js +38 -0
  45. package/dist/src/command-line/migrate/migrate-commits.d.ts +29 -17
  46. package/dist/src/command-line/migrate/migrate-commits.js +86 -32
  47. package/dist/src/command-line/migrate/migrate-config.d.ts +6 -6
  48. package/dist/src/command-line/migrate/migrate-config.js +12 -6
  49. package/dist/src/command-line/migrate/migrate-output.d.ts +1 -6
  50. package/dist/src/command-line/migrate/migrate-output.js +1 -10
  51. package/dist/src/command-line/migrate/migrate.d.ts +6 -30
  52. package/dist/src/command-line/migrate/migrate.js +257 -255
  53. package/dist/src/command-line/migrate/resolve-package-version.d.ts +2 -4
  54. package/dist/src/command-line/migrate/resolve-package-version.js +2 -35
  55. package/dist/src/command-line/migrate/run/agent-output.d.ts +2 -0
  56. package/dist/src/command-line/migrate/run/agent-output.js +9 -0
  57. package/dist/src/command-line/migrate/run/broker.d.ts +14 -5
  58. package/dist/src/command-line/migrate/run/broker.js +69 -21
  59. package/dist/src/command-line/migrate/run/clean-retry.js +4 -2
  60. package/dist/src/command-line/migrate/run/existing-run-report.d.ts +1 -1
  61. package/dist/src/command-line/migrate/run/existing-run-report.js +7 -5
  62. package/dist/src/command-line/migrate/run/give-up.d.ts +4 -3
  63. package/dist/src/command-line/migrate/run/give-up.js +8 -5
  64. package/dist/src/command-line/migrate/run/index.d.ts +7 -5
  65. package/dist/src/command-line/migrate/run/index.js +16 -1
  66. package/dist/src/command-line/migrate/run/issues.js +7 -2
  67. package/dist/src/command-line/migrate/run/orchestrator.d.ts +61 -4
  68. package/dist/src/command-line/migrate/run/orchestrator.js +397 -133
  69. package/dist/src/command-line/migrate/run/run-state.d.ts +17 -1
  70. package/dist/src/command-line/migrate/run/run-state.js +41 -2
  71. package/dist/src/command-line/migrate/run/runbook.d.ts +1 -0
  72. package/dist/src/command-line/migrate/run/runbook.js +115 -18
  73. package/dist/src/command-line/migrate/run/state-lock.d.ts +29 -4
  74. package/dist/src/command-line/migrate/run/state-lock.js +102 -10
  75. package/dist/src/command-line/migrate/run/state-machine.js +4 -1
  76. package/dist/src/command-line/migrate/run/util.d.ts +7 -2
  77. package/dist/src/command-line/migrate/run/util.js +30 -3
  78. package/dist/src/command-line/migrate/run/worker.d.ts +1 -1
  79. package/dist/src/command-line/migrate/run/worker.js +252 -250
  80. package/dist/src/command-line/migrate/sort-migrations.d.ts +1 -7
  81. package/dist/src/command-line/migrate/sort-migrations.js +1 -12
  82. package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +17 -12
  83. package/dist/src/command-line/release/changelog.js +9 -6
  84. package/dist/src/command-line/release/config/config.d.ts +1 -0
  85. package/dist/src/command-line/release/config/config.js +48 -9
  86. package/dist/src/command-line/release/release.js +2 -2
  87. package/dist/src/command-line/release/utils/git.js +8 -7
  88. package/dist/src/command-line/release/utils/interpolate-release-config.d.ts +6 -0
  89. package/dist/src/command-line/release/utils/interpolate-release-config.js +39 -0
  90. package/dist/src/command-line/release/utils/release-graph.d.ts +5 -0
  91. package/dist/src/command-line/release/utils/release-graph.js +1 -0
  92. package/dist/src/command-line/release/utils/remote-release-clients/github.js +5 -6
  93. package/dist/src/command-line/release/utils/remote-release-clients/gitlab.js +5 -6
  94. package/dist/src/command-line/release/utils/resolve-changelog-renderer.js +2 -2
  95. package/dist/src/command-line/release/utils/shared.js +10 -10
  96. package/dist/src/command-line/release/utils/version-plan-utils.d.ts +3 -12
  97. package/dist/src/command-line/release/utils/version-plan-utils.js +33 -55
  98. package/dist/src/command-line/release/version/version-actions.js +2 -1
  99. package/dist/src/command-line/release/version.js +5 -2
  100. package/dist/src/command-line/repair/repair.js +1 -1
  101. package/dist/src/command-line/report/report.js +1 -1
  102. package/dist/src/command-line/reset/reset.js +58 -62
  103. package/dist/src/command-line/run/executor-utils.js +1 -0
  104. package/dist/src/command-line/run/run-one.d.ts +20 -1
  105. package/dist/src/command-line/run/run-one.js +153 -5
  106. package/dist/src/command-line/run/run.js +2 -1
  107. package/dist/src/command-line/show/projects.js +5 -3
  108. package/dist/src/command-line/show/show-target/info.d.ts +10 -1
  109. package/dist/src/command-line/show/show-target/info.js +100 -15
  110. package/dist/src/command-line/show/show-target/inputs.js +10 -2
  111. package/dist/src/command-line/show/show-target/utils.js +1 -0
  112. package/dist/src/command-line/watch/command-object.js +3 -22
  113. package/dist/src/command-line/watch/watch.d.ts +0 -7
  114. package/dist/src/command-line/watch/watch.js +1 -1
  115. package/dist/src/command-line/yargs-utils/shared-options.js +1 -1
  116. package/dist/src/config/misc-interfaces.d.ts +18 -0
  117. package/dist/src/config/nx-json.d.ts +22 -11
  118. package/dist/src/config/task-graph.d.ts +1 -1
  119. package/dist/src/config/workspace-json-project-json.d.ts +88 -0
  120. package/dist/src/core/graph/main.js +1 -1
  121. package/dist/src/core/graph/runtime.js +1 -1
  122. package/dist/src/core/graph/styles.css +1 -1
  123. package/dist/src/core/graph/styles.js +1 -1
  124. package/dist/src/daemon/client/client.d.ts +15 -0
  125. package/dist/src/daemon/client/client.js +181 -71
  126. package/dist/src/daemon/client/daemon-socket-messenger.d.ts +1 -1
  127. package/dist/src/daemon/client/daemon-socket-messenger.js +2 -2
  128. package/dist/src/daemon/server/client-socket-context.js +1 -1
  129. package/dist/src/daemon/server/handle-configure-ai-agents.js +2 -2
  130. package/dist/src/daemon/server/handle-process-in-background.js +21 -9
  131. package/dist/src/daemon/server/handle-select-affected-tasks.d.ts +2 -1
  132. package/dist/src/daemon/server/handle-select-affected-tasks.js +21 -13
  133. package/dist/src/daemon/server/native-js-loader.d.ts +1 -0
  134. package/dist/src/daemon/server/native-js-loader.js +21 -0
  135. package/dist/src/daemon/server/nx-console-operations.js +2 -2
  136. package/dist/src/daemon/server/project-graph-incremental-recomputation.js +4 -8
  137. package/dist/src/daemon/server/server.d.ts +2 -1
  138. package/dist/src/daemon/server/server.js +17 -4
  139. package/dist/src/daemon/server/shutdown-utils.js +1 -4
  140. package/dist/src/daemon/server/start.d.ts +1 -0
  141. package/dist/src/daemon/server/start.js +2 -0
  142. package/dist/src/daemon/socket-utils.d.ts +11 -9
  143. package/dist/src/daemon/socket-utils.js +31 -61
  144. package/dist/src/daemon/tmp-dir.d.ts +19 -14
  145. package/dist/src/daemon/tmp-dir.js +142 -354
  146. package/dist/src/devkit-exports.d.ts +9 -1
  147. package/dist/src/devkit-exports.js +12 -1
  148. package/dist/src/devkit-internals.d.ts +6 -5
  149. package/dist/src/devkit-internals.js +13 -4
  150. package/dist/src/executors/run-commands/run-commands.impl.d.ts +4 -2
  151. package/dist/src/executors/run-commands/run-commands.impl.js +53 -27
  152. package/dist/src/executors/run-commands/running-tasks.d.ts +13 -4
  153. package/dist/src/executors/run-commands/running-tasks.js +148 -95
  154. package/dist/src/executors/run-commands/schema.json +1 -1
  155. package/dist/src/hasher/check-task-files.js +5 -8
  156. package/dist/src/hasher/file-hasher.js +9 -7
  157. package/dist/src/hasher/hash-task.js +4 -0
  158. package/dist/src/hasher/native-task-hasher-impl.js +3 -1
  159. package/dist/src/hasher/task-hasher.d.ts +2 -0
  160. package/dist/src/hasher/task-hasher.js +4 -1
  161. package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.d.ts +1 -2
  162. package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.js +6 -11
  163. package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.md +2 -2
  164. package/dist/src/migrations/update-24-0-0/enable-legacy-affected.d.ts +4 -0
  165. package/dist/src/migrations/update-24-0-0/enable-legacy-affected.js +15 -0
  166. package/dist/src/migrations/update-24-0-0/enable-legacy-affected.md +30 -0
  167. package/dist/src/migrations/update-24-0-0/find-custom-hashers.d.ts +3 -0
  168. package/dist/src/migrations/update-24-0-0/find-custom-hashers.js +100 -0
  169. package/dist/src/migrations/update-24-0-0/find-custom-hashers.md +63 -0
  170. package/dist/src/migrations/update-24-0-0/move-custom-hashers-to-inputs.md +146 -0
  171. package/dist/src/migrations/update-24-0-0/rename-ultracache-ignore-options.d.ts +3 -0
  172. package/dist/src/migrations/update-24-0-0/rename-ultracache-ignore-options.js +130 -0
  173. package/dist/src/migrations/update-24-0-0/rename-ultracache-ignore-options.md +62 -0
  174. package/dist/src/native/assert-supported-platform.js +1 -1
  175. package/dist/src/native/index.d.ts +238 -18
  176. package/dist/src/native/index.js +6 -1
  177. package/dist/src/native/native-bindings.js +15 -6
  178. package/dist/src/native/nx.openharmony-arm64.node +0 -0
  179. package/dist/src/native/nx.wasi-browser.js +1 -0
  180. package/dist/src/native/nx.wasi.cjs +1 -0
  181. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  182. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  183. package/dist/src/nx-cloud/update-manager.d.ts +14 -0
  184. package/dist/src/nx-cloud/update-manager.js +237 -50
  185. package/dist/src/plugins/js/lock-file/pnpm-parser.js +3 -4
  186. package/dist/src/plugins/js/lock-file/pruned-output.d.ts +2 -2
  187. package/dist/src/plugins/js/lock-file/pruned-output.js +4 -4
  188. package/dist/src/plugins/js/utils/register.js +9 -4
  189. package/dist/src/project-graph/affected/affected-tasks.d.ts +9 -6
  190. package/dist/src/project-graph/affected/affected-tasks.js +31 -7
  191. package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +7 -0
  192. package/dist/src/project-graph/plugins/loaded-nx-plugin.js +49 -6
  193. package/dist/src/project-graph/utils/project-configuration/target-merging.js +1 -1
  194. package/dist/src/project-graph/utils/project-configuration/target-normalization.js +12 -133
  195. package/dist/src/project-graph/utils/project-configuration-utils.js +2 -2
  196. package/dist/src/project-graph/utils/retrieve-workspace-files.js +27 -6
  197. package/dist/src/tasks-runner/create-task-graph.d.ts +8 -7
  198. package/dist/src/tasks-runner/create-task-graph.js +50 -27
  199. package/dist/src/tasks-runner/default-tasks-runner.js +1 -1
  200. package/dist/src/tasks-runner/init-tasks-runner.js +116 -59
  201. package/dist/src/tasks-runner/life-cycle.d.ts +4 -1
  202. package/dist/src/tasks-runner/life-cycle.js +7 -0
  203. package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +5 -2
  204. package/dist/src/tasks-runner/life-cycles/performance-analysis.js +13 -7
  205. package/dist/src/tasks-runner/life-cycles/performance-life-cycle.d.ts +4 -3
  206. package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +9 -2
  207. package/dist/src/tasks-runner/life-cycles/static-run-many-terminal-output-life-cycle.js +5 -2
  208. package/dist/src/tasks-runner/life-cycles/static-run-one-terminal-output-life-cycle.js +4 -1
  209. package/dist/src/tasks-runner/life-cycles/summary-terminal-output-life-cycle.js +5 -1
  210. package/dist/src/tasks-runner/pseudo-ipc.d.ts +1 -0
  211. package/dist/src/tasks-runner/pseudo-ipc.js +4 -0
  212. package/dist/src/tasks-runner/pseudo-terminal.d.ts +1 -1
  213. package/dist/src/tasks-runner/pseudo-terminal.js +2 -2
  214. package/dist/src/tasks-runner/readiness/executor-ready-signal.d.ts +7 -0
  215. package/dist/src/tasks-runner/readiness/executor-ready-signal.js +43 -0
  216. package/dist/src/tasks-runner/readiness/probes.d.ts +14 -0
  217. package/dist/src/tasks-runner/readiness/probes.js +101 -0
  218. package/dist/src/tasks-runner/readiness/ready-signal-server.d.ts +18 -0
  219. package/dist/src/tasks-runner/readiness/ready-signal-server.js +72 -0
  220. package/dist/src/tasks-runner/readiness/ready-signal.d.ts +33 -0
  221. package/dist/src/tasks-runner/readiness/ready-signal.js +92 -0
  222. package/dist/src/tasks-runner/readiness/ready-when.d.ts +84 -0
  223. package/dist/src/tasks-runner/readiness/ready-when.js +371 -0
  224. package/dist/src/tasks-runner/readiness/startup-host.d.ts +1 -0
  225. package/dist/src/tasks-runner/readiness/startup-host.js +32 -0
  226. package/dist/src/tasks-runner/run-command.d.ts +6 -2
  227. package/dist/src/tasks-runner/run-command.js +21 -31
  228. package/dist/src/tasks-runner/run-outcome.d.ts +16 -0
  229. package/dist/src/tasks-runner/run-outcome.js +39 -0
  230. package/dist/src/tasks-runner/running-tasks/batch-process.js +6 -0
  231. package/dist/src/tasks-runner/running-tasks/node-child-process.js +5 -4
  232. package/dist/src/tasks-runner/running-tasks/running-task.d.ts +1 -1
  233. package/dist/src/tasks-runner/task-env.d.ts +10 -0
  234. package/dist/src/tasks-runner/task-env.js +37 -5
  235. package/dist/src/tasks-runner/task-graph-utils.d.ts +9 -6
  236. package/dist/src/tasks-runner/task-graph-utils.js +17 -12
  237. package/dist/src/tasks-runner/task-orchestrator.d.ts +62 -18
  238. package/dist/src/tasks-runner/task-orchestrator.js +750 -150
  239. package/dist/src/tasks-runner/tasks-runner.d.ts +1 -1
  240. package/dist/src/tasks-runner/tasks-schedule.d.ts +18 -1
  241. package/dist/src/tasks-runner/tasks-schedule.js +63 -6
  242. package/dist/src/tasks-runner/tokens.d.ts +54 -0
  243. package/dist/src/tasks-runner/tokens.js +161 -0
  244. package/dist/src/tasks-runner/utils.d.ts +35 -6
  245. package/dist/src/tasks-runner/utils.js +149 -68
  246. package/dist/src/ultracache/ci-status.d.ts +24 -0
  247. package/dist/src/ultracache/ci-status.js +87 -0
  248. package/dist/src/ultracache/config.d.ts +3 -3
  249. package/dist/src/ultracache/config.js +4 -4
  250. package/dist/src/ultracache/fetch.d.ts +2 -0
  251. package/dist/src/ultracache/fetch.js +1 -0
  252. package/dist/src/ultracache/overrides.js +4 -0
  253. package/dist/src/utils/acknowledge-build-scripts.d.ts +34 -1
  254. package/dist/src/utils/acknowledge-build-scripts.js +171 -32
  255. package/dist/src/utils/child-process.js +2 -1
  256. package/dist/src/utils/find-matching-projects.js +9 -6
  257. package/dist/src/utils/git-utils.d.ts +12 -5
  258. package/dist/src/utils/git-utils.js +69 -11
  259. package/dist/src/utils/long-running-target.d.ts +1 -3
  260. package/dist/src/utils/long-running-target.js +1 -3
  261. package/dist/src/utils/nx-package-group.js +1 -1
  262. package/dist/src/utils/output.d.ts +4 -3
  263. package/dist/src/utils/output.js +4 -3
  264. package/dist/src/utils/owned-private-dir.d.ts +1 -17
  265. package/dist/src/utils/owned-private-dir.js +10 -62
  266. package/dist/src/utils/package-json.js +1 -1
  267. package/dist/src/utils/package-manager.d.ts +8 -0
  268. package/dist/src/utils/package-manager.js +65 -8
  269. package/dist/src/utils/perf-hooks.d.ts +1 -0
  270. package/dist/src/utils/plugins/core-plugins.js +5 -1
  271. package/dist/src/utils/progress-topics.d.ts +1 -0
  272. package/dist/src/utils/progress-topics.js +1 -0
  273. package/dist/src/utils/safe-spawn.js +1 -5
  274. package/dist/src/utils/shell-quoting.d.ts +8 -1
  275. package/dist/src/utils/shell-quoting.js +12 -1
  276. package/dist/src/utils/spawn-without-terminal.d.ts +27 -0
  277. package/dist/src/utils/spawn-without-terminal.js +110 -0
  278. package/dist/src/utils/string-similarity.d.ts +38 -0
  279. package/dist/src/utils/string-similarity.js +108 -0
  280. package/dist/src/utils/versions.js +1 -1
  281. package/migrations.json +20 -6
  282. package/package.json +15 -24
  283. package/schemas/migrations-schema.json +11 -0
  284. package/schemas/nx-schema.json +133 -11
  285. package/schemas/project-schema.json +114 -4
  286. package/dist/src/command-line/migrate/agentic/prompts/generic-validation.d.ts +0 -56
  287. package/dist/src/command-line/migrate/agentic/prompts/generic-validation.js +0 -67
  288. package/dist/src/command-line/migrate/agentic/prompts/hybrid-prompt-migration.d.ts +0 -49
  289. package/dist/src/command-line/migrate/agentic/prompts/hybrid-prompt-migration.js +0 -53
  290. package/dist/src/command-line/migrate/agentic/prompts/prompt-migration.d.ts +0 -26
  291. package/dist/src/command-line/migrate/agentic/prompts/prompt-migration.js +0 -27
  292. package/dist/src/command-line/migrate/agentic/prompts/system-prompt.d.ts +0 -70
  293. package/dist/src/command-line/migrate/agentic/prompts/system-prompt.js +0 -113
  294. package/dist/src/command-line/migrate/agentic/run-step.d.ts +0 -68
  295. package/dist/src/command-line/migrate/agentic/run-step.js +0 -148
  296. package/dist/src/command-line/migrate/agentic/runner.d.ts +0 -21
  297. package/dist/src/command-line/migrate/agentic/runner.js +0 -262
  298. package/dist/src/migrations/update-23-0-0/consolidate-release-tag-config.d.ts +0 -9
  299. package/dist/src/migrations/update-23-0-0/consolidate-release-tag-config.js +0 -18
  300. package/dist/src/tasks-runner/legacy-depends-on-warning.d.ts +0 -18
  301. package/dist/src/tasks-runner/legacy-depends-on-warning.js +0 -109
@@ -5,7 +5,16 @@ exports.runOrchestratorResume = runOrchestratorResume;
5
5
  exports.holdRunToContinue = holdRunToContinue;
6
6
  exports.activeRunToReplace = activeRunToReplace;
7
7
  exports.releaseRunToHandOff = releaseRunToHandOff;
8
+ exports.activeRunForClassic = activeRunForClassic;
9
+ exports.checkRunForStartFresh = checkRunForStartFresh;
10
+ exports.deleteRunForStartFresh = deleteRunForStartFresh;
11
+ exports.refuseNewRunAlongsideLiveRun = refuseNewRunAlongsideLiveRun;
12
+ exports.checkRunsForSingleMigration = checkRunsForSingleMigration;
13
+ exports.prepareSingleMigrationRun = prepareSingleMigrationRun;
14
+ exports.removeSingleMigrationRun = removeSingleMigrationRun;
8
15
  exports.runOrchestratorReconcile = runOrchestratorReconcile;
16
+ exports.dispenseSingleMigrationStep = dispenseSingleMigrationStep;
17
+ exports.lastCoveringLandedEntry = lastCoveringLandedEntry;
9
18
  exports.completionWarnings = completionWarnings;
10
19
  const child_process_1 = require("child_process");
11
20
  const crypto_1 = require("crypto");
@@ -42,7 +51,7 @@ const issues_1 = require("./issues");
42
51
  const runbook_1 = require("./runbook");
43
52
  const existing_run_report_1 = require("./existing-run-report");
44
53
  const package_manager_1 = require("../../../utils/package-manager");
45
- // The dark migrate orchestrator: drives a durable run one dispense at a time.
54
+ // The migrate orchestrator: drives a durable run one dispense at a time.
46
55
  // An outer AI agent runs each dispensed command and re-invokes `nx migrate
47
56
  // --run-id=<id>` to reconcile; there is no long-lived process.
48
57
  const PLAN_SNAPSHOT_0 = 'plan-0.json';
@@ -55,7 +64,11 @@ const HANG_THRESHOLD_MS = 15 * 60 * 1000;
55
64
  const NO_PROGRESS_THRESHOLD = 3;
56
65
  // Two rearms per step: one for the diagnosed fix and one for its correction.
57
66
  const REARM_ESCALATION_CAP = 2;
58
- const INIT_CONTINUE_HINT = 're-run the command, or unset NX_MIGRATE_ORCHESTRATOR to use the standard migrate flow.';
67
+ const INIT_CONTINUE_HINT = 're-run the command, or re-run it with --agentic=false to use the standard migrate flow.';
68
+ const ID_PROBLEM_REFUSALS = {
69
+ 'not-shell-safe': 'contains characters that are not shell-safe. Orchestrated runs require shell-safe migration ids.',
70
+ duplicate: 'is listed more than once in the plan. Orchestrated runs require each migration id once.',
71
+ };
59
72
  function continueRunHint(runId) {
60
73
  return `re-run the command to continue run '${runId}'.`;
61
74
  }
@@ -90,20 +103,20 @@ function refuseUnsafeScratchExposure(exposure, thenWhat) {
90
103
  }
91
104
  }
92
105
  async function runOrchestratorInit(input) {
93
- const { root, migrationsJson, migrationsPath, createCommits, commitPrefix, skipInstall, installedNxVersion, validate, finalValidation, emitAgentInstructions = true, onExistingRun = 'report', replaceRunId, confirmStart, } = input;
106
+ const { root, migrationsJson, migrationsPath, createCommits, commitPrefix, skipInstall, installedNxVersion, validate, finalValidation, emitAgentInstructions = true, onExistingRun = 'report', replaceRunId, confirmStart, singleMigration = false, } = input;
94
107
  const migrations = (migrationsJson.migrations ?? []);
95
- const sorted = (0, sort_migrations_1.sortMigrations)(migrations.slice(), {
96
- hoistHandoffGitignore: true,
97
- });
108
+ const sorted = (0, sort_migrations_1.sortMigrations)(migrations.slice());
98
109
  const plannedIds = sorted.map((m) => `${m.package}:${m.name}`);
99
110
  // An active run means a prior init already happened (e.g. it crashed before
100
111
  // the agent's first reconcile); starting a second run would compete with it,
101
112
  // and whether to continue it or replace it is the user's call, not a guess
102
113
  // from the plans. Reported before the migration id check below: this plan
103
114
  // is not being started. NewerRunStateFormatError propagates.
104
- const active = onExistingRun === 'start-fresh'
105
- ? activeRunToReplace(root, replaceRunId)
106
- : findActiveRunForInit(root);
115
+ const active = singleMigration
116
+ ? null
117
+ : onExistingRun === 'start-fresh'
118
+ ? activeRunToReplace(root, replaceRunId)
119
+ : findActiveRunForInit(root);
107
120
  if (active &&
108
121
  (onExistingRun === 'report' ||
109
122
  (replaceRunId !== undefined && active.runId !== replaceRunId))) {
@@ -118,19 +131,19 @@ async function runOrchestratorInit(input) {
118
131
  }
119
132
  if ((0, fs_1.existsSync)((0, run_state_1.migrateRunsDir)(root))) {
120
133
  (0, state_lock_1.withRunCreationLock)(root, () => {
121
- refuseLiveReservation(root);
134
+ refuseNewRunAlongsideLiveRun(root, singleMigration);
122
135
  if (active) {
123
136
  refuseUndeletableRun(root, active.runId, active.state);
124
137
  }
125
138
  });
126
139
  }
127
- // Dispensed commands interpolate migration ids verbatim, so every init
128
- // validates the incoming plan's ids. Before the delete below: a run must
129
- // not be thrown away for a plan that cannot start.
130
- for (const id of plannedIds) {
131
- if (!run_state_1.SHELL_SAFE_VALUE.test(id)) {
132
- throw new Error(`The migration id '${id}' contains characters that are not shell-safe. Orchestrated runs require shell-safe migration ids.`);
133
- }
140
+ // Dispensed commands interpolate migration ids verbatim and a worker finds
141
+ // its step by id, so every init validates the incoming plan's ids. Before
142
+ // the delete below: a run must not be thrown away for a plan that cannot
143
+ // start.
144
+ const [idProblem] = (0, run_state_1.migrationIdProblems)(sorted);
145
+ if (idProblem !== undefined) {
146
+ throw new Error(`The migration id '${idProblem.id}' ${ID_PROBLEM_REFUSALS[idProblem.problem]} To run the plan without orchestration, re-run with --agentic=false.`);
134
147
  }
135
148
  if (confirmStart && !(await confirmStart())) {
136
149
  return { kind: 'refused' };
@@ -150,19 +163,11 @@ async function runOrchestratorInit(input) {
150
163
  refuseUnsafeScratchExposure(scratchExposure, INIT_CONTINUE_HINT);
151
164
  }
152
165
  // Applied before the checkpoint so the entry (when it can be added) already
153
- // covers older scratch by the time the checkpoint's `git add -A` runs; the
154
- // fallback's standalone commit is suppressed because that checkpoint
155
- // carries the edit. Unlike the classic loop, a planned ignore migration
156
- // can't be deferred to: the run dir is created below, before that
157
- // migration runs.
166
+ // covers older scratch by the time the checkpoint's `git add -A` runs.
158
167
  await (0, handoff_gitignore_1.applyAgenticHandoffGitignoreFallback)({
159
168
  migrations: sorted,
160
169
  installedNxVersion,
161
- effectiveCreateCommits: createCommits,
162
- commitPrefix,
163
170
  root,
164
- applyWhenPlanned: true,
165
- commitStandalone: false,
166
171
  });
167
172
  if (scratchExposure === 'unignored') {
168
173
  // The fallback was the workspace's last chance at ignore coverage;
@@ -176,17 +181,21 @@ async function runOrchestratorInit(input) {
176
181
  // migrate would wait on this process. The .gitignore fallback is idempotent.
177
182
  let deleted = false;
178
183
  const reserved = (0, state_lock_1.withRunCreationLock)(root, () => {
179
- refuseLiveReservation(root);
180
- const nowActive = findActiveRunForInit(root, active !== null);
181
- if (nowActive && nowActive.runId !== active?.runId) {
182
- return nowActive;
184
+ if (singleMigration) {
185
+ refuseNewRunAlongsideLiveRun(root, true);
183
186
  }
184
- if (active) {
185
- // Completed meanwhile (a reconcile finished it): nothing to replace.
186
- if (!nowActive) {
187
- throw new Error(noActiveRunToReplace(active.runId));
187
+ else {
188
+ const nowActive = findActiveRunForInit(root, active !== null);
189
+ if (nowActive && nowActive.runId !== active?.runId) {
190
+ return nowActive;
191
+ }
192
+ if (active) {
193
+ // Completed meanwhile (a reconcile finished it): nothing to replace.
194
+ if (!nowActive) {
195
+ throw new Error(noActiveRunToReplace(active.runId));
196
+ }
197
+ deleted = deleteRunRecord(root, active.runId);
188
198
  }
189
- deleted = deleteRunRecord(root, active.runId);
190
199
  }
191
200
  // The snapshot must exist before run.json makes the run discoverable: a
192
201
  // crash in between must not leave an active run without its plan.
@@ -224,7 +233,7 @@ async function runOrchestratorInit(input) {
224
233
  // step's gitRefBefore and rules out clean retries for the whole run. A
225
234
  // failed probe counts as dirty: mistaking it for clean would let a later
226
235
  // retry-clean reset destroy the very work this flag exists to protect.
227
- const checkpointFailed = createCommits && (0, git_utils_1.getWorkingTreeStatus)(root) !== 'clean';
236
+ const checkpointFailed = createCommits && (0, migrate_commits_1.migrateTreeStatus)(root) !== 'clean';
228
237
  const branch = (0, git_utils_1.getGitCurrentBranch)(root);
229
238
  const state = {
230
239
  formatVersion: run_state_1.CURRENT_RUN_STATE_FORMAT_VERSION,
@@ -248,6 +257,7 @@ async function runOrchestratorInit(input) {
248
257
  steps: buildSteps(sorted, finalValidation !== false),
249
258
  commits: checkpoint ? [checkpoint] : [],
250
259
  ...(checkpointFailed ? { checkpointFailed: true } : {}),
260
+ ...(singleMigration ? { singleMigration: true } : {}),
251
261
  analytics: { startEmitted: false, completeEmitted: false },
252
262
  };
253
263
  (0, state_lock_1.withRunCreationLock)(root, () => {
@@ -273,9 +283,9 @@ function runOrchestratorResume(input) {
273
283
  * Holds the active run `runId` names and returns its state. A continue calls
274
284
  * this before the install and the agent selection: a concurrent start-fresh
275
285
  * must not delete the run meanwhile. Throws when the id names no run or a
276
- * finished one, and while another process holds the run: continuing would
277
- * open a second session over a live one. Under WASM nothing is held and no
278
- * holder refuses.
286
+ * finished one, and while another process holds the run or works on a
287
+ * different run: continuing would open a second session over a live one.
288
+ * Under WASM nothing is held and no holder refuses.
279
289
  */
280
290
  function holdRunToContinue(root, runId) {
281
291
  if (!run_id_1.RUN_ID_SAFE.test(runId)) {
@@ -314,52 +324,194 @@ function activeRunToReplace(root, runId) {
314
324
  function releaseRunToHandOff(root, runId) {
315
325
  (0, state_lock_1.releaseRunActivity)((0, run_state_1.runDir)(root, runId));
316
326
  }
327
+ /**
328
+ * The active run a classic `--run-migrations` must not run over, held while
329
+ * the caller reports it or asks what to do; null when no run is active. Run
330
+ * directories that cannot be read refuse as they do for init.
331
+ */
332
+ function activeRunForClassic(root, plannedIds) {
333
+ const active = findActiveRunForInit(root);
334
+ if (!active)
335
+ return null;
336
+ // Held before the facts are read, as init's report does.
337
+ (0, state_lock_1.holdRunActivity)(root, active.runId);
338
+ return {
339
+ runId: active.runId,
340
+ facts: (0, existing_run_report_1.collectExistingRunFacts)(root, active.runId, active.state, plannedIds),
341
+ };
342
+ }
343
+ /**
344
+ * Throws when a classic start fresh could not delete the active run `runId`.
345
+ * Run before the commit prompt, as init does, so the user is not asked for a
346
+ * run that is then refused; deleteRunForStartFresh repeats the checks.
347
+ */
348
+ function checkRunForStartFresh(root, runId) {
349
+ (0, state_lock_1.withRunCreationLock)(root, () => {
350
+ const active = activeRunToDelete(root, runId);
351
+ refuseUndeletableRun(root, runId, active.state);
352
+ });
353
+ }
354
+ /**
355
+ * Deletes the record of the active run `runId` for a classic start fresh,
356
+ * under the refusals init applies before it replaces a run. The classic loop
357
+ * records no run of its own afterwards.
358
+ */
359
+ function deleteRunForStartFresh(root, runId) {
360
+ const deleted = (0, state_lock_1.withRunCreationLock)(root, () => {
361
+ activeRunToDelete(root, runId);
362
+ return deleteRunRecord(root, runId);
363
+ });
364
+ if (deleted) {
365
+ removeDeletedRunDir(root, runId);
366
+ }
367
+ }
368
+ // Call under the creation lock. A run that became active since the user chose
369
+ // is refused, never deleted in place of the one they saw.
370
+ function activeRunToDelete(root, runId) {
371
+ const active = findActiveRunForInit(root, true);
372
+ if (!active) {
373
+ throw new Error(noActiveRunToReplace(runId));
374
+ }
375
+ if (active.runId !== runId) {
376
+ throw new Error(`Not starting fresh: migrate run '${active.runId}' became active while this command was starting. Re-run the command to see it.`);
377
+ }
378
+ return active;
379
+ }
317
380
  // Reads the newest active run; null when no run is active. Uninterpretable
318
381
  // run dirs refuse a fresh start (one of them could be an active run this
319
- // init would compete with) but only warn when a healthy active run is found,
320
- // unless `refuseUninterpretable`: a start-fresh would delete that run and
321
- // then find only the unreadable ones. NewerRunStateFormatError propagates
322
- // from the read.
382
+ // invocation would compete with) but only warn when a healthy active run is
383
+ // found, unless `refuseUninterpretable`: a start-fresh would delete that run
384
+ // and then find only the unreadable ones. NewerRunStateFormatError propagates
385
+ // from the read. Refuses while another process works on a run it wouldn't
386
+ // report.
323
387
  function findActiveRunForInit(root, refuseUninterpretable = false) {
388
+ refuseNewRunAlongsideLiveRun(root);
324
389
  const { active, uninterpretable } = (0, run_state_1.findActiveRun)(root);
325
390
  if (uninterpretable.length > 0) {
326
- const noun = uninterpretable.length === 1 ? 'directory' : 'directories';
327
- // A directory name is whatever is on disk and a reason quotes what it
328
- // found, so neither can be trusted to stay on the line it is put on.
329
- // Sanitized here rather than left to the gateway: these same lines are
330
- // joined into the throw below, which leaves through handleErrors.
331
- const details = uninterpretable.map((u) => `${types_1.MIGRATE_RUNS_RELATIVE_DIR}/${(0, text_1.singleLine)(u.dirName)}: ${(0, text_1.singleLine)(u.reason)}`);
332
391
  if (!active || refuseUninterpretable) {
392
+ const noun = uninterpretable.length === 1 ? 'directory' : 'directories';
333
393
  throw new Error([
334
- `Whether a migrate run is still active could not be determined; starting a new run could re-apply migrations an unfinished run already applied.`,
335
- ...details,
394
+ `Whether a migrate run is still active could not be determined; running the plan could re-apply migrations an unfinished run already applied.`,
395
+ ...uninterpretableDetails(uninterpretable),
336
396
  `Fix or remove the listed ${noun}, then re-run the command.`,
337
397
  ].join('\n'));
338
398
  }
399
+ warnIgnoredRunDirs(uninterpretable);
400
+ }
401
+ return active;
402
+ }
403
+ // A directory name is whatever is on disk and a reason quotes what it found,
404
+ // so neither can be trusted to stay on the line it is put on. Sanitized here
405
+ // rather than left to the gateway: these lines are also joined into a throw,
406
+ // which leaves through handleErrors.
407
+ function uninterpretableDetails(uninterpretable) {
408
+ return uninterpretable.map((u) => `${types_1.MIGRATE_RUNS_RELATIVE_DIR}/${(0, text_1.singleLine)(u.dirName)}: ${(0, text_1.singleLine)(u.reason)}`);
409
+ }
410
+ function warnIgnoredRunDirs(uninterpretable) {
411
+ const noun = uninterpretable.length === 1 ? 'directory' : 'directories';
412
+ (0, agent_output_1.warnToAgent)({
413
+ title: `Ignoring ${uninterpretable.length} migrate run ${noun} that could not be read.`,
414
+ bodyLines: uninterpretableDetails(uninterpretable),
415
+ });
416
+ }
417
+ /**
418
+ * Throws while another process works on a run that init wouldn't report: a
419
+ * `--run-migration` run, a run being started, or a run nx can't read. A new
420
+ * run would install and commit over its in-progress edits. An active
421
+ * `--run-migrations` run is reported instead, except to a `--run-migration`
422
+ * run, and records no process holds block nothing.
423
+ */
424
+ function refuseNewRunAlongsideLiveRun(root, singleMigration = false) {
425
+ const live = (0, state_lock_1.findOtherLiveRuns)(root).find((run) => singleMigration || !run.state || (0, run_state_1.runKind)(run.state) !== 'active');
426
+ if (live) {
427
+ throw (0, state_lock_1.liveRunRefusal)(singleMigration
428
+ ? 'Not running the migration'
429
+ : 'Not running the migrations', live);
430
+ }
431
+ }
432
+ /**
433
+ * The refusals of prepareSingleMigrationRun alone, for the CLI to make before
434
+ * its install. Removes and warns about nothing.
435
+ */
436
+ function checkRunsForSingleMigration(root) {
437
+ if (!(0, fs_1.existsSync)((0, run_state_1.migrateRunsDir)(root)))
438
+ return;
439
+ (0, state_lock_1.withRunCreationLock)(root, () => refuseNewRunAlongsideLiveRun(root, true));
440
+ }
441
+ /**
442
+ * The check every `--run-migration` run without a run id makes before it runs
443
+ * anything. Throws while another nx migrate process works on a run, removes
444
+ * `--run-migration` records no process holds, and warns about idle active
445
+ * runs, which do not record this migration.
446
+ */
447
+ function prepareSingleMigrationRun(root) {
448
+ if (!(0, fs_1.existsSync)((0, run_state_1.migrateRunsDir)(root)))
449
+ return;
450
+ const idleRunIds = [];
451
+ const uninterpretable = [];
452
+ (0, state_lock_1.withRunCreationLock)(root, () => {
453
+ refuseNewRunAlongsideLiveRun(root, true);
454
+ // No other process holds what is left.
455
+ for (const run of (0, state_lock_1.listOtherRuns)(root)) {
456
+ if (run.starting)
457
+ continue;
458
+ if (run.state?.singleMigration === true) {
459
+ removeSingleMigrationRun(root, run.dirName);
460
+ }
461
+ else if (run.state) {
462
+ idleRunIds.push(run.dirName);
463
+ }
464
+ else if (!(run.readError instanceof run_state_1.NewerRunStateFormatError)) {
465
+ // A newer nx's run is that nx's to report.
466
+ uninterpretable.push({
467
+ dirName: run.dirName,
468
+ reason: (0, util_1.summarizeError)(run.readError),
469
+ });
470
+ }
471
+ }
472
+ });
473
+ if (uninterpretable.length > 0) {
474
+ warnIgnoredRunDirs(uninterpretable);
475
+ }
476
+ if (idleRunIds.length > 0) {
477
+ const runs = idleRunIds.map((id) => `'${(0, text_1.singleLine)(id)}'`).join(', ');
339
478
  (0, agent_output_1.warnToAgent)({
340
- title: `Ignoring ${uninterpretable.length} migrate run ${noun} that could not be read.`,
341
- bodyLines: details,
479
+ title: `This migration won't be recorded into the active migrate ${idleRunIds.length === 1 ? 'run' : 'runs'} ${runs}.`,
480
+ bodyLines: [
481
+ idleRunIds.length === 1
482
+ ? 'Continuing that run may apply the migration again.'
483
+ : 'Continuing one of those runs may apply the migration again.',
484
+ ],
342
485
  });
343
486
  }
344
- return active;
345
487
  }
346
- // A run directory without run.json but with a live activity lock is an init
347
- // between reserving the directory and writing run.json: the checkpoint commit
348
- // is in flight and no run may start alongside it. Refused at once, never
349
- // waited for: a git hook re-entering nx migrate during that commit would
350
- // otherwise wait on the process waiting for the hook. An unlocked one is the
351
- // remains of an init that died, which discovery ignores like any other
352
- // directory without run.json. Call under the creation lock.
353
- function refuseLiveReservation(root) {
354
- for (const entry of (0, fs_1.readdirSync)((0, run_state_1.migrateRunsDir)(root), {
355
- withFileTypes: true,
356
- })) {
357
- if (!entry.isDirectory())
358
- continue;
359
- const dir = (0, path_1.join)((0, run_state_1.migrateRunsDir)(root), entry.name);
360
- if ((0, run_state_1.hasRunState)(dir) || !(0, state_lock_1.hasAnyLiveRunActivity)(dir))
361
- continue;
362
- throw new Error(`Another nx migrate process is starting a run (${types_1.MIGRATE_RUNS_RELATIVE_DIR}/${(0, text_1.singleLine)(entry.name)}). Wait for it to finish, then re-run the command.`);
488
+ /**
489
+ * Removes a `--run-migration` run's record. Call under the creation lock. Kept
490
+ * while another process still works on the run, such as a worker the agent
491
+ * left running: other runs refuse alongside it, and the next
492
+ * `--run-migration` removes it once none does. Otherwise best effort: what is
493
+ * left blocks nothing.
494
+ */
495
+ function removeSingleMigrationRun(root, runId) {
496
+ const dir = (0, run_state_1.runDir)(root, runId);
497
+ const path = `${types_1.MIGRATE_RUNS_RELATIVE_DIR}/${runId}`;
498
+ try {
499
+ const deletion = deleteUnheldRunRecord(root, runId);
500
+ if (typeof deletion === 'object') {
501
+ (0, state_lock_1.releaseRunActivity)(dir);
502
+ (0, agent_output_1.warnToAgent)({
503
+ title: deletion.holders === 'unknown'
504
+ ? `Kept ${path}: nx cannot tell whether another nx migrate process is still working on it. Make sure none is, then remove it.`
505
+ : `Kept ${path}: ${(0, state_lock_1.describeHolders)(deletion.holders)}. Other nx migrate runs refuse until it ends, and the next --run-migration removes it.`,
506
+ });
507
+ return;
508
+ }
509
+ (0, fs_1.rmSync)(dir, { recursive: true, force: true });
510
+ }
511
+ catch (e) {
512
+ (0, agent_output_1.warnToAgent)({
513
+ title: `Could not remove ${path}: ${(0, util_1.summarizeError)(e)}. It blocks nothing; remove it when convenient.`,
514
+ });
363
515
  }
364
516
  }
365
517
  // The report for an init that found a run already active. On the agent path
@@ -405,7 +557,7 @@ function refuseUndeletableRun(root, runId, state) {
405
557
  throw new Error(`Not deleting migrate run '${runId}': without a native file lock nx cannot tell whether another nx migrate process is acting on it. Make sure none is, remove ${types_1.MIGRATE_RUNS_RELATIVE_DIR}/${runId}, then re-run the command.`);
406
558
  }
407
559
  const holders = (0, state_lock_1.liveRunActivityPids)((0, run_state_1.runDir)(root, runId));
408
- if (holders === 'unknown' || holders.length > 0) {
560
+ if ((0, state_lock_1.isHeld)(holders)) {
409
561
  throw (0, state_lock_1.heldRunError)('deleting', runId, holders);
410
562
  }
411
563
  const workers = (0, existing_run_report_1.liveWorkers)(state);
@@ -421,19 +573,36 @@ function refuseUndeletableRun(root, runId, state) {
421
573
  function noActiveRunToReplace(runId) {
422
574
  return `Not starting fresh: ${runId === undefined ? 'no migrate run' : `no migrate run '${runId}'`} is active, so there is nothing to replace. To start a run, re-run the command without --start-fresh and --run-id.`;
423
575
  }
424
- // Removes run.json under the state lock, so no reconcile or worker can start
425
- // against a directory about to disappear. The refusals run again on fresh
426
- // state: the early pass predates the prompt. False when already gone.
576
+ // The refusals run again on fresh state: the early pass predates the prompt.
577
+ // False when already gone.
427
578
  function deleteRunRecord(root, runId) {
579
+ const deletion = deleteUnheldRunRecord(root, runId, (state) => refuseUndeletableRun(root, runId, state));
580
+ if (deletion === 'deleted')
581
+ return true;
582
+ if (deletion === 'gone')
583
+ return false;
584
+ throw (0, state_lock_1.heldRunError)('deleting', runId, deletion.holders);
585
+ }
586
+ /**
587
+ * Removes run.json unless another process holds the run, and returns its
588
+ * holders instead. Call under the creation lock, which every claim takes, so
589
+ * no process starts holding the run between the check and the removal. The
590
+ * state lock keeps a reconcile or worker from starting against a directory
591
+ * about to disappear. `refuse` sees the state read under it.
592
+ */
593
+ function deleteUnheldRunRecord(root, runId, refuse) {
428
594
  const dir = (0, run_state_1.runDir)(root, runId);
429
595
  return (0, state_lock_1.withRunStateLock)(dir, () => {
430
596
  if (!(0, run_state_1.hasRunState)(dir))
431
- return false;
432
- refuseUndeletableRun(root, runId, (0, run_state_1.readRunState)(dir));
433
- // The hold's lock file is inside the directory removeDeletedRunDir removes.
597
+ return 'gone';
598
+ refuse?.((0, run_state_1.readRunState)(dir));
599
+ const holders = (0, state_lock_1.liveRunActivityPids)(dir);
600
+ if ((0, state_lock_1.isHeld)(holders))
601
+ return { holders };
602
+ // The hold's lock file is inside the directory removed afterwards.
434
603
  (0, state_lock_1.releaseRunActivity)(dir);
435
604
  (0, fs_1.rmSync)((0, path_1.join)(dir, run_state_1.RUN_STATE_FILE_NAME), { force: true });
436
- return true;
605
+ return 'deleted';
437
606
  });
438
607
  }
439
608
  // The rest of a deleted run's directory, removed outside the locks and best
@@ -518,7 +687,7 @@ function ensureCheckpoint(root, dir, state) {
518
687
  // The retried checkpoint captured everything, so clean retries are safe
519
688
  // again. Only a verified-clean tree clears the flag: a failed probe proves
520
689
  // nothing was captured.
521
- const cleared = (0, git_utils_1.getWorkingTreeStatus)(root) === 'clean';
690
+ const cleared = (0, migrate_commits_1.migrateTreeStatus)(root) === 'clean';
522
691
  if (!checkpoint && !cleared)
523
692
  return state;
524
693
  return (0, state_lock_1.updateRunState)(dir, (fresh) => {
@@ -548,7 +717,7 @@ function ensureCheckpoint(root, dir, state) {
548
717
  function checkpointEntry(root, commitPrefix, before) {
549
718
  // Skip only on a verified-clean tree; on a failed probe the commit attempt
550
719
  // below re-probes and may succeed once the transient failure passes.
551
- if ((0, git_utils_1.getWorkingTreeStatus)(root) === 'clean') {
720
+ if ((0, migrate_commits_1.migrateTreeStatus)(root) === 'clean') {
552
721
  return null;
553
722
  }
554
723
  (0, migrate_commits_1.commitCheckpointBeforeMigrations)(root, commitPrefix);
@@ -566,7 +735,8 @@ function finishInit(root, dir, runId, state, origin, emitAgentInstructions,
566
735
  // which must fail before its git and state side effects).
567
736
  runbook) {
568
737
  let current = state;
569
- if (!current.analytics.startEmitted) {
738
+ if (reportsOrchestratorAnalytics(current) &&
739
+ !current.analytics.startEmitted) {
570
740
  // Claim the watermark on the fresh state first: of two concurrent inits
571
741
  // exactly one flips it, and only that one reports.
572
742
  let claimed = false;
@@ -588,7 +758,7 @@ runbook) {
588
758
  });
589
759
  }
590
760
  }
591
- if (origin === 'resumed') {
761
+ if (origin === 'resumed' && reportsOrchestratorAnalytics(current)) {
592
762
  (0, migrate_analytics_1.reportMigrateOrchestratorResume)((0, state_machine_1.runTallies)(current));
593
763
  }
594
764
  const content = runbook ?? ensureRunbook(root, dir, runId, current);
@@ -694,6 +864,7 @@ function runbookContext(root, runId, state) {
694
864
  // The pass guidance follows the plan: an opted-out run, or one recorded
695
865
  // before the pass existed, has no pass step to dispense.
696
866
  finalValidation: state.steps.some((s) => s.kind === 'final-validation'),
867
+ singleMigration: state.singleMigration === true,
697
868
  };
698
869
  }
699
870
  // Only the newest recorded commit is checked, so the warning stops on its own
@@ -760,7 +931,7 @@ async function runOrchestratorReconcile(input) {
760
931
  try {
761
932
  const result = await applyReconcileStepAction(root, dir, state, stepAction, scope);
762
933
  if (result.kind === 'error') {
763
- emitError(root, runId, result.reason);
934
+ emitError(root, runId, state, result.reason);
764
935
  return; // no transition was written
765
936
  }
766
937
  const target = result.targetStep;
@@ -772,10 +943,12 @@ async function runOrchestratorReconcile(input) {
772
943
  !result.resetTree) {
773
944
  const outcome = await (0, broker_1.giveUpStepTree)(root, dir, target, state.skipInstall === true, reconcileCommand(root, runId), scope);
774
945
  if (outcome.kind === 'refused') {
775
- emitError(root, runId, outcome.reason);
946
+ emitError(root, runId, state, outcome.reason);
776
947
  return;
777
948
  }
778
- (0, give_up_1.warnAboutGiveUp)(target, outcome);
949
+ (0, give_up_1.warnAboutGiveUp)(target, outcome, (0, util_1.commitFailureRecovery)(state));
950
+ if (target.installFailed === true)
951
+ warnInstallLeftToUser(root, target);
779
952
  // Settled already; the dispense refuses a tree still held.
780
953
  scope.lease?.release();
781
954
  scope.lease = undefined;
@@ -786,8 +959,9 @@ async function runOrchestratorReconcile(input) {
786
959
  // sections stay synchronous); the transition and any unrecorded entry then
787
960
  // land in one write. Adopt and skip keep the tree, so the install they owe
788
961
  // runs here: the next dispense would take the changed deps as its baseline.
789
- // Giving up keeps it the same way when the run does not commit. Retries
790
- // owe nothing: the rearmed attempt reconciles itself.
962
+ // Giving up keeps it the same way when the run does not commit. An install
963
+ // that already failed is left to the step's mark instead. Retries owe
964
+ // nothing: the rearmed attempt reconciles itself.
791
965
  const { entry, installFailed, recorded } = await stepActionSideEffects(root, dir, state, target, stepAction, result.resetTree, scope);
792
966
  // A rearm starts a fresh attempt; drop the stale handoff before the rearm
793
967
  // is persisted so a crash in between can't refold the old outcome into the
@@ -855,7 +1029,7 @@ async function runOrchestratorReconcile(input) {
855
1029
  : next;
856
1030
  });
857
1031
  if (freshRejection) {
858
- emitError(root, runId, entry?.kind === 'landed' && entry.sha
1032
+ emitError(root, runId, state, entry?.kind === 'landed' && entry.sha
859
1033
  ? `${freshRejection} Note: this action's commit ${entry.sha} had already landed and stays in history; resolve the step against the tree as it stands now.`
860
1034
  : freshRejection);
861
1035
  return;
@@ -863,6 +1037,11 @@ async function runOrchestratorReconcile(input) {
863
1037
  if (unresolvedArchiveError !== undefined) {
864
1038
  (0, give_up_1.warnUnresolvedNotArchived)((0, state_machine_1.stepLabel)(target), unresolvedArchiveError);
865
1039
  }
1040
+ // A retry's rearm drops the mark: its next attempt installs.
1041
+ if (target.installFailed === true &&
1042
+ written.steps.find((s) => s.id === target.id)?.installFailed === true) {
1043
+ warnInstallLeftToUser(root, target);
1044
+ }
866
1045
  state = written;
867
1046
  }
868
1047
  finally {
@@ -1103,7 +1282,7 @@ async function foldLedgerEntry(root, dir, state, step, promptOutcome, scope) {
1103
1282
  // is not verifiably clean (see foldLedgerEntry for why).
1104
1283
  async function retainedTreeSideEffects(root, dir, state, step, seam, scope) {
1105
1284
  const installFailed = await installFailedForStep(root, dir, state, step, seam, scope);
1106
- const entry = state.createCommits && (0, git_utils_1.getWorkingTreeStatus)(root) !== 'clean'
1285
+ const entry = state.createCommits && (0, migrate_commits_1.migrateTreeStatus)(root) !== 'clean'
1107
1286
  ? { kind: 'failed', stepIds: [step.id] }
1108
1287
  : null;
1109
1288
  return { entry, installFailed };
@@ -1131,6 +1310,14 @@ async function installFailedForStep(root, dir, state, step, seam, scope) {
1131
1310
  return true;
1132
1311
  }
1133
1312
  }
1313
+ function warnInstallLeftToUser(root, step) {
1314
+ (0, agent_output_1.warnToAgent)({
1315
+ title: `The dependency changes made by ${(0, state_machine_1.stepLabel)(step)} are not installed.`,
1316
+ bodyLines: [
1317
+ `Run \`${(0, util_1.pmInstallCommand)(root)}\` once the cause of the failed install is fixed. The run's final report repeats this unless a later install covers them.`,
1318
+ ],
1319
+ });
1320
+ }
1134
1321
  // A failed handoff fails the prompt; a success handoff completes it, unless it
1135
1322
  // marks the prompt not applicable via `extras.outcome === 'skipped'`.
1136
1323
  function handoffToPromptOutcome(handoff) {
@@ -1232,7 +1419,7 @@ async function applyReconcileStepAction(root, dir, state, action, scope) {
1232
1419
  // Only the tree can say whether the reset left it clean. Anything else is
1233
1420
  // refused: accepting would rearm a step whose next attempt reruns the
1234
1421
  // generator over the previous attempt's output.
1235
- if ((0, git_utils_1.getWorkingTreeStatus)(root) !== 'clean') {
1422
+ if ((0, migrate_commits_1.migrateTreeStatus)(root) !== 'clean') {
1236
1423
  return {
1237
1424
  kind: 'error',
1238
1425
  reason: `Cannot apply action 'retry-clean' to step '${step.id}': the working tree is not verifiably clean after the reset to ${step.gitRefBefore}. Inspect it with \`git status\`, then re-run it. ${fallback}`,
@@ -1261,7 +1448,7 @@ async function applyReconcileStepAction(root, dir, state, action, scope) {
1261
1448
  reason: `Cannot apply action 'unresolved' to step '${step.id}': the reset to ${step.gitRefBefore} failed: ${e instanceof Error ? e.message : String(e)}`,
1262
1449
  };
1263
1450
  }
1264
- if ((0, git_utils_1.getWorkingTreeStatus)(root) !== 'clean') {
1451
+ if ((0, migrate_commits_1.migrateTreeStatus)(root) !== 'clean') {
1265
1452
  return {
1266
1453
  kind: 'error',
1267
1454
  reason: `Cannot apply action 'unresolved' to step '${step.id}': the working tree is not verifiably clean after the reset to ${step.gitRefBefore}. Inspect it with \`git status\`, then re-run it.`,
@@ -1316,11 +1503,13 @@ function unresolvedResetsTree(state, step, cleanRetry) {
1316
1503
  async function stepActionSideEffects(root, dir, state, step, action, resetTree, scope) {
1317
1504
  switch (action) {
1318
1505
  case 'adopt':
1319
- // A died worker's unrecorded commit request is reused. A failed step, or
1320
- // a recorded commit, needs an adopt request of its own, or the old answer
1321
- // would be replayed.
1506
+ // A died worker's commit request is reused while nothing records its
1507
+ // outcome, since the session may still answer it. Otherwise the adopt
1508
+ // asks under its own request, or the old answer would be replayed.
1322
1509
  return state.createCommits
1323
1510
  ? commitForStep(root, dir, state, step, scope, step.status === 'failed' ||
1511
+ step.commitLedgerIndex !== undefined ||
1512
+ step.installFailed === true ||
1324
1513
  (0, state_machine_1.coveringLandedEntries)(state, step.id).length > 0
1325
1514
  ? 'adopt'
1326
1515
  : undefined)
@@ -1356,9 +1545,10 @@ async function commitForStep(root, dir, state, step, scope, commitAs) {
1356
1545
  const name = (0, state_machine_1.commitNameForStep)(step);
1357
1546
  const absorbedStepIds = (0, state_machine_1.uncoveredFailedStepIds)(state).filter((id) => id !== step.id);
1358
1547
  const skipInstall = state.skipInstall === true;
1548
+ const recovery = (0, util_1.commitFailureRecovery)(state);
1359
1549
  let commit;
1360
1550
  try {
1361
- commit = await (0, broker_1.commitStepTree)(dir, step, absorbedStepIds, () => (0, migrate_commits_1.commitMigrationIfRequested)(root, { name }, true, state.commitPrefix, () => (0, util_1.installDepsChangedSinceDispense)(root, dir, step, skipInstall, reconcileCommand(root, state.runId)), (0, state_machine_1.stepsToPendingMigrations)(state, absorbedStepIds)), scope, commitAs);
1551
+ commit = await (0, broker_1.commitStepTree)(dir, step, absorbedStepIds, () => (0, migrate_commits_1.commitMigrationIfRequested)(root, { name }, true, state.commitPrefix, () => (0, util_1.installDepsChangedSinceDispense)(root, dir, step, skipInstall, reconcileCommand(root, state.runId)), (0, state_machine_1.stepsToPendingMigrations)(state, absorbedStepIds), recovery), scope, commitAs);
1362
1552
  }
1363
1553
  catch (e) {
1364
1554
  // Nothing to record: the step moved on, another process holds the tree, or
@@ -1372,14 +1562,14 @@ async function commitForStep(root, dir, state, step, scope, commitAs) {
1372
1562
  // reports through result.status. The debt cannot stand in for the install
1373
1563
  // failure, since a later commit absorbing this diff clears the debt while
1374
1564
  // the dependencies are still missing.
1375
- (0, util_1.warnCommitFailed)(name, e);
1565
+ (0, util_1.warnCommitFailed)(name, recovery, e);
1376
1566
  return {
1377
1567
  entry: { kind: 'failed', stepIds: [step.id] },
1378
1568
  installFailed: true,
1379
1569
  };
1380
1570
  }
1381
1571
  if (commit.result.status === 'failed') {
1382
- (0, util_1.warnCommitFailed)(name);
1572
+ (0, util_1.warnCommitFailed)(name, recovery);
1383
1573
  }
1384
1574
  return {
1385
1575
  entry: (0, state_machine_1.commitResultToLedgerEntry)(commit.result, step.id, commit.absorbedStepIds),
@@ -1501,11 +1691,7 @@ function dispenseNextStep(root, dir, runId, state, step, noProgress) {
1501
1691
  // Read the pre-step baselines (git and package.json reads) before the
1502
1692
  // lock; the dispense transition and the baselines then apply to the fresh
1503
1693
  // state in one write.
1504
- const baselines = {
1505
- gitRefBefore: (0, git_utils_1.getLatestCommitSha)(root) ?? undefined,
1506
- treeCleanAtDispense: (0, git_utils_1.getWorkingTreeStatus)(root) === 'clean',
1507
- depsHashAtDispense: (0, util_1.depsHash)(root),
1508
- };
1694
+ const baselines = readDispenseBaselines(root);
1509
1695
  let advancedElsewhere = false;
1510
1696
  let held;
1511
1697
  const current = (0, state_lock_1.updateRunState)(dir, (fresh) => {
@@ -1536,10 +1722,12 @@ function dispenseNextStep(root, dir, runId, state, step, noProgress) {
1536
1722
  return;
1537
1723
  }
1538
1724
  const dispensed = current.steps.find((s) => s.id === step.id);
1539
- (0, migrate_analytics_1.reportMigrateOrchestratorStepDispensed)({
1540
- attempt: dispensed.attempt,
1541
- ordinal: (0, state_machine_1.runTallies)(current).dispenseCount,
1542
- });
1725
+ if (reportsOrchestratorAnalytics(current)) {
1726
+ (0, migrate_analytics_1.reportMigrateOrchestratorStepDispensed)({
1727
+ attempt: dispensed.attempt,
1728
+ ordinal: (0, state_machine_1.runTallies)(current).dispenseCount,
1729
+ });
1730
+ }
1543
1731
  switch (dispensed.kind) {
1544
1732
  case 'migration':
1545
1733
  emitNextStep(root, runId, current, dispensed, noProgress);
@@ -1553,6 +1741,25 @@ function dispenseNextStep(root, dir, runId, state, step, noProgress) {
1553
1741
  }
1554
1742
  }
1555
1743
  }
1744
+ function readDispenseBaselines(root) {
1745
+ return {
1746
+ gitRefBefore: (0, git_utils_1.getLatestCommitSha)(root) ?? undefined,
1747
+ treeCleanAtDispense: (0, migrate_commits_1.migrateTreeStatus)(root) === 'clean',
1748
+ depsHashAtDispense: (0, util_1.depsHash)(root),
1749
+ };
1750
+ }
1751
+ /**
1752
+ * Dispenses a `--run-migration` run's step without a response: the parent
1753
+ * runs the step's first attempt itself, and an agent it starts afterwards
1754
+ * gets the step from the "next" command.
1755
+ */
1756
+ function dispenseSingleMigrationStep(root, runId) {
1757
+ const baselines = readDispenseBaselines(root);
1758
+ (0, state_lock_1.updateRunState)((0, run_state_1.runDir)(root, runId), (fresh) => {
1759
+ const [step] = fresh.steps;
1760
+ return setDispenseBaselines(applyEventOrThrow(fresh, dispenseEvent(step)), step.id, baselines);
1761
+ });
1762
+ }
1556
1763
  function dispenseEvent(step) {
1557
1764
  switch (step.kind) {
1558
1765
  case 'migration':
@@ -1631,6 +1838,7 @@ function emitRetryFailed(root, runId, state, step, noProgress) {
1631
1838
  ` current HEAD: ${head ?? '(unknown)'}`,
1632
1839
  ` working tree: ${tree === null ? '(unknown)' : tree ? `\n${tree}` : '(clean)'}`,
1633
1840
  ``,
1841
+ ...unfinishedOperationLines(root, state, step),
1634
1842
  retryBudgetLine(step, committed),
1635
1843
  ``,
1636
1844
  `Decide how to proceed and re-run reconcile with one of:`,
@@ -1639,13 +1847,17 @@ function emitRetryFailed(root, runId, state, step, noProgress) {
1639
1847
  : [retryOptionLine(retrySafety, reconcileCommand(root, runId, 'retry'))]),
1640
1848
  ];
1641
1849
  if (cleanRetry && !capReached) {
1642
- lines.push(` retry-clean: reset the tree to ${step.gitRefBefore ?? 'the pre-migration ref'} (discarding its uncommitted tracked changes and the untracked files git does not ignore, ${types_1.MIGRATE_RUNS_RELATIVE_DIR} kept) and retry from that clean state by running: ${reconcileCommand(root, runId, 'retry-clean')}`);
1643
- }
1850
+ lines.push(` retry-clean: reset the tree to ${step.gitRefBefore ?? 'the pre-migration ref'} (discarding its uncommitted tracked changes and the untracked files git does not ignore, keeping ${KEPT_OWNED_PATHS}) and retry from that clean state by running: ${reconcileCommand(root, runId, 'retry-clean')}`);
1851
+ }
1852
+ // A failed commit is already named above the choices.
1853
+ const adoptWhen = recordedCommitFailed(state, step)
1854
+ ? ''
1855
+ : committed
1856
+ ? `a commit of this ${(0, state_machine_1.stepNoun)(step)}'s changes was started and never recorded; `
1857
+ : `the ${(0, state_machine_1.stepNoun)(step)} was ${step.kind === 'migration' ? 'applied' : 'done'} by hand; `;
1644
1858
  lines.push(landed
1645
1859
  ? ` adopt: keep the landed commit${landed.sha ? ` ${landed.sha}` : ''} and the current working-tree state as the ${(0, state_machine_1.stepNoun)(step)}'s result, then run: ${reconcileCommand(root, runId, 'adopt')}`
1646
- : ` adopt: ${committed
1647
- ? `a commit of this ${(0, state_machine_1.stepNoun)(step)}'s changes was started and never recorded`
1648
- : `the ${(0, state_machine_1.stepNoun)(step)} was ${step.kind === 'migration' ? 'applied' : 'done'} by hand`}; keep the current working-tree state as its result, then run: ${reconcileCommand(root, runId, 'adopt')}`);
1860
+ : ` adopt: ${adoptWhen}keep the current working-tree state as its result, then run: ${reconcileCommand(root, runId, 'adopt')}`);
1649
1861
  // Refused by the state machine: the migration is, or may be, committed.
1650
1862
  if (!committed) {
1651
1863
  lines.push(` skip: ${reconcileCommand(root, runId, 'skip')}`, unresolvedOptionLine(root, runId, state, step, unresolvedResetsTree(state, step, cleanRetry)));
@@ -1677,6 +1889,26 @@ function retryBudgetLine(step, committed) {
1677
1889
  ? `adopt only once you have inspected the tree and finished the ${(0, state_machine_1.stepNoun)(step)}; otherwise stop and report`
1678
1890
  : 'give the step up with unresolved and continue'}.`;
1679
1891
  }
1892
+ // A recorded failed commit with the mark standing withdraws skip and
1893
+ // unresolved, so every choice left commits.
1894
+ function unfinishedOperationLines(root, state, step) {
1895
+ const lines = [];
1896
+ if (step.installFailed === true) {
1897
+ lines.push(`Its dependency install did not complete. A retry runs it again, so fix the cause first if it can be fixed. Any other choice settles the step without installing and leaves the install to you; the run's final report asks for \`${(0, util_1.pmInstallCommand)(root)}\`.`, ``);
1898
+ }
1899
+ if (recordedCommitFailed(state, step)) {
1900
+ lines.push(`Its commit did not complete. Every choice below commits again.`, ``);
1901
+ }
1902
+ return lines;
1903
+ }
1904
+ function recordedCommitFailed(state, step) {
1905
+ // Not commitReceipt, which throws on an index the ledger lacks: a dispense
1906
+ // has to render whatever run.json holds.
1907
+ const receipt = step.commitLedgerIndex === undefined
1908
+ ? undefined
1909
+ : state.commits[step.commitLedgerIndex];
1910
+ return step.commitStarted === true && receipt?.kind === 'failed';
1911
+ }
1680
1912
  // Opens a capped dispense and is the reason a retry past the cap is refused.
1681
1913
  function rearmCapLine(step, committed) {
1682
1914
  return `This ${(0, state_machine_1.stepNoun)(step)} has already been retried ${step.attempt - 1} times without completing, and no further retry is accepted. ${committed
@@ -1734,7 +1966,7 @@ function assessPreMarkerRetry(root, step) {
1734
1966
  reason: `HEAD is at ${head ?? '(unreadable)'} rather than the ${step.gitRefBefore ?? '(unrecorded)'} this migration started from, so the failed attempt's changes may already be committed and rerunning the generator could apply them twice.`,
1735
1967
  };
1736
1968
  }
1737
- if ((0, git_utils_1.getWorkingTreeStatus)(root) !== 'clean') {
1969
+ if ((0, migrate_commits_1.migrateTreeStatus)(root) !== 'clean') {
1738
1970
  return {
1739
1971
  kind: 'unsafe',
1740
1972
  reason: `the working tree is not verifiably clean, and the failed attempt may have written to it before failing; rerunning the generator over those changes could apply them twice.`,
@@ -1751,10 +1983,12 @@ function lastCoveringLandedEntry(state, step) {
1751
1983
  // reset, a partial commit, or the tree left as it stands.
1752
1984
  function unresolvedOptionLine(root, runId, state, step, resetTree) {
1753
1985
  const command = reconcileCommand(root, runId, 'unresolved');
1754
- const recorded = `The failure is recorded as a run issue and listed in the completion report.`;
1986
+ const recorded = state.singleMigration === true
1987
+ ? `The failure is listed in the completion report.`
1988
+ : `The failure is recorded as a run issue and listed in the completion report.`;
1755
1989
  const noun = (0, state_machine_1.stepNoun)(step);
1756
1990
  if (resetTree) {
1757
- return ` unresolved: give up on this ${noun}, resetting the tree to ${step.gitRefBefore ?? 'the pre-migration ref'} (discarding what the failed attempt left, ${types_1.MIGRATE_RUNS_RELATIVE_DIR} kept), then run: ${command}. ${recorded}`;
1991
+ return ` unresolved: give up on this ${noun}, resetting the tree to ${step.gitRefBefore ?? 'the pre-migration ref'} (discarding what the failed attempt left, keeping ${KEPT_OWNED_PATHS}), then run: ${command}. ${recorded}`;
1758
1992
  }
1759
1993
  if (state.createCommits) {
1760
1994
  return ` unresolved: give up on this ${noun}; its partial changes are committed under its name, marked unresolved, and the run moves on. Then run: ${command}. ${recorded}`;
@@ -1776,6 +2010,7 @@ function emitDied(root, runId, state, step, noProgress) {
1776
2010
  ` current HEAD: ${head ?? '(unknown)'}`,
1777
2011
  ` working tree: ${tree === null ? '(unknown)' : tree ? `\n${tree}` : '(clean)'}`,
1778
2012
  ``,
2013
+ ...unfinishedOperationLines(root, state, step),
1779
2014
  retryBudgetLine(step, committed),
1780
2015
  ``,
1781
2016
  ];
@@ -1784,7 +2019,7 @@ function emitDied(root, runId, state, step, noProgress) {
1784
2019
  options.push(` retry: keep everything this migration already produced (its commit, if any, and the current tree) and run only the part that did not complete, then run: ${reconcileCommand(root, runId, 'retry')}`);
1785
2020
  }
1786
2021
  if (cleanRetry && !capReached) {
1787
- options.push(` retry-clean: reset the tree to ${ref ?? 'the pre-migration ref'} (discarding its uncommitted tracked changes and the untracked files git does not ignore, ${types_1.MIGRATE_RUNS_RELATIVE_DIR} kept) and retry from that clean state by running: ${reconcileCommand(root, runId, 'retry-clean')}`);
2022
+ options.push(` retry-clean: reset the tree to ${ref ?? 'the pre-migration ref'} (discarding its uncommitted tracked changes and the untracked files git does not ignore, keeping ${KEPT_OWNED_PATHS}) and retry from that clean state by running: ${reconcileCommand(root, runId, 'retry-clean')}`);
1788
2023
  }
1789
2024
  else if (!capReached) {
1790
2025
  lines.push(`A clean retry is unavailable: ${(0, clean_retry_1.cleanRetryUnavailableReason)(root, state, step, head)}`);
@@ -1810,9 +2045,19 @@ function emitDied(root, runId, state, step, noProgress) {
1810
2045
  }, noProgress);
1811
2046
  }
1812
2047
  function emitHeld(root, runId, state, step, held, noProgress) {
2048
+ const heldStep = state.steps.find((s) => s.id === held.stepId);
2049
+ const heldStepName = heldStep && (0, state_machine_1.stepLabel)(heldStep);
1813
2050
  emit(root, runId, state, step, 'held', {
1814
2051
  next: reconcileCommand(root, runId),
1815
- instructionLines: [(0, broker_1.treeBusyMessage)(held)],
2052
+ instructionLines: [
2053
+ (0, broker_1.treeBusyMessage)(held, heldStepName),
2054
+ // Quitting this session frees the terminal only for its own parent.
2055
+ ...(held.owner === process.env[broker_1.BROKER_ENV_VAR]
2056
+ ? [
2057
+ `If it seems stuck, the user can ${(0, broker_1.sessionOperationWayOut)((0, broker_1.treeOperationLabel)(held, heldStepName))}.`,
2058
+ ]
2059
+ : []),
2060
+ ],
1816
2061
  }, noProgress);
1817
2062
  }
1818
2063
  function emitStillRunning(root, runId, state, step, held, noProgress) {
@@ -1830,7 +2075,7 @@ function emitStillRunning(root, runId, state, step, held, noProgress) {
1830
2075
  if (ageMs >= HANG_THRESHOLD_MS) {
1831
2076
  const hung = `It has been running for ${Math.floor(ageMs / 60000)} minutes and may be hung.`;
1832
2077
  lines.push(parentOperation
1833
- ? `${hung} Report this to the user: they can quit this session, then press Ctrl+C in the terminal to end ${parentOperation}, and resume the run afterwards.`
2078
+ ? `${hung} Report this to the user: they can ${(0, broker_1.sessionOperationWayOut)(parentOperation)}.`
1834
2079
  : `${hung} Verify pid ${step.pid}; either keep waiting, or kill it so the next reconcile can classify it as died.`);
1835
2080
  }
1836
2081
  emit(root, runId, state, step, 'still-running', {
@@ -2004,9 +2249,8 @@ function planPromptPath(dir, state, migrationId) {
2004
2249
  }
2005
2250
  return null;
2006
2251
  }
2007
- // Wording mirrors the classic runner's ambiguous-outcome cause lines. A
2008
- // readable handoff whose issue report is invalid is a rejection too: the fold
2009
- // refused to consume it, and only this response can tell the agent why.
2252
+ // A readable handoff whose issue report is invalid is a rejection too: the
2253
+ // fold refused to consume it, and only this response can tell the agent why.
2010
2254
  function describeRejectedHandoff(dir, state, step) {
2011
2255
  const result = readStepHandoff(dir, step.id);
2012
2256
  const followUp = 'Rewrite the handoff file, then run the "next" command.';
@@ -2049,7 +2293,7 @@ function describeRejectedHandoff(dir, state, step) {
2049
2293
  // to the request, and it carries the reconcile command to run next. A non-zero
2050
2294
  // exit would tell the driving agent that reconcile itself crashed, and it
2051
2295
  // would stop reading for the correction it is being handed.
2052
- function emitError(root, runId, reason) {
2296
+ function emitError(root, runId, state, reason) {
2053
2297
  // A rejected action is an active response like any dispense, so it carries
2054
2298
  // the runbook footer: the master receiving it may have just lost its
2055
2299
  // context. One sanitized array feeds both outputs, as in emit().
@@ -2062,7 +2306,9 @@ function emitError(root, runId, reason) {
2062
2306
  next: reconcileCommand(root, runId),
2063
2307
  instructions: lines.join('\n'),
2064
2308
  });
2065
- (0, migrate_analytics_1.reportMigrateOrchestratorDispense)({ action: 'error', attempt: 0 });
2309
+ if (reportsOrchestratorAnalytics(state)) {
2310
+ (0, migrate_analytics_1.reportMigrateOrchestratorDispense)({ action: 'error', attempt: 0 });
2311
+ }
2066
2312
  }
2067
2313
  /**
2068
2314
  * What a completed run leaves behind for whoever reads its summary, one line
@@ -2074,14 +2320,19 @@ function completionWarnings(root, runId, state) {
2074
2320
  // fact absorbed; suppress the warning only on a verified-clean tree. A dirty
2075
2321
  // tree can still be unrelated edits, so the warning only claims the changes
2076
2322
  // "may remain".
2077
- const commitDebt = (0, state_machine_1.hasPendingCommitDebt)(state) && (0, git_utils_1.getWorkingTreeStatus)(root) !== 'clean';
2323
+ const debtStepIds = (0, state_machine_1.uncoveredFailedStepIds)(state);
2324
+ const uncommitted = debtStepIds.length > 0 && (0, migrate_commits_1.migrateTreeStatus)(root) !== 'clean'
2325
+ ? state.steps.filter((s) => debtStepIds.includes(s.id))
2326
+ : [];
2078
2327
  const uninstalled = state.steps.filter((s) => s.installFailed);
2079
2328
  const issueLines = (0, issues_1.renderUnresolvedIssueLines)(state, runId);
2080
2329
  return [
2081
- ...(commitDebt
2330
+ ...(uncommitted.length > 0
2082
2331
  ? [
2083
2332
  [
2084
- 'Some migration changes could not be committed and may remain in the working tree; review and commit them manually.',
2333
+ `The changes made by ${uncommitted
2334
+ .map(state_machine_1.stepLabel)
2335
+ .join(', ')} could not be committed and may remain in the working tree; review and commit them manually.`,
2085
2336
  ],
2086
2337
  ]
2087
2338
  : []),
@@ -2117,7 +2368,7 @@ function completeRun(root, dir, runId, state) {
2117
2368
  };
2118
2369
  });
2119
2370
  }
2120
- if (shouldEmit) {
2371
+ if (shouldEmit && reportsOrchestratorAnalytics(current)) {
2121
2372
  (0, migrate_analytics_1.reportMigrateOrchestratorComplete)((0, state_machine_1.runTallies)(current));
2122
2373
  }
2123
2374
  const warnings = completionWarnings(root, runId, current);
@@ -2156,11 +2407,18 @@ function emit(root, runId, state, step, action, payload, noProgress) {
2156
2407
  ...rest,
2157
2408
  ...(lines ? { instructions: lines.join('\n') } : {}),
2158
2409
  });
2159
- (0, migrate_analytics_1.reportMigrateOrchestratorDispense)({
2160
- action: effectiveAction,
2161
- attempt: step.attempt,
2162
- ordinal: (0, state_machine_1.runTallies)(state).dispenseCount,
2163
- });
2410
+ if (reportsOrchestratorAnalytics(state)) {
2411
+ (0, migrate_analytics_1.reportMigrateOrchestratorDispense)({
2412
+ action: effectiveAction,
2413
+ attempt: step.attempt,
2414
+ ordinal: (0, state_machine_1.runTallies)(state).dispenseCount,
2415
+ });
2416
+ }
2417
+ }
2418
+ // A `--run-migration` run reports its own events, so only `--run-migrations`
2419
+ // runs report orchestrator events.
2420
+ function reportsOrchestratorAnalytics(state) {
2421
+ return state.singleMigration !== true;
2164
2422
  }
2165
2423
  function noProgressLines(step, streak) {
2166
2424
  let subject;
@@ -2254,12 +2512,18 @@ function removeHandoff(dir, stepId) {
2254
2512
  (0, fs_1.rmSync)((0, handoff_1.runStepHandoffPath)(dir, stepId), { force: true });
2255
2513
  }
2256
2514
  }
2515
+ const KEPT_OWNED_PATHS = `${migrate_commits_1.MIGRATE_OWNED_PATHS.slice(0, -1).join(', ')} and ${migrate_commits_1.MIGRATE_OWNED_PATHS[migrate_commits_1.MIGRATE_OWNED_PATHS.length - 1]}`;
2257
2516
  // null means the probe itself failed; the death dispense renders that as
2258
2517
  // '(unknown)', because '(clean)' would invite a retry-clean reset over
2259
2518
  // evidence that was never gathered (getWorkingTreeStatus's contract).
2260
2519
  function dirtyTreeSummary(root) {
2261
2520
  try {
2262
- return (0, child_process_1.execSync)('git status --porcelain', {
2521
+ return (0, child_process_1.execFileSync)('git', [
2522
+ 'status',
2523
+ '--porcelain',
2524
+ '--',
2525
+ ...(0, migrate_commits_1.migrateCommitExcludes)(root).map((path) => `:(exclude)${path}`),
2526
+ ], {
2263
2527
  encoding: 'utf8',
2264
2528
  cwd: root,
2265
2529
  stdio: ['ignore', 'pipe', 'pipe'],