nx 23.0.0-beta.20 → 23.0.0-beta.22

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 (54) hide show
  1. package/dist/src/adapter/compat.d.ts +1 -1
  2. package/dist/src/adapter/compat.js +1 -0
  3. package/dist/src/command-line/examples.js +4 -4
  4. package/dist/src/command-line/migrate/agentic/prompts/generic-validation.d.ts +5 -0
  5. package/dist/src/command-line/migrate/agentic/prompts/generic-validation.js +4 -11
  6. package/dist/src/command-line/migrate/agentic/prompts/hybrid-prompt-migration.d.ts +5 -0
  7. package/dist/src/command-line/migrate/agentic/prompts/hybrid-prompt-migration.js +4 -11
  8. package/dist/src/command-line/migrate/agentic/prompts/prompt-migration.d.ts +5 -0
  9. package/dist/src/command-line/migrate/agentic/prompts/prompt-migration.js +6 -8
  10. package/dist/src/command-line/migrate/agentic/prompts/shared-rendering.d.ts +10 -0
  11. package/dist/src/command-line/migrate/agentic/prompts/shared-rendering.js +58 -0
  12. package/dist/src/command-line/migrate/agentic/run-step.d.ts +7 -0
  13. package/dist/src/command-line/migrate/agentic/run-step.js +3 -1
  14. package/dist/src/command-line/migrate/agentic/runner.js +3 -0
  15. package/dist/src/command-line/migrate/agentic/select.js +120 -32
  16. package/dist/src/command-line/migrate/command-object.d.ts +42 -0
  17. package/dist/src/command-line/migrate/command-object.js +37 -7
  18. package/dist/src/command-line/migrate/migrate-config.d.ts +27 -0
  19. package/dist/src/command-line/migrate/migrate-config.js +103 -0
  20. package/dist/src/command-line/migrate/migrate-output.d.ts +70 -11
  21. package/dist/src/command-line/migrate/migrate-output.js +52 -35
  22. package/dist/src/command-line/migrate/migrate.d.ts +37 -2
  23. package/dist/src/command-line/migrate/migrate.js +180 -88
  24. package/dist/src/command-line/migrate/run-migration-process.js +9 -1
  25. package/dist/src/command-line/release/changelog/version-plan-filtering.d.ts +3 -1
  26. package/dist/src/command-line/release/changelog/version-plan-filtering.js +7 -3
  27. package/dist/src/command-line/release/changelog.d.ts +7 -0
  28. package/dist/src/command-line/release/changelog.js +22 -9
  29. package/dist/src/command-line/release/release.js +65 -55
  30. package/dist/src/command-line/release/utils/git.d.ts +6 -0
  31. package/dist/src/command-line/release/utils/git.js +33 -0
  32. package/dist/src/command-line/release/version/derive-specifier-from-conventional-commits.js +3 -2
  33. package/dist/src/command-line/release/version.d.ts +3 -0
  34. package/dist/src/command-line/release/version.js +13 -3
  35. package/dist/src/config/misc-interfaces.d.ts +8 -0
  36. package/dist/src/config/nx-json.d.ts +49 -0
  37. package/dist/src/core/graph/main.js +1 -1
  38. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  39. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  40. package/dist/src/plugins/js/lock-file/lock-file.d.ts +5 -0
  41. package/dist/src/plugins/js/lock-file/lock-file.js +34 -24
  42. package/dist/src/plugins/js/project-graph/affected/lock-file-changes.d.ts +2 -4
  43. package/dist/src/plugins/js/project-graph/affected/lock-file-changes.js +121 -43
  44. package/dist/src/project-graph/file-utils.d.ts +7 -0
  45. package/dist/src/project-graph/file-utils.js +78 -10
  46. package/dist/src/tasks-runner/init-tasks-runner.d.ts +2 -2
  47. package/dist/src/tasks-runner/init-tasks-runner.js +6 -6
  48. package/dist/src/tasks-runner/task-orchestrator.d.ts +2 -2
  49. package/dist/src/tasks-runner/task-orchestrator.js +6 -6
  50. package/dist/src/utils/git-utils.d.ts +1 -0
  51. package/dist/src/utils/git-utils.js +65 -0
  52. package/migrations.json +18 -9
  53. package/package.json +12 -12
  54. package/schemas/nx-schema.json +41 -0
@@ -1,2 +1,2 @@
1
1
  export declare const allowedProjectExtensions: readonly ["tags", "implicitDependencies", "configFilePath", "$schema", "generators", "namedInputs", "name", "files", "root", "sourceRoot", "projectType", "release", "includedScripts", "metadata", "owners", "nxCloudImplicitDependencies"];
