nx 23.2.1 → 23.3.0-beta.1

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 (149) hide show
  1. package/dist/src/command-line/format/format.js +2 -18
  2. package/dist/src/command-line/graph/graph.js +42 -3
  3. package/dist/src/command-line/init/implementation/format.js +2 -3
  4. package/dist/src/command-line/migrate/agentic/definitions.js +75 -19
  5. package/dist/src/command-line/migrate/agentic/format-command.d.ts +14 -0
  6. package/dist/src/command-line/migrate/agentic/format-command.js +44 -0
  7. package/dist/src/command-line/migrate/agentic/handoff-gitignore.d.ts +3 -3
  8. package/dist/src/command-line/migrate/agentic/handoff-gitignore.js +3 -3
  9. package/dist/src/command-line/migrate/agentic/handoff.d.ts +27 -17
  10. package/dist/src/command-line/migrate/agentic/handoff.js +107 -24
  11. package/dist/src/command-line/migrate/agentic/instruction-files.d.ts +21 -0
  12. package/dist/src/command-line/migrate/agentic/instruction-files.js +36 -0
  13. package/dist/src/command-line/migrate/agentic/prompts/fragments.d.ts +16 -0
  14. package/dist/src/command-line/migrate/agentic/prompts/fragments.js +70 -0
  15. package/dist/src/command-line/migrate/agentic/prompts/generic-validation.js +4 -3
  16. package/dist/src/command-line/migrate/agentic/prompts/system-prompt.d.ts +25 -1
  17. package/dist/src/command-line/migrate/agentic/prompts/system-prompt.js +57 -34
  18. package/dist/src/command-line/migrate/agentic/run-step.js +28 -4
  19. package/dist/src/command-line/migrate/agentic/runner.d.ts +17 -8
  20. package/dist/src/command-line/migrate/agentic/runner.js +110 -55
  21. package/dist/src/command-line/migrate/agentic/types.d.ts +23 -8
  22. package/dist/src/command-line/migrate/agentic/types.js +10 -7
  23. package/dist/src/command-line/migrate/migrate-commits.d.ts +1 -0
  24. package/dist/src/command-line/migrate/migrate-commits.js +9 -0
  25. package/dist/src/command-line/migrate/migrate-output.d.ts +3 -7
  26. package/dist/src/command-line/migrate/migrate-output.js +3 -7
  27. package/dist/src/command-line/migrate/migrate.js +21 -8
  28. package/dist/src/command-line/migrate/run/agent-output.d.ts +10 -0
  29. package/dist/src/command-line/migrate/run/agent-output.js +16 -2
  30. package/dist/src/command-line/migrate/run/agent-work-payload.d.ts +37 -0
  31. package/dist/src/command-line/migrate/run/agent-work-payload.js +142 -0
  32. package/dist/src/command-line/migrate/run/atomic-write.d.ts +8 -0
  33. package/dist/src/command-line/migrate/run/atomic-write.js +17 -0
  34. package/dist/src/command-line/migrate/run/index.d.ts +1 -1
  35. package/dist/src/command-line/migrate/run/issues.d.ts +106 -0
  36. package/dist/src/command-line/migrate/run/issues.js +980 -0
  37. package/dist/src/command-line/migrate/run/orchestrator.d.ts +1 -0
  38. package/dist/src/command-line/migrate/run/orchestrator.js +662 -105
  39. package/dist/src/command-line/migrate/run/run-state.d.ts +43 -8
  40. package/dist/src/command-line/migrate/run/run-state.js +184 -20
  41. package/dist/src/command-line/migrate/run/runbook.d.ts +17 -0
  42. package/dist/src/command-line/migrate/run/runbook.js +97 -0
  43. package/dist/src/command-line/migrate/run/state-lock.d.ts +2 -2
  44. package/dist/src/command-line/migrate/run/state-lock.js +2 -2
  45. package/dist/src/command-line/migrate/run/state-machine.d.ts +5 -1
  46. package/dist/src/command-line/migrate/run/state-machine.js +27 -1
  47. package/dist/src/command-line/migrate/run/worker.js +206 -56
  48. package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +5 -1
  49. package/dist/src/command-line/release/publish.js +11 -4
  50. package/dist/src/command-line/release/utils/remote-release-clients/github.js +3 -3
  51. package/dist/src/command-line/release/utils/remote-release-clients/gitlab.js +3 -3
  52. package/dist/src/command-line/release/utils/remote-release-clients/remote-release-client.d.ts +2 -0
  53. package/dist/src/command-line/release/utils/remote-release-clients/remote-release-client.js +35 -0
  54. package/dist/src/command-line/yargs-utils/shared-options.d.ts +1 -1
  55. package/dist/src/command-line/yargs-utils/shared-options.js +22 -4
  56. package/dist/src/config/workspace-json-project-json.d.ts +2 -0
  57. package/dist/src/core/graph/main.js +1 -1
  58. package/dist/src/daemon/client/client.d.ts +4 -2
  59. package/dist/src/daemon/client/client.js +20 -11
  60. package/dist/src/daemon/message-types/run-tasks-execution-hooks.d.ts +3 -2
  61. package/dist/src/daemon/server/handle-hash-tasks.d.ts +8 -2
  62. package/dist/src/daemon/server/handle-hash-tasks.js +16 -3
  63. package/dist/src/daemon/server/handle-outputs-changes.d.ts +2 -2
  64. package/dist/src/daemon/server/handle-outputs-changes.js +21 -6
  65. package/dist/src/daemon/server/handle-tasks-execution-hooks.d.ts +3 -2
  66. package/dist/src/daemon/server/outputs-tracking.d.ts +6 -0
  67. package/dist/src/daemon/server/outputs-tracking.js +18 -0
  68. package/dist/src/daemon/server/project-graph-incremental-recomputation.d.ts +22 -7
  69. package/dist/src/daemon/server/project-graph-incremental-recomputation.js +99 -69
  70. package/dist/src/daemon/server/restart-checks.d.ts +22 -0
  71. package/dist/src/daemon/server/restart-checks.js +77 -0
  72. package/dist/src/daemon/server/server.js +28 -18
  73. package/dist/src/daemon/server/shutdown-utils.d.ts +0 -5
  74. package/dist/src/daemon/server/shutdown-utils.js +3 -26
  75. package/dist/src/hasher/hash-task.js +20 -16
  76. package/dist/src/hasher/native-task-hasher-impl.d.ts +8 -0
  77. package/dist/src/hasher/native-task-hasher-impl.js +46 -3
  78. package/dist/src/hasher/task-hasher.d.ts +9 -0
  79. package/dist/src/hasher/task-hasher.js +14 -0
  80. package/dist/src/native/index.d.ts +202 -21
  81. package/dist/src/native/index.js +3 -3
  82. package/dist/src/native/native-bindings.js +3 -1
  83. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  84. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  85. package/dist/src/plugins/js/lock-file/pnpm-parser.js +1 -1
  86. package/dist/src/plugins/js/lock-file/utils/pnpm-normalizer.d.ts +2 -0
  87. package/dist/src/plugins/js/lock-file/utils/pnpm-normalizer.js +22 -0
  88. package/dist/src/plugins/js/utils/register.js +2 -1
  89. package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +2 -1
  90. package/dist/src/project-graph/plugins/isolation/messaging.d.ts +3 -2
  91. package/dist/src/project-graph/plugins/isolation/plugin-worker.js +5 -1
  92. package/dist/src/project-graph/plugins/loaded-nx-plugin.d.ts +3 -2
  93. package/dist/src/project-graph/plugins/loaded-nx-plugin.js +5 -1
  94. package/dist/src/project-graph/plugins/task-results-stub.d.ts +50 -0
  95. package/dist/src/project-graph/plugins/task-results-stub.js +111 -0
  96. package/dist/src/project-graph/plugins/tasks-execution-hooks.d.ts +3 -2
  97. package/dist/src/project-graph/plugins/tasks-execution-hooks.js +2 -1
  98. package/dist/src/project-graph/project-graph.js +2 -0
  99. package/dist/src/tasks-runner/batch/run-batch.js +15 -0
  100. package/dist/src/tasks-runner/cache.d.ts +35 -0
  101. package/dist/src/tasks-runner/cache.js +40 -0
  102. package/dist/src/tasks-runner/create-task-graph.js +1 -0
  103. package/dist/src/tasks-runner/default-tasks-runner.js +1 -1
  104. package/dist/src/tasks-runner/forked-process-task-runner.d.ts +1 -1
  105. package/dist/src/tasks-runner/forked-process-task-runner.js +8 -7
  106. package/dist/src/tasks-runner/init-tasks-runner.js +5 -1
  107. package/dist/src/tasks-runner/is-tui-enabled.js +4 -3
  108. package/dist/src/tasks-runner/life-cycle.d.ts +7 -0
  109. package/dist/src/tasks-runner/life-cycle.js +7 -0
  110. package/dist/src/tasks-runner/life-cycles/summary-terminal-output-life-cycle.d.ts +35 -0
  111. package/dist/src/tasks-runner/life-cycles/summary-terminal-output-life-cycle.js +146 -0
  112. package/dist/src/tasks-runner/prune-task-graph.d.ts +17 -0
  113. package/dist/src/tasks-runner/prune-task-graph.js +45 -0
  114. package/dist/src/tasks-runner/run-command.js +14 -3
  115. package/dist/src/tasks-runner/running-tasks/batch-process.d.ts +93 -19
  116. package/dist/src/tasks-runner/running-tasks/batch-process.js +184 -88
  117. package/dist/src/tasks-runner/task-orchestrator.d.ts +64 -2
  118. package/dist/src/tasks-runner/task-orchestrator.js +232 -46
  119. package/dist/src/tasks-runner/terminal-output-path.d.ts +11 -0
  120. package/dist/src/tasks-runner/terminal-output-path.js +18 -0
  121. package/dist/src/tasks-runner/utils.js +5 -2
  122. package/dist/src/utils/ab-testing.d.ts +3 -0
  123. package/dist/src/utils/ab-testing.js +11 -3
  124. package/dist/src/utils/chunkify.d.ts +1 -7
  125. package/dist/src/utils/chunkify.js +2 -8
  126. package/dist/src/utils/command-line-utils.d.ts +19 -0
  127. package/dist/src/utils/command-line-utils.js +10 -3
  128. package/dist/src/utils/formatters/index.d.ts +6 -0
  129. package/dist/src/utils/formatters/index.js +5 -0
  130. package/dist/src/utils/formatters/prettier.d.ts +0 -8
  131. package/dist/src/utils/formatters/prettier.js +4 -26
  132. package/dist/src/utils/installed-nx-version.js +2 -48
  133. package/dist/src/utils/min-release-age/packument.js +1 -1
  134. package/dist/src/utils/output.d.ts +22 -3
  135. package/dist/src/utils/output.js +28 -5
  136. package/dist/src/utils/package-json.d.ts +25 -0
  137. package/dist/src/utils/package-json.js +41 -6
  138. package/dist/src/utils/package-manager.d.ts +6 -0
  139. package/dist/src/utils/package-manager.js +10 -0
  140. package/dist/src/utils/provenance.js +20 -4
  141. package/dist/src/utils/workspace-context.d.ts +47 -3
  142. package/dist/src/utils/workspace-context.js +162 -10
  143. package/package.json +12 -12
  144. package/schemas/nx-schema.json +4 -0
  145. package/schemas/project-schema.json +4 -0
  146. package/dist/src/daemon/server/file-watching/route-workspace-changes.d.ts +0 -9
  147. package/dist/src/daemon/server/file-watching/route-workspace-changes.js +0 -76
  148. package/dist/src/daemon/server/watcher.d.ts +0 -18
  149. package/dist/src/daemon/server/watcher.js +0 -126
