nx 24.0.0-beta.1 → 24.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/src/command-line/graph/graph.js +2 -1
  2. package/dist/src/command-line/migrate/agentic/handoff-gitignore.d.ts +19 -23
  3. package/dist/src/command-line/migrate/agentic/handoff-gitignore.js +16 -19
  4. package/dist/src/command-line/migrate/agentic/master/run-master-session.js +25 -13
  5. package/dist/src/command-line/migrate/agentic/master/spawn-master.js +6 -7
  6. package/dist/src/command-line/migrate/agentic/select.js +3 -3
  7. package/dist/src/command-line/migrate/agentic/types.js +1 -3
  8. package/dist/src/command-line/migrate/command-object.js +5 -9
  9. package/dist/src/command-line/migrate/migrate-analytics.d.ts +6 -0
  10. package/dist/src/command-line/migrate/migrate-analytics.js +12 -0
  11. package/dist/src/command-line/migrate/migrate-config.d.ts +6 -6
  12. package/dist/src/command-line/migrate/migrate-config.js +12 -6
  13. package/dist/src/command-line/migrate/migrate.d.ts +3 -30
  14. package/dist/src/command-line/migrate/migrate.js +163 -231
  15. package/dist/src/command-line/migrate/run/existing-run-report.d.ts +1 -1
  16. package/dist/src/command-line/migrate/run/existing-run-report.js +7 -5
  17. package/dist/src/command-line/migrate/run/index.d.ts +3 -3
  18. package/dist/src/command-line/migrate/run/index.js +5 -1
  19. package/dist/src/command-line/migrate/run/orchestrator.d.ts +21 -0
  20. package/dist/src/command-line/migrate/run/orchestrator.js +77 -19
  21. package/dist/src/command-line/migrate/run/run-state.d.ts +13 -0
  22. package/dist/src/command-line/migrate/run/run-state.js +21 -0
  23. package/dist/src/command-line/migrate/sort-migrations.d.ts +1 -7
  24. package/dist/src/command-line/migrate/sort-migrations.js +1 -12
  25. package/dist/src/config/nx-json.d.ts +13 -4
  26. package/dist/src/config/workspace-json-project-json.d.ts +12 -0
  27. package/dist/src/core/graph/main.js +1 -1
  28. package/dist/src/hasher/task-hasher.d.ts +2 -0
  29. package/dist/src/hasher/task-hasher.js +4 -1
  30. package/dist/src/native/index.d.ts +32 -3
  31. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  32. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  33. package/dist/src/project-graph/utils/project-configuration/target-normalization.js +6 -1
  34. package/dist/src/project-graph/utils/retrieve-workspace-files.js +27 -6
  35. package/dist/src/tasks-runner/default-tasks-runner.js +1 -1
  36. package/dist/src/tasks-runner/run-command.js +2 -2
  37. package/dist/src/tasks-runner/running-tasks/batch-process.js +6 -0
  38. package/dist/src/tasks-runner/task-orchestrator.d.ts +10 -6
  39. package/dist/src/tasks-runner/task-orchestrator.js +89 -59
  40. package/dist/src/tasks-runner/tasks-runner.d.ts +1 -1
  41. package/dist/src/utils/plugins/core-plugins.js +4 -0
  42. package/package.json +12 -11
  43. package/schemas/nx-schema.json +28 -4
  44. package/schemas/project-schema.json +22 -2
@@ -13,7 +13,6 @@ exports.isRunPhaseInvocation = isRunPhaseInvocation;
13
13
  exports.migrate = migrate;
14
14
  exports.readLocalNxVersion = readLocalNxVersion;
15
15
  exports.runMigration = runMigration;
16
- exports.resolveMigrationForRun = resolveMigrationForRun;
17
16
  exports.nxCliPath = nxCliPath;
18
17
  const tslib_1 = require("tslib");
19
18
  const pc = tslib_1.__importStar(require("picocolors"));
@@ -57,6 +56,7 @@ const multi_major_1 = require("./multi-major");
57
56
  const prompt_files_1 = require("./prompt-files");
58
57
  const command_object_1 = require("./command-object");
59
58
  const migrate_config_1 = require("./migrate-config");
59
+ const types_1 = require("./agentic/types");
60
60
  const migrate_commits_1 = require("./migrate-commits");
61
61
  const migrate_output_1 = require("./migrate-output");
62
62
  const migration_shape_1 = require("./migration-shape");
@@ -786,9 +786,7 @@ async function parseMigrationsOptions(options, fetch) {
786
786
  options.stepAction !== undefined) {
787
787
  throw new Error(`Error: '--step-action' cannot be combined with '--run-migrations'.`);
788
788
  }
789
- // A bare '--run-id' reconciles the run it names. Ungated, unlike init:
790
- // the id has to name a run directory that exists, and only a gated init
791
- // ever creates one.
789
+ // A bare '--run-id' reconciles the run it names.
792
790
  // yargs' choices already reject bad CLI values; this guards programmatic
793
791
  // callers, where silently dropping the action would reconcile without it.
794
792
  if (options.stepAction !== undefined && !(0, step_actions_1.isStepAction)(options.stepAction)) {
@@ -1761,23 +1759,12 @@ function formatSkippedPromptsNextStep(skipped) {
1761
1759
  ...skipped.map((m) => ` - ${m.prompt}`),
1762
1760
  ].join('\n');
1763
1761
  }