2
- export declare const allowedWorkspaceExtensions: readonly ["$schema", "implicitDependencies", "affected", "defaultBase", "tasksRunnerOptions", "workspaceLayout", "plugins", "targetDefaults", "files", "generators", "namedInputs", "extends", "cli", "pluginsConfig", "defaultProject", "installation", "release", "nxCloudAccessToken", "nxCloudId", "nxCloudUrl", "nxCloudEncryptionKey", "parallel", "cacheDirectory", "useDaemonProcess", "useInferencePlugins", "neverConnectToCloud", "analytics", "sync", "useLegacyCache", "maxCacheSize", "tui", "owners"];
2
+ export declare const allowedWorkspaceExtensions: readonly ["$schema", "implicitDependencies", "affected", "defaultBase", "tasksRunnerOptions", "workspaceLayout", "plugins", "targetDefaults", "files", "generators", "namedInputs", "extends", "cli", "pluginsConfig", "defaultProject", "installation", "release", "nxCloudAccessToken", "nxCloudId", "nxCloudUrl", "nxCloudEncryptionKey", "parallel", "cacheDirectory", "useDaemonProcess", "useInferencePlugins", "neverConnectToCloud", "analytics", "sync", "migrate", "useLegacyCache", "maxCacheSize", "tui", "owners"];
@@ -66,6 +66,7 @@ exports.allowedWorkspaceExtensions = [
66
66
  'neverConnectToCloud',
67
67
  'analytics',
68
68
  'sync',
69
+ 'migrate',
69
70
  'useLegacyCache',
70
71
  'maxCacheSize',
71
72
  'tui',
@@ -356,19 +356,19 @@ exports.examples = {
356
356
  description: 'Prints the specified + inferred configuration for `my-app:build`',
357
357
  },
358
358
  {
359
- command: 'show target my-app:build inputs',
359
+ command: 'show target inputs my-app:build',
360
360
  description: 'Prints the resolved inputs for `my-app:build`',
361
361
  },
362
362
  {
363
- command: 'show target my-app:build inputs --check packages/my-app/index.html',
363
+ command: 'show target inputs my-app:build --check packages/my-app/index.html',
364
364
  description: 'Checks if `packages/my-app/index.html` is an input for `my-app:build`',
365
365
  },
366
366
  {
367
- command: 'show target my-app:build outputs',
367
+ command: 'show target outputs my-app:build',
368
368
  description: 'Prints the outputs detected on disk for `my-app:build`',
369
369
  },
370
370
  {
371
- command: 'show target my-app:build outputs --check packages/my-app/dist/index.html',
371
+ command: 'show target outputs my-app:build --check packages/my-app/dist/index.html',
372
372
  description: 'Checks if `packages/my-app/dist/index.html` is an output for `my-app:build`',
373
373
  },
374
374
  ],