@@ -1,9 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WINDOWS_COMMAND_LINE_BUDGET = void 0;
3
4
  exports.runAgentic = runAgentic;
4
5
  exports.adaptSpawnForWindowsShim = adaptSpawnForWindowsShim;
6
+ const tslib_1 = require("tslib");
5
7
  const child_process_1 = require("child_process");
6
8
  const path_1 = require("path");
9
+ const pc = tslib_1.__importStar(require("picocolors"));
10
+ const logger_1 = require("../../../utils/logger");
7
11
  const output_1 = require("../../../utils/output");
8
12
  const migrate_analytics_1 = require("../migrate-analytics");
9
13
  const safe_prompt_1 = require("../safe-prompt");
@@ -11,7 +15,7 @@ const handoff_1 = require("./handoff");
11
15
  // How long to wait for the agent to exit gracefully after sending SIGINT once
12
16
  // a valid handoff has been written. Long enough for an interactive agent to
13
17
  // finish its current render and clean up; short enough that a frozen child
14
- // still gets escalated to SIGTERM in a sensible time.
18
+ // still gets escalated to SIGKILL in a sensible time.
15
19
  const AGENT_GRACEFUL_EXIT_MS = 5_000;
16
20
  /**
17
21
  * Spawns the selected agent with `stdio: 'inherit'`, swallows SIGINT while the
@@ -19,14 +23,8 @@ const AGENT_GRACEFUL_EXIT_MS = 5_000;
19
23
  * the handoff file (or the user when the file is missing).
20
24
  */
