nx 23.3.0-beta.2 → 23.3.0-beta.3

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 (58) hide show
  1. package/dist/bin/init-local.js +7 -1
  2. package/dist/src/command-line/add/add.js +5 -1
  3. package/dist/src/command-line/init/implementation/utils.js +6 -1
  4. package/dist/src/command-line/migrate/agentic/capture-generator-output.d.ts +25 -0
  5. package/dist/src/command-line/migrate/agentic/capture-generator-output.js +121 -5
  6. package/dist/src/command-line/migrate/agentic/close-agent-session.d.ts +21 -0
  7. package/dist/src/command-line/migrate/agentic/close-agent-session.js +126 -0
  8. package/dist/src/command-line/migrate/agentic/definitions.d.ts +24 -0
  9. package/dist/src/command-line/migrate/agentic/definitions.js +5 -2
  10. package/dist/src/command-line/migrate/agentic/handoff.d.ts +23 -0
  11. package/dist/src/command-line/migrate/agentic/handoff.js +62 -3
  12. package/dist/src/command-line/migrate/agentic/master/invocations.d.ts +12 -0
  13. package/dist/src/command-line/migrate/agentic/master/invocations.js +54 -0
  14. package/dist/src/command-line/migrate/agentic/master/run-master-session.d.ts +12 -0
  15. package/dist/src/command-line/migrate/agentic/master/run-master-session.js +99 -0
  16. package/dist/src/command-line/migrate/agentic/master/spawn-master.d.ts +31 -0
  17. package/dist/src/command-line/migrate/agentic/master/spawn-master.js +217 -0
  18. package/dist/src/command-line/migrate/agentic/prompts/fragments.js +3 -0
  19. package/dist/src/command-line/migrate/agentic/runner.d.ts +0 -21
  20. package/dist/src/command-line/migrate/agentic/runner.js +12 -249
  21. package/dist/src/command-line/migrate/agentic/terminal-repair.d.ts +1 -0
  22. package/dist/src/command-line/migrate/agentic/terminal-repair.js +27 -0
  23. package/dist/src/command-line/migrate/agentic/windows-cmd.d.ts +33 -0
  24. package/dist/src/command-line/migrate/agentic/windows-cmd.js +88 -0
  25. package/dist/src/command-line/migrate/deferred-output.d.ts +44 -0
  26. package/dist/src/command-line/migrate/deferred-output.js +92 -0
  27. package/dist/src/command-line/migrate/execute-migration.d.ts +9 -3
  28. package/dist/src/command-line/migrate/execute-migration.js +53 -16
  29. package/dist/src/command-line/migrate/migrate-commits.d.ts +4 -4
  30. package/dist/src/command-line/migrate/migrate-commits.js +7 -6
  31. package/dist/src/command-line/migrate/migrate.js +28 -13
  32. package/dist/src/command-line/migrate/run/broker.d.ts +138 -0
  33. package/dist/src/command-line/migrate/run/broker.js +507 -0
  34. package/dist/src/command-line/migrate/run/clean-retry.d.ts +12 -0
  35. package/dist/src/command-line/migrate/run/clean-retry.js +98 -0
  36. package/dist/src/command-line/migrate/run/index.d.ts +4 -3
  37. package/dist/src/command-line/migrate/run/index.js +7 -1
  38. package/dist/src/command-line/migrate/run/issues.d.ts +8 -1
  39. package/dist/src/command-line/migrate/run/issues.js +40 -6
  40. package/dist/src/command-line/migrate/run/orchestrator.d.ts +18 -1
  41. package/dist/src/command-line/migrate/run/orchestrator.js +409 -377
  42. package/dist/src/command-line/migrate/run/run-state.d.ts +16 -0
  43. package/dist/src/command-line/migrate/run/run-state.js +33 -2
  44. package/dist/src/command-line/migrate/run/runbook.js +8 -4
  45. package/dist/src/command-line/migrate/run/state-machine.d.ts +24 -1
  46. package/dist/src/command-line/migrate/run/state-machine.js +112 -21
  47. package/dist/src/command-line/migrate/run/util.d.ts +2 -1
  48. package/dist/src/command-line/migrate/run/util.js +3 -3
  49. package/dist/src/command-line/migrate/run/worker.js +170 -164
  50. package/dist/src/core/graph/main.js +1 -1
  51. package/dist/src/daemon/server/server.js +11 -8
  52. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  53. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  54. package/dist/src/tasks-runner/run-command.js +3 -0
  55. package/dist/src/utils/git-utils.d.ts +12 -0
  56. package/dist/src/utils/git-utils.js +76 -30
  57. package/dist/src/utils/package-json.js +8 -0
  58. package/package.json +11 -11