@@ -6,6 +6,11 @@ export interface GenericValidationPromptContext {
6
6
  description?: string;
7
7
  /** Absolute path the agent must write its handoff file to. */
8
8
  handoffFileAbsolutePath: string;
9
+ /**
10
+ * Path to the migration's documentation file, if any - workspace-relative,
11
+ * or absolute when it resolves outside the workspace.
12
+ */
13
+ documentationPath?: string;
9
14
  /** Context captured from the deterministic generator phase. */
10
15
  impl: {
11
16
  /** Raw output from the generator (devkit logger + console). */
@@ -27,16 +27,9 @@ exports.GENERIC_VALIDATION_FILE_LIST_CAP = 50;
27
27
  function buildGenericValidationUserPrompt(ctx) {
28
28
  const lines = [
29
29
  `You are validating the output of an Nx migration's deterministic generator phase. The generator has already run; inspect what it produced, verify the workspace is in a consistent state for what this migration intended to accomplish, apply any minor in-scope fixes the generator should have produced cleanly, and report findings.`,
30
- ``,
31
- `<migration>`,
32
- `package: ${(0, shared_rendering_1.escapeXmlBody)(ctx.package)}`,
33
- `version: ${(0, shared_rendering_1.escapeXmlBody)(ctx.version)}`,
34
- `name: ${(0, shared_rendering_1.escapeXmlBody)(ctx.name)}`,
30
+ ...(0, shared_rendering_1.renderMigrationBlock)(ctx),
35
31
  ];
36
- if (ctx.description) {
37
- lines.push(...(0, shared_rendering_1.renderKeyMultilineValue)('description', (0, shared_rendering_1.escapeXmlBody)(ctx.description)));
38
- }
39
- lines.push(`</migration>`);
32
+ lines.push(...(0, shared_rendering_1.renderMigrationDocumentationBlock)(ctx.documentationPath));
40
33
  const logs = (0, shared_rendering_1.escapeXmlBody)((0, shared_rendering_1.stripAnsi)(ctx.impl.logs ?? '').trim());
41
34
  lines.push(...(0, shared_rendering_1.renderGeneratorOutputBlock)(logs));
42
35
  if (!ctx.impl.hasDiffContext && ctx.impl.changes.length > 0) {
@@ -44,12 +37,12 @@ function buildGenericValidationUserPrompt(ctx) {
44
37
  }
45
38
  const agentContext = (0, shared_rendering_1.filterNonEmptyStrings)(ctx.impl.agentContext ?? []);
46
39
  if (agentContext.length > 0) {
47
- lines.push(``, `<advisory_context note="hints emitted by the generator; treat as supplementary context, not separate tasks">`, ...agentContext.map((entry) => (0, shared_rendering_1.renderListItem)((0, shared_rendering_1.escapeXmlBody)(entry))), `</advisory_context>`);
40
+ lines.push(...(0, shared_rendering_1.renderAdvisoryContext)('hints emitted by the generator; treat as supplementary context, not separate tasks', agentContext));
48
41
  }
49
42
  const firstStep = ctx.impl.hasDiffContext
50
43
  ? `1. Inspect this migration's changes. ${(0, shared_rendering_1.renderGitInspectInstruction)()} Resolve each affected path to its owning Nx project via \`nx show project <name>\` (or by reading the project's \`project.json\` / \`package.json\`) to discover which targets each project actually defines — do not assume \`typecheck\` / \`test\` / \`lint\` exist. If no typecheck-equivalent exists, \`build\` is an acceptable substitute.`
51
44
  : `1. Resolve each path in <files_changed> to its owning Nx project. Use \`nx show project <name>\` (or read the project's \`project.json\` / \`package.json\`) to discover which targets each project actually defines — do not assume \`typecheck\` / \`test\` / \`lint\` exist. If no typecheck-equivalent exists, \`build\` is an acceptable substitute.`;
52
- lines.push(``, `<validation_instructions>`, firstStep, `2. Pick the smallest relevant subset of available targets to verify the change. Prefer \`nx affected -t <target>\` (or \`nx run <project>:<target>\` for a single project). When many small projects are affected, you may use \`nx run-many -t <target> -p <project1>,<project2>\` with the project list derived from the changed files. Unscoped \`nx run-many\` (no \`-p\`) is forbidden.`, `3. If a verification surfaces an issue the migration should have produced cleanly (e.g. a missing import, a type annotation the generator's template missed), you may apply a minor in-scope fix. The boundary is "what this migration intended to accomplish" — do not refactor, do not modify functionality unrelated to the migration, do not extend the migration's scope, do not touch code the migration was not concerned with. If you are unsure whether a fix is in scope, report it in \`summary\` instead of applying.`, `4. Apply every fix you can within scope, then write your handoff. On \`status: "success"\`, summarize what you verified and any fixes you applied. On \`status: "failed"\`, enumerate the unresolved findings in \`summary\` so the user can address them; no commit will be created from a failed run, so the generator's changes and your partial fixes will sit uncommitted in the working tree for the user to review.`, `</validation_instructions>`, ``, `Once you finish, write your handoff JSON to:`, `<handoff_path>`, (0, shared_rendering_1.escapeXmlBody)(ctx.handoffFileAbsolutePath), `</handoff_path>`);
45
+ lines.push(``, `<validation_instructions>`, firstStep, `2. Pick the smallest relevant subset of available targets to verify the change. Prefer \`nx affected -t <target>\` (or \`nx run <project>:<target>\` for a single project). When many small projects are affected, you may use \`nx run-many -t <target> -p <project1>,<project2>\` with the project list derived from the changed files. Unscoped \`nx run-many\` (no \`-p\`) is forbidden.`, `3. If a verification surfaces an issue the migration should have produced cleanly (e.g. a missing import, a type annotation the generator's template missed), you may apply a minor in-scope fix. The boundary is "what this migration intended to accomplish" — do not refactor, do not modify functionality unrelated to the migration, do not extend the migration's scope, do not touch code the migration was not concerned with. If you are unsure whether a fix is in scope, report it in \`summary\` instead of applying.`, `4. Apply every fix you can within scope, then write your handoff. On \`status: "success"\`, summarize what you verified and any fixes you applied. On \`status: "failed"\`, enumerate the unresolved findings in \`summary\` so the user can address them; no commit will be created from a failed run, so the generator's changes and your partial fixes will sit uncommitted in the working tree for the user to review.`, `</validation_instructions>`, ``, `Once you finish, write your handoff JSON to:`, ...(0, shared_rendering_1.renderHandoffPathFooter)(ctx.handoffFileAbsolutePath));
53
46
  return lines.join('\n');
54
47
  }
55
48
  function renderFileListBody(changes) {
@@ -8,6 +8,11 @@ export interface HybridPromptMigrationContext {
8
8
  promptPath: string;
9
9
  /** Absolute path the agent must write its handoff file to. */
10
10
  handoffFileAbsolutePath: string;
11
+ /**
12
+ * Path to the migration's documentation file, if any - workspace-relative,
13
+ * or absolute when it resolves outside the workspace.
14
+ */
15
+ documentationPath?: string;
11
16
  /** Context captured from the deterministic generator phase. */
12
17
  impl?: {
13
18
  /** Raw output from the generator (devkit logger + console). */
@@ -18,16 +18,9 @@ const shared_rendering_1 = require("./shared-rendering");
18
18
  function buildHybridPromptUserPrompt(ctx) {
19
19
  const lines = [
20
20
  `Complete the AI-driven step that follows the generator phase of a two-phase Nx migration. The deterministic generator phase has already run; the sections below summarize what it did. The step may apply additional changes, verify the generator's output, or both — follow the instructions file.`,
21
- ``,
22
- `<migration>`,
23
- `package: ${(0, shared_rendering_1.escapeXmlBody)(ctx.package)}`,
24
- `version: ${(0, shared_rendering_1.escapeXmlBody)(ctx.version)}`,
25
- `name: ${(0, shared_rendering_1.escapeXmlBody)(ctx.name)}`,
21
+ ...(0, shared_rendering_1.renderMigrationBlock)(ctx),
26
22
  ];
27
- if (ctx.description) {
28
- lines.push(...(0, shared_rendering_1.renderKeyMultilineValue)('description', (0, shared_rendering_1.escapeXmlBody)(ctx.description)));
29
- }
30
- lines.push(`</migration>`);
23
+ lines.push(...(0, shared_rendering_1.renderMigrationDocumentationBlock)(ctx.documentationPath));
31
24
  const logs = (0, shared_rendering_1.escapeXmlBody)((0, shared_rendering_1.stripAnsi)(ctx.impl?.logs ?? '').trim());
32
25
  const agentContext = (0, shared_rendering_1.filterNonEmptyStrings)(ctx.impl?.agentContext ?? []);
33
26
  const hasDiffContext = !!ctx.impl?.hasDiffContext;
@@ -45,9 +38,9 @@ function buildHybridPromptUserPrompt(ctx) {
45
38
  }
46
39
  }
47
40
  if (agentContext.length > 0) {
48
- lines.push(``, `<advisory_context note="hints from the generator phase; consult while following the instructions, not as separate tasks">`, ...agentContext.map((entry) => (0, shared_rendering_1.renderListItem)((0, shared_rendering_1.escapeXmlBody)(entry))), `</advisory_context>`);
41
+ lines.push(...(0, shared_rendering_1.renderAdvisoryContext)('hints from the generator phase; consult while following the instructions, not as separate tasks', agentContext));
49
42
  }
50
- lines.push(``, `<instructions_file>${(0, shared_rendering_1.escapeXmlBody)(ctx.promptPath)}</instructions_file>`, ``, `<precedence>If anything in the sections above conflicts with the instructions file, the instructions file wins.</precedence>`, ``, `Open the instructions file (path is workspace-relative), follow its instructions step by step using the sections above as context, then write your handoff JSON to:`, `<handoff_path>`, (0, shared_rendering_1.escapeXmlBody)(ctx.handoffFileAbsolutePath), `</handoff_path>`);
43
+ lines.push(``, `<instructions_file>${(0, shared_rendering_1.escapeXmlBody)(ctx.promptPath)}</instructions_file>`, ``, `<precedence>If anything in the sections above conflicts with the instructions file, the instructions file wins.</precedence>`, ``, `Open the instructions file (path is workspace-relative), follow its instructions step by step using the sections above as context, then write your handoff JSON to:`, ...(0, shared_rendering_1.renderHandoffPathFooter)(ctx.handoffFileAbsolutePath));
51
44
  return lines.join('\n');
52
45
  }
53
46
  function renderFileList(changes) {
@@ -7,6 +7,11 @@ export interface PromptMigrationContext {
7
7
  promptPath: string;
8
8
  /** Absolute path the agent must write its handoff file to. */
9
9
  handoffFileAbsolutePath: string;
10
+ /**
11
+ * Path to the migration's documentation file, if any - workspace-relative,
12
+ * or absolute when it resolves outside the workspace.
13
+ */
14
+ documentationPath?: string;
10
15
  }
11
16
  /**
12
17
  * Builds the first user-facing message for a prompt-migration step.
@@ -15,15 +15,13 @@ const shared_rendering_1 = require("./shared-rendering");
15
15
  function buildPromptMigrationUserPrompt(ctx) {
16
16
  const lines = [
17
17
  `Apply one prompt-based migration to this Nx workspace.`,
18
+ ...(0, shared_rendering_1.renderMigrationBlock)(ctx),
19
+ ...(0, shared_rendering_1.renderMigrationDocumentationBlock)(ctx.documentationPath),
18
20
  ``,
19
- `<migration>`,
20
- `package: ${(0, shared_rendering_1.escapeXmlBody)(ctx.package)}`,
21
- `version: ${(0, shared_rendering_1.escapeXmlBody)(ctx.version)}`,
22
- `name: ${(0, shared_rendering_1.escapeXmlBody)(ctx.name)}`,
21
+ `<instructions_file>${(0, shared_rendering_1.escapeXmlBody)(ctx.promptPath)}</instructions_file>`,
22
+ ``,
23
+ `Open the instructions file (path is workspace-relative), follow its instructions step by step, then write your handoff JSON to:`,
24
+ ...(0, shared_rendering_1.renderHandoffPathFooter)(ctx.handoffFileAbsolutePath),
23
25
  ];
24
- if (ctx.description) {
25
- lines.push(...(0, shared_rendering_1.renderKeyMultilineValue)('description', (0, shared_rendering_1.escapeXmlBody)(ctx.description)));
26
- }
27
- lines.push(`</migration>`, ``, `<instructions_file>${(0, shared_rendering_1.escapeXmlBody)(ctx.promptPath)}</instructions_file>`, ``, `Open the instructions file (path is workspace-relative), follow its instructions step by step, then write your handoff JSON to:`, `<handoff_path>`, (0, shared_rendering_1.escapeXmlBody)(ctx.handoffFileAbsolutePath), `</handoff_path>`);
28
26
  return lines.join('\n');
29
27
  }
@@ -7,3 +7,13 @@ export declare function filterNonEmptyStrings(entries: unknown[]): string[];
7
7
  export declare function escapeXmlBody(value: string): string;
8
8
  export declare function renderGitInspectInstruction(): string;
9
9
  export declare function renderGeneratorOutputBlock(logs: string): string[];
10
+ export interface MigrationBlockContext {
11
+ package: string;
12
+ name: string;
13
+ version: string;
14
+ description?: string;
15
+ }
16
+ export declare function renderMigrationBlock(ctx: MigrationBlockContext): string[];
17
+ export declare function renderHandoffPathFooter(handoffFileAbsolutePath: string): string[];
18
+ export declare function renderAdvisoryContext(note: string, entries: string[]): string[];
19
+ export declare function renderMigrationDocumentationBlock(documentationPath: string | undefined): string[];
@@ -8,6 +8,10 @@ exports.filterNonEmptyStrings = filterNonEmptyStrings;
8
8
  exports.escapeXmlBody = escapeXmlBody;
9
9
  exports.renderGitInspectInstruction = renderGitInspectInstruction;
10
10
  exports.renderGeneratorOutputBlock = renderGeneratorOutputBlock;
11
+ exports.renderMigrationBlock = renderMigrationBlock;
12
+ exports.renderHandoffPathFooter = renderHandoffPathFooter;
13
+ exports.renderAdvisoryContext = renderAdvisoryContext;
14
+ exports.renderMigrationDocumentationBlock = renderMigrationDocumentationBlock;
11
15
  function renderFileEntry(change) {
12
16
  return `[${change.type}] ${change.path}`;
13
17
  }
@@ -85,3 +89,57 @@ function renderGeneratorOutputBlock(logs) {
85
89
  `</generator_output>`,
86
90
  ];
87
91
  }
92
+ // Identical `<migration>` block used by prompt-migration, hybrid, and
93
+ // generic-validation builders. Leading blank included so callers can spread
94
+ // directly after a lead sentence. Centralized so the schema and the
95
+ // `escapeXmlBody` contract on values stay in lock-step.
96
+ function renderMigrationBlock(ctx) {
97
+ const lines = [
98
+ ``,
99
+ `<migration>`,
100
+ `package: ${escapeXmlBody(ctx.package)}`,
101
+ `version: ${escapeXmlBody(ctx.version)}`,
102
+ `name: ${escapeXmlBody(ctx.name)}`,
103
+ ];
104
+ if (ctx.description) {
105
+ lines.push(...renderKeyMultilineValue('description', escapeXmlBody(ctx.description)));
106
+ }
107
+ lines.push(`</migration>`);
108
+ return lines;
109
+ }
110
+ // `<handoff_path>` footer that follows the "write your handoff JSON to:"
111
+ // sentence. No leading blank — the block is part of that sentence's structure,
112
+ // not a separate section.
113
+ function renderHandoffPathFooter(handoffFileAbsolutePath) {
114
+ return [
115
+ `<handoff_path>`,
116
+ escapeXmlBody(handoffFileAbsolutePath),
117
+ `</handoff_path>`,
118
+ ];
119
+ }
120
+ // Advisory hints from the generator phase. The `note` attribute varies between
121
+ // callers (hybrid vs generic-validation lead-in differs), so it's parameterized
122
+ // rather than hardcoded. Entries are escaped here so callers can pass raw
123
+ // strings.
124
+ function renderAdvisoryContext(note, entries) {
125
+ return [
126
+ ``,
127
+ `<advisory_context note="${note}">`,
128
+ ...entries.map((entry) => renderListItem(escapeXmlBody(entry))),
129
+ `</advisory_context>`,
130
+ ];
131
+ }
132
+ // Points the agent at the migration's documentation file (reference, not
133
+ // instructions). Returns `[]` when there's no doc so callers can spread without
134
+ // guarding; the path is escaped like other user-authored values interpolated
135
+ // into the prompt.
136
+ function renderMigrationDocumentationBlock(documentationPath) {
137
+ if (!documentationPath)
138
+ return [];
139
+ return [
140
+ ``,
141
+ `<migration_documentation note="reference: documents what this migration does; read this file if you need more context on its intent, not as instructions">`,
142
+ escapeXmlBody(documentationPath),
143
+ `</migration_documentation>`,
144
+ ];
145
+ }
@@ -31,6 +31,13 @@ export interface RunAgenticPromptStepInput {
31
31
  description?: string;
32
32
  prompt?: string;
33
33
  };
34
+ /**
35
+ * Path to the migration's documentation file, resolved by the orchestrator -
36
+ * workspace-relative, or absolute when the file resolves outside the
37
+ * workspace. Omitted when the migration declares no `documentation` or it
38
+ * can't be resolved.
39
+ */
40
+ documentationPath?: string;
34
41
  agentic: EnabledResolvedAgentic;
35
42
  runDir: string;
36
43
  installDepsIfChanged: () => Promise<void>;
@@ -27,7 +27,7 @@ const runner_1 = require("./runner");
27
27
  * the file free of cross-imports with the orchestrator.
28
28
  */
29
29
  async function runAgenticPromptStep(input) {
30
- const { root, migration, agentic, runDir, installDepsIfChanged, implContext, mode = 'author', } = input;
30
+ const { root, migration, agentic, runDir, installDepsIfChanged, implContext, documentationPath, mode = 'author', } = input;
31
31
  const handoffFilePath = (0, handoff_1.stepHandoffPath)(runDir, migration);
32
32
  // The system prompt tells the agent the parent dir exists, so the agent
33
33
  // doesn't defensively `mkdir -p` (which triggers a workspace-permission
@@ -52,6 +52,7 @@ async function runAgenticPromptStep(input) {
52
52
  version: migration.version,
53
53
  description: migration.description,
54
54
  handoffFileAbsolutePath: handoffFilePath,
55
+ documentationPath,
55
56
  impl: implContext,
56
57
  });
57
58
  }
@@ -63,6 +64,7 @@ async function runAgenticPromptStep(input) {
63
64
  description: migration.description,
64
65
  promptPath: migration.prompt,
65
66
  handoffFileAbsolutePath: handoffFilePath,
67
+ documentationPath,
66
68
  };
67
69
  userPrompt = implContext
68
70
  ? (0, hybrid_prompt_migration_1.buildHybridPromptUserPrompt)({ ...promptCtx, impl: implContext })
@@ -27,6 +27,9 @@ async function runAgentic(args) {
27
27
  windowsHide: true,
28
28
  });
29
29
  let child;
30
+ // Local alias so `@nx/workspace-require-windows-hide` recognizes the
31
+ // options arg as a tracked Identifier rather than giving up on a
32
+ // member-expression skip — keeps the lint rule strict on other call sites.
30
33
  const spawnOptions = adapted.options;
31
34
  try {
32
35
  child = (0, child_process_1.spawn)(adapted.binary, adapted.args, spawnOptions);
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveAgentic = resolveAgentic;
4
+ const fs_1 = require("fs");
5
+ const jsonc_parser_1 = require("jsonc-parser");
6
+ const path_1 = require("path");
4
7
  const output_1 = require("../../../utils/output");
8
+ const workspace_root_1 = require("../../../utils/workspace-root");
5
9
  const safe_prompt_1 = require("../safe-prompt");
6
10
  const detect_installed_1 = require("./detect-installed");
7
11
  const inception_1 = require("./inception");
@@ -32,20 +36,35 @@ async function resolveAgentic(input) {
32
36
  return { kind: 'disabled' };
33
37
  }
34
38
  const detected = await (0, detect_installed_1.detectInstalledAgents)(definitions_1.AGENT_DEFINITIONS);
35
- const { enabled, explicitId } = await resolveFlag(input, isInteractive, detected);
39
+ const { enabled, explicitId, persist } = await resolveFlag(input, isInteractive, detected);
36
40
  if (!enabled) {
41
+ if (persist === false) {
42
+ persistAgenticChoice(false);
43
+ }
37
44
  return { kind: 'disabled' };
38
45
  }
39
46
  const selected = await selectAgent(detected, explicitId, isInteractive);
47
+ if (persist === true) {
48
+ persistAgenticChoice(true);
49
+ }
50
+ else if (persist === 'pin') {
51
+ persistAgenticChoice(selected.id);
52
+ }
40
53
  return { kind: 'enabled', selectedAgent: selected };
41
54
  }
42
55
  async function resolveFlag(input, isInteractive, detected) {
43
56
  if (input.agentic === true) {
44
- requireInteractiveOrAbort(isInteractive);
57
+ if (!isInteractive) {
58
+ warnAgenticInteractiveOnly();
59
+ return { enabled: false };
60
+ }
45
61
  return { enabled: true };
46
62
  }
47
63
  if (typeof input.agentic === 'string') {
48
- requireInteractiveOrAbort(isInteractive);
64
+ if (!isInteractive) {
65
+ warnAgenticInteractiveOnly();
66
+ return { enabled: false };
67
+ }
49
68
  return { enabled: true, explicitId: input.agentic };
50
69
  }
51
70
  // undefined — fire the up-front prompt only when we have a TTY, something
@@ -61,7 +80,7 @@ async function resolveFlag(input, isInteractive, detected) {
61
80
  if (detected.length === 0) {
62
81
  return { enabled: false };
63
82
  }
64
- return { enabled: await firePromptForAgentic(input.migrations) };
83
+ return firePromptForAgentic(input.migrations, detected);
65
84
  }
66
85
  function requireInteractiveOrAbort(isInteractive) {
67
86
  if (isInteractive)
@@ -74,29 +93,102 @@ function requireInteractiveOrAbort(isInteractive) {
74
93
  });
75
94
  throw new Error('Agentic flow requires an interactive terminal.');
76
95
  }
77
- async function firePromptForAgentic(migrations) {
78
- // The "Yes"/"No" hints below assume at least one prompt-bearing migration is
79
- // queued. If we later extend the prompt to fire for generator-only runs
80
- // (validation-only), the hints need to branch.
96
+ function warnAgenticInteractiveOnly() {
97
+ output_1.output.warn({
98
+ title: 'Skipping the agentic flow: it is interactive-only in this release and this is a non-interactive terminal.',
99
+ bodyLines: [
100
+ 'Continuing the migration without the agentic flow. Re-run in an interactive terminal to use it.',
101
+ ],
102
+ });
103
+ }
104
+ async function firePromptForAgentic(migrations, detected) {
105
+ // The apply hint assumes at least one prompt-bearing migration is queued. If
106
+ // we later extend the prompt to fire for generator-only runs (validation-
107
+ // only), the hint needs to branch.
81
108
  const promptCount = migrations.filter((m) => !!m.prompt).length;
82
- const yesHint = `Apply ${promptCount} prompt migration${promptCount === 1 ? '' : 's'} and validate generator output with an AI agent`;
83
- const noHint = `Skip prompts and run generators without AI validation`;
109
+ const applyHint = `Apply ${promptCount} prompt migration${promptCount === 1 ? '' : 's'} and validate generator output with an AI agent`;
110
+ const skipHint = `Skip prompts and run generators without AI validation`;
111
+ const rememberHint = `Saved to nx.json so Nx won't ask again`;
112
+ // The pin-vs-flexible distinction only matters when more than one agent is
113
+ // installed. With a single agent, "always" simply persists `true` and the
114
+ // pin option is dropped.
115
+ const multipleAgents = detected.length > 1;
116
+ const choices = [
117
+ { name: 'yes-once', message: 'Yes, just this time', hint: applyHint },
118
+ {
119
+ name: 'yes-flex',
120
+ message: multipleAgents
121
+ ? "Yes, always (I'll pick the agent each run)"
122
+ : 'Yes, always',
123
+ hint: rememberHint,
124
+ },
125
+ ...(multipleAgents
126
+ ? [
127
+ {
128
+ name: 'yes-pin',
129
+ message: 'Yes, always with the same agent',
130
+ hint: rememberHint,
131
+ },
132
+ ]
133
+ : []),
134
+ { name: 'no-once', message: 'No, just this time', hint: skipHint },
135
+ { name: 'no-never', message: 'No, never', hint: rememberHint },
136
+ ];
84
137
  // Blank line keeps the prompt from gluing to the previous `npm install`
85
138
  // output or any earlier orchestrator line.
86
139
  console.log();
87
140
  // `as any` because enquirer's TS types lag the runtime (per-choice `hint`
88
- // and `value` are supported but not in the .d.ts).
141
+ // is supported but not in the .d.ts).
89
142
  const response = await (0, safe_prompt_1.migratePrompt)({
90
- name: 'enable',
91
- type: 'autocomplete',
143
+ name: 'choice',
144
+ type: 'select',
92
145
  message: 'Enable the agentic flow?',
93
- choices: [
94
- { name: 'yes', message: 'Yes', hint: yesHint },
95
- { name: 'no', message: 'No', hint: noHint },
96
- ],
146
+ choices,
97
147
  initial: 0,
98
148
  });
99
- return response.enable === 'yes';
149
+ switch (response.choice) {
150
+ case 'yes-once':
151
+ return { enabled: true };
152
+ case 'yes-flex':
153
+ return { enabled: true, persist: true };
154
+ case 'yes-pin':
155
+ return { enabled: true, persist: 'pin' };
156
+ case 'no-never':
157
+ return { enabled: false, persist: false };
158
+ case 'no-once':
159
+ default:
160
+ return { enabled: false };
161
+ }
162
+ }
163
+ // Persists the user's agentic choice to `nx.json` so the up-front prompt is
164
+ // skipped on future runs. Edits the raw file in place via jsonc-parser, so it
165
+ // touches only the `migrate.agentic` key and preserves comments, formatting,
166
+ // and any `extends` preset (the prompt fires mid-migration, so a silent
167
+ // reformat would be surprising). Never throws - a failed write only costs the
168
+ // user the prompt again next time.
169
+ function persistAgenticChoice(value) {
170
+ const nxJsonPath = (0, path_1.join)(workspace_root_1.workspaceRoot, 'nx.json');
171
+ if (!(0, fs_1.existsSync)(nxJsonPath)) {
172
+ output_1.output.warn({
173
+ title: `Could not save your agentic choice: no nx.json found at the workspace root.`,
174
+ });
175
+ return;
176
+ }
177
+ try {
178
+ const content = (0, fs_1.readFileSync)(nxJsonPath, 'utf-8');
179
+ const edits = (0, jsonc_parser_1.modify)(content, ['migrate', 'agentic'], value, {
180
+ formattingOptions: { insertSpaces: true, tabSize: 2 },
181
+ });
182
+ (0, fs_1.writeFileSync)(nxJsonPath, (0, jsonc_parser_1.applyEdits)(content, edits));
183
+ output_1.output.log({
184
+ title: `Saved your choice to nx.json (migrate.agentic = ${JSON.stringify(value)}). Nx won't ask again.`,
185
+ });
186
+ }
187
+ catch (e) {
188
+ output_1.output.warn({
189
+ title: `Could not save your agentic choice to nx.json: ${e instanceof Error ? e.message : String(e)}`,
190
+ });
191
+ }
100
192
  }
101
193
  async function selectAgent(detected, explicitId, isInteractive) {
102
194
  if (explicitId) {
@@ -104,22 +196,18 @@ async function selectAgent(detected, explicitId, isInteractive) {
104
196
  if (match) {
105
197
  return match;
106
198
  }
107
- if (detected.length === 0) {
108
- output_1.output.error({
109
- title: `The agent "${explicitId}" was requested via --agentic but no supported AI agent is installed on this machine.`,
110
- bodyLines: INSTALL_SUPPORTED_AGENTS_HINT,
199
+ // The requested agent isn't installed. Rather than aborting the migration,
200
+ // warn and fall through to resolve from the agents that ARE installed
201
+ // (pick when 2+, auto-select the only one, error only when none exist).
202
+ if (detected.length > 0) {
203
+ output_1.output.warn({
204
+ title: `The requested agent "${explicitId}" is not installed; using the installed agent(s) instead.`,
205
+ bodyLines: [
206
+ 'Currently installed agents:',
207
+ ...detected.map((d) => ` - ${d.displayName} (${d.id})`),
208
+ ],
111
209
  });
112
- throw new Error(`The requested agent "${explicitId}" is not installed.`);
113
210
  }
114
- output_1.output.error({
115
- title: `The agent "${explicitId}" was requested via --agentic but is not installed.`,
116
- bodyLines: [
117
- 'Install the requested agent and re-run, or pass --agentic without an explicit agent to choose from installed ones.',
118
- 'Currently installed agents:',
119
- ...detected.map((d) => ` - ${d.displayName} (${d.id})`),
120
- ],
121
- });
122
- throw new Error(`The requested agent "${explicitId}" is not installed.`);
123
211
  }
124
212
  if (detected.length === 0) {
125
213
  output_1.output.error({
@@ -1,4 +1,46 @@
1
1
  import { CommandModule } from 'yargs';
2
+ import type { AgenticArg } from './agentic/select';
2
3
  export declare const yargsMigrateCommand: CommandModule;
3
4
  export declare const yargsInternalMigrateCommand: CommandModule;
4
5
  export declare const DEFAULT_MIGRATION_COMMIT_PREFIX = "chore: [nx migration] ";
6
+ /** Allowed values for `--mode` / `migrate.mode`. */
7
+ export declare const MIGRATE_MODES: readonly ["first-party", "third-party", "all"];
8
+ export type MigrateMode = (typeof MIGRATE_MODES)[number];
9
+ /** Allowed values for `--multi-major-mode` / `migrate.multiMajorMode`. */
10
+ export declare const MULTI_MAJOR_MODES: readonly ["direct", "gradual"];
11
+ export type MultiMajorMode = (typeof MULTI_MAJOR_MODES)[number];
12
+ /**
13
+ * The `nx migrate` args bag. Types the keys the nx.json overlay and the
14
+ * commit-prefix invariant read/write; the index signature keeps the rest of
15
+ * the yargs args flowing through untouched.
16
+ */
17
+ export interface MigrateArgs {
18
+ packageAndVersion?: string;
19
+ runMigrations?: string;
20
+ mode?: MigrateMode;
21
+ /**
22
+ * nx.json `migrate.mode` default. Consumed by `resolveMode` only when the
23
+ * target is Nx itself; kept separate from `mode` so it is never mistaken for
24
+ * an explicit `--mode` (which would hard-fail for non-Nx targets).
25
+ */
26
+ modeFromConfig?: MigrateMode;
27
+ multiMajorMode?: MultiMajorMode;
28
+ createCommits?: boolean;
29
+ commitPrefix?: string;
30
+ agentic?: AgenticArg;
31
+ validate?: boolean;
32
+ [key: string]: any;
33
+ }
34
+ /**
35
+ * Whether a custom commit prefix would be silently ignored: commits aren't
36
+ * enabled and the agentic flow can't enable them either. Shared by the yargs
37
+ * `.check()` (CLI args) and the nx.json overlay (merged args) so the rule lives
38
+ * in one place. `agentic` may flip commits on by default, so a configured
39
+ * agentic value (other than `false`, and not paired with `--no-create-commits`)
40
+ * keeps the prefix in play.
41
+ */
42
+ export declare function customCommitPrefixHasNoEffect(args: {
43
+ createCommits: boolean | undefined;
44
+ commitPrefix: string | undefined;
45
+ agentic: unknown;
46
+ }): boolean;