1764
- async function executeMigrations(root, migrations, isVerbose, shouldCreateCommits, commitPrefix, shouldSkipInstall = false, agentic, agenticHasDiffContext = false, shouldRunValidation = false) {
1762
+ async function executeMigrations(root, migrations, isVerbose, shouldCreateCommits, commitPrefix, shouldSkipInstall = false,
1763
+ // An enabled agent drives the whole run from one session instead.
1764
+ agentic) {
1765
1765
  const changedDepInstaller = new execute_migration_1.ChangedDepInstaller(root, shouldSkipInstall);
1766
1766
  const migrationsWithNoChanges = [];
1767
- const sortedMigrations = (0, sort_migrations_1.sortMigrations)(migrations, {
1768
- hoistHandoffGitignore: agentic?.kind === 'enabled',
1769
- });
1770
- // Lazy-load the agentic chain so non-agentic runs don't pay its startup cost.
1771
- let agenticRun;
1772
- if (agentic?.kind === 'enabled' && sortedMigrations.length > 0) {
1773
- const { initRunDir, resolveAgenticRunId } = require('./agentic/handoff');
1774
- const { runAgenticPromptStep } = require('./agentic/run-step');
1775
- agenticRun = {
1776
- agentic,
1777
- runDir: initRunDir(root, resolveAgenticRunId(sortedMigrations)),
1778
- runStep: runAgenticPromptStep,
1779
- };
1780
- }
1767
+ const sortedMigrations = (0, sort_migrations_1.sortMigrations)(migrations);
1781
1768
  const printDroppedAgentContext = agentic?.kind === 'inside-agent'
1782
1769
  ? require('./agentic/print-dropped-agent-context').printDroppedAgentContextForOuterAgent
1783
1770
  : undefined;
@@ -1812,12 +1799,6 @@ async function executeMigrations(root, migrations, isVerbose, shouldCreateCommit
1812
1799
  const pendingForCommitBody = () => outcomes
1813
1800
  .filter((o) => o.commit.kind === 'failed')
1814
1801
  .map((o) => ({ package: o.migration.package, name: o.migration.name }));
1815
- // True while at least one prior migration's commit has failed and its
1816
- // diff hasn't been absorbed yet. While true, the working tree carries
1817
- // prior-migration state, so the `hasDiffContext` flag in the hybrid-
1818
- // agentic and validation-agentic prompt branches is suppressed (the
1819
- // prompt-only-with-agentic branch doesn't use `hasDiffContext`).
1820
- const hasPendingCommitDebt = () => outcomes.some((o) => o.commit.kind === 'failed');
1821
1802
  // Single funnel for per-migration commit attempts. Returns the
1822
1803
  // `CommitState` to record on the migration's outcome. On `committed`,
1823
1804
  // back-annotates any prior failed-commit outcomes to `kind: 'absorbed'`
@@ -1850,8 +1831,7 @@ async function executeMigrations(root, migrations, isVerbose, shouldCreateCommit
1850
1831
  return { kind: 'landed', sha: result.sha };
1851
1832
  }
1852
1833
  if (result.status === 'failed') {
1853
- // Diff is still in WT. Subsequent prompts cannot claim git-isolation
1854
- // until a later commit absorbs the backlog.
1834
+ // The diff stays in the working tree until a later commit absorbs it.
1855
1835
  return { kind: 'failed' };
1856
1836
  }
1857
1837
  // `no-changes` and `disabled` — no commit attempted, nothing to record
@@ -1884,89 +1864,36 @@ async function executeMigrations(root, migrations, isVerbose, shouldCreateCommit
1884
1864
  // Content-sensitive so a dirty→dirty case (this migration mutating an
1885
1865
  // already-dirty shared file like `package.json`) doesn't collapse.
1886
1866
  const baselineWorkingTreeSnapshot = (0, git_utils_1.getUncommittedChangesSnapshot)(root);
1887
- // Tracks whether a failure originated in the agentic step so the error
1888
- // event classifies it as 'agentic' rather than 'migration_exec'.
1889
- let inAgenticStep = false;
1890
1867
  try {
1891
- // Read this migration's collection once and derive everything from it:
1892
- // the implementation context (passed to runNxOrAngularMigration) and the
1893
- // documentation path (passed to the agent). Read fresh per iteration so a
1894
- // prior migration's reinstall is reflected.
1895
- const { resolvedCollection, documentationPath } = resolveMigrationForRun(root, m, !!agenticRun);
1868
+ // Read fresh per migration so a prior migration's reinstall is reflected.
1869
+ const resolvedCollection = (0, migration_shape_1.isPromptOnlyMigration)(m)
1870
+ ? undefined
1871
+ : (0, execute_migration_1.readMigrationCollection)(m.package, root);
1896
1872
  let outcome;
1897
1873
  let commit = { kind: 'none' };
1898
- // Set when the migration returned `skipAgentic: true` and something was
1899
- // actually waived, so the end-of-run recaps can report it. A hybrid's
1900
- // prompt is owed in every agentic mode, so waiving it always counts; a
1901
- // generator-only migration only counts when validation would have run.
1874
+ // Set when a hybrid returned `skipAgentic: true`, so the end-of-run
1875
+ // recaps can report the waived prompt.
1902
1876
  let waivedAgenticStep = false;
1903
1877
  if ((0, migration_shape_1.isPromptOnlyMigration)(m)) {
1904
- if (agenticRun) {
1905
- inAgenticStep = true;
1906
- const stepResult = await agenticRun.runStep({
1907
- root,
1908
- migration: m,
1909
- agentic: agenticRun.agentic,
1910
- runDir: agenticRun.runDir,
1911
- installDepsIfChanged,
1912
- documentationPath,
1913
- });
1914
- inAgenticStep = false;
1915
- commit = await attemptMigrationCommit(m);
1916
- (0, migrate_output_1.logAgenticSuccessOutcome)(stepResult.ambiguous ? 'Marked complete by user' : 'Applied', commit.kind === 'landed' ? commit.sha : null, stepResult.summary);
1917
- outcome = 'applied';
1918
- }
1919
- else {
1920
- logger_1.logger.info(pc.dim(`↷ Skipped — ${skipReason}. Listed in next steps.`));
1921
- skippedPrompts.push(m);
1922
- notRunMigrationsCount++;
1923
- outcome = 'deferred';
1924
- }
1878
+ logger_1.logger.info(pc.dim(`↷ Skipped — ${skipReason}. Listed in next steps.`));
1879
+ skippedPrompts.push(m);
1880
+ notRunMigrationsCount++;
1881
+ outcome = 'deferred';
1925
1882
  }
1926
1883
  else if ((0, migration_shape_1.isHybridMigration)(m)) {
1927
- const { changes, nextSteps, agentContext, skipAgentic, logs, madeChanges, } = await (0, execute_migration_1.runNxOrAngularMigration)(root, m, isVerbose,
1928
- /* captureGeneratorOutput: */ !!agenticRun, resolvedCollection);
1884
+ const { nextSteps, agentContext, skipAgentic, madeChanges } = await (0, execute_migration_1.runNxOrAngularMigration)(root, m, isVerbose,
1885
+ /* captureGeneratorOutput: */ false, resolvedCollection);
1929
1886
  migrationEmittedNextSteps.push(...nextSteps);
1930
1887
  if (skipAgentic) {
1931
1888
  // The generator reported the prompt half unnecessary, so nothing is
1932
- // owed: no agent run, and no next-steps entry telling the user to
1933
- // run the prompt themselves. Runs in all three agentic modes.
1889
+ // owed: no next-steps entry telling anyone to run the prompt.
1934
1890
  (0, migrate_output_1.logWaivedAgenticStep)(m, agentContext);
1935
1891
  waivedAgenticStep = true;
1936
1892
  commit = await commitOrRecordNoChanges(m, madeChanges);
1937
1893
  outcome = madeChanges ? 'applied' : 'no-changes';
1938
1894
  }
1939
- else if (agenticRun) {
1940
- // Install any deps the deterministic phase added/bumped before the
1941
- // agent runs — the prompt half may depend on them being present in
1942
- // node_modules.
1943
- await installDepsIfChanged();
1944
- inAgenticStep = true;
1945
- const stepResult = await agenticRun.runStep({
1946
- root,
1947
- migration: m,
1948
- agentic: agenticRun.agentic,
1949
- runDir: agenticRun.runDir,
1950
- installDepsIfChanged,
1951
- documentationPath,
1952
- implContext: {
1953
- logs,
1954
- changes,
1955
- agentContext,
1956
- // When prior commits failed, the working tree carries their
1957
- // diff. The git-inspect path of the prompt would mislead the
1958
- // agent in that case; fall back to embedded `<files_changed>`.
1959
- hasDiffContext: agenticHasDiffContext && !hasPendingCommitDebt(),
1960
- },
1961
- });
1962
- inAgenticStep = false;
1963
- commit = await attemptMigrationCommit(m);
1964
- (0, migrate_output_1.logAgenticSuccessOutcome)(stepResult.ambiguous ? 'Marked complete by user' : 'Applied', commit.kind === 'landed' ? commit.sha : null, stepResult.summary);
1965
- outcome = 'applied';
1966
- }
1967
1895
  else {
1968
- // The inner prompt step doesn't run here (agentic disabled, or
1969
- // running inside an outer agent). Under `inside-agent`, surface the
1896
+ // The prompt half is deferred. Under `inside-agent`, surface the
1970
1897
  // generator-emitted `agentContext` to stdout so the outer driving
1971
1898
  // agent can ingest it. Under `disabled` the run is human-driven;
1972
1899
  // agent-targeted context would only add noise — drop.
@@ -1980,59 +1907,16 @@ async function executeMigrations(root, migrations, isVerbose, shouldCreateCommit
1980
1907
  }
1981
1908
  }
1982
1909
  else {
1983
- // Defer commit until validation succeeds; failed validation leaves
1984
- // changes uncommitted in the working tree for the user to review.
1985
- const validationRun = agenticRun && shouldRunValidation ? agenticRun : undefined;
1986
- const { changes, nextSteps, agentContext, skipAgentic, logs, madeChanges, } = await (0, execute_migration_1.runNxOrAngularMigration)(root, m, isVerbose,
1987
- /* captureGeneratorOutput: */ !!validationRun, resolvedCollection);
1910
+ const { nextSteps, agentContext, madeChanges } = await (0, execute_migration_1.runNxOrAngularMigration)(root, m, isVerbose,
1911
+ /* captureGeneratorOutput: */ false, resolvedCollection);
1988
1912
  migrationEmittedNextSteps.push(...nextSteps);
1989
- // Whether a validation step was on the table at all. `skipAgentic`
1990
- // waives nothing when it wasn't, so the whole waived path hangs off
1991
- // this, not just the log line.
1992
- const validationApplies = !!validationRun && changes.length > 0;
1993
- const canRunValidation = validationApplies && !skipAgentic;
1994
- if (canRunValidation) {
1995
- // Install any deps the deterministic phase added/bumped before the
1996
- // validation agent runs — the agent may run tasks that need them.
1997
- await installDepsIfChanged();
1998
- inAgenticStep = true;
1999
- const stepResult = await validationRun.runStep({
2000
- root,
2001
- migration: m,
2002
- agentic: validationRun.agentic,
2003
- runDir: validationRun.runDir,
2004
- installDepsIfChanged,
2005
- documentationPath,
2006
- implContext: {
2007
- logs,
2008
- changes,
2009
- agentContext,
2010
- // See the hybrid agentic branch above for the rationale on
2011
- // why pending commit debt gates git-inspect context.
2012
- hasDiffContext: agenticHasDiffContext && !hasPendingCommitDebt(),
2013
- },
2014
- mode: 'generic-validation',
2015
- });
2016
- inAgenticStep = false;
2017
- commit = await attemptMigrationCommit(m);
2018
- (0, migrate_output_1.logAgenticSuccessOutcome)(stepResult.ambiguous
2019
- ? 'Marked complete by user'
2020
- : 'Validation passed', commit.kind === 'landed' ? commit.sha : null, stepResult.summary);
2021
- outcome = 'applied';
2022
- }
2023
- else {
2024
- if (skipAgentic && validationApplies) {
2025
- (0, migrate_output_1.logWaivedAgenticStep)(m, agentContext);
2026
- waivedAgenticStep = true;
2027
- }
2028
- else if (printDroppedAgentContext && agentContext.length > 0) {
2029
- // Inner validation step didn't run. Surface `agentContext` under
2030
- // `inside-agent` so the outer driving agent can ingest it.
2031
- printDroppedAgentContext({ migration: m, agentContext });
2032
- }
2033
- commit = await commitOrRecordNoChanges(m, madeChanges);
2034
- outcome = madeChanges ? 'applied' : 'no-changes';
1913
+ if (printDroppedAgentContext && agentContext.length > 0) {
1914
+ // Surface `agentContext` under `inside-agent` so the outer driving
1915
+ // agent can ingest it.
1916
+ printDroppedAgentContext({ migration: m, agentContext });
2035
1917
  }
1918
+ commit = await commitOrRecordNoChanges(m, madeChanges);
1919
+ outcome = madeChanges ? 'applied' : 'no-changes';
2036
1920
  }
2037
1921
  outcomes.push({
2038
1922
  migration: { package: m.package, name: m.name },
@@ -2062,9 +1946,7 @@ async function executeMigrations(root, migrations, isVerbose, shouldCreateCommit
2062
1946
  (0, migrate_analytics_1.reportMigrateRunError)({
2063
1947
  code: e instanceof execute_migration_1.NpmPeerDepsInstallError
2064
1948
  ? 'npm_install'
2065
- : inAgenticStep
2066
- ? 'agentic'
2067
- : 'migration_exec',
1949
+ : 'migration_exec',
2068
1950
  migrationPackage: m.package,
2069
1951
  migrationName: m.name,
2070
1952
  migrationCount: totalMigrations,
@@ -2126,8 +2008,70 @@ async function executeMigrations(root, migrations, isVerbose, shouldCreateCommit
2126
2008
  retainedAtSuccess: (0, migrate_output_1.retainedMigrations)(outcomes).map((p) => `${p.package}: ${p.name}`),
2127
2009
  };
2128
2010
  }
2129
- function orchestratorFlagNeedsOrchestrator(flag) {
2130
- return `'${flag}' acts on an orchestrated migrate run (NX_MIGRATE_ORCHESTRATOR=true with an enabled agent), and this invocation is not orchestrated.`;
2011
+ const CONTINUE_NEEDS_ORCHESTRATION = `'--run-id' continues an orchestrated migrate run, and this invocation is not orchestrated. Orchestration needs an enabled agent, or an AI agent running nx outside CI; --agentic=false and the WASM build turn it off.`;
2012
+ const ID_PROBLEM_LABELS = {
2013
+ 'not-shell-safe': 'not shell-safe',
2014
+ duplicate: 'listed more than once',
2015
+ };
2016
+ // The classic loop records no run, so it must not run over an active one. The
2017
+ // run is reported or asked about before the default-branch prompt; a start
2018
+ // fresh is checked there too, and the caller deletes the run only after it.
2019
+ async function settleActiveRunForClassic(root, opts, migrations) {
2020
+ const { activeRunForClassic, checkRunForStartFresh, renderContinueCommand, renderExistingRunCommands, renderExistingRunReport, } = require('./run');
2021
+ const active = activeRunForClassic(root, migrations.map((m) => `${m.package}:${m.name}`));
2022
+ // With no active run left, the check refuses as init does: the run
2023
+ // completed after the pre-install check, so its plan already ran.
2024
+ if (opts.startFresh === true &&
2025
+ (active === null || active.runId === opts.runId)) {
2026
+ checkRunForStartFresh(root, opts.runId);
2027
+ return { kind: 'run', replaceRunId: opts.runId };
2028
+ }
2029
+ if (!active) {
2030
+ return { kind: 'run' };
2031
+ }
2032
+ const { runId, facts } = active;
2033
+ // Start fresh refuses while another process holds the run, so a held run
2034
+ // leaves nothing to ask; an agent cannot answer a prompt.
2035
+ const held = facts.otherHolders === 'unknown' || facts.otherHolders.length > 0;
2036
+ if (opts.startFresh !== true &&
2037
+ !held &&
2038
+ (0, safe_prompt_1.canPrompt)(opts.interactive) &&
2039
+ !(0, inception_1.isInsideAgent)()) {
2040
+ output_1.output.log(renderExistingRunReport(facts));
2041
+ const choice = await (0, safe_prompt_1.migrateChoice)({
2042
+ message: 'What do you want to do with the active migrate run?',
2043
+ choices: [
2044
+ {
2045
+ value: 'start-fresh',
2046
+ label: 'Start fresh',
2047
+ hint: 'deletes the run record, then runs the whole plan without an agent',
2048
+ },
2049
+ { value: 'abort', label: 'Abort', hint: 'leaves the run as it is' },
2050
+ ],
2051
+ });
2052
+ switch (choice) {
2053
+ case 'start-fresh':
2054
+ checkRunForStartFresh(root, runId);
2055
+ return { kind: 'run', replaceRunId: runId };
2056
+ case 'abort':
2057
+ output_1.output.log({
2058
+ title: `Leaving migrate run ${runId} as it is. To continue it with an agent, run ${renderContinueCommand(root, runId, facts.policy)}.`,
2059
+ });
2060
+ (0, migrate_analytics_1.reportMigrateRunStopped)('aborted');
2061
+ return { kind: 'stop', exitCode: undefined };
2062
+ default: {
2063
+ const unhandled = choice;
2064
+ throw new Error(`Unhandled choice: ${unhandled}`);
2065
+ }
2066
+ }
2067
+ }
2068
+ const report = renderExistingRunReport(facts, renderExistingRunCommands(root, facts, opts.runMigrations, opts.agentic === false ? false : undefined));
2069
+ if (native_1.IS_WASM) {
2070
+ report.bodyLines.push(`The WASM build can run neither command. Continue the run with the native nx binary, or make sure no nx migrate process is acting on it, remove ${types_1.MIGRATE_RUNS_RELATIVE_DIR}/${runId}, then run the plan again.`);
2071
+ }
2072
+ output_1.output.warn(report);
2073
+ (0, migrate_analytics_1.reportMigrateRunStopped)('existing_run');
2074
+ return { kind: 'stop', exitCode: 1 };
2131
2075
  }
2132
2076
  // nx is located at spawn time, after the gated pre-install, so the child runs
2133
2077
  // the bytes that install put in place.
@@ -2143,20 +2087,18 @@ function handOffToLocalNx(args) {
2143
2087
  return exitCode !== 0 ? exitCode : undefined;
2144
2088
  }
2145
2089
  async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits, commitPrefix, shouldSkipInstall = false) {
2146
- // Both flags act on an orchestrated run: refuse them where none can run
2147
- // (an explicit --agentic=false outside an agent cannot reach one either),
2148
- // before the install and before --if-exists could return silently.
2149
- const orchestratorFlag = opts.startFresh === true
2150
- ? '--start-fresh'
2151
- : opts.runId !== undefined
2152
- ? '--run-id'
2153
- : undefined;
2154
- if (orchestratorFlag !== undefined &&
2155
- (process.env.NX_MIGRATE_ORCHESTRATOR !== 'true' ||
2156
- (opts.agentic === false && !(0, inception_1.isInsideAgent)()))) {
2157
- throw new Error(orchestratorFlagNeedsOrchestrator(orchestratorFlag));
2158
- }
2090
+ // The WASM build lacks the native locks a durable run relies on.
2091
+ const outerAgentDrivesRun = (0, inception_1.isInsideAgent)() && opts.agentic !== false && !(0, is_ci_1.isCI)() && !native_1.IS_WASM;
2159
2092
  const isContinue = opts.runId !== undefined && opts.startFresh !== true;
2093
+ // Refused before the install, and before --if-exists could return silently,
2094
+ // when neither the outer agent nor a master session (an agent enabled
2095
+ // outside one, off WASM) can continue the run. A start fresh may reach the
2096
+ // classic loop, which deletes the run's record and runs the plan.
2097
+ if (isContinue &&
2098
+ !outerAgentDrivesRun &&
2099
+ ((0, inception_1.isInsideAgent)() || opts.agentic === false || native_1.IS_WASM)) {
2100
+ throw new Error(CONTINUE_NEEDS_ORCHESTRATION);
2101
+ }
2160
2102
  let continued;
2161
2103
  if (isContinue) {
2162
2104
  // Before the install: a concurrent start-fresh must not delete the run
@@ -2251,11 +2193,19 @@ async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits, c
2251
2193
  validate: opts.validate,
2252
2194
  finalValidation: opts.finalValidation,
2253
2195
  });
2196
+ // Orchestration refuses ids it cannot dispense verbatim or resolve to one
2197
+ // step, so a new run with one falls back to the classic loop. A continue
2198
+ // has no classic fallback, since it runs the plan its run recorded, and a
2199
+ // start fresh still reaches init's refusal.
2200
+ const idProblems = isContinue || opts.startFresh === true
2201
+ ? []
2202
+ : require('./run').migrationIdProblems(migrations);
2203
+ const idProblemLines = idProblems.map(({ id, problem }) => `- ${(0, text_1.singleLine)(id)} (${ID_PROBLEM_LABELS[problem]})`);
2254
2204
  // An outer agent drives the loop, so hand off to the orchestrator instead of
2255
2205
  // the classic loop: init starts a fresh run or reports an already-active
2256
2206
  // one; `--run-id` continues that run. Bare `--run-id` reconciles are
2257
2207
  // dispatched separately and never reach here.
2258
- if (process.env.NX_MIGRATE_ORCHESTRATOR === 'true' && (0, inception_1.isInsideAgent)()) {
2208
+ if (outerAgentDrivesRun && idProblems.length === 0) {
2259
2209
  const { runOrchestratorInit, runOrchestratorResume } = require('./run');
2260
2210
  // Orchestrated runs are agent-driven, so commits default on exactly as they
2261
2211
  // do under `--agentic=enabled`; the orchestrator replaces `resolveAgentic`.
@@ -2316,11 +2266,20 @@ async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits, c
2316
2266
  });
2317
2267
  return;
2318
2268
  }
2269
+ if (outerAgentDrivesRun) {
2270
+ output_1.output.warn({
2271
+ title: 'Running the migrations without an orchestrated run: orchestrated runs need each migration id to be shell-safe (letters, digits and @/:._-) and listed once, and these are not:',
2272
+ bodyLines: [
2273
+ ...idProblemLines,
2274
+ 'No run is recorded, so --run-id cannot continue this one. Prompt work is listed as next steps for the AI agent driving this run, and no validation runs.',
2275
+ ],
2276
+ });
2277
+ }
2319
2278
  (0, migrate_analytics_1.reportMigrateRunStart)({
2320
2279
  createCommits: shouldCreateCommits ?? false,
2321
2280
  migrationCount: migrations.length,
2322
2281
  });
2323
- const { resolveAgentic, resolveShouldRunValidation } = require('./agentic/select');
2282
+ const { resolveAgentic } = require('./agentic/select');
2324
2283
  let agentic;
2325
2284
  try {
2326
2285
  agentic = await resolveAgentic({
@@ -2333,7 +2292,25 @@ async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits, c
2333
2292
  (0, migrate_analytics_1.reportMigrateRunError)({ code: 'agentic', error: e });
2334
2293
  throw e;
2335
2294
  }
2336
- const { effective: effectiveCreateCommits, agenticHasDiffContext, warning: createCommitsWarning, error: createCommitsError, } = (0, migrate_commits_1.resolveCreateCommits)({
2295
+ // Resolved before the commit policy, which defaults on only for an agent.
2296
+ if (agentic.kind === 'enabled' && native_1.IS_WASM) {
2297
+ output_1.output.warn({
2298
+ title: 'Skipping the agentic flow: it needs the native nx binary, and this run loaded the WASM build.',
2299
+ bodyLines: ['Continuing the migration without the agentic flow.'],
2300
+ });
2301
+ agentic = { kind: 'disabled' };
2302
+ }
2303
+ if (agentic.kind === 'enabled' && idProblems.length > 0) {
2304
+ output_1.output.warn({
2305
+ title: 'Skipping the agentic flow: it needs each migration id to be shell-safe (letters, digits and @/:._-) and listed once, and these are not:',
2306
+ bodyLines: [
2307
+ ...idProblemLines,
2308
+ 'Continuing the migration without the agentic flow. Generators still run. Prompt-only migrations and the prompt part of hybrid migrations are skipped and listed as next steps, and no AI validation runs.',
2309
+ ],
2310
+ });
2311
+ agentic = { kind: 'disabled' };
2312
+ }
2313
+ const { effective: effectiveCreateCommits, warning: createCommitsWarning, error: createCommitsError, } = (0, migrate_commits_1.resolveCreateCommits)({
2337
2314
  createCommits: shouldCreateCommits,
2338
2315
  mode: agentic.kind,
2339
2316
  isGitRepo: (0, git_utils_1.isGitRepository)(root),
@@ -2351,12 +2328,8 @@ async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits, c
2351
2328
  const confirmNewRunCommits = async () => !effectiveCreateCommits ||
2352
2329
  !(0, safe_prompt_1.canPrompt)(opts.interactive) ||
2353
2330
  (0, migrate_commits_1.confirmMigrationCommitsOnDefaultBranch)(root, 'running migrations');
2354
- // Dark: with the env var set, the agent drives the whole run through the
2355
- // orchestrator from one session instead of being spawned per step. Not
2356
- // under WASM, where the broker has no native lock to detect a dead parent.
2357
- if (agentic.kind === 'enabled' &&
2358
- process.env.NX_MIGRATE_ORCHESTRATOR === 'true' &&
2359
- !native_1.IS_WASM) {
2331
+ // The agent drives the whole run through the orchestrator from one session.
2332
+ if (agentic.kind === 'enabled') {
2360
2333
  const init = orchestratorInitInput(effectiveCreateCommits);
2361
2334
  const { runMasterSession } = require('./agentic/master/run-master-session');
2362
2335
  return await runMasterSession({
@@ -2368,16 +2341,22 @@ async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits, c
2368
2341
  confirmStart: confirmNewRunCommits,
2369
2342
  });
2370
2343
  }
2371
- if (orchestratorFlag !== undefined) {
2372
- throw new Error(orchestratorFlagNeedsOrchestrator(orchestratorFlag));
2344
+ if (isContinue) {
2345
+ throw new Error(CONTINUE_NEEDS_ORCHESTRATION);
2346
+ }
2347
+ const classicGate = await settleActiveRunForClassic(root, opts, migrations);
2348
+ if (classicGate.kind === 'stop') {
2349
+ return classicGate.exitCode;
2373
2350
  }
2374
2351
  if (!(await confirmNewRunCommits())) {
2352
+ (0, migrate_analytics_1.reportMigrateRunStopped)('declined_commits');
2375
2353
  return;
2376
2354
  }
2377
- const shouldRunValidation = resolveShouldRunValidation({
2378
- validate: opts.validate,
2379
- agenticKind: agentic.kind,
2380
- });
2355
+ // After the prompt, so declining it keeps the run.
2356
+ if (classicGate.replaceRunId !== undefined) {
2357
+ const { deleteRunForStartFresh } = require('./run');
2358
+ deleteRunForStartFresh(root, classicGate.replaceRunId);
2359
+ }
2381
2360
  output_1.output.log({
2382
2361
  title: `Running migrations from '${opts.runMigrations}'` +
2383
2362
  (effectiveCreateCommits
@@ -2387,18 +2366,7 @@ async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits, c
2387
2366
  if (effectiveCreateCommits) {
2388
2367
  (0, migrate_commits_1.commitCheckpointBeforeMigrations)(root, commitPrefix);
2389
2368
  }
2390
- if (agentic.kind === 'enabled') {
2391
- const { applyAgenticHandoffGitignoreFallback } = require('./agentic/handoff-gitignore');
2392
- const { packageJson: nxPackageJson } = (0, package_json_1.readModulePackageJson)('nx', (0, installation_directory_1.getNxRequirePaths)(root));
2393
- await applyAgenticHandoffGitignoreFallback({
2394
- migrations,
2395
- installedNxVersion: nxPackageJson.version,
2396
- effectiveCreateCommits,
2397
- commitPrefix,
2398
- root,
2399
- });
2400
- }
2401
- const { migrationsWithNoChanges, skippedPromptsCount, notRunMigrationsCount, skippedPrompts, migrationEmittedNextSteps, committedShasCount, waivedAgenticStepsCount, retainedAtSuccess, } = await executeMigrations(root, migrations, isVerbose, effectiveCreateCommits, commitPrefix, shouldSkipInstall, agentic, agenticHasDiffContext, shouldRunValidation);
2369
+ const { migrationsWithNoChanges, skippedPromptsCount, notRunMigrationsCount, skippedPrompts, migrationEmittedNextSteps, committedShasCount, waivedAgenticStepsCount, retainedAtSuccess, } = await executeMigrations(root, migrations, isVerbose, effectiveCreateCommits, commitPrefix, shouldSkipInstall, agentic);
2402
2370
  const ranWithChangesCount = migrations.length - notRunMigrationsCount - migrationsWithNoChanges.length;
2403
2371
  // The "applied" tally counts fully-completed migrations — those that
2404
2372
  // left no deferred work behind. Hybrid migrations whose prompt half was
@@ -2428,9 +2396,13 @@ async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits, c
2428
2396
  ? 'Finished running migrations with uncommitted state retained'
2429
2397
  : 'Successfully finished running migrations';
2430
2398
  if (notRunMigrationsCount === migrations.length && migrations.length > 0) {
2399
+ // Under WASM or with ids orchestration refuses, an --agentic re-run lands
2400
+ // here again.
2431
2401
  const remediation = insideAgent
2432
2402
  ? 'The AI agent driving this run should apply each prompt — see next steps below.'
2433
- : 'Re-run with --agentic to apply them. See next steps below.';
2403
+ : native_1.IS_WASM || idProblems.length > 0
2404
+ ? 'Apply each prompt yourself. See next steps below.'
2405
+ : 'Re-run with --agentic to apply them. See next steps below.';
2434
2406
  output_1.output.warn({
2435
2407
  title: `No migrations from '${opts.runMigrations}' were applied — every entry is a prompt-only migration. ${remediation}`,
2436
2408
  bodyLines: tallyBody,
@@ -2486,7 +2458,6 @@ async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits, c
2486
2458
  }
2487
2459
  (0, migrate_analytics_1.reportMigrateRunComplete)({
2488
2460
  agenticOutcome: agentic.kind,
2489
- agentUsed: agentic.kind === 'enabled' ? agentic.selectedAgent.id : undefined,
2490
2461
  migrationCount: migrations.length,
2491
2462
  appliedCount,
2492
2463
  });
@@ -2871,45 +2842,6 @@ async function runMigration() {
2871
2842
  return runLocalMigrate();
2872
2843
  });
2873
2844
  }
2874
- /**
2875
- * Resolves a migration's collection once and derives everything the run loop
2876
- * needs from that single read: the implementation context (`collection` +
2877
- * `collectionPath`, handed to `runNxOrAngularMigration`) and, for agentic runs,
2878
- * the workspace-relative documentation path handed to the agent.
2879
- *
2880
- * Read fresh per migration (not cached across the loop) so a prior migration's
2881
- * reinstall is reflected, exactly as before. Error handling matches each field's
2882
- * role:
2883
- * - Migrations that run an implementation REQUIRE the collection; an unreadable
2884
- * collection throws and aborts that migration (caught by the run loop).
2885
- * - Prompt-only migrations don't run an implementation, so the collection is
2886
- * read only to resolve documentation - a failure there is non-fatal: the
2887
- * prompt still runs and the supplementary doc is skipped with a warning.
2888
- */
2889
- function resolveMigrationForRun(root, migration, resolveDocumentation) {
2890
- let resolvedCollection;
2891
- if (!(0, migration_shape_1.isPromptOnlyMigration)(migration)) {
2892
- resolvedCollection = (0, execute_migration_1.readMigrationCollection)(migration.package, root);
2893
- }
2894
- else if (resolveDocumentation && migration.documentation) {
2895
- try {
2896
- resolvedCollection = (0, execute_migration_1.readMigrationCollection)(migration.package, root);
2897
- }
2898
- catch {
2899
- // Non-fatal: documentation is supplementary; the warning below fires.
2900
- }
2901
- }
2902
- let documentationPath;
2903
- if (resolveDocumentation && migration.documentation) {
2904
- documentationPath = resolvedCollection
2905
- ? (0, execute_migration_1.resolveDocumentationFileToWorkspacePath)(root, (0, path_1.dirname)(resolvedCollection.collectionPath), migration.documentation)
2906
- : undefined;
2907
- if (!documentationPath) {
2908
- logger_1.logger.warn(`Could not resolve the "documentation" file "${migration.documentation}" declared for migration "${migration.package}: ${migration.name}". It will be skipped as additional context for the AI agent.`);
2909
- }
2910
- }
2911
- return { resolvedCollection, documentationPath };
2912
- }
2913
2845
  async function nxCliPath(nxWorkspaceRoot) {
2914
2846
  const version = process.env.NX_MIGRATE_CLI_VERSION || 'latest';
2915
2847
  const isVerbose = process.env.NX_VERBOSE_LOGGING === 'true';
@@ -41,7 +41,7 @@ export interface ExistingRunCommands {
41
41
  migrationsPath: string;
42
42
  };
43
43
  }
44
- export declare function renderExistingRunCommands(root: string, facts: ExistingRunFacts, migrationsPath: string | undefined, agentId?: string): ExistingRunCommands;
44
+ export declare function renderExistingRunCommands(root: string, facts: ExistingRunFacts, migrationsPath: string | undefined, agentic?: string | false): ExistingRunCommands;
45
45
  /**
46
46
  * Title plus body lines, in the shape the agent-output writers take. Every
47
47
  * body line goes through `singleLine`: branch names and timestamps came from
@@ -108,16 +108,18 @@ function otherActiveRuns(root, runId) {
108
108
  function renderContinueCommand(root, runId, policy, agentId) {
109
109
  return `${(0, util_1.pmExecPrefix)(root)} nx migrate --run-migrations --agentic${agentId === undefined ? '' : `=${agentId}`} --run-id=${runId} ${policy.createCommits ? '--create-commits' : '--no-create-commits'}${policy.skipInstall ? ' --skip-install' : ''}`;
110
110
  }
111
- function renderExistingRunCommands(root, facts, migrationsPath, agentId) {
111
+ // `agentic` echoes the invocation's choice into the start-fresh command; the
112
+ // continue command needs an agent, so it takes only an agent id.
113
+ function renderExistingRunCommands(root, facts, migrationsPath, agentic) {
112
114
  const flag = migrationsPath === undefined
113
115
  ? '--run-migrations'
114
116
  : (0, util_1.runMigrationsFlag)(migrationsPath);
115
117
  return {
116
- continueCommand: renderContinueCommand(root, facts.runId, facts.policy, agentId),
118
+ continueCommand: renderContinueCommand(root, facts.runId, facts.policy, agentic === false ? undefined : agentic),
117
119
  startFresh: flag === null
118
120
  ? { migrationsPath }
119
121
  : {
120
- command: `${(0, util_1.pmExecPrefix)(root)} nx migrate ${flag}${agentId === undefined ? '' : ` --agentic=${agentId}`} --start-fresh --run-id=${facts.runId}`,
122
+ command: `${(0, util_1.pmExecPrefix)(root)} nx migrate ${flag}${agentic === undefined ? '' : ` --agentic=${agentic}`} --start-fresh --run-id=${facts.runId}`,
121
123
  },
122
124
  };
123
125
  }
@@ -153,12 +155,12 @@ function renderExistingRunReport(facts, commands) {
153
155
  lines.push(` other active runs on disk: ${facts.otherActiveRuns.join(', ')}`);
154
156
  }
155
157
  if (facts.appliedStillPlanned !== undefined) {
156
- lines.push(` plan overlap: ${facts.appliedStillPlanned} of the applied migrations ${facts.appliedStillPlanned === 1 ? 'is' : 'are'} still in the plan; a new run applies ${facts.appliedStillPlanned === 1 ? 'it' : 'them'} again`);
158
+ lines.push(` plan overlap: ${facts.appliedStillPlanned} of the applied migrations ${facts.appliedStillPlanned === 1 ? 'is' : 'are'} still in the plan; running the plan applies ${facts.appliedStillPlanned === 1 ? 'it' : 'them'} again`);
157
159
  }
158
160
  if (commands) {
159
161
  lines.push(``, `To continue the run: ${commands.continueCommand}`, `To start fresh (deletes the run record, then runs the whole plan again): ${'command' in commands.startFresh
160
162
  ? commands.startFresh.command
161
- : `re-run this command with --start-fresh --run-id=${facts.runId}, keeping the --run-migrations argument, which names ${displayPath(commands.startFresh.migrationsPath)}; that path cannot be rendered as a command for this shell`}`, `Run either command with NX_MIGRATE_ORCHESTRATOR=true set in the environment.`);
163
+ : `re-run this command with --start-fresh --run-id=${facts.runId}, keeping the --run-migrations argument, which names ${displayPath(commands.startFresh.migrationsPath)}; that path cannot be rendered as a command for this shell`}`);
162
164
  }
163
165
  return {
164
166
  title: `A migrate run is already active: ${facts.runId}`,
@@ -1,5 +1,5 @@
1
- export { CURRENT_RUN_STATE_FORMAT_VERSION, NewerRunStateFormatError, migrateRunsDir, runDir, runHandoffsDir, readRunState, writeRunState, findActiveRun, createRun, TERMINAL_STEP_STATUSES, } from './run-state';
2
- export type { MigrateRunStatus, MigrateRunRound, MigrateStepStatus, PromptOutcomeStatus, MigrateStepAwaitingKind, MigrateStepOutcome, MigrateStepPromptOutcome, MigrateStep, MigrateCommitKind, MigrateCommitLedgerEntry, MigrateIssueDisposition, MigrateRunIssue, MigrateRunAnalytics, MigrateRunNoProgress, MigrateRunPolicy, MigrateRunState, } from './run-state';
1
+ export { CURRENT_RUN_STATE_FORMAT_VERSION, NewerRunStateFormatError, migrateRunsDir, runDir, runHandoffsDir, readRunState, writeRunState, findActiveRun, createRun, TERMINAL_STEP_STATUSES, migrationIdProblems, } from './run-state';
2
+ export type { MigrateRunStatus, MigrateRunRound, MigrateStepStatus, PromptOutcomeStatus, MigrateStepAwaitingKind, MigrateStepOutcome, MigrateStepPromptOutcome, MigrateStep, MigrateCommitKind, MigrateCommitLedgerEntry, MigrateIssueDisposition, MigrateRunIssue, MigrateRunAnalytics, MigrateRunNoProgress, MigrateRunPolicy, MigrateRunState, MigrationIdProblem, } from './run-state';
3
3
  export { applyStepEvent, completionSummaryLines, hasPendingCommitDebt, latestRound, runTallies, stepLabel, tallySteps, } from './state-machine';
4
4
  export { hasUnresolvedIssues } from './issues';
5
5
  export type { StepAction, StepEvent, ApplyStepEventResult, } from './state-machine';
@@ -7,7 +7,7 @@ export { createRunId } from './run-id';
7
7
  export { pmInstallCommand } from './util';
8
8
  export { runSingleMigrationWorker } from './worker';
9
9
  export type { RunSingleMigrationWorkerInput } from './worker';
10
- export { activeRunToReplace, completionWarnings, holdRunToContinue, releaseRunToHandOff, runOrchestratorInit, runOrchestratorReconcile, runOrchestratorResume, } from './orchestrator';
10
+ export { activeRunForClassic, activeRunToReplace, checkRunForStartFresh, completionWarnings, deleteRunForStartFresh, holdRunToContinue, releaseRunToHandOff, runOrchestratorInit, runOrchestratorReconcile, runOrchestratorResume, } from './orchestrator';
11
11
  export type { OrchestratorInitResult, RunOrchestratorInitInput, RunOrchestratorReconcileInput, } from './orchestrator';
12
12
  export { renderContinueCommand, renderExistingRunCommands, renderExistingRunReport, } from './existing-run-report';
13
13
  export { BROKER_ENV_VAR, COMMIT_INSTALLS_FIRST, formatElapsed, MigrateCommitBroker, commitsStep, treeOperationLabel, } from './broker';