@@ -20,6 +20,7 @@ exports.isAngularMigration = isAngularMigration;
20
20
  const tslib_1 = require("tslib");
21
21
  const pc = tslib_1.__importStar(require("picocolors"));
22
22
  const child_process_1 = require("child_process");
23
+ const string_decoder_1 = require("string_decoder");
23
24
  const path_1 = require("path");
24
25
  const semver_1 = require("semver");
25
26
  const handle_import_1 = require("../../utils/handle-import");
@@ -29,12 +30,12 @@ const logger_1 = require("../../utils/logger");
29
30
  const text_1 = require("./text");
30
31
  const package_json_1 = require("../../utils/package-json");
31
32
  const package_manager_1 = require("../../utils/package-manager");
32
- const output_1 = require("../../utils/output");
33
33
  const fs_1 = require("fs");
34
34
  const installation_directory_1 = require("../../utils/installation-directory");
35
35
  const shell_quoting_1 = require("../../utils/shell-quoting");
36
36
  const project_graph_1 = require("../../project-graph/project-graph");
37
37
  const version_utils_1 = require("./version-utils");
38
+ const deferred_output_1 = require("./deferred-output");
38
39
  function readPackageMigrationConfig(packageName, dir) {
39
40
  const { path: packageJsonPath, packageJson: json } = (0, package_json_1.readModulePackageJson)(packageName, (0, installation_directory_1.getNxRequirePaths)(dir));
40
41
  const config = (0, package_json_1.readNxMigrateConfig)(json);
@@ -61,32 +62,68 @@ function readPackageMigrationConfig(packageName, dir) {
61
62
  };
62
63
  }
63
64
  }