21
25
  async function runAgentic(args) {
22
- const { detected, definition, invocationContext, handoffFilePath, handoffPollIntervalMs, gracefulExitMs = AGENT_GRACEFUL_EXIT_MS, forceKillWaitMs = FORCE_KILL_WAIT_MS, } = args;
23
- const spec = definition.buildInteractive(invocationContext);
24
- const adapted = adaptSpawnForWindowsShim(detected.binary, spec.args, {
25
- stdio: 'inherit',
26
- cwd: spec.cwd ?? invocationContext.workspaceRoot,
27
- env: spec.env ? { ...process.env, ...spec.env } : process.env,
28
- windowsHide: true,
29
- });
26
+ const { detected, definition, invocationContext, handoffFilePath, handoffsDir, handoffPollIntervalMs, gracefulExitMs = AGENT_GRACEFUL_EXIT_MS, forceKillWaitMs = FORCE_KILL_WAIT_MS, } = args;
27
+ const adapted = adaptWithinCommandLineBudget(detected, definition, invocationContext);
30
28
  let child;
31
29
  // Local alias so `@nx/workspace-require-windows-hide` recognizes the
32
30
  // options arg as a tracked Identifier rather than giving up on a
@@ -36,7 +34,7 @@ async function runAgentic(args) {
36
34
  child = (0, child_process_1.spawn)(adapted.binary, adapted.args, spawnOptions);
37
35
  }
38
36
  catch (err) {
39
- return resolveFromHandoffOrPrompt(handoffFilePath, false, {
37
+ return resolveFromHandoffOrPrompt(handoffFilePath, handoffsDir, false, {
40
38
  spawnError: err instanceof Error ? err.message : String(err),
41
39
  });
42
40
  }
@@ -53,7 +51,7 @@ async function runAgentic(args) {
53
51
  process.on('SIGINT', swallowSigint);
54
52
  const handoffWatchAbort = new AbortController();
55
53
  const exitPromise = waitForExit(child);
56
- const handoffPromise = (0, handoff_1.waitForValidHandoff)(handoffFilePath, {
54
+ const handoffPromise = (0, handoff_1.waitForValidHandoff)(handoffFilePath, handoffsDir, {
57
55
  signal: handoffWatchAbort.signal,
58
56
  intervalMs: handoffPollIntervalMs,
59
57
  });
@@ -82,7 +80,56 @@ async function runAgentic(args) {
82
80
  process.removeListener('SIGINT', swallowSigint);
83
81
  restoreTermiosAfterAgent();
84
82
  }
85
- return resolveFromHandoffOrPrompt(handoffFilePath, userInterruptCount > 0, exitInfoToCause(exitInfo));
83
+ return resolveFromHandoffOrPrompt(handoffFilePath, handoffsDir, userInterruptCount > 0, exitInfoToCause(exitInfo));
84
+ }
85
+ // "The maximum length of the string that you can use at the command prompt is
86
+ // 8191 characters".
87
+ // https://learn.microsoft.com/troubleshoot/windows-client/shell-experience/command-line-string-limitation
88
+ const WINDOWS_COMMAND_LINE_LIMIT = 8191;
89
+ // Deliberate headroom below the documented limit for argument growth.
90
+ const WINDOWS_COMMAND_LINE_RESERVE = 1000;
91
+ exports.WINDOWS_COMMAND_LINE_BUDGET = WINDOWS_COMMAND_LINE_LIMIT - WINDOWS_COMMAND_LINE_RESERVE;
92
+ /**
93
+ * Builds Windows shim arguments within budget, trying the shorter context
94
+ * before aborting. Rejects overflow to avoid truncated instructions.
95
+ * Leaves command lines unmeasured off the shim path.
96
+ */
97
+ function adaptWithinCommandLineBudget(detected, definition, invocationContext) {
98
+ const adapt = (ctx) => {
99
+ const spec = definition.buildInteractive(ctx);
100
+ return adaptSpawnForWindowsShim(detected.binary, spec.args, {
101
+ stdio: 'inherit',
102
+ cwd: spec.cwd ?? ctx.workspaceRoot,
103
+ env: spec.env ? { ...process.env, ...spec.env } : process.env,
104
+ windowsHide: true,
105
+ });
106
+ };
107
+ const adapted = adapt(invocationContext);
108
+ if (withinCommandLineBudget(adapted)) {
109
+ return adapted;
110
+ }
111
+ const reduced = adapt({
112
+ ...invocationContext,
113
+ inlineSystemContext: invocationContext.inlineSystemContextFallback,
114
+ });
115
+ if (withinCommandLineBudget(reduced)) {
116
+ logger_1.logger.info(pc.dim(` Passing the agent a reduced system context. The full one does not fit on a Windows command line alongside these paths.`));
117
+ return reduced;
118
+ }
119
+ output_1.output.error({
120
+ title: `${detected.displayName} cannot be started for this migration step`,
121
+ bodyLines: [
122
+ `Launching it needs a ${reduced.commandLineLength}-character command line. cmd.exe runs at most ${WINDOWS_COMMAND_LINE_LIMIT} characters, and nx stops at ${exports.WINDOWS_COMMAND_LINE_BUDGET} to leave room for what it cannot measure from here.`,
123
+ `The length is paths: the workspace root, at ${invocationContext.workspaceRoot.length} characters, and the migration's package and name, each repeated across several of them.`,
124
+ ``,
125
+ `Re-run with \`--agentic=false\` to apply the remaining migrations yourself. Moving the workspace to a shorter path can help too, but only for agents that put it on the command line.`,
126
+ ],
127
+ });
128
+ throw new Error(`Cannot start ${detected.displayName}: the command line exceeds the Windows limit.`);
129
+ }
130
+ function withinCommandLineBudget(adapted) {
131
+ return (adapted.commandLineLength === undefined ||
132
+ adapted.commandLineLength <= exports.WINDOWS_COMMAND_LINE_BUDGET);
86
133
  }
87
134
  function exitInfoToCause(info) {
88
135
  const cause = {};
@@ -130,23 +177,14 @@ function restoreTermiosAfterAgent() {
130
177
  // before the process actually exits.
131
178
  const FORCE_KILL_WAIT_MS = 500;
132
179
  /**
133
- * Stops the agent process after a successful handoff. Platform-branched:
180
+ * Stops the agent process after a successful handoff.
134
181
  *
135
- * - POSIX: SIGINT (graceful, equivalent to user Ctrl+C) → wait
136
- * `gracefulExitMs` for the child to exit → SIGKILL → wait
137
- * `FORCE_KILL_WAIT_MS` (bounded) → return. SIGTERM is intentionally
138
- * skipped: a process that ignores SIGINT for 5s will hit the same
139
- * handler on SIGTERM, the extra step only delays the inevitable.
182
+ * POSIX: SIGINT, then SIGKILL after a bounded wait. SIGTERM is skipped so a
183
+ * child that ignored the first graceful signal does not get a second one.
140
184
  *
141
- * - Windows: skip SIGINT entirely. `child.kill('*')` on Windows is a
142
- * `TerminateProcess` call regardless of the signal name (Windows has
143
- * no POSIX signals), and on the `cmd.exe /d /s /c "..."` shim path it
144
- * would terminate cmd.exe while leaving the agent orphaned (parent
145
- * death doesn't cascade to children on Windows). `taskkill /T /F`
146
- * walks the process tree and kills cmd.exe AND the agent atomically;
147
- * that's the only reliable shutdown path here. `taskkill` failures
148
- * (binary missing, race with already-dead pid) are swallowed; the
149
- * safety bound returns regardless.
185
+ * Windows: `taskkill /T /F`. `child.kill` is a `TerminateProcess` call whatever
186
+ * the signal name, which on the `cmd.exe` shim path would kill the shim and
187
+ * orphan the agent. `taskkill` failures are swallowed.
150
188
  */
151
189
  async function closeAgentSession(child, exitPromise, gracefulExitMs, forceKillWaitMs) {
152
190
  if (child.exitCode !== null || child.signalCode !== null)
@@ -265,8 +303,8 @@ function waitForExit(child) {
265
303
  });
266
304
  });
267
305
  }
268
- async function resolveFromHandoffOrPrompt(handoffFilePath, userInterrupted = false, cause = {}) {
269
- const read = (0, handoff_1.readHandoffWithReason)(handoffFilePath);
306
+ async function resolveFromHandoffOrPrompt(handoffFilePath, handoffsDir, userInterrupted = false, cause = {}) {
307
+ const read = (0, handoff_1.readHandoffWithReason)(handoffFilePath, handoffsDir);
270
308
  if (read.ok === true) {
271
309
  return {
272
310
  kind: read.handoff.status,
@@ -286,18 +324,8 @@ async function resolveFromHandoffOrPrompt(handoffFilePath, userInterrupted = fal
286
324
  // unverified, so the skip stays. The orchestrator's standard failure
287
325
  // cascade surfaces the abort outcome.
288
326
  //
289
- // Forward the underlying cause as pre-rendered summary lines so the
290
- // caller can log it before "Aborted by user" — a Ctrl+C that masked
291
- // a SEPARATE crash still needs to show the user what crashed. Scrub
292
- // fields that are just the Ctrl+C itself reverberating: exit code
293
- // 130 (SIGINT) / 143 (SIGTERM from our escalation) and signals
294
- // SIGINT / SIGTERM reflect the user's own keystroke (and our
295
- // graceful-exit handling of it); surfacing them as "agent crashed"
296
- // would be noise. Anything else — code 1, code 137 (OOM), an
297
- // unrelated signal — is a separate diagnostic worth keeping. Note
298
- // that `spawnError` is structurally impossible here: the spawn-throw
299
- // path returns directly without registering the SIGINT listener, so
300
- // `userInterrupted` can never be true on that branch.
327
+ // After user interruption, suppress conventional stop statuses and retain
328
+ // independent failure diagnostics for the caller to display.
301
329
  const exitWasCtrlC = cause.exitCode === 130 ||
302
330
  cause.exitCode === 143 ||
303
331
  cause.exitSignal === 'SIGINT' ||
@@ -321,8 +349,8 @@ async function resolveFromHandoffOrPrompt(handoffFilePath, userInterrupted = fal
321
349
  /**
322
350
  * Node's `spawn` cannot directly execute `.cmd` / `.bat` shims on Windows;
323
351
  * `which` resolves to those when an agent was installed via npm. Wrap them in
324
- * a `cmd.exe /d /s /c` invocation with `windowsVerbatimArguments` so quoting
325
- * follows the cmd.exe convention rather than Node's default cooking.
352
+ * a `cmd.exe /c` invocation with `windowsVerbatimArguments` so quoting follows
353
+ * the cmd.exe convention rather than Node's default cooking.
326
354
  *
327
355
  * On non-Windows or for non-shim binaries this is a passthrough.
328
356
  */
@@ -334,28 +362,55 @@ function adaptSpawnForWindowsShim(binary, args, options) {
334
362
  if (ext !== '.cmd' && ext !== '.bat') {
335
363
  return { binary, args: [...args], options };
336
364
  }
365
+ assertNoLineBreaks(binary, args);
337
366
  const cmdLine = [escapeCmdCommand(binary), ...args.map(escapeCmdArg)].join(' ');
367
+ const comspec = process.env.comspec || 'cmd.exe';
368
+ // Both modes are set rather than inherited, since a machine-wide registry
369
+ // setting can flip either: `/e:on` for the `%cd:~,%` substring, `/v:off` so a
370
+ // `!` stays literal. The outer quotes stop `cmd.exe /c` stripping the inner
371
+ // ones around the binary path.
372
+ const cmdArgs = ['/e:on', '/v:off', '/d', '/s', '/c', `"${cmdLine}"`];
338
373
  return {
339
- binary: process.env.comspec || 'cmd.exe',
340
- // Outer pair of quotes is required so cmd.exe /c does not strip the inner
341
- // quotes around the binary path.
342
- args: ['/d', '/s', '/c', `"${cmdLine}"`],
374
+ binary: comspec,
375
+ args: cmdArgs,
343
376
  options: { ...options, windowsVerbatimArguments: true },
377
+ // `windowsVerbatimArguments` makes the command line the argv joined by
378
+ // single spaces, so this is what CreateProcess and then cmd.exe see.
379
+ commandLineLength: [comspec, ...cmdArgs].join(' ').length,
344
380
  };
345
381
  }
346
- const CMD_META_CHARS = /([()\][%!^"`<>&|;, ])/g;
347
- // Backslash-escape embedded quotes per MS C runtime convention, wrap in
348
- // quotes, then caret-escape cmd.exe metacharacters.
382
+ /** Rejects line breaks in shim commands to avoid truncated instructions. */
383
+ function assertNoLineBreaks(binary, args) {
384
+ const offending = [binary, ...args].find((value) => /[\r\n]/.test(value));
385
+ if (offending !== undefined) {
386
+ throw new Error(`Cannot pass a multi-line argument to "${binary}" on Windows: cmd.exe truncates the command line at the line break. Offending argument: ${JSON.stringify(offending.slice(0, 120))}`);
387
+ }
388
+ }
389
+ const CMD_META_CHARS = /([()\][!^"`<>&|;, ])/g;
349
390
  function escapeCmdArg(arg) {
350
- const quoted = `"${arg
351
- .replace(/(\\*)"/g, '$1$1\\"')
352
- .replace(/(\\*)$/, '$1$1')}"`;
353
- return quoted.replace(CMD_META_CHARS, '^$1');
391
+ return neutralizePercent(caretEscape(quoteCmdArg(arg)));
354
392
  }
355
393
  function escapeCmdCommand(arg) {
356
394
  // cmd.exe interprets the command portion through an extra parsing pass;
357
395
  // apply the caret-escape twice so the .cmd shim sees the original.
358
- return escapeCmdArg(arg).replace(CMD_META_CHARS, '^$1');
396
+ return neutralizePercent(caretEscape(caretEscape(quoteCmdArg(arg))));
397
+ }
398
+ // Double backslashes before quotes for the MS C runtime argv parser.
399
+ function quoteCmdArg(arg) {
400
+ return `"${arg.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, '$1$1')}"`;
401
+ }
402
+ function caretEscape(quoted) {
403
+ return quoted.replace(CMD_META_CHARS, '^$1');
404
+ }
405
+ /**
406
+ * Stops cmd.exe expanding `%VAR%` inside an argument. A caret cannot escape
407
+ * `%`, since expansion runs before caret processing, so each `%` becomes
408
+ * `%%cd:~,%`: `%cd:~,%` is a zero-length substring of a built-in and expands to
409
+ * nothing, leaving the leading `%`. Runs after the caret passes so the `,` it
410
+ * introduces stays uncareted, which the substring syntax requires.
411
+ */
412
+ function neutralizePercent(escaped) {
413
+ return escaped.replace(/%/g, '%%cd:~,%');
359
414
  }
360
415
  async function promptAmbiguous(cause) {
361
416
  // stderr blank line so the spacer lands in the same stream as output.warn
@@ -10,14 +10,17 @@ export type { AgentId };
10
10
  */
11
11
  export declare const MIGRATE_RUNS_RELATIVE_DIR = ".nx/migrate-runs";
12
12
  /**
13
- * The one subtree of a run directory an agent writes: its handoff files.
14
- * Everything beside it is state Nx owns and reads back, the orchestrator's
15
- * run state and plan snapshots included, so the pre-authorized write scope
16
- * stops at this segment. Package names make up the rest of a handoff path,
17
- * so without it they would occupy the run directory's top level and leave Nx
18
- * no name it could add there safely.
13
+ * The one subtree of a run directory an agent writes; everything beside it is
14
+ * state Nx owns, so the pre-authorized write scope stops here. Handoff names
15
+ * derive from step ids or package and migration names, so flattening them into
16
+ * the run dir would leave Nx no name it could safely add.
19
17
  */
20
18
  export declare const HANDOFFS_DIR_NAME = "handoffs";
19
+ /**
20
+ * Sibling subtree holding the per-step prompt files Nx writes for the agent
21
+ * to read. They stay out of `handoffs/`, which is the agent's to write.
22
+ */
23
+ export declare const PROMPTS_DIR_NAME = "prompts";
21
24
  export declare function isHandoffGitignoreMigration(m: {
22
25
  package: string;
23
26
  name: string;
@@ -38,10 +41,22 @@ export interface DetectedInstalledAgent {
38
41
  * Inputs the runner provides when asking an agent definition to build its
39
42
  * spawn arguments. Kept minimal — agent-specific quirks (e.g. transient agent
40
43
  * name for OpenCode) are encoded inside the definition, not here.
44
+ *
45
+ * Both prompts are written to disk (see `instruction-files.ts`) and reached
46
+ * through `systemPromptFilePath` / `instructionsPointer`. The inline fields
47
+ * below are the exception, for agents that cannot be pointed at a file.
41
48
  */
42
49
  export interface InvocationContext {
43
- systemContext: string;
44
- userPrompt: string;
50
+ /** Absolute path of the file holding the step's system prompt. */
51
+ systemPromptFilePath: string;
52
+ /** Verbatim system prompt, only for a step with no file-loading path. */
53
+ systemPrompt: string;
54
+ /** Single-line command-line text pointing the agent at its instructions. */
55
+ instructionsPointer: string;
56
+ /** Inline handoff contract and pointer to `systemPromptFilePath`. */
57
+ inlineSystemContext: string;
58
+ /** Shorter `inlineSystemContext`, substituted on Windows shim overflow. */
59
+ inlineSystemContextFallback: string;
45
60
  workspaceRoot: string;
46
61
  /**
47
62
  * Name of the run directory under `MIGRATE_RUNS_RELATIVE_DIR` holding this
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HANDOFFS_DIR_NAME = exports.MIGRATE_RUNS_RELATIVE_DIR = void 0;
3
+ exports.PROMPTS_DIR_NAME = exports.HANDOFFS_DIR_NAME = exports.MIGRATE_RUNS_RELATIVE_DIR = void 0;
4
4
  exports.isHandoffGitignoreMigration = isHandoffGitignoreMigration;
5
5
  /**
6
6
  * Workspace-relative directory holding all migrate-run scratch: a run
@@ -12,14 +12,17 @@ exports.isHandoffGitignoreMigration = isHandoffGitignoreMigration;
12
12
  */
13
13
  exports.MIGRATE_RUNS_RELATIVE_DIR = '.nx/migrate-runs';
14
14
  /**
15
- * The one subtree of a run directory an agent writes: its handoff files.
16
- * Everything beside it is state Nx owns and reads back, the orchestrator's
17
- * run state and plan snapshots included, so the pre-authorized write scope
18
- * stops at this segment. Package names make up the rest of a handoff path,
19
- * so without it they would occupy the run directory's top level and leave Nx
20
- * no name it could add there safely.
15
+ * The one subtree of a run directory an agent writes; everything beside it is
16
+ * state Nx owns, so the pre-authorized write scope stops here. Handoff names
17
+ * derive from step ids or package and migration names, so flattening them into
18
+ * the run dir would leave Nx no name it could safely add.
21
19
  */
22
20
  exports.HANDOFFS_DIR_NAME = 'handoffs';
21
+ /**
22
+ * Sibling subtree holding the per-step prompt files Nx writes for the agent
23
+ * to read. They stay out of `handoffs/`, which is the agent's to write.
24
+ */
25
+ exports.PROMPTS_DIR_NAME = 'prompts';
23
26
  /**
24
27
  * Composite identity of the v23 migration that adds `.nx/migrate-runs` to
25
28
  * `.gitignore`. Hard-coded because the agentic preflight is a deliberate
@@ -64,6 +64,7 @@ export declare function resolveCreateCommits(args: {
64
64
  warning?: string;
65
65
  error?: string;
66
66
  };
67
+ export declare function currentBranchIfDefault(root: string): string | null;
67
68
  /**
68
69
  * Asks before a run starts committing on the workspace's default branch, and
69
70
  * reports the decision when the answer is no. Returns whether to proceed.
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.commitMigrationIfRequested = commitMigrationIfRequested;
4
4
  exports.commitCheckpointBeforeMigrations = commitCheckpointBeforeMigrations;
5
5
  exports.resolveCreateCommits = resolveCreateCommits;
6
+ exports.currentBranchIfDefault = currentBranchIfDefault;
6
7
  exports.confirmMigrationCommitsOnDefaultBranch = confirmMigrationCommitsOnDefaultBranch;
7
8
  exports.confirmCommitsOnDefaultBranch = confirmCommitsOnDefaultBranch;
8
9
  const tslib_1 = require("tslib");
@@ -180,6 +181,14 @@ function defaultBranchToCompare(baseRef, currentBranch, root) {
180
181
  .sort((a, b) => b.length - a.length)[0];
181
182
  return remote ? baseRef.slice(remote.length + 1) : baseRef;
182
183
  }
184
+ function currentBranchIfDefault(root) {
185
+ const currentBranch = (0, git_utils_1.getGitCurrentBranch)(root);
186
+ if (!currentBranch) {
187
+ return null;
188
+ }
189
+ const defaultBranch = defaultBranchToCompare((0, command_line_utils_1.getBaseRef)((0, configuration_1.readNxJson)(root)), currentBranch, root);
190
+ return currentBranch === defaultBranch ? currentBranch : null;
191
+ }
183
192
  /**
184
193
  * Asks before a run starts committing on the workspace's default branch, and
185
194
  * reports the decision when the answer is no. Returns whether to proceed.
@@ -24,13 +24,9 @@ export declare function logMigrationBoundary(index: number, total: number, pkg:
24
24
  */
25
25
  export declare function logAgenticSuccessOutcome(label: string, sha: string | null, summary: string): void;
26
26
  /**
27
- * Logs the skip line for a migration that waived its AI step through
28
- * `skipAgentic`, plus a verbose note for any `agentContext` the waiver
29
- * dropped. A hybrid waives its paired prompt; a generator-only migration
30
- * waives the validation pass, so callers must reach here only once they know
31
- * one was on the table. Under `inside-agent` only a hybrid can, so the
32
- * hand-off dropped alongside is always a prompt's; a waived generator-only
33
- * migration keeps its own. The note is author-facing, hence `--verbose`.
27
+ * Callers must already know an agentic step was owed; this only picks which one
28
+ * the waiver dropped. The dropped-`agentContext` note is aimed at the migration
29
+ * author, hence `--verbose`.
34
30
  */
35
31
  export declare function logWaivedAgenticStep(migration: {
36
32
  package: string;
@@ -52,13 +52,9 @@ function logAgenticSuccessOutcome(label, sha, summary) {
52
52
  logger_1.logger.info(`${pc.green('✓')} ${label}${shaPart}: ${summary}`);
53
53
  }
54
54
  /**
55
- * Logs the skip line for a migration that waived its AI step through
56
- * `skipAgentic`, plus a verbose note for any `agentContext` the waiver
57
- * dropped. A hybrid waives its paired prompt; a generator-only migration
58
- * waives the validation pass, so callers must reach here only once they know
59
- * one was on the table. Under `inside-agent` only a hybrid can, so the
60
- * hand-off dropped alongside is always a prompt's; a waived generator-only
61
- * migration keeps its own. The note is author-facing, hence `--verbose`.
55
+ * Callers must already know an agentic step was owed; this only picks which one
56
+ * the waiver dropped. The dropped-`agentContext` note is aimed at the migration
57
+ * author, hence `--verbose`.
62
58
  */
63
59
  function logWaivedAgenticStep(migration, agentContext) {
64
60
  logger_1.logger.info(pc.dim((0, migration_shape_1.isHybridMigration)(migration)
@@ -1227,7 +1227,7 @@ async function getPackageMigrationsConfigFromRegistry(packageName, packageVersio
1227
1227
  if (!result) {
1228
1228
  return null;
1229
1229
  }
1230
- const json = JSON.parse(result);
1230
+ const json = (0, package_manager_1.parseRegistryViewJson)(result);
1231
1231
  if (!json['nx-migrations'] && !json['ng-update']) {
1232
1232
  const registry = new node_url_1.URL('dist' in json ? json.dist.tarball : json.tarball)
1233
1233
  .hostname;
@@ -2145,12 +2145,24 @@ async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits, c
2145
2145
  if (createCommitsWarning) {
2146
2146
  output_1.output.warn({ title: createCommitsWarning });
2147
2147
  }
2148
- // The run commits on the user's behalf across many invocations, so the
2149
- // default-branch confirmation belongs here, once, before any of them.
2150
- if (effectiveCreateCommits &&
2151
- (0, safe_prompt_1.canPrompt)(opts.interactive) &&
2152
- !(await (0, migrate_commits_1.confirmMigrationCommitsOnDefaultBranch)(root, 'running migrations'))) {
2153
- return;
2148
+ // The run commits on the user's behalf across many invocations and the
2149
+ // agent driving it cannot answer a terminal prompt, so a commit policy the
2150
+ // user never asked for stops the run on the default branch before any of
2151
+ // them. `--create-commits` or nx.json `migrate.createCommits` is that ask.
2152
+ if (effectiveCreateCommits && shouldCreateCommits === undefined) {
2153
+ const defaultBranch = (0, migrate_commits_1.currentBranchIfDefault)(root);
2154
+ if (defaultBranch) {
2155
+ output_1.output.log({
2156
+ title: `Not starting the run: you are on the default branch '${defaultBranch}' and nx migrate would create a commit for each migration on it.`,
2157
+ bodyLines: [
2158
+ 'Ask the user how to proceed, then either:',
2159
+ '- re-run with --create-commits to commit on this branch for this run,',
2160
+ '- set "migrate": { "createCommits": true } in nx.json to always allow it, then re-run,',
2161
+ '- or switch to another branch and re-run.',
2162
+ ],
2163
+ });
2164
+ return;
2165
+ }
2154
2166
  }
2155
2167
  const { packageJson: orchestratorNxPackageJson } = (0, package_json_1.readModulePackageJson)('nx', (0, installation_directory_1.getNxRequirePaths)(root));
2156
2168
  const { runOrchestratorInit } = require('./run');
@@ -2164,6 +2176,7 @@ async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits, c
2164
2176
  // what the user asked for.
2165
2177
  skipInstall: shouldSkipInstall,
2166
2178
  installedNxVersion: orchestratorNxPackageJson.version,
2179
+ validate: opts.validate,
2167
2180
  });
2168
2181
  }
2169
2182
  (0, migrate_analytics_1.reportMigrateRunStart)({
@@ -2412,7 +2425,7 @@ function stringifyCaught(e) {
2412
2425
  }
2413
2426
  }
2414
2427
  }
2415
- // A resolver-based lookup (including `resolvePackageJsonWithoutCachePollution`,
2428
+ // A resolver-based lookup (including `resolveWithoutCachePollution`,
2416
2429
  // which does defeat Node's package self-reference) is the wrong tool here:
2417
2430
  // resolvers fall back to NODE_PATH after the explicit paths, and NODE_PATH
2418
2431
  // names the temp installation when this runs there. The scan below reads its
@@ -18,3 +18,13 @@ export declare function logToAgent(message: AgentMessage): void;
18
18
  export declare function warnToAgent(message: AgentMessage): void;
19
19
  export declare function emitStepBlock(runId: string, stepId: string, action: string, payload: object): void;
20
20
  export declare function emitPromptBlock(migrationId: string, payload: object): void;
21
+ /**
22
+ * Markdown, not JSON: the block carries the runbook file's bytes, so the agent
23
+ * reads what a resume re-emits from disk. Every `<` opening or closing an
24
+ * `<nx_migrate_*>` tag is neutralized wherever it sits: a positional check
25
+ * (line starts plus a blank-character class) can be sidestepped by characters
26
+ * that render as nothing but match no enumerable class. The renderer never
27
+ * emits the literal sequence (runbook.spec pins that), so only tampered bytes
28
+ * change.
29
+ */
30
+ export declare function emitRunbookBlock(runId: string, content: string): void;
@@ -15,6 +15,7 @@ exports.logToAgent = logToAgent;
15
15
  exports.warnToAgent = warnToAgent;
16
16
  exports.emitStepBlock = emitStepBlock;
17
17
  exports.emitPromptBlock = emitPromptBlock;
18
+ exports.emitRunbookBlock = emitRunbookBlock;
18
19
  const output_1 = require("../../../utils/output");
19
20
  const print_dropped_agent_context_1 = require("../agentic/print-dropped-agent-context");
20
21
  const text_1 = require("../text");
@@ -47,11 +48,11 @@ function blockPayload(payload) {
47
48
  return JSON.stringify(payload, null, 2).replace(/</g, '\\u003c');
48
49
  }
49
50
  // A bare newline pair frames the block so adjacent stdout cannot run into it.
50
- function writeBlock(tag, attrs, json) {
51
+ function writeBlock(tag, attrs, body) {
51
52
  const attrText = attrs
52
53
  .map(([name, value]) => ` ${name}="${(0, print_dropped_agent_context_1.escapeXmlAttr)((0, text_1.singleLine)(value))}"`)
53
54
  .join('');
54
- process.stdout.write(`\n<${tag}${attrText}>\n${json}\n</${tag}>\n\n`);
55
+ process.stdout.write(`\n<${tag}${attrText}>\n${body}\n</${tag}>\n\n`);
55
56
  }
56
57
  function emitStepBlock(runId, stepId, action, payload) {
57
58
  writeBlock('nx_migrate_step', [
@@ -63,3 +64,16 @@ function emitStepBlock(runId, stepId, action, payload) {
63
64
  function emitPromptBlock(migrationId, payload) {
64
65
  writeBlock('nx_migrate_prompt', [['migration', migrationId]], blockPayload(payload));
65
66
  }
67
+ /**
68
+ * Markdown, not JSON: the block carries the runbook file's bytes, so the agent
69
+ * reads what a resume re-emits from disk. Every `<` opening or closing an
70
+ * `<nx_migrate_*>` tag is neutralized wherever it sits: a positional check
71
+ * (line starts plus a blank-character class) can be sidestepped by characters
72
+ * that render as nothing but match no enumerable class. The renderer never
73
+ * emits the literal sequence (runbook.spec pins that), so only tampered bytes
74
+ * change.
75
+ */
76
+ function emitRunbookBlock(runId, content) {
77
+ const neutralized = content.replace(/<(?=\/?nx_migrate_)/gi, '\\u003c');
78
+ writeBlock('nx_migrate_runbook', [['run-id', runId]], neutralized);
79
+ }
@@ -0,0 +1,37 @@
1
+ import type { MigrateStepAwaitingKind } from './run-state';
2
+ export declare function agentWorkPayloadPath(runDirPath: string, stepId: string, attempt: number): string;
3
+ export interface ExpectedAgentWorkPayload {
4
+ migrationId: string;
5
+ kind: MigrateStepAwaitingKind;
6
+ promptPath?: string;
7
+ }
8
+ /**
9
+ * Failures propagate: the runbook promises a parked step's block is
10
+ * re-emitted, so a caller that cannot store the payload must fail the attempt
11
+ * rather than park.
12
+ */
13
+ export declare function persistAgentWorkPayload(filePath: string, payload: object): void;
14
+ /**
15
+ * The stored payload, or null when nothing usable is on disk: the file is
16
+ * missing (the park predates this mechanism), does not parse to a plain
17
+ * object, or does not match what the step awaits. Null is undifferentiated on
18
+ * purpose: every caller's remediation is the same freshly derived payload.
19
+ * The returned object is never re-emitted as text; both re-emission sites go
20
+ * back through the block writer's escaping, so a tampered file cannot break
21
+ * the framing.
22
+ */
23
+ export declare function readAgentWorkPayload(filePath: string, expected: ExpectedAgentWorkPayload): Record<string, unknown> | null;
24
+ /**
25
+ * Returns the newest payload from the current generator lineage, or null.
26
+ * `fromAttempt` excludes generator runs discarded by reset-backed retries; an
27
+ * absent boundary fails closed for states written by older Nx.
28
+ */
29
+ export declare function latestStoredAgentWorkPayload(runDirPath: string, stepId: string, beforeAttempt: number, fromAttempt: number | undefined, expected: ExpectedAgentWorkPayload): Record<string, unknown> | null;
30
+ /**
31
+ * Best effort per file: the callers run after the outcome or rearm is already
32
+ * decided, so an entry that cannot be removed (a permission failure, a
33
+ * directory planted at the path) is left to whole-run pruning rather than
34
+ * failing the reconcile. A planted directory is never re-handed either way:
35
+ * reading it fails, which the reader treats as no stored copy.
36
+ */
37
+ export declare function removeAgentWorkPayloads(runDirPath: string, stepId: string, throughAttempt: number): void;