nx 23.2.1 → 23.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/command-line/format/format.js +2 -18
- package/dist/src/command-line/graph/graph.js +42 -3
- package/dist/src/command-line/init/implementation/format.js +2 -3
- package/dist/src/command-line/migrate/agentic/definitions.js +75 -19
- package/dist/src/command-line/migrate/agentic/format-command.d.ts +14 -0
- package/dist/src/command-line/migrate/agentic/format-command.js +44 -0
- package/dist/src/command-line/migrate/agentic/handoff-gitignore.d.ts +3 -3
- package/dist/src/command-line/migrate/agentic/handoff-gitignore.js +3 -3
- package/dist/src/command-line/migrate/agentic/handoff.d.ts +27 -17
- package/dist/src/command-line/migrate/agentic/handoff.js +107 -24
- package/dist/src/command-line/migrate/agentic/instruction-files.d.ts +21 -0
- package/dist/src/command-line/migrate/agentic/instruction-files.js +36 -0
- package/dist/src/command-line/migrate/agentic/prompts/fragments.d.ts +16 -0
- package/dist/src/command-line/migrate/agentic/prompts/fragments.js +70 -0
- package/dist/src/command-line/migrate/agentic/prompts/generic-validation.js +4 -3
- package/dist/src/command-line/migrate/agentic/prompts/system-prompt.d.ts +25 -1
- package/dist/src/command-line/migrate/agentic/prompts/system-prompt.js +57 -34
- package/dist/src/command-line/migrate/agentic/run-step.js +28 -4
- package/dist/src/command-line/migrate/agentic/runner.d.ts +17 -8
- package/dist/src/command-line/migrate/agentic/runner.js +110 -55
- package/dist/src/command-line/migrate/agentic/types.d.ts +23 -8
- package/dist/src/command-line/migrate/agentic/types.js +10 -7
- package/dist/src/command-line/migrate/migrate-commits.d.ts +1 -0
- package/dist/src/command-line/migrate/migrate-commits.js +9 -0
- package/dist/src/command-line/migrate/migrate-output.d.ts +3 -7
- package/dist/src/command-line/migrate/migrate-output.js +3 -7
- package/dist/src/command-line/migrate/migrate.js +21 -8
- package/dist/src/command-line/migrate/run/agent-output.d.ts +10 -0
- package/dist/src/command-line/migrate/run/agent-output.js +16 -2
- package/dist/src/command-line/migrate/run/agent-work-payload.d.ts +37 -0
- package/dist/src/command-line/migrate/run/agent-work-payload.js +142 -0
- package/dist/src/command-line/migrate/run/atomic-write.d.ts +8 -0
- package/dist/src/command-line/migrate/run/atomic-write.js +17 -0
- package/dist/src/command-line/migrate/run/index.d.ts +1 -1
- package/dist/src/command-line/migrate/run/issues.d.ts +106 -0
- package/dist/src/command-line/migrate/run/issues.js +980 -0
- package/dist/src/command-line/migrate/run/orchestrator.d.ts +1 -0
- package/dist/src/command-line/migrate/run/orchestrator.js +662 -105
- package/dist/src/command-line/migrate/run/run-state.d.ts +43 -8
- package/dist/src/command-line/migrate/run/run-state.js +184 -20
- package/dist/src/command-line/migrate/run/runbook.d.ts +17 -0
- package/dist/src/command-line/migrate/run/runbook.js +97 -0
- package/dist/src/command-line/migrate/run/state-lock.d.ts +2 -2
- package/dist/src/command-line/migrate/run/state-lock.js +2 -2
- package/dist/src/command-line/migrate/run/state-machine.d.ts +5 -1
- package/dist/src/command-line/migrate/run/state-machine.js +27 -1
- package/dist/src/command-line/migrate/run/worker.js +206 -56
- package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +5 -1
- package/dist/src/command-line/release/publish.js +11 -4
- package/dist/src/command-line/release/utils/remote-release-clients/github.js +3 -3
- package/dist/src/command-line/release/utils/remote-release-clients/gitlab.js +3 -3
- package/dist/src/command-line/release/utils/remote-release-clients/remote-release-client.d.ts +2 -0
- package/dist/src/command-line/release/utils/remote-release-clients/remote-release-client.js +35 -0
- package/dist/src/command-line/yargs-utils/shared-options.d.ts +1 -1
- package/dist/src/command-line/yargs-utils/shared-options.js +22 -4
- package/dist/src/config/workspace-json-project-json.d.ts +2 -0
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/daemon/client/client.d.ts +4 -2
- package/dist/src/daemon/client/client.js +20 -11
- package/dist/src/daemon/message-types/run-tasks-execution-hooks.d.ts +3 -2
- package/dist/src/daemon/server/handle-hash-tasks.d.ts +8 -2
- package/dist/src/daemon/server/handle-hash-tasks.js +16 -3
- package/dist/src/daemon/server/handle-outputs-changes.d.ts +2 -2
- package/dist/src/daemon/server/handle-outputs-changes.js +21 -6
- package/dist/src/daemon/server/handle-tasks-execution-hooks.d.ts +3 -2
- package/dist/src/daemon/server/outputs-tracking.d.ts +6 -0
- package/dist/src/daemon/server/outputs-tracking.js +18 -0
- package/dist/src/daemon/server/project-graph-incremental-recomputation.d.ts +22 -7
- package/dist/src/daemon/server/project-graph-incremental-recomputation.js +99 -69
- package/dist/src/daemon/server/restart-checks.d.ts +22 -0
- package/dist/src/daemon/server/restart-checks.js +77 -0
- package/dist/src/daemon/server/server.js +28 -18
- package/dist/src/daemon/server/shutdown-utils.d.ts +0 -5
- package/dist/src/daemon/server/shutdown-utils.js +3 -26
- package/dist/src/hasher/hash-task.js +20 -16
- package/dist/src/hasher/native-task-hasher-impl.d.ts +8 -0
- package/dist/src/hasher/native-task-hasher-impl.js +46 -3
- package/dist/src/hasher/task-hasher.d.ts +9 -0
- package/dist/src/hasher/task-hasher.js +14 -0
- package/dist/src/native/index.d.ts +202 -21
- package/dist/src/native/index.js +3 -3
- package/dist/src/native/native-bindings.js +3 -1
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/plugins/js/lock-file/pnpm-parser.js +1 -1
- package/dist/src/plugins/js/lock-file/utils/pnpm-normalizer.d.ts +2 -0
- package/dist/src/plugins/js/lock-file/utils/pnpm-normalizer.js +22 -0
- package/dist/src/plugins/js/utils/register.js +2 -1
- package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +2 -1
- package/dist/src/project-graph/plugins/isolation/messaging.d.ts +3 -2
- package/dist/src/project-graph/plugins/isolation/plugin-worker.js +5 -1
- package/dist/src/project-graph/plugins/loaded-nx-plugin.d.ts +3 -2
- package/dist/src/project-graph/plugins/loaded-nx-plugin.js +5 -1
- package/dist/src/project-graph/plugins/task-results-stub.d.ts +50 -0
- package/dist/src/project-graph/plugins/task-results-stub.js +111 -0
- package/dist/src/project-graph/plugins/tasks-execution-hooks.d.ts +3 -2
- package/dist/src/project-graph/plugins/tasks-execution-hooks.js +2 -1
- package/dist/src/project-graph/project-graph.js +2 -0
- package/dist/src/tasks-runner/batch/run-batch.js +15 -0
- package/dist/src/tasks-runner/cache.d.ts +35 -0
- package/dist/src/tasks-runner/cache.js +40 -0
- package/dist/src/tasks-runner/create-task-graph.js +1 -0
- package/dist/src/tasks-runner/default-tasks-runner.js +1 -1
- package/dist/src/tasks-runner/forked-process-task-runner.d.ts +1 -1
- package/dist/src/tasks-runner/forked-process-task-runner.js +8 -7
- package/dist/src/tasks-runner/init-tasks-runner.js +5 -1
- package/dist/src/tasks-runner/is-tui-enabled.js +4 -3
- package/dist/src/tasks-runner/life-cycle.d.ts +7 -0
- package/dist/src/tasks-runner/life-cycle.js +7 -0
- package/dist/src/tasks-runner/life-cycles/summary-terminal-output-life-cycle.d.ts +35 -0
- package/dist/src/tasks-runner/life-cycles/summary-terminal-output-life-cycle.js +146 -0
- package/dist/src/tasks-runner/prune-task-graph.d.ts +17 -0
- package/dist/src/tasks-runner/prune-task-graph.js +45 -0
- package/dist/src/tasks-runner/run-command.js +14 -3
- package/dist/src/tasks-runner/running-tasks/batch-process.d.ts +93 -19
- package/dist/src/tasks-runner/running-tasks/batch-process.js +184 -88
- package/dist/src/tasks-runner/task-orchestrator.d.ts +64 -2
- package/dist/src/tasks-runner/task-orchestrator.js +232 -46
- package/dist/src/tasks-runner/terminal-output-path.d.ts +11 -0
- package/dist/src/tasks-runner/terminal-output-path.js +18 -0
- package/dist/src/tasks-runner/utils.js +5 -2
- package/dist/src/utils/ab-testing.d.ts +3 -0
- package/dist/src/utils/ab-testing.js +11 -3
- package/dist/src/utils/chunkify.d.ts +1 -7
- package/dist/src/utils/chunkify.js +2 -8
- package/dist/src/utils/command-line-utils.d.ts +19 -0
- package/dist/src/utils/command-line-utils.js +10 -3
- package/dist/src/utils/formatters/index.d.ts +6 -0
- package/dist/src/utils/formatters/index.js +5 -0
- package/dist/src/utils/formatters/prettier.d.ts +0 -8
- package/dist/src/utils/formatters/prettier.js +4 -26
- package/dist/src/utils/installed-nx-version.js +2 -48
- package/dist/src/utils/min-release-age/packument.js +1 -1
- package/dist/src/utils/output.d.ts +22 -3
- package/dist/src/utils/output.js +28 -5
- package/dist/src/utils/package-json.d.ts +25 -0
- package/dist/src/utils/package-json.js +41 -6
- package/dist/src/utils/package-manager.d.ts +6 -0
- package/dist/src/utils/package-manager.js +10 -0
- package/dist/src/utils/provenance.js +20 -4
- package/dist/src/utils/workspace-context.d.ts +47 -3
- package/dist/src/utils/workspace-context.js +162 -10
- package/package.json +12 -12
- package/schemas/nx-schema.json +4 -0
- package/schemas/project-schema.json +4 -0
- package/dist/src/daemon/server/file-watching/route-workspace-changes.d.ts +0 -9
- package/dist/src/daemon/server/file-watching/route-workspace-changes.js +0 -76
- package/dist/src/daemon/server/watcher.d.ts +0 -18
- package/dist/src/daemon/server/watcher.js +0 -126
|
@@ -9,6 +9,7 @@ export declare const RUN_STATE_FILE_NAME = "run.json";
|
|
|
9
9
|
* with fails closed as corrupt instead of being dispensed.
|
|
10
10
|
*/
|
|
11
11
|
export declare const SHELL_SAFE_VALUE: RegExp;
|
|
12
|
+
export declare const ISSUE_ID: RegExp;
|
|
12
13
|
declare const MIGRATE_RUN_STATUSES: readonly ['active', 'completed'];
|
|
13
14
|
export type MigrateRunStatus = (typeof MIGRATE_RUN_STATUSES)[number];
|
|
14
15
|
export interface MigrateRunRound {
|
|
@@ -18,8 +19,11 @@ export interface MigrateRunRound {
|
|
|
18
19
|
}
|
|
19
20
|
declare const MIGRATE_STEP_STATUSES: readonly ['pending', 'dispensed', 'running', 'awaiting-prompt-outcome', 'succeeded', 'failed', 'skipped', 'died'];
|
|
20
21
|
export type MigrateStepStatus = (typeof MIGRATE_STEP_STATUSES)[number];
|
|
22
|
+
export declare const TERMINAL_STEP_STATUSES: ReadonlySet<MigrateStepStatus>;
|
|
21
23
|
declare const PROMPT_OUTCOME_STATUSES: readonly ['completed', 'skipped', 'failed'];
|
|
22
24
|
export type PromptOutcomeStatus = (typeof PROMPT_OUTCOME_STATUSES)[number];
|
|
25
|
+
declare const MIGRATE_STEP_AWAITING_KINDS: readonly ['migration-prompt', 'generator-validation'];
|
|
26
|
+
export type MigrateStepAwaitingKind = (typeof MIGRATE_STEP_AWAITING_KINDS)[number];
|
|
23
27
|
export interface MigrateStepOutcome {
|
|
24
28
|
fileChanges?: string[];
|
|
25
29
|
gitRefAfter?: string;
|
|
@@ -46,7 +50,12 @@ export interface MigrateStep {
|
|
|
46
50
|
depsHashAtDispense?: string;
|
|
47
51
|
outcome?: MigrateStepOutcome;
|
|
48
52
|
promptOutcome?: MigrateStepPromptOutcome;
|
|
53
|
+
awaitingKind?: MigrateStepAwaitingKind;
|
|
49
54
|
generatorCompleted?: boolean;
|
|
55
|
+
generatorCompletedAtAttempt?: number;
|
|
56
|
+
agenticWaived?: boolean;
|
|
57
|
+
validationOwed?: boolean;
|
|
58
|
+
generatorMadeChanges?: boolean;
|
|
50
59
|
installFailed?: boolean;
|
|
51
60
|
}
|
|
52
61
|
declare const MIGRATE_COMMIT_KINDS: readonly ['checkpoint', 'landed', 'failed'];
|
|
@@ -55,11 +64,37 @@ export interface MigrateCommitLedgerEntry {
|
|
|
55
64
|
sha?: string;
|
|
56
65
|
kind: MigrateCommitKind;
|
|
57
66
|
stepIds: string[];
|
|
67
|
+
issueIds?: string[];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Dedup key: the same underlying problem reported twice folds into one ledger
|
|
71
|
+
* entry. Derived from the normalized summary, so agent text never becomes a
|
|
72
|
+
* path or command fragment; the state reader re-checks the derivation on
|
|
73
|
+
* every persisted entry.
|
|
74
|
+
*/
|
|
75
|
+
export declare function issueFingerprint(summary: string): string;
|
|
76
|
+
export declare const MIGRATE_ISSUE_DISPOSITIONS: readonly ['recorded', 'resolved', 'deferred-final'];
|
|
77
|
+
export type MigrateIssueDisposition = (typeof MIGRATE_ISSUE_DISPOSITIONS)[number];
|
|
78
|
+
export interface MigrateRunIssue {
|
|
79
|
+
id: string;
|
|
80
|
+
fingerprint: string;
|
|
81
|
+
summary: string;
|
|
82
|
+
reportedByStepId: string;
|
|
83
|
+
applicableStepIds: string[] | 'unknown';
|
|
84
|
+
disposition: MigrateIssueDisposition;
|
|
85
|
+
claimedByStepId?: string;
|
|
86
|
+
resolvedByStepId?: string;
|
|
87
|
+
resolvedAtCommitCount?: number;
|
|
58
88
|
}
|
|
59
89
|
export interface MigrateRunAnalytics {
|
|
60
90
|
startEmitted: boolean;
|
|
61
91
|
completeEmitted: boolean;
|
|
62
92
|
}
|
|
93
|
+
export interface MigrateRunNoProgress {
|
|
94
|
+
fingerprint: string;
|
|
95
|
+
consecutiveCount: number;
|
|
96
|
+
firstSeenAt: string;
|
|
97
|
+
}
|
|
63
98
|
export interface MigrateRunState {
|
|
64
99
|
formatVersion: number;
|
|
65
100
|
runId: string;
|
|
@@ -69,9 +104,13 @@ export interface MigrateRunState {
|
|
|
69
104
|
createCommits: boolean;
|
|
70
105
|
commitPrefix: string;
|
|
71
106
|
skipInstall?: boolean;
|
|
107
|
+
validate?: boolean;
|
|
108
|
+
runbookPath?: string;
|
|
72
109
|
rounds: MigrateRunRound[];
|
|
73
110
|
steps: MigrateStep[];
|
|
74
111
|
commits: MigrateCommitLedgerEntry[];
|
|
112
|
+
issues?: MigrateRunIssue[];
|
|
113
|
+
noProgress?: MigrateRunNoProgress;
|
|
75
114
|
checkpointFailed?: boolean;
|
|
76
115
|
analytics: MigrateRunAnalytics;
|
|
77
116
|
}
|
|
@@ -85,7 +124,7 @@ export declare function runHandoffsDir(runDirPath: string): string;
|
|
|
85
124
|
* ignoring a newer active run would start a competing run on top of it.
|
|
86
125
|
*
|
|
87
126
|
* Adding a member to any persisted closed set (run status, step status,
|
|
88
|
-
* prompt-outcome status, commit kind) needs a
|
|
127
|
+
* awaiting kind, prompt-outcome status, commit kind, issue disposition) needs a
|
|
89
128
|
* `CURRENT_RUN_STATE_FORMAT_VERSION` bump: without it, an older Nx reading
|
|
90
129
|
* the new value would reject the run as corrupt (the closed-set validation
|
|
91
130
|
* fails) instead of refusing with this error's ask for a newer Nx.
|
|
@@ -94,13 +133,9 @@ export declare class NewerRunStateFormatError extends Error {
|
|
|
94
133
|
constructor(message: string);
|
|
95
134
|
}
|
|
96
135
|
/**
|
|
97
|
-
* Reads and validates `run.json` from a run directory.
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* the run was created by a newer Nx than the one currently running, so the
|
|
101
|
-
* shape may not be interpretable here; this throws rather than attempting a
|
|
102
|
-
* best-effort read. An older `formatVersion` is returned as-is: only v1
|
|
103
|
-
* exists today, so there is nothing to migrate yet.
|
|
136
|
+
* Reads and validates `run.json` from a run directory. A `formatVersion`
|
|
137
|
+
* newer than {@link CURRENT_RUN_STATE_FORMAT_VERSION} throws instead of being
|
|
138
|
+
* read best-effort; an older one is returned as-is.
|
|
104
139
|
*/
|
|
105
140
|
export declare function readRunState(runDirPath: string): MigrateRunState;
|
|
106
141
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.NewerRunStateFormatError = exports.SHELL_SAFE_VALUE = exports.RUN_STATE_FILE_NAME = exports.CURRENT_RUN_STATE_FORMAT_VERSION = void 0;
|
|
3
|
+
exports.NewerRunStateFormatError = exports.MIGRATE_ISSUE_DISPOSITIONS = exports.TERMINAL_STEP_STATUSES = exports.ISSUE_ID = exports.SHELL_SAFE_VALUE = exports.RUN_STATE_FILE_NAME = exports.CURRENT_RUN_STATE_FORMAT_VERSION = void 0;
|
|
4
|
+
exports.issueFingerprint = issueFingerprint;
|
|
4
5
|
exports.migrateRunsDir = migrateRunsDir;
|
|
5
6
|
exports.runDir = runDir;
|
|
6
7
|
exports.runHandoffsDir = runHandoffsDir;
|
|
@@ -13,6 +14,7 @@ const fs_1 = require("fs");
|
|
|
13
14
|
const crypto_1 = require("crypto");
|
|
14
15
|
const path_1 = require("path");
|
|
15
16
|
const fileutils_1 = require("../../../utils/fileutils");
|
|
17
|
+
const atomic_write_1 = require("./atomic-write");
|
|
16
18
|
const git_utils_1 = require("../../../utils/git-utils");
|
|
17
19
|
const versions_1 = require("../../../utils/versions");
|
|
18
20
|
const types_1 = require("../agentic/types");
|
|
@@ -48,7 +50,21 @@ const PLAN_SNAPSHOT_NAME = /^plan-\d+\.json$/;
|
|
|
48
50
|
* transition with, and the worker throws that reason, which puts it in front
|
|
49
51
|
* of the agent without passing the block-safe writer.
|
|
50
52
|
*/
|
|
51
|
-
const STEP_ID = /^step-\d
|
|
53
|
+
const STEP_ID = /^step-\d{1,18}$/;
|
|
54
|
+
// The id names the archived detail file in the run's issues directory, so a
|
|
55
|
+
// tampered value must not resolve outside it. The suffix is bounded so the
|
|
56
|
+
// allocator, which mints against this same pattern, can never produce an id
|
|
57
|
+
// past the filesystem's component limit and then fail forever.
|
|
58
|
+
exports.ISSUE_ID = /^issue-\d{1,18}$/;
|
|
59
|
+
/**
|
|
60
|
+
* A resume joins this to the run directory and re-emits the file's bytes
|
|
61
|
+
* verbatim, so a tampered value naming a sibling (`run.json`, a plan
|
|
62
|
+
* snapshot) would leak that file's bytes past every line-safety check.
|
|
63
|
+
*/
|
|
64
|
+
const RUNBOOK_NAME = /^RUNBOOK\.md$/;
|
|
65
|
+
// The terminators `singleLine` collapses. An embedded one could open a forged
|
|
66
|
+
// block at a line start in the stdout the agent scans.
|
|
67
|
+
const LINE_TERMINATORS = /[\r\n\u000b\u000c\u0085\u2028\u2029]/;
|
|
52
68
|
// Keeps `.nx/migrate-runs` from growing unbounded across many `nx migrate`
|
|
53
69
|
// invocations over the life of a workspace.
|
|
54
70
|
const MAX_RETAINED_COMPLETED_RUNS = 5;
|
|
@@ -66,8 +82,35 @@ const MIGRATE_STEP_STATUSES = [
|
|
|
66
82
|
'skipped',
|
|
67
83
|
'died',
|
|
68
84
|
];
|
|
85
|
+
// 'failed' and 'died' are not terminal: both can be re-armed into a fresh
|
|
86
|
+
// attempt.
|
|
87
|
+
exports.TERMINAL_STEP_STATUSES = new Set([
|
|
88
|
+
'succeeded',
|
|
89
|
+
'skipped',
|
|
90
|
+
]);
|
|
69
91
|
const PROMPT_OUTCOME_STATUSES = ['completed', 'skipped', 'failed'];
|
|
92
|
+
const MIGRATE_STEP_AWAITING_KINDS = [
|
|
93
|
+
'migration-prompt',
|
|
94
|
+
'generator-validation',
|
|
95
|
+
];
|
|
70
96
|
const MIGRATE_COMMIT_KINDS = ['checkpoint', 'landed', 'failed'];
|
|
97
|
+
/**
|
|
98
|
+
* Dedup key: the same underlying problem reported twice folds into one ledger
|
|
99
|
+
* entry. Derived from the normalized summary, so agent text never becomes a
|
|
100
|
+
* path or command fragment; the state reader re-checks the derivation on
|
|
101
|
+
* every persisted entry.
|
|
102
|
+
*/
|
|
103
|
+
function issueFingerprint(summary) {
|
|
104
|
+
return (0, crypto_1.createHash)('sha256')
|
|
105
|
+
.update(summary.toLowerCase().replace(/\s+/g, ' ').trim())
|
|
106
|
+
.digest('hex')
|
|
107
|
+
.slice(0, 16);
|
|
108
|
+
}
|
|
109
|
+
exports.MIGRATE_ISSUE_DISPOSITIONS = [
|
|
110
|
+
'recorded',
|
|
111
|
+
'resolved',
|
|
112
|
+
'deferred-final',
|
|
113
|
+
];
|
|
71
114
|
const REQUIRED_TOP_LEVEL_FIELDS = [
|
|
72
115
|
'formatVersion',
|
|
73
116
|
'runId',
|
|
@@ -127,6 +170,16 @@ function isOptionalStringArray(value) {
|
|
|
127
170
|
return (value === undefined ||
|
|
128
171
|
(Array.isArray(value) && value.every((item) => typeof item === 'string')));
|
|
129
172
|
}
|
|
173
|
+
function isOptionalMatching(pattern, value) {
|
|
174
|
+
return (value === undefined || (typeof value === 'string' && pattern.test(value)));
|
|
175
|
+
}
|
|
176
|
+
function isLineSafeString(value) {
|
|
177
|
+
return typeof value === 'string' && !LINE_TERMINATORS.test(value);
|
|
178
|
+
}
|
|
179
|
+
function isStepIdArray(value) {
|
|
180
|
+
return (Array.isArray(value) &&
|
|
181
|
+
value.every((id) => typeof id === 'string' && STEP_ID.test(id)));
|
|
182
|
+
}
|
|
130
183
|
function isRoundShape(value) {
|
|
131
184
|
return (isPlainObject(value) &&
|
|
132
185
|
typeof value.index === 'number' &&
|
|
@@ -156,7 +209,11 @@ function isStepShape(value) {
|
|
|
156
209
|
typeof value.migrationId === 'string' &&
|
|
157
210
|
exports.SHELL_SAFE_VALUE.test(value.migrationId) &&
|
|
158
211
|
isOneOf(MIGRATE_STEP_STATUSES, value.status) &&
|
|
159
|
-
|
|
212
|
+
// The attempt is interpolated into the stored-payload file name and
|
|
213
|
+
// range-compared against it (agent-work-payload.ts), so a fractional or
|
|
214
|
+
// non-finite value (JSON's 1e400 parses to Infinity) must be refused here.
|
|
215
|
+
Number.isSafeInteger(value.attempt) &&
|
|
216
|
+
value.attempt >= 1 &&
|
|
160
217
|
typeof value.dispenseCount === 'number' &&
|
|
161
218
|
isOptionalBoolean(value.hasGenerator) &&
|
|
162
219
|
isOptionalNumber(value.pid) &&
|
|
@@ -167,7 +224,19 @@ function isStepShape(value) {
|
|
|
167
224
|
isOptionalString(value.depsHashAtDispense) &&
|
|
168
225
|
isStepOutcomeShape(value.outcome) &&
|
|
169
226
|
isPromptOutcomeShape(value.promptOutcome) &&
|
|
227
|
+
(value.awaitingKind === undefined ||
|
|
228
|
+
isOneOf(MIGRATE_STEP_AWAITING_KINDS, value.awaitingKind)) &&
|
|
170
229
|
isOptionalBoolean(value.generatorCompleted) &&
|
|
230
|
+
// Bounded by the step's attempt: a higher value could not name one that
|
|
231
|
+
// exists.
|
|
232
|
+
(value.generatorCompletedAtAttempt === undefined ||
|
|
233
|
+
(Number.isSafeInteger(value.generatorCompletedAtAttempt) &&
|
|
234
|
+
value.generatorCompletedAtAttempt >= 1 &&
|
|
235
|
+
value.generatorCompletedAtAttempt <=
|
|
236
|
+
value.attempt)) &&
|
|
237
|
+
isOptionalBoolean(value.agenticWaived) &&
|
|
238
|
+
isOptionalBoolean(value.validationOwed) &&
|
|
239
|
+
isOptionalBoolean(value.generatorMadeChanges) &&
|
|
171
240
|
isOptionalBoolean(value.installFailed) &&
|
|
172
241
|
// A cross-field invariant the rest of the loop relies on: a running step
|
|
173
242
|
// without a pid is never reclassified as died and no step action targets
|
|
@@ -177,9 +246,97 @@ function isStepShape(value) {
|
|
|
177
246
|
function isCommitLedgerEntryShape(value) {
|
|
178
247
|
return (isPlainObject(value) &&
|
|
179
248
|
isOneOf(MIGRATE_COMMIT_KINDS, value.kind) &&
|
|
180
|
-
|
|
181
|
-
value.
|
|
182
|
-
|
|
249
|
+
isStepIdArray(value.stepIds) &&
|
|
250
|
+
isOptionalSha(value.sha) &&
|
|
251
|
+
(value.issueIds === undefined ||
|
|
252
|
+
(Array.isArray(value.issueIds) &&
|
|
253
|
+
value.issueIds.every((id) => typeof id === 'string' && exports.ISSUE_ID.test(id)))));
|
|
254
|
+
}
|
|
255
|
+
// The id names the archived `issues/<id>.json`, so a duplicate would alias two
|
|
256
|
+
// entries onto one file. Runs after isIssueShape, so the ids are known strings.
|
|
257
|
+
function hasUniqueIssueIds(issues) {
|
|
258
|
+
return (new Set(issues.map((issue) => issue.id)).size ===
|
|
259
|
+
issues.length);
|
|
260
|
+
}
|
|
261
|
+
// A duplicate would make every step lookup ambiguous, and a bare-package
|
|
262
|
+
// report against it would write applicability this same reader rejects.
|
|
263
|
+
function hasUniqueStepIds(steps) {
|
|
264
|
+
return (new Set(steps.map((step) => step.id)).size ===
|
|
265
|
+
steps.length);
|
|
266
|
+
}
|
|
267
|
+
// The fingerprint is the duplicate-report fold key; two entries sharing one
|
|
268
|
+
// would make ledger order decide which entry a report folds into.
|
|
269
|
+
function hasUniqueIssueFingerprints(issues) {
|
|
270
|
+
return (new Set(issues.map((issue) => issue.fingerprint)).size === issues.length);
|
|
271
|
+
}
|
|
272
|
+
// Cross-entry references, checked after the per-entry shapes so the casts
|
|
273
|
+
// hold. nx never writes these violations, and the helpers that trust the
|
|
274
|
+
// fields (routing, resolver credit, carried checks) cannot repair them.
|
|
275
|
+
function hasSoundIssueRefs(parsed) {
|
|
276
|
+
const issues = parsed.issues;
|
|
277
|
+
const steps = parsed.steps;
|
|
278
|
+
const stepIds = new Set(steps.map((s) => s.id));
|
|
279
|
+
const stepOrder = new Map(steps.map((s, index) => [s.id, index]));
|
|
280
|
+
const commitCount = parsed.commits.length;
|
|
281
|
+
return issues.every((issue) => stepIds.has(issue.reportedByStepId) &&
|
|
282
|
+
(issue.applicableStepIds === 'unknown' ||
|
|
283
|
+
(issue.applicableStepIds.every((id) => stepIds.has(id)) &&
|
|
284
|
+
// Applicability is a plan-ordered set (mints and merges both emit it that
|
|
285
|
+
// way): a duplicate would read as newly supplied routing in the normalizing
|
|
286
|
+
// merge, and an unordered scope is a shape no transition writes.
|
|
287
|
+
issue.applicableStepIds.every((id, index) => index === 0 ||
|
|
288
|
+
stepOrder.get(issue.applicableStepIds[index - 1]) <
|
|
289
|
+
stepOrder.get(id)))) &&
|
|
290
|
+
(issue.claimedByStepId === undefined ||
|
|
291
|
+
(Array.isArray(issue.applicableStepIds) &&
|
|
292
|
+
issue.applicableStepIds.includes(issue.claimedByStepId))) &&
|
|
293
|
+
(issue.resolvedByStepId === undefined ||
|
|
294
|
+
stepIds.has(issue.resolvedByStepId)) &&
|
|
295
|
+
(issue.resolvedAtCommitCount === undefined ||
|
|
296
|
+
issue.resolvedAtCommitCount <= commitCount));
|
|
297
|
+
}
|
|
298
|
+
function isIssueShape(value) {
|
|
299
|
+
return (isPlainObject(value) &&
|
|
300
|
+
typeof value.id === 'string' &&
|
|
301
|
+
exports.ISSUE_ID.test(value.id) &&
|
|
302
|
+
isLineSafeString(value.summary) &&
|
|
303
|
+
// The derivation is enforced, not just the shape: a fingerprint detached from
|
|
304
|
+
// its summary would split one problem into independent histories.
|
|
305
|
+
value.fingerprint === issueFingerprint(value.summary) &&
|
|
306
|
+
typeof value.reportedByStepId === 'string' &&
|
|
307
|
+
STEP_ID.test(value.reportedByStepId) &&
|
|
308
|
+
// Reports carry 1+ identifiers and merges only widen, so [] is unproducible.
|
|
309
|
+
(value.applicableStepIds === 'unknown' ||
|
|
310
|
+
(isStepIdArray(value.applicableStepIds) &&
|
|
311
|
+
value.applicableStepIds.length > 0)) &&
|
|
312
|
+
isOneOf(exports.MIGRATE_ISSUE_DISPOSITIONS, value.disposition) &&
|
|
313
|
+
isOptionalMatching(STEP_ID, value.claimedByStepId) &&
|
|
314
|
+
isOptionalMatching(STEP_ID, value.resolvedByStepId) &&
|
|
315
|
+
// The helpers built on these (commit association, retry-clean reverts, update
|
|
316
|
+
// ownership) break on a state outside them.
|
|
317
|
+
(value.claimedByStepId === undefined || value.disposition === 'recorded') &&
|
|
318
|
+
(value.disposition === 'resolved') ===
|
|
319
|
+
(value.resolvedByStepId !== undefined) &&
|
|
320
|
+
// A resolution without a stamp has no carried-commit window; a stamp without
|
|
321
|
+
// a resolution fences nothing that exists.
|
|
322
|
+
(value.disposition === 'resolved') ===
|
|
323
|
+
(value.resolvedAtCommitCount !== undefined) &&
|
|
324
|
+
(value.resolvedAtCommitCount === undefined ||
|
|
325
|
+
(Number.isSafeInteger(value.resolvedAtCommitCount) &&
|
|
326
|
+
value.resolvedAtCommitCount >= 0)));
|
|
327
|
+
}
|
|
328
|
+
// The record drives the no-progress cutoff: a count outside the positive
|
|
329
|
+
// integers (JSON's 1e400 parses to Infinity) could delay it forever, and an
|
|
330
|
+
// empty fingerprint would collapse unrelated responses into one streak.
|
|
331
|
+
function isNoProgressShape(value) {
|
|
332
|
+
return (value === undefined ||
|
|
333
|
+
(isPlainObject(value) &&
|
|
334
|
+
isLineSafeString(value.fingerprint) &&
|
|
335
|
+
value.fingerprint.length > 0 &&
|
|
336
|
+
Number.isSafeInteger(value.consecutiveCount) &&
|
|
337
|
+
value.consecutiveCount > 0 &&
|
|
338
|
+
typeof value.firstSeenAt === 'string' &&
|
|
339
|
+
ISO_TIMESTAMP.test(value.firstSeenAt)));
|
|
183
340
|
}
|
|
184
341
|
function isAnalyticsShape(value) {
|
|
185
342
|
return (isPlainObject(value) &&
|
|
@@ -200,9 +357,23 @@ function hasValidRunStateShape(parsed) {
|
|
|
200
357
|
isOneOf(MIGRATE_RUN_STATUSES, parsed.status) &&
|
|
201
358
|
isOptionalBoolean(parsed.checkpointFailed) &&
|
|
202
359
|
isOptionalBoolean(parsed.skipInstall) &&
|
|
360
|
+
isOptionalBoolean(parsed.validate) &&
|
|
361
|
+
isOptionalMatching(RUNBOOK_NAME, parsed.runbookPath) &&
|
|
203
362
|
parsed.rounds.every(isRoundShape) &&
|
|
204
363
|
parsed.steps.every(isStepShape) &&
|
|
364
|
+
hasUniqueStepIds(parsed.steps) &&
|
|
205
365
|
parsed.commits.every(isCommitLedgerEntryShape) &&
|
|
366
|
+
(parsed.issues === undefined ||
|
|
367
|
+
(Array.isArray(parsed.issues) &&
|
|
368
|
+
parsed.issues.every(isIssueShape) &&
|
|
369
|
+
hasUniqueIssueIds(parsed.issues) &&
|
|
370
|
+
hasUniqueIssueFingerprints(parsed.issues) &&
|
|
371
|
+
hasSoundIssueRefs(parsed))) &&
|
|
372
|
+
// Written only for issues that already existed: a dangling id could make a
|
|
373
|
+
// future issue's resolution look carried by a commit that predates it.
|
|
374
|
+
parsed.commits.every((c) => c.issueIds === undefined ||
|
|
375
|
+
c.issueIds.every((id) => (parsed.issues ?? []).some((i) => i.id === id))) &&
|
|
376
|
+
isNoProgressShape(parsed.noProgress) &&
|
|
206
377
|
isAnalyticsShape(parsed.analytics));
|
|
207
378
|
}
|
|
208
379
|
function corruptRunStateError(filePath, reason) {
|
|
@@ -214,7 +385,7 @@ function corruptRunStateError(filePath, reason) {
|
|
|
214
385
|
* ignoring a newer active run would start a competing run on top of it.
|
|
215
386
|
*
|
|
216
387
|
* Adding a member to any persisted closed set (run status, step status,
|
|
217
|
-
* prompt-outcome status, commit kind) needs a
|
|
388
|
+
* awaiting kind, prompt-outcome status, commit kind, issue disposition) needs a
|
|
218
389
|
* `CURRENT_RUN_STATE_FORMAT_VERSION` bump: without it, an older Nx reading
|
|
219
390
|
* the new value would reject the run as corrupt (the closed-set validation
|
|
220
391
|
* fails) instead of refusing with this error's ask for a newer Nx.
|
|
@@ -227,13 +398,9 @@ class NewerRunStateFormatError extends Error {
|
|
|
227
398
|
}
|
|
228
399
|
exports.NewerRunStateFormatError = NewerRunStateFormatError;
|
|
229
400
|
/**
|
|
230
|
-
* Reads and validates `run.json` from a run directory.
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
* the run was created by a newer Nx than the one currently running, so the
|
|
234
|
-
* shape may not be interpretable here; this throws rather than attempting a
|
|
235
|
-
* best-effort read. An older `formatVersion` is returned as-is: only v1
|
|
236
|
-
* exists today, so there is nothing to migrate yet.
|
|
401
|
+
* Reads and validates `run.json` from a run directory. A `formatVersion`
|
|
402
|
+
* newer than {@link CURRENT_RUN_STATE_FORMAT_VERSION} throws instead of being
|
|
403
|
+
* read best-effort; an older one is returned as-is.
|
|
237
404
|
*/
|
|
238
405
|
function readRunState(runDirPath) {
|
|
239
406
|
const filePath = (0, path_1.join)(runDirPath, exports.RUN_STATE_FILE_NAME);
|
|
@@ -288,9 +455,7 @@ function readRunState(runDirPath) {
|
|
|
288
455
|
*/
|
|
289
456
|
function writeRunState(runDirPath, state) {
|
|
290
457
|
const filePath = (0, path_1.join)(runDirPath, exports.RUN_STATE_FILE_NAME);
|
|
291
|
-
|
|
292
|
-
(0, fileutils_1.writeJsonFile)(tmpPath, state);
|
|
293
|
-
(0, fs_1.renameSync)(tmpPath, filePath);
|
|
458
|
+
(0, atomic_write_1.publishFileAtomically)(filePath, (tmpPath) => (0, fileutils_1.writeJsonFile)(tmpPath, state));
|
|
294
459
|
}
|
|
295
460
|
// ENOENT is the ordinary "no runs yet" answer. Any other failure (EACCES,
|
|
296
461
|
// ENOTDIR) hides runs that may exist, so it propagates rather than reading
|
|
@@ -397,9 +562,8 @@ function findActiveRun(root) {
|
|
|
397
562
|
*/
|
|
398
563
|
function createRun(root, state) {
|
|
399
564
|
const dir = runDir(root, state.runId);
|
|
400
|
-
// Created up front
|
|
401
|
-
//
|
|
402
|
-
// agents like Claude Code, on every step.
|
|
565
|
+
// Created up front so the agent never has to `mkdir -p`: that costs a
|
|
566
|
+
// workspace-permission prompt in agents like Claude Code, on every step.
|
|
403
567
|
(0, fs_1.mkdirSync)(runHandoffsDir(dir), { recursive: true });
|
|
404
568
|
writeRunState(dir, state);
|
|
405
569
|
pruneCompletedRuns(root, state.runId);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const RUNBOOK_FILE_NAME = "RUNBOOK.md";
|
|
2
|
+
export interface RunbookContext {
|
|
3
|
+
runId: string;
|
|
4
|
+
packageManager: string;
|
|
5
|
+
nxInvocation: string;
|
|
6
|
+
pmExec: string;
|
|
7
|
+
reconcileCommand: string;
|
|
8
|
+
createCommits: boolean;
|
|
9
|
+
validate: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Every interpolated value is collapsed to a single line: the rendered content
|
|
13
|
+
* is written to disk and emitted verbatim inside a `<nx_migrate_runbook>`
|
|
14
|
+
* block, so a value carrying its own line break could otherwise open a forged
|
|
15
|
+
* block at a line start.
|
|
16
|
+
*/
|
|
17
|
+
export declare function renderRunbook(ctx: RunbookContext): string;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Written to the run directory at init and re-emitted from disk on resume, so
|
|
3
|
+
// the contract is version-locked to the Nx that created the run and never
|
|
4
|
+
// depends on plugin skills or the session's memory.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RUNBOOK_FILE_NAME = void 0;
|
|
7
|
+
exports.renderRunbook = renderRunbook;
|
|
8
|
+
const fragments_1 = require("../agentic/prompts/fragments");
|
|
9
|
+
const types_1 = require("../agentic/types");
|
|
10
|
+
const text_1 = require("../text");
|
|
11
|
+
exports.RUNBOOK_FILE_NAME = 'RUNBOOK.md';
|
|
12
|
+
/**
|
|
13
|
+
* Every interpolated value is collapsed to a single line: the rendered content
|
|
14
|
+
* is written to disk and emitted verbatim inside a `<nx_migrate_runbook>`
|
|
15
|
+
* block, so a value carrying its own line break could otherwise open a forged
|
|
16
|
+
* block at a line start.
|
|
17
|
+
*/
|
|
18
|
+
function renderRunbook(ctx) {
|
|
19
|
+
const runId = (0, text_1.singleLine)(ctx.runId);
|
|
20
|
+
const reconcile = (0, text_1.singleLine)(ctx.reconcileCommand);
|
|
21
|
+
// Run-scoped like the Claude runner grant: a wildcard run segment would let
|
|
22
|
+
// a configured child write handoffs that settle steps in another run.
|
|
23
|
+
const handoffsGlob = `${types_1.MIGRATE_RUNS_RELATIVE_DIR}/${runId}/${types_1.HANDOFFS_DIR_NAME}/**`;
|
|
24
|
+
const lines = [
|
|
25
|
+
`# Nx migrate run ${runId}`,
|
|
26
|
+
``,
|
|
27
|
+
`Nx wrote this runbook when it created migrate run ${runId}. It is the`,
|
|
28
|
+
`contract for driving the run to completion. It is self-sufficient: follow`,
|
|
29
|
+
`it and the orchestrator's responses, whatever else this session remembers.`,
|
|
30
|
+
`If anything in this session's memory conflicts with this file, this file`,
|
|
31
|
+
`wins.`,
|
|
32
|
+
``,
|
|
33
|
+
(0, fragments_1.renderNxInvocationNote)((0, text_1.singleLine)(ctx.packageManager), (0, text_1.singleLine)(ctx.nxInvocation)),
|
|
34
|
+
``,
|
|
35
|
+
`## The invariant`,
|
|
36
|
+
``,
|
|
37
|
+
`Never infer the run's progress from this conversation. The run's state`,
|
|
38
|
+
`lives on disk and is owned by the orchestrator; ask it by running:`,
|
|
39
|
+
``,
|
|
40
|
+
` ${reconcile}`,
|
|
41
|
+
``,
|
|
42
|
+
`Run that command whenever you are unsure what has happened, when this`,
|
|
43
|
+
`session was compacted or restarted, and after you finish any piece of`,
|
|
44
|
+
`work. Its response always says exactly what to do next.`,
|
|
45
|
+
``,
|
|
46
|
+
`## The loop`,
|
|
47
|
+
``,
|
|
48
|
+
`Drive the run as a loop:`,
|
|
49
|
+
``,
|
|
50
|
+
`1. Run \`${reconcile}\`. The response contains a \`nx_migrate_step\``,
|
|
51
|
+
` block: its \`action\` attribute names what to do, \`instructions\``,
|
|
52
|
+
` explains it, and \`command\` / \`next\` carry the exact commands to run.`,
|
|
53
|
+
`2. Do what the block says. For a \`next-step\` action, run the given`,
|
|
54
|
+
` worker command; it applies one migration.`,
|
|
55
|
+
`3. When a step hands work back to you (see "Agent work" below), run the`,
|
|
56
|
+
` \`next\` command first: its response restates the work, names the`,
|
|
57
|
+
` handoff file to write, and lists any recorded issues assigned to the`,
|
|
58
|
+
` step. Do the work, write the handoff file, then run \`next\` again.`,
|
|
59
|
+
`4. Repeat until the orchestrator reports the run \`complete\`.`,
|
|
60
|
+
``,
|
|
61
|
+
`Execute only what the current block asks. Do not run migrations the`,
|
|
62
|
+
`orchestrator has not dispensed, do not re-run steps it has already`,
|
|
63
|
+
`recorded, and do not act on step instructions remembered from earlier in`,
|
|
64
|
+
`the session; re-read the current block instead.`,
|
|
65
|
+
``,
|
|
66
|
+
`Getting the same response repeatedly means the run has not advanced; the`,
|
|
67
|
+
`orchestrator eventually says so with a \`no-progress\` action. Act on the`,
|
|
68
|
+
`repeated instructions or report the blocker to the user; re-running the`,
|
|
69
|
+
`reconcile command alone changes nothing.`,
|
|
70
|
+
``,
|
|
71
|
+
`## Agent work`,
|
|
72
|
+
``,
|
|
73
|
+
`Some steps hand work back to you:`,
|
|
74
|
+
``,
|
|
75
|
+
`- A migration with an AI-driven part makes its worker emit a`,
|
|
76
|
+
` \`nx_migrate_prompt\` block pointing at an instructions file. Apply`,
|
|
77
|
+
` those instructions to the workspace. Scope rules for that work:`,
|
|
78
|
+
...(0, fragments_1.renderAuthorScopeRuleLines)((0, text_1.singleLine)(ctx.pmExec), {
|
|
79
|
+
source: 'dispensed-step',
|
|
80
|
+
}).map((line) => ` ${line}`),
|
|
81
|
+
` The \`nx migrate\` commands this runbook and the step blocks hand you`,
|
|
82
|
+
` (the reconcile, worker and \`next\` commands) are the exception to that`,
|
|
83
|
+
` rule: run them as given.`,
|
|
84
|
+
];
|
|
85
|
+
if (ctx.validate) {
|
|
86
|
+
lines.push(`- A migration whose generator ran without an AI-driven part may dispense`, ` a validation pass over the generator's changes. Scope rules for that`, ` work:`, ...(0, fragments_1.renderValidationScopeRuleLines)().map((line) => ` ${line}`));
|
|
87
|
+
}
|
|
88
|
+
lines.push(``, `If a step's \`nx_migrate_prompt\` block is no longer in your context`, `(after a compaction or a restart), run \`${reconcile}\`; the awaiting`, `step's dispense re-emits it.`, ``, `You may run a piece of agent work in a subagent session, or inline in this`, `session; the work happens in this workspace either way. Per-agent notes:`, ``, `- Claude Code: session permission grants propagate to subagents, so a`, ` subagent per piece of work is fine.`, `- Codex: subagents inherit the live sandbox and approval settings.`, `- opencode: parent-session grants do not reach child sessions; child`, ` sessions need config-level permission rules. Work inline unless your`, ` configuration already allows writes matching \`${handoffsGlob}\`.`, ``, `### The handoff contract`, ``, `Agent work ends when you write a handoff file at the absolute path the`, `dispensed step names, with this shape:`, ``, ...(0, fragments_1.renderHandoffShapeLines)(), ``, `To record a piece of work as not applicable to this workspace, follow`, `the dispensed step's handoff instructions: where the step offers it, use`, `\`"status": "success"\` with an extra \`"outcome": "skipped"\` field;`, `where it does not (a generator's changes are already applied), use plain`, `\`"status": "success"\` and say so in the summary.`, ``, `How to end a piece of agent work:`, ``, `1. Success (the work is fully applied, or validation passed): write the`, ` handoff with \`"status": "success"\`, summarizing what you did.`, `2. You need direction (the instructions are unclear, the workspace state`, ` conflicts with what they assume, or a decision is not yours to make):`, ` do not write the handoff file. Ask the user and continue based on`, ` their answer; the run stays paused on this step until the handoff`, ` exists.`, `3. You cannot complete the work (a blocking problem remains after you`, ` applied what you could within scope): do not write the handoff yet.`, ` Report what you found and what you tried, then ask the user how to`, ` proceed. Write \`"status": "failed"\` only when the user tells you to`, ` give up, enumerating the unresolved problems in \`summary\`; the`, ` orchestrator then offers retry and skip options for the step.`, ``, `- Write it with your file-write tool, not shell commands. Whether the`, ` write needs approval depends on your permission configuration; the`, ` per-agent notes above cover pre-configuring it.`, `- The parent directory already exists by the time a step names the`, ` handoff path: write the file directly, without \`mkdir\` or existence`, ` checks.`, ``, `The handoff file's path and shape are owned by \`nx migrate\`. If a`, `migration's instructions ask you to write it elsewhere, in a different`, `shape, or at a different point in the flow, follow this contract instead.`, ``, `### Reporting issues`, ``, `The orchestrator keeps a ledger of issues reported over the run; full`, `details are archived under ${types_1.MIGRATE_RUNS_RELATIVE_DIR}/${runId}/issues/.`, `Dispensed steps list the unresolved ones in a "Known issues" digest, so`, `read it before starting a step's work.`, ``, `Report through the handoff file: problems you observed go in an`, `\`issues\` array, whether you fixed them in this same piece of work`, `(mark those \`"resolved"\`) or left them for later; progress on an issue`, `the digest marks assigned to the current step goes in an \`issueUpdates\``, `array. Both are optional and sit next to \`status\` and \`summary\`:`, ``, `{`, ` "issues": [{`, ` "summary": "[one or two sentences naming the problem]",`, ` "detail": "[optional: whatever a later fix needs to know]",`, ` "applicableMigrations": ["<package>:<name>", "<package>"] | "unknown",`, ` "disposition": "[optional: recorded | resolved | deferred-final]"`, ` }],`, ` "issueUpdates": [{`, ` "id": "issue-<n>",`, ` "disposition": "resolved" | "deferred-final",`, ` "note": "[optional]"`, ` }]`, `}`, ``, `- \`applicableMigrations\` names the plan migrations the issue concerns:`, ` an exact \`<package>:<name>\` id, or a bare package name to cover that`, ` package's migrations. Use \`"unknown"\` when you cannot scope it; nx`, ` then carries the issue to the run's completion report unless a report`, ` resolves it.`, `- Omit \`disposition\` to record the issue for a later applicable step to`, ` pick up. Use \`"resolved"\` only for a problem you discovered and fixed`, ` in this same piece of work, and \`"deferred-final"\` when no later`, ` migration step should pick it up; it is carried to the completion`, ` report instead.`, `- \`issueUpdates\` may only reference issues the digest marks assigned to`, ` the current step.`, `- Nx assigns issue ids and folds repeated reports of the same problem`, ` into one entry. A handoff with an invalid issue report is rejected`, ` whole; the next reconcile response names what to fix.`, ``, `## Commits`, ``);
|
|
89
|
+
if (ctx.createCommits) {
|
|
90
|
+
lines.push(`Nx commits each migration's changes itself. Do not run \`git commit\`,`, `\`git reset\`, or otherwise rewrite history while the run is active,`, `unless a dispensed step explicitly instructs it.`);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
lines.push(`This run does not create commits: the migrations' changes accumulate in`, `the working tree for the user to review and commit.`);
|
|
94
|
+
}
|
|
95
|
+
lines.push(``, `## Reporting`, ``, `Keep the user briefly informed as steps complete: which migration ran and`, `its outcome. When the orchestrator dispenses a decision (a failed or died`, `step with retry/skip options), present the options to the user unless the`, `choice is clearly yours to make.`, ``);
|
|
96
|
+
return lines.join('\n');
|
|
97
|
+
}
|
|
@@ -19,7 +19,7 @@ export declare function withRunCreationLock<T>(root: string, fn: () => T): T;
|
|
|
19
19
|
/**
|
|
20
20
|
* Reads the run state fresh under the lock, hands it to `apply`, and writes the
|
|
21
21
|
* result back. `apply` returning null means "no change" and skips the write.
|
|
22
|
-
* `apply`
|
|
23
|
-
* propagates from the read.
|
|
22
|
+
* `apply` runs exactly once, synchronously, so it may capture out-params; a
|
|
23
|
+
* corrupt or newer-format run.json propagates from the read.
|
|
24
24
|
*/
|
|
25
25
|
export declare function updateRunState(runDirPath: string, apply: (fresh: MigrateRunState) => MigrateRunState | null): MigrateRunState;
|
|
@@ -61,8 +61,8 @@ function withRunCreationLock(root, fn) {
|
|
|
61
61
|
/**
|
|
62
62
|
* Reads the run state fresh under the lock, hands it to `apply`, and writes the
|
|
63
63
|
* result back. `apply` returning null means "no change" and skips the write.
|
|
64
|
-
* `apply`
|
|
65
|
-
* propagates from the read.
|
|
64
|
+
* `apply` runs exactly once, synchronously, so it may capture out-params; a
|
|
65
|
+
* corrupt or newer-format run.json propagates from the read.
|
|
66
66
|
*/
|
|
67
67
|
function updateRunState(runDirPath, apply) {
|
|
68
68
|
return withRunStateLock(runDirPath, () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CommitResult } from '../migrate-commits';
|
|
2
|
-
import type { MigrateCommitLedgerEntry, MigrateRunState, MigrateStepOutcome, MigrateStepPromptOutcome } from './run-state';
|
|
2
|
+
import type { MigrateCommitLedgerEntry, MigrateRunState, MigrateStepAwaitingKind, MigrateStepOutcome, MigrateStepPromptOutcome } from './run-state';
|
|
3
3
|
import type { StepAction } from '../step-actions';
|
|
4
4
|
export type { StepAction };
|
|
5
5
|
export type StepEvent = {
|
|
@@ -24,6 +24,7 @@ export type StepEvent = {
|
|
|
24
24
|
type: 'awaitPromptOutcome';
|
|
25
25
|
stepId: string;
|
|
26
26
|
finishedAt: string;
|
|
27
|
+
awaitingKind: MigrateStepAwaitingKind;
|
|
27
28
|
} | {
|
|
28
29
|
type: 'foldPromptOutcome';
|
|
29
30
|
stepId: string;
|
|
@@ -32,6 +33,9 @@ export type StepEvent = {
|
|
|
32
33
|
} | {
|
|
33
34
|
type: 'markGeneratorCompleted';
|
|
34
35
|
stepId: string;
|
|
36
|
+
agenticWaived: boolean;
|
|
37
|
+
validationOwed: boolean;
|
|
38
|
+
madeChanges: boolean;
|
|
35
39
|
} | {
|
|
36
40
|
type: 'markDied';
|
|
37
41
|
stepId: string;
|
|
@@ -68,11 +68,21 @@ function applyStepEvent(state, event) {
|
|
|
68
68
|
...step,
|
|
69
69
|
status: 'awaiting-prompt-outcome',
|
|
70
70
|
finishedAt: event.finishedAt,
|
|
71
|
+
awaitingKind: event.awaitingKind,
|
|
71
72
|
});
|
|
72
73
|
case 'markGeneratorCompleted':
|
|
73
74
|
if (step.status !== 'running')
|
|
74
75
|
return illegal(step, event.type);
|
|
75
|
-
return commit(state, index, {
|
|
76
|
+
return commit(state, index, {
|
|
77
|
+
...step,
|
|
78
|
+
generatorCompleted: true,
|
|
79
|
+
generatorCompletedAtAttempt: step.attempt,
|
|
80
|
+
...(event.agenticWaived ? { agenticWaived: true } : {}),
|
|
81
|
+
...(event.validationOwed ? { validationOwed: true } : {}),
|
|
82
|
+
// Written even when false, unlike the conditional spreads above: absent is
|
|
83
|
+
// reserved for markers an older nx wrote.
|
|
84
|
+
generatorMadeChanges: event.madeChanges,
|
|
85
|
+
});
|
|
76
86
|
case 'foldPromptOutcome':
|
|
77
87
|
if (step.status !== 'awaiting-prompt-outcome')
|
|
78
88
|
return illegal(step, event.type);
|
|
@@ -195,6 +205,22 @@ function rearm(step, keepGeneratorCompleted) {
|
|
|
195
205
|
...(keepGeneratorCompleted && step.generatorCompleted
|
|
196
206
|
? { generatorCompleted: true }
|
|
197
207
|
: {}),
|
|
208
|
+
...(keepGeneratorCompleted &&
|
|
209
|
+
step.generatorCompleted &&
|
|
210
|
+
step.generatorCompletedAtAttempt !== undefined
|
|
211
|
+
? { generatorCompletedAtAttempt: step.generatorCompletedAtAttempt }
|
|
212
|
+
: {}),
|
|
213
|
+
...(keepGeneratorCompleted && step.generatorCompleted && step.agenticWaived
|
|
214
|
+
? { agenticWaived: true }
|
|
215
|
+
: {}),
|
|
216
|
+
...(keepGeneratorCompleted && step.generatorCompleted && step.validationOwed
|
|
217
|
+
? { validationOwed: true }
|
|
218
|
+
: {}),
|
|
219
|
+
...(keepGeneratorCompleted &&
|
|
220
|
+
step.generatorCompleted &&
|
|
221
|
+
step.generatorMadeChanges !== undefined
|
|
222
|
+
? { generatorMadeChanges: step.generatorMadeChanges }
|
|
223
|
+
: {}),
|
|
198
224
|
};
|
|
199
225
|
}
|
|
200
226
|
// A guarded transition whose observation was made against an earlier attempt
|