64
- function runInstall(nxWorkspaceRoot, phase = 'pre-migration', rerunCommand) {
65
+ /**
66
+ * With a `sink`, stdin is ignored and stdout and stderr are collected for a
67
+ * caller sharing the terminal. On POSIX the child stays in nx's process
68
+ * group. Without a sink, the install owns the terminal.
69
+ */
70
+ function runInstall(nxWorkspaceRoot, phase = 'pre-migration', rerunCommand, sink) {
65
71
  const cwd = nxWorkspaceRoot ?? process.cwd();
66
72
  const packageManager = (0, package_manager_1.detectPackageManager)(cwd);
67
73
  const pmCommands = (0, package_manager_1.getPackageManagerCommand)(packageManager, cwd);
74
+ const out = sink ?? deferred_output_1.terminalOutput;
68
75
  const installCommand = `${pmCommands.install} ${pmCommands.ignoreScriptsFlag ?? ''}`;
69
- output_1.output.log({
76
+ out.notice('log', {
70
77
  title: `Running '${installCommand}' to make sure necessary packages are installed`,
71
78
  });
72
79
  return new Promise((resolve, reject) => {
73
- // For npm, pipe stderr so we can detect peer dependency errors while still
74
- // mirroring it live to the user's terminal. Other package managers inherit
75
- // stderr directly since we don't need to inspect their output.
80
+ // npm's stderr is piped so peer dependency errors can be detected in it.
81
+ // Without a sink it is mirrored live and other package managers inherit
82
+ // stderr directly, since their output is not inspected.
76
83
  const shouldCaptureStderr = packageManager === 'npm';
77
84
  const child = (0, child_process_1.spawn)(installCommand, {
78
85
  shell: true,
79
- stdio: ['inherit', 'inherit', shouldCaptureStderr ? 'pipe' : 'inherit'],
86
+ stdio: sink
87
+ ? ['ignore', 'pipe', 'pipe']
88
+ : ['inherit', 'inherit', shouldCaptureStderr ? 'pipe' : 'inherit'],
80
89
  windowsHide: true,
81
90
  cwd,
82
91
  });
92
+ // Decoded per stream: a chunk boundary can split a multi-byte character,
93
+ // and a sequence still incomplete at the end is what `end()` returns.
94
+ const stdoutText = new string_decoder_1.StringDecoder('utf8');
95
+ const stderrText = new string_decoder_1.StringDecoder('utf8');
96
+ child.stdout?.on('data', (chunk) => out.raw(stdoutText.write(chunk)));
97
+ child.stdout?.on('end', () => out.raw(stdoutText.end()));
83
98
  const stderrChunks = [];
84
99
  child.stderr?.on('data', (chunk) => {
85
- process.stderr.write(chunk);
86
- stderrChunks.push(chunk);
100
+ if (sink) {
101
+ out.raw(stderrText.write(chunk));
102
+ }
103
+ else {
104
+ process.stderr.write(chunk);
105
+ }
106
+ if (shouldCaptureStderr)
107
+ stderrChunks.push(chunk);
108
+ });
109
+ if (sink)
110
+ child.stderr.on('end', () => out.raw(stderrText.end()));
111
+ // With a sink the rejection waits for `close`, which follows the streams'
112
+ // `end`: a caller must be able to render what it collected on rejection.
113
+ let spawnError = null;
114
+ child.on('error', (error) => {
115
+ if (sink) {
116
+ spawnError = error;
117
+ }
118
+ else {
119
+ reject(error);
120
+ }
87
121
  });
88
- child.on('error', reject);
89
122
  child.on('close', (code) => {
123
+ if (spawnError) {
124
+ reject(spawnError);
125
+ return;
126
+ }
90
127
  if (code === 0) {
91
128
  resolve();
92
129
  return;
@@ -98,7 +135,7 @@ function runInstall(nxWorkspaceRoot, phase = 'pre-migration', rerunCommand) {
98
135
  // (CLI migrate, `nx repair`, single-migration runner, etc.) surfaces
99
136
  // it consistently. Top-level callers catch `NpmPeerDepsInstallError`
100
137
  // and return a non-zero exit code without re-logging.
101
- logNpmPeerDepsError(phase, rerunCommand);
138
+ logNpmPeerDepsError(phase, rerunCommand, out);
102
139
  reject(new NpmPeerDepsInstallError());
103
140
  return;
104
141
  }
@@ -143,7 +180,7 @@ function formatSingleMigrationRerunCommand(migrationId) {
143
180
  : migrationId;
144
181
  return `nx migrate --run-migration=${id}`;
145
182
  }
146
- function logNpmPeerDepsError(phase, rerunCommand = 'nx migrate --run-migrations') {
183
+ function logNpmPeerDepsError(phase, rerunCommand = 'nx migrate --run-migrations', out = deferred_output_1.terminalOutput) {
147
184
  const peerDepsResolutionSteps = [
148
185
  'Recommended approaches (in order of preference):',
149
186
  '',
@@ -160,7 +197,7 @@ function logNpmPeerDepsError(phase, rerunCommand = 'nx migrate --run-migrations'
160
197
  ` ${rerunCommand} --skip-install`,
161
198
  ];
162
199
  if (phase === 'pre-migration') {
163
- output_1.output.error({
200
+ out.notice('error', {
164
201
  title: 'You need to resolve the peer dependency conflicts before the migration can continue',
165
202
  bodyLines: [
166
203
  ...peerDepsResolutionSteps,
@@ -173,7 +210,7 @@ function logNpmPeerDepsError(phase, rerunCommand = 'nx migrate --run-migrations'
173
210
  });
174
211
  }
175
212
  else {
176
- output_1.output.error({
213
+ out.notice('error', {
177
214
  title: 'Some migrations have been applied, but installing the updated dependencies failed',
178
215
  bodyLines: [
179
216
  ...peerDepsResolutionSteps,
@@ -187,10 +224,10 @@ function logNpmPeerDepsError(phase, rerunCommand = 'nx migrate --run-migrations'
187
224
  });
188
225
  }
189
226
  }
190
- function logSkippedPostMigrationInstall(root) {
227
+ function logSkippedPostMigrationInstall(root, out = deferred_output_1.terminalOutput) {
191
228
  const packageManager = (0, package_manager_1.detectPackageManager)(root);
192
229
  const installCommand = (0, package_manager_1.getPackageManagerCommand)(packageManager, root).install;
193
- output_1.output.warn({
230
+ out.notice('warn', {
194
231
  title: 'Migrations updated your dependencies, but the install was skipped',
195
232
  bodyLines: [`Run "${installCommand}" to install the updated dependencies.`],
196
233
  });
@@ -1,4 +1,5 @@
1
1
  import type { ResolvedAgentic } from './agentic/types';
2
+ import { type MigrateOutputSink } from './deferred-output';
2
3
  /**
3
4
  * Discriminated result for `commitMigrationIfRequested`. Distinguishes the
4
5
  * shapes the executor needs to react to:
@@ -7,9 +8,8 @@ import type { ResolvedAgentic } from './agentic/types';
7
8
  * HEAD` failed transiently — by contract the diff is no longer in the
8
9
  * working tree.
9
10
  * - `no-changes`: commits were requested but there was nothing to commit.
10
- * - `failed`: the commit attempt itself errored. The diff remains in the
11
- * working tree; the executor uses this signal to track pending migrations
12
- * so the next successful commit can annotate its body.
11
+ * - `failed`: the attempt errored, possibly after the commit landed; tracked
12
+ * as pending so the next successful commit can annotate its body.
13
13
  * - `disabled`: commits are off for this run.
14
14
  */
15
15
  export type CommitResult = {
@@ -37,7 +37,7 @@ export declare function commitMigrationIfRequested(root: string, migration: {
37
37
  }, shouldCreateCommits: boolean, commitPrefix: string, installDepsIfChanged: () => Promise<void>, pendingMigrations?: ReadonlyArray<{
38
38
  package: string;
39
39
  name: string;
40
- }>, failureGuidance?: string): Promise<CommitResult>;
40
+ }>, failureGuidance?: string, out?: MigrateOutputSink): Promise<CommitResult>;
41
41
  /**
42
42
  * Commits any pre-existing working-tree state into a dedicated "checkpoint"
43
43
  * commit before the first migration runs. Without this, the first migration's
@@ -15,6 +15,7 @@ const logger_1 = require("../../utils/logger");
15
15
  const output_1 = require("../../utils/output");
16
16
  const types_1 = require("./agentic/types");
17
17
  const safe_prompt_1 = require("./safe-prompt");
18
+ const deferred_output_1 = require("./deferred-output");
18
19
  // `git add -A` captures an orchestrated run's scratch state whenever the
19
20
  // ignore rule that normally hides it goes missing mid-run (a checkout, a
20
21
  // .gitignore edit, or the migration's own changes).
@@ -28,7 +29,7 @@ const MIGRATE_COMMIT_EXCLUDES = [types_1.MIGRATE_RUNS_RELATIVE_DIR];
28
29
  * behavior; a caller with no later commit or recap to absorb the diff (the
29
30
  * standalone single-migration worker) passes its own.
30
31
  */
31
- async function commitMigrationIfRequested(root, migration, shouldCreateCommits, commitPrefix, installDepsIfChanged, pendingMigrations = [], failureGuidance = 'The next successful commit will absorb it and reference this migration in its body; if no later commit lands, the end-of-run output will list this migration so you can commit or revert manually.') {
32
+ async function commitMigrationIfRequested(root, migration, shouldCreateCommits, commitPrefix, installDepsIfChanged, pendingMigrations = [], failureGuidance = 'Any uncommitted changes will be included in the next successful commit, which will reference this migration; if they remain uncommitted, the end-of-run output will list this migration so you can commit or revert them manually.', out = deferred_output_1.terminalOutput) {
32
33
  if (!shouldCreateCommits)
33
34
  return { status: 'disabled' };
34
35
  await installDepsIfChanged();
@@ -36,22 +37,22 @@ async function commitMigrationIfRequested(root, migration, shouldCreateCommits,
36
37
  // dir, or the prompt half made no change: log neutrally, not as an error.
37
38
  // The probe excludes what the commit excludes, else the commit fails empty.
38
39
  if (!(0, git_utils_1.hasUncommittedChanges)(root, MIGRATE_COMMIT_EXCLUDES)) {
39
- logger_1.logger.info(pc.dim(`- No changes to commit for ${migration.name}.`));
40
+ out.line('dim', `- No changes to commit for ${migration.name}.`);
40
41
  return { status: 'no-changes' };
41
42
  }
42
43
  const commitMessage = buildCommitMessage(`${commitPrefix}${migration.name}`, pendingMigrations);
43
44
  try {
44
- const sha = (0, git_utils_1.tryCommitChanges)(commitMessage, root, MIGRATE_COMMIT_EXCLUDES);
45
+ const sha = await (0, git_utils_1.tryCommitChangesAsync)(commitMessage, root, MIGRATE_COMMIT_EXCLUDES);
45
46
  if (sha)
46
47
  return { status: 'committed', sha };
47
48
  // null = commit landed but `git rev-parse HEAD` failed (see
48
- // `tryCommitChanges`). Degraded-but-correct — log yellow, not red.
49
- logger_1.logger.info(pc.yellow(`The commit for ${migration.name} was created, but its sha could not be resolved (\`git rev-parse HEAD\` failed transiently). Continuing without recording the sha for this step.`));
49
+ // `tryCommitChangesAsync`). Degraded-but-correct — log yellow, not red.
50
+ out.line('yellow', `The commit for ${migration.name} was created, but its sha could not be resolved (\`git rev-parse HEAD\` failed transiently). Continuing without recording the sha for this step.`);
50
51
  return { status: 'committed', sha: null };
51
52
  }
52
53
  catch (err) {
53
54
  const reason = err instanceof Error ? err.message : String(err);
54
- logger_1.logger.info(pc.red(`Could not create a commit for ${migration.name}:\n${reason}\nThe migration's diff remains in the working tree; inspect with \`git status\` / \`git diff\` to review. ${failureGuidance}`));
55
+ out.line('red', `The commit for ${migration.name} failed:\n${reason}\nCheck \`git status\` and \`git log\`; the commit may have landed despite this error. ${failureGuidance}`);
55
56
  return { status: 'failed', reason };
56
57
  }
57
58
  }
@@ -30,6 +30,7 @@ const tar_1 = require("../../utils/tar");
30
30
  const write_formatted_json_file_1 = require("../../utils/write-formatted-json-file");
31
31
  const shell_quoting_1 = require("../../utils/shell-quoting");
32
32
  const logger_1 = require("../../utils/logger");
33
+ const native_1 = require("../../native");
33
34
  const git_utils_1 = require("../../utils/git-utils");
34
35
  const package_json_1 = require("../../utils/package-json");
35
36
  const package_manager_1 = require("../../utils/package-manager");
@@ -2126,6 +2127,20 @@ async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits, c
2126
2127
  }
2127
2128
  const migrationsJson = (0, fileutils_1.readJsonFile)((0, path_1.join)(root, opts.runMigrations));
2128
2129
  const migrations = migrationsJson.migrations;
2130
+ // Defer the nx package lookup until an orchestrated branch needs this payload.
2131
+ const orchestratorInitInput = (createCommits) => ({
2132
+ root,
2133
+ migrationsJson,
2134
+ createCommits,
2135
+ commitPrefix,
2136
+ // The flag only, never NX_MIGRATE_SKIP_INSTALL: the wrapper's local
2137
+ // re-exec sets that env var for its own hop, and it says nothing about
2138
+ // what the user asked for.
2139
+ skipInstall: shouldSkipInstall,
2140
+ installedNxVersion: (0, package_json_1.readModulePackageJson)('nx', (0, installation_directory_1.getNxRequirePaths)(root))
2141
+ .packageJson.version,
2142
+ validate: opts.validate,
2143
+ });
2129
2144
  // An outer agent drives the loop, so hand off to the orchestrator instead of
2130
2145
  // the classic loop: init either starts a fresh run or resumes an already-
2131
2146
  // active one. `--run-id` reconciles are dispatched separately and never
@@ -2164,20 +2179,10 @@ async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits, c
2164
2179
  return;
2165
2180
  }
2166
2181
  }
2167
- const { packageJson: orchestratorNxPackageJson } = (0, package_json_1.readModulePackageJson)('nx', (0, installation_directory_1.getNxRequirePaths)(root));
2182
+ const init = orchestratorInitInput(effectiveCreateCommits);
2168
2183
  const { runOrchestratorInit } = require('./run');
2169
- return await runOrchestratorInit({
2170
- root,
2171
- migrationsJson,
2172
- createCommits: effectiveCreateCommits,
2173
- commitPrefix,
2174
- // The flag only, never NX_MIGRATE_SKIP_INSTALL: the wrapper's local
2175
- // re-exec sets that env var for its own hop, and it says nothing about
2176
- // what the user asked for.
2177
- skipInstall: shouldSkipInstall,
2178
- installedNxVersion: orchestratorNxPackageJson.version,
2179
- validate: opts.validate,
2180
- });
2184
+ await runOrchestratorInit(init);
2185
+ return;
2181
2186
  }
2182
2187
  (0, migrate_analytics_1.reportMigrateRunStart)({
2183
2188
  createCommits: shouldCreateCommits ?? false,
@@ -2213,6 +2218,16 @@ async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits, c
2213
2218
  !(await (0, migrate_commits_1.confirmMigrationCommitsOnDefaultBranch)(root, 'running migrations'))) {
2214
2219
  return;
2215
2220
  }
2221
+ // Dark: with the env var set, the agent drives the whole run through the
2222
+ // orchestrator from one session instead of being spawned per step. Not
2223
+ // under WASM, where the broker has no native lock to detect a dead parent.
2224
+ if (agentic.kind === 'enabled' &&
2225
+ process.env.NX_MIGRATE_ORCHESTRATOR === 'true' &&
2226
+ !native_1.IS_WASM) {
2227
+ const init = orchestratorInitInput(effectiveCreateCommits);
2228
+ const { runMasterSession } = require('./agentic/master/run-master-session');
2229
+ return await runMasterSession({ ...init, agent: agentic.selectedAgent });
2230
+ }
2216
2231
  const shouldRunValidation = resolveShouldRunValidation({
2217
2232
  validate: opts.validate,
2218
2233
  agenticKind: agentic.kind,
@@ -0,0 +1,138 @@
1
+ import { type DeferredOutputRecord } from '../deferred-output';
2
+ import { type CommitResult } from '../migrate-commits';
3
+ import { type MigrateRunPolicy, type MigrateRunState, type MigrateStep, type MigrateTreeOperation } from './run-state';
4
+ export declare const BROKER_ENV_VAR = "NX_MIGRATE_BROKER";
5
+ export type BrokerRequestKind = 'commit' | 'install' | 'fold-install' | 'action-install' | 'reset';
6
+ export type InstallSeam = 'install' | 'fold-install' | 'action-install';
7
+ export interface BrokerRequest {
8
+ kind: BrokerRequestKind;
9
+ stepId: string;
10
+ attempt: number;
11
+ invocation?: string;
12
+ }
13
+ export type BrokerResult = {
14
+ kind: 'commit';
15
+ result: CommitResult;
16
+ absorbedStepIds: string[];
17
+ output: DeferredOutputRecord[];
18
+ } | {
19
+ kind: 'installed';
20
+ output: DeferredOutputRecord[];
21
+ } | {
22
+ kind: 'install-failed';
23
+ message: string;
24
+ peerDeps: boolean;
25
+ output: DeferredOutputRecord[];
26
+ } | {
27
+ kind: 'reset';
28
+ error?: string;
29
+ } | {
30
+ kind: 'stale';
31
+ };
32
+ export interface BrokeredCommit {
33
+ result: CommitResult;
34
+ absorbedStepIds: string[];
35
+ /**
36
+ * True when the session's parent ran the commit and recorded whatever it
37
+ * produced before answering, so the caller appends nothing. False for an
38
+ * in-process commit, which the caller records.
39
+ */
40
+ recorded: boolean;
41
+ }
42
+ /** The request no longer matches the step: another attempt owns it. */
43
+ export declare class BrokerStaleRequestError extends Error {
44
+ }
45
+ /**
46
+ * The advertised parent could not accept the request, or went away before
47
+ * answering it. It may have landed the install or the commit; the tree and
48
+ * the run state say.
49
+ */
50
+ export declare class BrokerUnavailableError extends Error {
51
+ }
52
+ /** Another live process holds the working tree for an operation of its own. */
53
+ export declare class TreeBusyError extends Error {
54
+ }
55
+ export interface TreeOperationRequest {
56
+ kind: BrokerRequestKind | 'checkpoint';
57
+ stepId?: string;
58
+ attempt?: number;
59
+ }
60
+ export interface TreeLease {
61
+ readonly owner: string;
62
+ markedStepId?: string;
63
+ release(): void;
64
+ }
65
+ /**
66
+ * Owned by the scope that runs an operation and its state write: a seam that
67
+ * acquires in process writes the lease here before its fallible callback
68
+ * runs, and the scope releases it in a `finally` once the write landed or
69
+ * failed. A brokered call leaves it empty; the parent holds its own.
70
+ */
71
+ export interface TreeScope {
72
+ lease?: TreeLease;
73
+ }
74
+ /**
75
+ * Reserves the working tree for one operation in a single fresh-state write:
76
+ * the request must still be at its seam (else the attempt moved on and the
77
+ * request is stale), and no other live process may hold a reservation.
78
+ * A reservation whose owner process is gone holds nothing.
79
+ */
80
+ export declare function acquireTreeOperation(dir: string, request: TreeOperationRequest, owner?: string): TreeLease;
81
+ /**
82
+ * Owner-checked: a lease released late never drops a newer reservation, nor
83
+ * the mark it set.
84
+ */
85
+ export declare function releaseTreeOperation(dir: string, owner: string, markedStepId?: string): void;
86
+ /** The reservation a live process other than `owner` holds, if any. */
87
+ export declare function liveTreeOperation(state: MigrateRunState, owner?: string): MigrateTreeOperation | undefined;
88
+ export declare function treeBusyMessage(held: MigrateTreeOperation): string;
89
+ export declare function treeOperationLabel(held: Pick<MigrateTreeOperation, 'kind' | 'stepId'>): string;
90
+ export declare function brokerDir(runDirPath: string): string;
91
+ /**
92
+ * Runs the step's install and commit where they can land: in this process
93
+ * unless a parent session advertised its broker, in which case the request
94
+ * goes to the parent and the answer comes back with the output the parent
95
+ * collected, printed here. The absorbed step ids come from whichever side
96
+ * ran the commit, so the ledger entry names what its `git add -A` took.
97
+ */
98
+ export declare function commitStepTree(dir: string, step: MigrateStep, absorbedStepIds: string[], commitInProcess: () => Promise<CommitResult>, scope: TreeScope): Promise<BrokeredCommit>;
99
+ /**
100
+ * The same for a step that owes only its install: no commit is due, or the
101
+ * commit waits for a fold.
102
+ */
103
+ export declare function installStepTree(dir: string, step: MigrateStep, seam: InstallSeam, installInProcess: () => Promise<void>, scope: TreeScope): Promise<void>;
104
+ /**
105
+ * The same for the reset a clean retry of a failed or died step needs. Runs
106
+ * `resetInProcess` under the reservation, or asks the parent, which resets
107
+ * against the state it reads then; a reset that could not run throws.
108
+ */
109
+ export declare function resetStepTree(dir: string, step: MigrateStep, resetInProcess: () => void, scope: TreeScope): Promise<void>;
110
+ /**
111
+ * The parent side. Holds one exclusive lock for the session's lifetime so a
112
+ * waiting step can tell a slow parent from a dead one, answers each request
113
+ * once, and removes its own requests on close; its answers stay for the steps
114
+ * still reading them. Requests carrying another session's nonce belong to
115
+ * that session and are never touched. Whether to install or commit comes from
116
+ * the policy the session started with, never from run state, which the
117
+ * agent's sandbox can write.
118
+ */
119
+ export declare class MigrateCommitBroker {
120
+ private readonly root;
121
+ private readonly dir;
122
+ private readonly reconcileCommand;
123
+ private readonly policy;
124
+ readonly nonce: string;
125
+ private readonly handled;
126
+ private inFlight;
127
+ private readonly lock;
128
+ constructor(root: string, dir: string, reconcileCommand: string, policy: MigrateRunPolicy);
129
+ /** The request whose operation this process is running right now. */
130
+ get requestInFlight(): BrokerRequest | null;
131
+ /** Answers this session's unanswered requests, one at a time. */
132
+ service(): Promise<void>;
133
+ private record;
134
+ private answer;
135
+ /** Releases the lock; call after the last `service` settled. */
136
+ close(): void;
137
+ private notADirectory;
138
+ }