@hominis/fireforge 0.52.0 → 0.53.0
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/CHANGELOG.md +26 -0
- package/dist/build-info.json +4 -4
- package/dist/src/commands/bootstrap.js +2 -3
- package/dist/src/commands/build-prelock.d.ts +3 -3
- package/dist/src/commands/build-prelock.js +3 -3
- package/dist/src/commands/build.js +4 -0
- package/dist/src/commands/declarations-report.d.ts +9 -3
- package/dist/src/commands/declarations-report.js +18 -11
- package/dist/src/commands/discard.js +3 -3
- package/dist/src/commands/doctor-furnace-config-sync.js +4 -4
- package/dist/src/commands/doctor-furnace.js +5 -5
- package/dist/src/commands/export-adopt-check.d.ts +5 -6
- package/dist/src/commands/export-adopt-check.js +5 -6
- package/dist/src/commands/export-flow.js +6 -9
- package/dist/src/commands/export-staged-registration.d.ts +4 -4
- package/dist/src/commands/export-staged-registration.js +4 -4
- package/dist/src/commands/forward-import-remedy.d.ts +2 -3
- package/dist/src/commands/forward-import-remedy.js +2 -3
- package/dist/src/commands/furnace/create-dry-run.d.ts +2 -2
- package/dist/src/commands/furnace/deploy-fragment-includers.d.ts +2 -2
- package/dist/src/commands/furnace/deploy-fragment-includers.js +2 -2
- package/dist/src/commands/furnace/index.js +2 -2
- package/dist/src/commands/lint.js +4 -4
- package/dist/src/commands/patch/compact.js +1 -1
- package/dist/src/commands/patch/lint-ignore-from.d.ts +3 -4
- package/dist/src/commands/patch/lint-ignore-from.js +3 -4
- package/dist/src/commands/patch/move-files-adopt.d.ts +33 -0
- package/dist/src/commands/patch/move-files-adopt.js +65 -0
- package/dist/src/commands/patch/move-files-create.js +41 -20
- package/dist/src/commands/patch/move-files-into.d.ts +18 -0
- package/dist/src/commands/patch/move-files-into.js +125 -60
- package/dist/src/commands/patch/move-files-retire.d.ts +39 -0
- package/dist/src/commands/patch/move-files-retire.js +91 -0
- package/dist/src/commands/patch/move-files.d.ts +3 -0
- package/dist/src/commands/patch/move-files.js +42 -12
- package/dist/src/commands/patch/move-size-tier.d.ts +27 -0
- package/dist/src/commands/patch/move-size-tier.js +66 -0
- package/dist/src/commands/patch/rename.js +1 -1
- package/dist/src/commands/patch/reorder-projection.d.ts +17 -0
- package/dist/src/commands/patch/reorder-projection.js +49 -0
- package/dist/src/commands/patch/reorder-shift.d.ts +30 -0
- package/dist/src/commands/patch/reorder-shift.js +307 -0
- package/dist/src/commands/patch/reorder.d.ts +9 -15
- package/dist/src/commands/patch/reorder.js +52 -50
- package/dist/src/commands/patch/split-plan.d.ts +31 -3
- package/dist/src/commands/patch/split-plan.js +36 -12
- package/dist/src/commands/patch/split-staged.d.ts +2 -2
- package/dist/src/commands/patch/split-staged.js +2 -2
- package/dist/src/commands/patch/split.js +18 -5
- package/dist/src/commands/patch/staged-dependency-entries.d.ts +9 -1
- package/dist/src/commands/patch/staged-dependency-entries.js +32 -4
- package/dist/src/commands/patch/staged-dependency-from.js +15 -9
- package/dist/src/commands/patch/staged-dependency.js +13 -9
- package/dist/src/commands/patch/tier.js +2 -2
- package/dist/src/commands/patch-rename-map.d.ts +14 -3
- package/dist/src/commands/patch-rename-map.js +21 -3
- package/dist/src/commands/re-export-adjacent.d.ts +4 -5
- package/dist/src/commands/re-export-lint.d.ts +4 -3
- package/dist/src/commands/re-export-lint.js +4 -3
- package/dist/src/commands/re-export-preflight.js +1 -2
- package/dist/src/commands/re-export-purity-scope.d.ts +1 -1
- package/dist/src/commands/re-export-purity-scope.js +13 -7
- package/dist/src/commands/re-export-retired-declarations.d.ts +22 -0
- package/dist/src/commands/re-export-retired-declarations.js +113 -0
- package/dist/src/commands/re-export-single.js +2 -2
- package/dist/src/commands/re-export-stale-registrations.d.ts +27 -6
- package/dist/src/commands/re-export-stale-registrations.js +65 -21
- package/dist/src/commands/re-export.js +4 -0
- package/dist/src/commands/rebase/continue.js +3 -3
- package/dist/src/commands/register.js +5 -4
- package/dist/src/commands/status.js +1 -1
- package/dist/src/commands/test-build-furnace.d.ts +1 -2
- package/dist/src/commands/test-build-furnace.js +1 -2
- package/dist/src/commands/test-build-phase.d.ts +8 -0
- package/dist/src/commands/test-build-phase.js +42 -7
- package/dist/src/commands/test-control-restore.d.ts +40 -0
- package/dist/src/commands/test-control-restore.js +86 -0
- package/dist/src/commands/test-dispatch.js +68 -7
- package/dist/src/commands/test-multi-verdict.js +1 -0
- package/dist/src/commands/test-orchestrate.d.ts +6 -0
- package/dist/src/commands/test-orchestrate.js +28 -0
- package/dist/src/commands/test-perf-scope.d.ts +1 -1
- package/dist/src/commands/test-perf-scope.js +1 -1
- package/dist/src/commands/test-prelock.d.ts +7 -8
- package/dist/src/commands/test-prelock.js +15 -12
- package/dist/src/commands/test-register.js +2 -2
- package/dist/src/commands/test-results.d.ts +10 -1
- package/dist/src/commands/test-results.js +20 -3
- package/dist/src/commands/test-run.d.ts +6 -0
- package/dist/src/commands/test-run.js +23 -4
- package/dist/src/commands/test-shards.d.ts +7 -3
- package/dist/src/commands/test-shards.js +24 -7
- package/dist/src/commands/test-stale-gate.d.ts +6 -1
- package/dist/src/commands/test-stale-gate.js +40 -2
- package/dist/src/commands/test-verdict.d.ts +5 -3
- package/dist/src/commands/test.js +10 -0
- package/dist/src/commands/token.js +2 -2
- package/dist/src/commands/verify.js +1 -1
- package/dist/src/commands/watch.js +1 -1
- package/dist/src/commands/wire.js +1 -1
- package/dist/src/core/build-audit-resolve.d.ts +4 -5
- package/dist/src/core/build-audit-resolve.js +4 -5
- package/dist/src/core/build-audit-skip.d.ts +2 -2
- package/dist/src/core/build-audit.d.ts +3 -4
- package/dist/src/core/build-baseline-types.d.ts +24 -3
- package/dist/src/core/build-baseline-validate.d.ts +30 -1
- package/dist/src/core/build-baseline-validate.js +127 -35
- package/dist/src/core/build-baseline.d.ts +8 -2
- package/dist/src/core/build-baseline.js +25 -16
- package/dist/src/core/build-jar-escalation.d.ts +4 -4
- package/dist/src/core/build-jar-escalation.js +4 -4
- package/dist/src/core/build-prepare.js +4 -4
- package/dist/src/core/config-validate.js +4 -4
- package/dist/src/core/coverage-extend.d.ts +5 -3
- package/dist/src/core/coverage-extend.js +8 -3
- package/dist/src/core/coverage-records.d.ts +4 -5
- package/dist/src/core/coverage-records.js +4 -5
- package/dist/src/core/destructive.d.ts +2 -2
- package/dist/src/core/destructive.js +2 -2
- package/dist/src/core/discard-baseline.d.ts +2 -2
- package/dist/src/core/discard-baseline.js +3 -3
- package/dist/src/core/engine-session-lock.d.ts +1 -1
- package/dist/src/core/engine-session-lock.js +1 -1
- package/dist/src/core/file-lock-owner.d.ts +5 -5
- package/dist/src/core/file-lock-owner.js +5 -5
- package/dist/src/core/file-lock-queue.js +6 -8
- package/dist/src/core/file-lock-shared.d.ts +4 -4
- package/dist/src/core/file-lock-shared.js +4 -4
- package/dist/src/core/file-lock.js +7 -10
- package/dist/src/core/firefox.js +2 -2
- package/dist/src/core/furnace-apply-overwrite-warn.d.ts +1 -1
- package/dist/src/core/furnace-apply-overwrite-warn.js +1 -1
- package/dist/src/core/furnace-apply.js +3 -3
- package/dist/src/core/furnace-operation.js +5 -5
- package/dist/src/core/furnace-registration-jar-inplace.js +5 -6
- package/dist/src/core/furnace-registration-rename.d.ts +5 -5
- package/dist/src/core/furnace-registration-rename.js +5 -5
- package/dist/src/core/furnace-registration-validate.d.ts +2 -1
- package/dist/src/core/furnace-registration-validate.js +2 -1
- package/dist/src/core/furnace-registration.js +2 -2
- package/dist/src/core/git-base.js +4 -3
- package/dist/src/core/git-diff.js +1 -1
- package/dist/src/core/git.js +1 -1
- package/dist/src/core/harness-orphans.d.ts +6 -6
- package/dist/src/core/harness-orphans.js +6 -6
- package/dist/src/core/harness-port-reservation.d.ts +60 -0
- package/dist/src/core/harness-port-reservation.js +122 -0
- package/dist/src/core/history-log-file.d.ts +9 -9
- package/dist/src/core/history-log-file.js +15 -15
- package/dist/src/core/history-log.d.ts +3 -3
- package/dist/src/core/history-log.js +3 -3
- package/dist/src/core/launchable-build.d.ts +39 -0
- package/dist/src/core/launchable-build.js +85 -0
- package/dist/src/core/marionette-port.js +1 -3
- package/dist/src/core/moz-manifest-rules.js +3 -2
- package/dist/src/core/negative-control-compiled.d.ts +13 -3
- package/dist/src/core/negative-control-compiled.js +27 -5
- package/dist/src/core/parser-fallback.d.ts +5 -5
- package/dist/src/core/parser-fallback.js +5 -5
- package/dist/src/core/patch-identifier-suggest.d.ts +6 -0
- package/dist/src/core/patch-identifier-suggest.js +11 -4
- package/dist/src/core/patch-lint-checkjs.d.ts +1 -1
- package/dist/src/core/patch-lint-checkjs.js +1 -1
- package/dist/src/core/patch-lint-css.js +2 -2
- package/dist/src/core/patch-lint-file-size.d.ts +5 -7
- package/dist/src/core/patch-lint-file-size.js +5 -7
- package/dist/src/core/patch-lint-ignore.d.ts +3 -3
- package/dist/src/core/patch-lint-ignore.js +3 -3
- package/dist/src/core/patch-lint-prettier.d.ts +4 -6
- package/dist/src/core/patch-lint-prettier.js +4 -6
- package/dist/src/core/patch-lint-projection.d.ts +1 -1
- package/dist/src/core/patch-lint-projection.js +1 -1
- package/dist/src/core/patch-lint-queue-types.d.ts +2 -2
- package/dist/src/core/patch-lint-queue-types.js +2 -2
- package/dist/src/core/patch-manifest-consistency.d.ts +2 -2
- package/dist/src/core/patch-manifest-consistency.js +2 -2
- package/dist/src/core/patch-manifest-io.d.ts +5 -1
- package/dist/src/core/patch-manifest-io.js +12 -10
- package/dist/src/core/patch-manifest-key-order.d.ts +3 -4
- package/dist/src/core/patch-manifest-key-order.js +3 -4
- package/dist/src/core/patch-parse.d.ts +3 -3
- package/dist/src/core/patch-parse.js +3 -3
- package/dist/src/core/patch-policy.d.ts +1 -1
- package/dist/src/core/patch-policy.js +1 -1
- package/dist/src/core/patch-source-metadata.d.ts +1 -1
- package/dist/src/core/patch-source-metadata.js +1 -1
- package/dist/src/core/patch-subset.d.ts +5 -3
- package/dist/src/core/patch-subset.js +8 -7
- package/dist/src/core/patch-transform.d.ts +2 -2
- package/dist/src/core/patch-transform.js +2 -2
- package/dist/src/core/rebase-session.js +4 -6
- package/dist/src/core/register-xpcshell-test.d.ts +28 -7
- package/dist/src/core/register-xpcshell-test.js +76 -27
- package/dist/src/core/run-log.d.ts +8 -8
- package/dist/src/core/run-log.js +8 -8
- package/dist/src/core/status-classify.d.ts +1 -2
- package/dist/src/core/status-classify.js +1 -2
- package/dist/src/core/test-harness-crash.d.ts +5 -3
- package/dist/src/core/test-harness-crash.js +13 -14
- package/dist/src/core/test-harness-infra.d.ts +32 -0
- package/dist/src/core/test-harness-infra.js +69 -0
- package/dist/src/core/test-harness-signature.d.ts +6 -0
- package/dist/src/core/test-stale-check.d.ts +38 -0
- package/dist/src/core/test-stale-check.js +82 -1
- package/dist/src/core/test-stall-triage.js +5 -7
- package/dist/src/core/token-category.js +2 -2
- package/dist/src/core/token-inventory.js +3 -3
- package/dist/src/core/token-manager.d.ts +2 -2
- package/dist/src/core/token-manager.js +5 -7
- package/dist/src/core/token-variant.d.ts +6 -6
- package/dist/src/core/token-variant.js +7 -7
- package/dist/src/core/typecheck-shim.js +1 -1
- package/dist/src/core/wire-destroy.d.ts +1 -1
- package/dist/src/core/wire-destroy.js +1 -1
- package/dist/src/core/wire-dom-fragment.js +1 -1
- package/dist/src/core/wire-init.d.ts +1 -1
- package/dist/src/core/wire-init.js +1 -1
- package/dist/src/core/wire-subscript.d.ts +1 -1
- package/dist/src/core/wire-subscript.js +1 -1
- package/dist/src/core/wire-utils.d.ts +2 -2
- package/dist/src/core/wire-utils.js +3 -3
- package/dist/src/errors/base.d.ts +1 -1
- package/dist/src/errors/base.js +1 -1
- package/dist/src/errors/rebase.d.ts +1 -1
- package/dist/src/errors/rebase.js +1 -1
- package/dist/src/types/commands/options.d.ts +26 -3
- package/dist/src/types/config.d.ts +5 -7
- package/dist/src/utils/options.js +2 -2
- package/dist/src/utils/process.js +2 -2
- package/dist/src/utils/regex.d.ts +2 -2
- package/dist/src/utils/regex.js +3 -3
- package/dist/src/utils/sleep.d.ts +5 -6
- package/dist/src/utils/sleep.js +5 -6
- package/dist/src/utils/validation.js +2 -2
- package/docs/configuration.md +21 -23
- package/docs/exit-codes.md +8 -11
- package/docs/furnace.md +3 -3
- package/docs/lifecycle-invariants.md +36 -45
- package/docs/machine-output.md +41 -24
- package/docs/patch-workflow.md +129 -24
- package/docs/run-logs.md +8 -9
- package/docs/testing.md +105 -46
- package/package.json +1 -1
|
@@ -21,6 +21,22 @@ import { warn } from '../../utils/logger.js';
|
|
|
21
21
|
import {} from '../export-flow.js';
|
|
22
22
|
import { projectEntryBody } from './entry-projection.js';
|
|
23
23
|
import { describeStagedMaintenance, findDeclarationsMadeUnused, keptRelocatedDeclarations, relocatedSourceDeclarations, rewriteSplitOwners, withoutRelocatedDeclarations, } from './split-staged.js';
|
|
24
|
+
/**
|
|
25
|
+
* The `reason` a move records on a forward declaration it writes, so the
|
|
26
|
+
* declaration explains itself in `patches.json` and `staged-dependency
|
|
27
|
+
* --add --from` accepts it without a hand-written `--reason`.
|
|
28
|
+
*
|
|
29
|
+
* @param source - Patch the files left
|
|
30
|
+
* @param destination - Patch the files moved into
|
|
31
|
+
* @param operation - The command that moved them
|
|
32
|
+
*/
|
|
33
|
+
export function moveDeclarationReason(source, destination, operation) {
|
|
34
|
+
return `${source} → ${destination} by ${operation}`;
|
|
35
|
+
}
|
|
36
|
+
/** The {@link moveDeclarationReason} of a split plan. */
|
|
37
|
+
export function splitDeclarationReason(plan) {
|
|
38
|
+
return moveDeclarationReason(plan.source.filename, plan.placement.newFilename, plan.operation ?? 'patch split');
|
|
39
|
+
}
|
|
24
40
|
/**
|
|
25
41
|
* Refuses the split unless the source patch currently owns every requested
|
|
26
42
|
* file. Splitting out a file the source does not own would produce a new patch
|
|
@@ -118,7 +134,7 @@ function buildProjectedSplitEntries(baseCtx, plan) {
|
|
|
118
134
|
* them once declared. Auto-declaring keeps the two in lock-step and lets a
|
|
119
135
|
* sound split read as sound.
|
|
120
136
|
*/
|
|
121
|
-
function computeSplitStagedDependencyAdditions(projectedEntries, newFilename) {
|
|
137
|
+
function computeSplitStagedDependencyAdditions(projectedEntries, newFilename, reason) {
|
|
122
138
|
const additions = new Map();
|
|
123
139
|
for (const edge of collectForwardImportEdges({ entries: projectedEntries })) {
|
|
124
140
|
if (edge.owner !== newFilename)
|
|
@@ -128,6 +144,7 @@ function computeSplitStagedDependencyAdditions(projectedEntries, newFilename) {
|
|
|
128
144
|
specifier: edge.specifier,
|
|
129
145
|
creates: edge.creates,
|
|
130
146
|
owner: newFilename,
|
|
147
|
+
reason,
|
|
131
148
|
};
|
|
132
149
|
const list = additions.get(edge.entry) ?? [];
|
|
133
150
|
const dup = list.some((d) => d.file === decl.file &&
|
|
@@ -179,15 +196,14 @@ export function runProjectedSplitLint(plan, baseCtx) {
|
|
|
179
196
|
const projectedEntries = buildProjectedSplitEntries(baseCtx, plan);
|
|
180
197
|
// Discover and auto-declare the forward edges this split introduces into
|
|
181
198
|
// the new patch, then inject them before linting so they resolve.
|
|
182
|
-
const stagedDependencyAdditions = computeSplitStagedDependencyAdditions(projectedEntries, plan.placement.newFilename);
|
|
199
|
+
const stagedDependencyAdditions = computeSplitStagedDependencyAdditions(projectedEntries, plan.placement.newFilename, splitDeclarationReason(plan));
|
|
183
200
|
injectStagedDependencyAdditions(projectedEntries, stagedDependencyAdditions);
|
|
184
201
|
const baselineAll = lintPatchQueue(baseCtx);
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
});
|
|
202
|
+
// Keep every queue-wide lint setting of the baseline (patch policy,
|
|
203
|
+
// forward-registration scope, token scales), so
|
|
204
|
+
// computeProjectedLintRegressions compares symmetric rule sets and a
|
|
205
|
+
// test-manifest suite entry is projected as the committed gate sees it.
|
|
206
|
+
const projectedAll = lintPatchQueue({ ...baseCtx, entries: projectedEntries });
|
|
191
207
|
const baselineIssues = baselineAll.filter((i) => i.severity === 'error');
|
|
192
208
|
const projectedIssues = projectedAll.filter((i) => i.severity === 'error');
|
|
193
209
|
const oldNameByNew = new Map([...plan.placement.renameMap].map(([oldName, entry]) => [entry.newFilename, oldName]));
|
|
@@ -257,6 +273,12 @@ export function buildSplitSummary(plan) {
|
|
|
257
273
|
const summary = [
|
|
258
274
|
`split ${plan.source.filename}`,
|
|
259
275
|
`moved files (${plan.movedFiles.length}): ${plan.movedFiles.join(', ')}`,
|
|
276
|
+
...(plan.adoptedFiles?.length
|
|
277
|
+
? [
|
|
278
|
+
`of which adopted from the worktree (no patch owned them) (${plan.adoptedFiles.length}): ` +
|
|
279
|
+
plan.adoptedFiles.join(', '),
|
|
280
|
+
]
|
|
281
|
+
: []),
|
|
260
282
|
`source keeps (${plan.remainingFiles.length}): ${plan.remainingFiles.join(', ')}`,
|
|
261
283
|
`new patch: ${plan.placement.newFilename} (order ${plan.placement.insertionOrder})`,
|
|
262
284
|
];
|
|
@@ -274,9 +296,11 @@ export function buildSplitSummary(plan) {
|
|
|
274
296
|
}
|
|
275
297
|
/**
|
|
276
298
|
* Maps a declaration the split projection proposes back onto the queue as
|
|
277
|
-
* it stands: renumbered patches take their current names
|
|
278
|
-
*
|
|
279
|
-
*
|
|
299
|
+
* it stands: renumbered patches take their current names. An owner that is
|
|
300
|
+
* the patch the split would create keeps the planned filename, so the
|
|
301
|
+
* report declares the owner the rerun creates (`staged-dependency --add`
|
|
302
|
+
* accepts an owner the queue does not hold yet, and the move then lands
|
|
303
|
+
* with the declaration already right).
|
|
280
304
|
*/
|
|
281
305
|
export function splitDeclarationRenamer(plan) {
|
|
282
306
|
const oldNameByNew = new Map([...plan.placement.renameMap].map(([oldName, entry]) => [entry.newFilename, oldName]));
|
|
@@ -284,6 +308,6 @@ export function splitDeclarationRenamer(plan) {
|
|
|
284
308
|
return ({ owner, ...declaration }) => ({
|
|
285
309
|
...declaration,
|
|
286
310
|
patch: current(declaration.patch),
|
|
287
|
-
|
|
311
|
+
owner: owner === plan.placement.newFilename ? owner : current(owner),
|
|
288
312
|
});
|
|
289
313
|
}
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* the imported file landed in one patch) is dropped
|
|
15
15
|
* ({@link findDeclarationsMadeUnused}).
|
|
16
16
|
*
|
|
17
|
-
* Without this the move
|
|
18
|
-
* per-patch lint to report `queue-dependency-creates-unowned` and
|
|
17
|
+
* Without this the move would write the bodies and leave the queue audit
|
|
18
|
+
* and the per-patch lint to report `queue-dependency-creates-unowned` and
|
|
19
19
|
* `staged-dependency-unused` afterwards, one `--remove`/`--add` pair per
|
|
20
20
|
* entry to repair by hand.
|
|
21
21
|
*/
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* the imported file landed in one patch) is dropped
|
|
16
16
|
* ({@link findDeclarationsMadeUnused}).
|
|
17
17
|
*
|
|
18
|
-
* Without this the move
|
|
19
|
-
* per-patch lint to report `queue-dependency-creates-unowned` and
|
|
18
|
+
* Without this the move would write the bodies and leave the queue audit
|
|
19
|
+
* and the per-patch lint to report `queue-dependency-creates-unowned` and
|
|
20
20
|
* `staged-dependency-unused` afterwards, one `--remove`/`--add` pair per
|
|
21
21
|
* entry to repair by hand.
|
|
22
22
|
*/
|
|
@@ -36,8 +36,10 @@ import { normalizePatchDisplayName, parsePositiveIntegerFlag } from '../../utils
|
|
|
36
36
|
import { withDeclarationsReport } from '../declarations-report.js';
|
|
37
37
|
import { proceedAfterDecision } from '../destructive-decision.js';
|
|
38
38
|
import { placementPlansEqual, resolvePlacementPlan } from '../export-flow.js';
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
39
|
+
import { writeRenameMapFile } from '../patch-rename-map.js';
|
|
40
|
+
import { assertAdoptedStillUnowned } from './move-files-adopt.js';
|
|
41
|
+
import { runMoveSideLint } from './move-size-tier.js';
|
|
42
|
+
import { assertSourceOwnsFiles, buildNewPatchMetadata, buildSplitDiff, buildSplitSummary, mergeStagedForwardImports, projectSplitManifest, runProjectedSplitLint, splitDeclarationReason, splitDeclarationRenamer, } from './split-plan.js';
|
|
41
43
|
import { describeStagedMaintenance, emptyStagedMaintenance, findOwnerRewriteHolders, rewriteSplitOwners, withoutDroppedDeclarations, withoutRelocatedDeclarations, } from './split-staged.js';
|
|
42
44
|
/**
|
|
43
45
|
* History payload for the declarations a split re-homed or dropped, omitted
|
|
@@ -79,6 +81,7 @@ export async function commitPatchSplit(patchesDir, plan, newMetadata, options, c
|
|
|
79
81
|
if (!current || current.filesAffected.join('\n') !== plan.source.filesAffected.join('\n')) {
|
|
80
82
|
throw new InvalidArgumentError('Patch queue changed while waiting for split confirmation. Re-run the command.', 'patch split');
|
|
81
83
|
}
|
|
84
|
+
assertAdoptedStillUnowned(manifest.patches, plan.adoptedFiles, 'patch split');
|
|
82
85
|
const currentPlacement = await resolvePlacementPlan(patchesDir, plan.placementOptions, plan.category, plan.name, config);
|
|
83
86
|
if (!placementPlansEqual(currentPlacement, plan.placement)) {
|
|
84
87
|
throw new InvalidArgumentError('Patch queue changed while waiting for split confirmation. Re-run the command.', 'patch split');
|
|
@@ -266,9 +269,11 @@ export async function patchSplitCommand(projectRoot, sourceId, options) {
|
|
|
266
269
|
// instead of linting each projected body blind.
|
|
267
270
|
const patchQueueCtx = await buildPatchQueueContext(paths.patches, config);
|
|
268
271
|
const ignoreChecks = buildLintIgnoreChecks(source.lintIgnore);
|
|
269
|
-
await
|
|
272
|
+
await runMoveSideLint({
|
|
270
273
|
engineDir: paths.engine,
|
|
271
274
|
filesAffected: remainingFiles,
|
|
275
|
+
filesBefore: source.filesAffected,
|
|
276
|
+
role: 'source',
|
|
272
277
|
diffContent: remainingDiff,
|
|
273
278
|
config,
|
|
274
279
|
skipLint: options.skipLint,
|
|
@@ -276,9 +281,10 @@ export async function patchSplitCommand(projectRoot, sourceId, options) {
|
|
|
276
281
|
ignoreChecks,
|
|
277
282
|
patchTier: source.tier,
|
|
278
283
|
});
|
|
279
|
-
await
|
|
284
|
+
await runMoveSideLint({
|
|
280
285
|
engineDir: paths.engine,
|
|
281
286
|
filesAffected: movedFiles,
|
|
287
|
+
role: 'new patch',
|
|
282
288
|
diffContent: movedDiff,
|
|
283
289
|
config,
|
|
284
290
|
skipLint: options.skipLint,
|
|
@@ -294,6 +300,7 @@ export async function patchSplitCommand(projectRoot, sourceId, options) {
|
|
|
294
300
|
regressions: projected.regressions,
|
|
295
301
|
out: options.declarationsOut,
|
|
296
302
|
rename: splitDeclarationRenamer(plan),
|
|
303
|
+
reason: splitDeclarationReason(plan),
|
|
297
304
|
});
|
|
298
305
|
const newMetadata = buildNewPatchMetadata(plan, config);
|
|
299
306
|
const decision = await confirmDestructive({
|
|
@@ -308,6 +315,10 @@ export async function patchSplitCommand(projectRoot, sourceId, options) {
|
|
|
308
315
|
if (!proceedAfterDecision(decision, 'Split cancelled'))
|
|
309
316
|
return;
|
|
310
317
|
await commitPatchSplit(paths.patches, plan, newMetadata, options, config);
|
|
318
|
+
if (options.mapOut !== undefined) {
|
|
319
|
+
const written = await writeRenameMapFile(options.mapOut, plan.placement.renameMap);
|
|
320
|
+
info(`Wrote the old-to-new filename map (${plan.placement.renameMap.size} rename(s)) to ${written}`);
|
|
321
|
+
}
|
|
311
322
|
success(`Split ${source.filename}: ${placement.newFilename} now owns ${plan.movedFiles.length} file(s)`);
|
|
312
323
|
reportSplitStagedChanges(plan);
|
|
313
324
|
outro('Split complete');
|
|
@@ -331,7 +342,8 @@ export function registerPatchSplit(parent, context) {
|
|
|
331
342
|
.option('-y, --yes', 'Skip confirmation prompt (required for non-TTY)')
|
|
332
343
|
.option('--force-unsafe', 'Bypass projected-lint refusals')
|
|
333
344
|
.option('--skip-lint', 'Skip per-patch lint of the projected bodies')
|
|
334
|
-
.option('--declarations-out <path>', 'Where a refusal writes its one --from-ready staged-declaration report (default: .fireforge/declarations/<timestamp>.json)')
|
|
345
|
+
.option('--declarations-out <path>', 'Where a refusal writes its one --from-ready staged-declaration report (default: .fireforge/declarations/<timestamp>.json)')
|
|
346
|
+
.option('--map-out <file>', 'Write the old-to-new filename map of the patches the split renumbers as JSON');
|
|
335
347
|
addWaitLockOption(command).action(withErrorHandling(async (sourceId, options) => {
|
|
336
348
|
await patchSplitCommand(getProjectRoot(), sourceId, {
|
|
337
349
|
files: options.files,
|
|
@@ -347,6 +359,7 @@ export function registerPatchSplit(parent, context) {
|
|
|
347
359
|
forceUnsafe: options.forceUnsafe,
|
|
348
360
|
skipLint: options.skipLint,
|
|
349
361
|
declarationsOut: options.declarationsOut,
|
|
362
|
+
mapOut: options.mapOut,
|
|
350
363
|
waitLock: options.waitLock,
|
|
351
364
|
}),
|
|
352
365
|
});
|
|
@@ -29,8 +29,16 @@ export declare function registrationView(dependency: PatchStagedRegistration): S
|
|
|
29
29
|
export declare function dependencyLabel(kind: StagedDependencyKind, view: StagedEntryView): string;
|
|
30
30
|
/**
|
|
31
31
|
* Applies one add/remove/clear to a declaration list: `add` appends unless
|
|
32
|
-
* an entry with the same file, matcher, creates and owner exists.
|
|
32
|
+
* an entry with the same file, matcher, creates and owner exists. A
|
|
33
|
+
* matching entry whose `reason` differs from the addition's takes
|
|
34
|
+
* the new reason in place, keeping its position in the list; an addition
|
|
35
|
+
* without a reason leaves the existing one alone.
|
|
33
36
|
*/
|
|
34
37
|
export declare function applyMode<T>(existing: readonly T[], mode: StagedDependencyMode, target: StagedEntryView | undefined, toView: (entry: T) => StagedEntryView, addition: T | undefined): T[];
|
|
38
|
+
/**
|
|
39
|
+
* Number of declarations in `after` whose key matches one in `before` but
|
|
40
|
+
* whose `reason` differs: the in-place reason updates of an `--add`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function countReasonUpdates(before: PatchStagedDependencies | undefined, after: PatchStagedDependencies | undefined): number;
|
|
35
43
|
/** Number of declarations of both kinds. */
|
|
36
44
|
export declare function countStagedEntries(staged: PatchStagedDependencies | undefined): number;
|
|
@@ -32,7 +32,10 @@ function matchesTarget(view, target) {
|
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
34
|
* Applies one add/remove/clear to a declaration list: `add` appends unless
|
|
35
|
-
* an entry with the same file, matcher, creates and owner exists.
|
|
35
|
+
* an entry with the same file, matcher, creates and owner exists. A
|
|
36
|
+
* matching entry whose `reason` differs from the addition's takes
|
|
37
|
+
* the new reason in place, keeping its position in the list; an addition
|
|
38
|
+
* without a reason leaves the existing one alone.
|
|
36
39
|
*/
|
|
37
40
|
export function applyMode(existing, mode, target, toView, addition) {
|
|
38
41
|
if (mode === 'clear')
|
|
@@ -41,10 +44,35 @@ export function applyMode(existing, mode, target, toView, addition) {
|
|
|
41
44
|
return [...existing];
|
|
42
45
|
if (mode === 'remove')
|
|
43
46
|
return existing.filter((entry) => !matchesTarget(toView(entry), target));
|
|
44
|
-
|
|
45
|
-
if (seen.has(dependencyKey(target)) || addition === undefined)
|
|
47
|
+
if (addition === undefined)
|
|
46
48
|
return [...existing];
|
|
47
|
-
|
|
49
|
+
const key = dependencyKey(target);
|
|
50
|
+
const index = existing.findIndex((entry) => dependencyKey(toView(entry)) === key);
|
|
51
|
+
if (index === -1)
|
|
52
|
+
return [...existing, addition];
|
|
53
|
+
const current = existing[index];
|
|
54
|
+
if (target.reason === undefined || toView(current).reason === target.reason) {
|
|
55
|
+
return [...existing];
|
|
56
|
+
}
|
|
57
|
+
const next = [...existing];
|
|
58
|
+
next[index] = addition;
|
|
59
|
+
return next;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Number of declarations in `after` whose key matches one in `before` but
|
|
63
|
+
* whose `reason` differs: the in-place reason updates of an `--add`.
|
|
64
|
+
*/
|
|
65
|
+
export function countReasonUpdates(before, after) {
|
|
66
|
+
const updates = (left, right, toView) => {
|
|
67
|
+
const reasons = new Map((left ?? []).map((entry) => [dependencyKey(toView(entry)), toView(entry).reason]));
|
|
68
|
+
return (right ?? []).filter((entry) => {
|
|
69
|
+
const view = toView(entry);
|
|
70
|
+
const key = dependencyKey(view);
|
|
71
|
+
return reasons.has(key) && reasons.get(key) !== view.reason;
|
|
72
|
+
}).length;
|
|
73
|
+
};
|
|
74
|
+
return (updates(before?.forwardImports, after?.forwardImports, importView) +
|
|
75
|
+
updates(before?.registrations, after?.registrations, registrationView));
|
|
48
76
|
}
|
|
49
77
|
/** Number of declarations of both kinds. */
|
|
50
78
|
export function countStagedEntries(staged) {
|
|
@@ -12,7 +12,7 @@ import { info, outro } from '../../utils/logger.js';
|
|
|
12
12
|
import { resolveWaitLockSeconds } from '../../utils/options.js';
|
|
13
13
|
import { requirePatchQueue, requirePatchTarget } from './patch-context.js';
|
|
14
14
|
import { readStagedDependencyBatch, stagedDependencyDelta, } from './staged-dependency-batch.js';
|
|
15
|
-
import { applyMode, countStagedEntries, importView, registrationView, } from './staged-dependency-entries.js';
|
|
15
|
+
import { applyMode, countReasonUpdates, countStagedEntries, importView, registrationView, } from './staged-dependency-entries.js';
|
|
16
16
|
import { validateStagedDependencyAdd } from './staged-dependency-validate.js';
|
|
17
17
|
/** Flags that describe one declaration and so cannot combine with `--from`. */
|
|
18
18
|
const SINGLE_DECLARATION_FLAGS = [
|
|
@@ -23,7 +23,10 @@ const SINGLE_DECLARATION_FLAGS = [
|
|
|
23
23
|
['--creates', 'creates'],
|
|
24
24
|
['--owner', 'owner'],
|
|
25
25
|
];
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Adds every `--from` declaration not already present, keyed like a single
|
|
28
|
+
* `--add`; a present one whose `reason` differs takes the new reason in place.
|
|
29
|
+
*/
|
|
27
30
|
function mergeBatch(staged, batch) {
|
|
28
31
|
let forwardImports = [...(staged?.forwardImports ?? [])];
|
|
29
32
|
for (const entry of batch.forwardImports) {
|
|
@@ -68,13 +71,16 @@ export async function runBatchAdd(projectRoot, identifier, mode, options) {
|
|
|
68
71
|
return;
|
|
69
72
|
}
|
|
70
73
|
const existing = targetPatch.stagedDependencies;
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
const describe = (before, after) => {
|
|
75
|
+
const added = countStagedEntries(after) - countStagedEntries(before);
|
|
76
|
+
const updated = countReasonUpdates(before, after);
|
|
77
|
+
const unchanged = supplied - added - updated;
|
|
78
|
+
return (`added ${added} of ${supplied} staged declaration(s) from ${from}` +
|
|
79
|
+
(updated > 0 ? `, updated the reason of ${updated}` : '') +
|
|
80
|
+
(unchanged > 0 ? ` (${unchanged} already present or repeated)` : ''));
|
|
81
|
+
};
|
|
76
82
|
if (options.dryRun === true) {
|
|
77
|
-
info(`[dry-run] ${targetPatch.filename}: ${describe(
|
|
83
|
+
info(`[dry-run] ${targetPatch.filename}: ${describe(existing, mergeBatch(existing, batch))}.`);
|
|
78
84
|
outro('Dry run complete — no changes made');
|
|
79
85
|
return;
|
|
80
86
|
}
|
|
@@ -92,7 +98,7 @@ export async function runBatchAdd(projectRoot, identifier, mode, options) {
|
|
|
92
98
|
}
|
|
93
99
|
const before = result.before.stagedDependencies;
|
|
94
100
|
const after = result.after.stagedDependencies;
|
|
95
|
-
info(`${targetPatch.filename}: ${describe(
|
|
101
|
+
info(`${targetPatch.filename}: ${describe(before, after)}.`);
|
|
96
102
|
await appendHistoryBestEffort(paths.patches, {
|
|
97
103
|
operation: 'patch-staged-dependency',
|
|
98
104
|
args: {
|
|
@@ -11,7 +11,7 @@ import { info, intro, outro } from '../../utils/logger.js';
|
|
|
11
11
|
import { addWaitLockOption, resolveWaitLockSeconds } from '../../utils/options.js';
|
|
12
12
|
import { requirePatchQueue, requirePatchTarget } from './patch-context.js';
|
|
13
13
|
import { stagedDependencyDelta } from './staged-dependency-batch.js';
|
|
14
|
-
import { applyMode, countStagedEntries, dependencyLabel, importView, registrationView, } from './staged-dependency-entries.js';
|
|
14
|
+
import { applyMode, countReasonUpdates, countStagedEntries, dependencyLabel, importView, registrationView, } from './staged-dependency-entries.js';
|
|
15
15
|
import { runBatchAdd } from './staged-dependency-from.js';
|
|
16
16
|
import { selectRemovalEntries } from './staged-dependency-remove.js';
|
|
17
17
|
import { validateStagedDependencyAdd } from './staged-dependency-validate.js';
|
|
@@ -89,7 +89,9 @@ function requireRegistrationOptions(options) {
|
|
|
89
89
|
/**
|
|
90
90
|
* Renders a one-line summary of a staged-dependency metadata change.
|
|
91
91
|
*/
|
|
92
|
-
function describeStagedDependencyChange(
|
|
92
|
+
function describeStagedDependencyChange(before, after, mode, kind, targets) {
|
|
93
|
+
const beforeCount = countStagedEntries(before);
|
|
94
|
+
const afterCount = countStagedEntries(after);
|
|
93
95
|
const noun = kind === 'import' ? 'staged forward-import' : 'staged registration';
|
|
94
96
|
if (mode === 'clear') {
|
|
95
97
|
return beforeCount === 0
|
|
@@ -100,9 +102,11 @@ function describeStagedDependencyChange(beforeCount, afterCount, mode, kind, tar
|
|
|
100
102
|
if (targets.length === 0)
|
|
101
103
|
return 'stagedDependencies unchanged';
|
|
102
104
|
if (mode === 'add') {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
if (afterCount !== beforeCount)
|
|
106
|
+
return `added ${noun}: ${label}`;
|
|
107
|
+
return countReasonUpdates(before, after) > 0
|
|
108
|
+
? `updated 1 ${noun} reason in place: ${label}`
|
|
109
|
+
: `${noun} already present: ${label}`;
|
|
106
110
|
}
|
|
107
111
|
return afterCount === beforeCount
|
|
108
112
|
? `no ${noun} matched: ${label}`
|
|
@@ -132,7 +136,7 @@ export async function patchStagedDependencyCommand(projectRoot, identifier, opti
|
|
|
132
136
|
const importDependency = mode === 'add' && kind === 'import' ? requireForwardImportOptions(options) : undefined;
|
|
133
137
|
const registrationDependency = mode === 'add' && kind === 'registration' ? requireRegistrationOptions(options) : undefined;
|
|
134
138
|
// Shape-check --add declarations against the loaded queue: a
|
|
135
|
-
// patch-name-shaped --creates/--file is refused
|
|
139
|
+
// patch-name-shaped --creates/--file is refused here instead of surfacing
|
|
136
140
|
// later as an undischargeable staged-dependency-unused.
|
|
137
141
|
const added = importDependency ?? registrationDependency;
|
|
138
142
|
if (added !== undefined) {
|
|
@@ -164,7 +168,7 @@ export async function patchStagedDependencyCommand(projectRoot, identifier, opti
|
|
|
164
168
|
};
|
|
165
169
|
const existing = targetPatch.stagedDependencies;
|
|
166
170
|
const projected = applyToStaged(existing);
|
|
167
|
-
const summary = describeStagedDependencyChange(
|
|
171
|
+
const summary = describeStagedDependencyChange(existing, projected, mode, kind, targets);
|
|
168
172
|
if (isDryRun) {
|
|
169
173
|
info(`[dry-run] ${targetPatch.filename}: ${summary}.`);
|
|
170
174
|
outro('Dry run complete — no changes made');
|
|
@@ -184,7 +188,7 @@ export async function patchStagedDependencyCommand(projectRoot, identifier, opti
|
|
|
184
188
|
}
|
|
185
189
|
const before = result.before.stagedDependencies;
|
|
186
190
|
const after = result.after.stagedDependencies;
|
|
187
|
-
info(`${targetPatch.filename}: ${describeStagedDependencyChange(
|
|
191
|
+
info(`${targetPatch.filename}: ${describeStagedDependencyChange(before, after, mode, kind, targets)}.`);
|
|
188
192
|
if (mode === 'remove' && countStagedEntries(after) === countStagedEntries(before)) {
|
|
189
193
|
// The selection matched when the queue was read; a peer removed the
|
|
190
194
|
// entries before the locked write. Still a miss, so still a refusal.
|
|
@@ -225,7 +229,7 @@ export function registerPatchStagedDependency(parent, context) {
|
|
|
225
229
|
.option('--line <text>', 'Registration/packaging line as the patch adds it, compared whitespace-insensitively (--kind registration)')
|
|
226
230
|
.option('--creates <path>', 'Later-created file path relative to engine/ (with --remove and no --file: removes every declaration for that path)')
|
|
227
231
|
.option('--owner <patch>', 'Exact later patch filename expected to create --creates')
|
|
228
|
-
.option('--reason <text>', 'Human-readable rationale stored with the declaration (with --from: the default for entries without one)')
|
|
232
|
+
.option('--reason <text>', 'Human-readable rationale stored with the declaration; --add on a declaration already present replaces its reason in place (with --from: the default for entries without one)')
|
|
229
233
|
.option('--from <json-file>', 'Add every declaration in a JSON file under one lock hold (--add only): a "lint --per-patch --report" ' +
|
|
230
234
|
"document (its findings' stagedDeclarations for this patch) or an array of " +
|
|
231
235
|
'{ file, specifier | line, creates, owner?, reason?, patch? } entries')
|
|
@@ -63,8 +63,8 @@ export async function patchTierCommand(projectRoot, identifier, options = {}) {
|
|
|
63
63
|
: `change tier from "${before}" to "${after}"`;
|
|
64
64
|
// Destructive-operation contract (docs/lifecycle-invariants.md): manifest
|
|
65
65
|
// metadata mutations get summary + dry-run + confirmation/--yes + history
|
|
66
|
-
// uniformly
|
|
67
|
-
// the
|
|
66
|
+
// uniformly, so --yes skips a real prompt rather than only appearing in
|
|
67
|
+
// the history record.
|
|
68
68
|
const decision = await confirmDestructive({
|
|
69
69
|
operation: 'patch-tier',
|
|
70
70
|
title: `Patch tier: ${target.filename}`,
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Structural equality for patch rename maps, shared by the placement-mode
|
|
3
3
|
* export preview/commit check (`export-flow.ts`) and `patch reorder`'s
|
|
4
|
-
* under-lock drift check.
|
|
5
|
-
*
|
|
6
|
-
* drifting.
|
|
4
|
+
* under-lock drift check. One helper for the sorted-entries comparison
|
|
5
|
+
* keeps the two commit gates from drifting.
|
|
7
6
|
*/
|
|
8
7
|
import type { PatchRenameEntry } from '../core/patch-manifest.js';
|
|
9
8
|
/**
|
|
@@ -17,3 +16,15 @@ export declare function getSortedRenameEntries(renameMap: Map<string, PatchRenam
|
|
|
17
16
|
* triple matches positionally.
|
|
18
17
|
*/
|
|
19
18
|
export declare function renameMapsEqual(left: Map<string, PatchRenameEntry>, right: Map<string, PatchRenameEntry>): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Writes a committed rename map as one JSON object, old filename to new
|
|
21
|
+
* filename in the queue's new order (`{}` when nothing was renumbered), so
|
|
22
|
+
* a session can rewrite its references to the renumbered patches from the
|
|
23
|
+
* file instead of from the printed summary. Used by `patch reorder
|
|
24
|
+
* --map-out` and `patch split --map-out`.
|
|
25
|
+
*
|
|
26
|
+
* @param path - Output path, relative to the working directory
|
|
27
|
+
* @param renameMap - The committed rename map
|
|
28
|
+
* @returns The absolute path written
|
|
29
|
+
*/
|
|
30
|
+
export declare function writeRenameMapFile(path: string, renameMap: Map<string, PatchRenameEntry>): Promise<string>;
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Structural equality for patch rename maps, shared by the placement-mode
|
|
4
4
|
* export preview/commit check (`export-flow.ts`) and `patch reorder`'s
|
|
5
|
-
* under-lock drift check.
|
|
6
|
-
*
|
|
7
|
-
* drifting.
|
|
5
|
+
* under-lock drift check. One helper for the sorted-entries comparison
|
|
6
|
+
* keeps the two commit gates from drifting.
|
|
8
7
|
*/
|
|
8
|
+
import { dirname, resolve } from 'node:path';
|
|
9
|
+
import { ensureDir, writeJson } from '../utils/fs.js';
|
|
9
10
|
/**
|
|
10
11
|
* Rename-map entries ordered by target order, so previews, history entries
|
|
11
12
|
* and equality checks all walk the queue in its final on-disk order.
|
|
@@ -34,3 +35,20 @@ export function renameMapsEqual(left, right) {
|
|
|
34
35
|
leftEntry.newOrder === rightEntry.newOrder);
|
|
35
36
|
});
|
|
36
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Writes a committed rename map as one JSON object, old filename to new
|
|
40
|
+
* filename in the queue's new order (`{}` when nothing was renumbered), so
|
|
41
|
+
* a session can rewrite its references to the renumbered patches from the
|
|
42
|
+
* file instead of from the printed summary. Used by `patch reorder
|
|
43
|
+
* --map-out` and `patch split --map-out`.
|
|
44
|
+
*
|
|
45
|
+
* @param path - Output path, relative to the working directory
|
|
46
|
+
* @param renameMap - The committed rename map
|
|
47
|
+
* @returns The absolute path written
|
|
48
|
+
*/
|
|
49
|
+
export async function writeRenameMapFile(path, renameMap) {
|
|
50
|
+
const absolute = resolve(path);
|
|
51
|
+
await ensureDir(dirname(absolute));
|
|
52
|
+
await writeJson(absolute, Object.fromEntries(getSortedRenameEntries(renameMap).map(([from, entry]) => [from, entry.newFilename])));
|
|
53
|
+
return absolute;
|
|
54
|
+
}
|
|
@@ -62,11 +62,10 @@ export declare function findMissingFiles(engineDir: string, files: readonly stri
|
|
|
62
62
|
/**
|
|
63
63
|
* One unmanaged file and the owned directory that made it adjacent.
|
|
64
64
|
*
|
|
65
|
-
* The anchor
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* adjacency by hand from the manifest.
|
|
65
|
+
* The anchor names which of the patch's owned locations the file sits
|
|
66
|
+
* beside, so an unattended run can triage a refusal on a patch that owns
|
|
67
|
+
* files in several directories without re-deriving the adjacency by hand
|
|
68
|
+
* from the manifest.
|
|
70
69
|
*/
|
|
71
70
|
export interface AdjacentUnmanagedFile {
|
|
72
71
|
/** Engine-relative path of the unmanaged file. */
|
|
@@ -108,9 +108,10 @@ export declare function storeReExportLintResult(lintCtx: ReExportLintContext, pa
|
|
|
108
108
|
* when the projection introduces error-severity cross-patch findings the
|
|
109
109
|
* current queue does not have (a forward import an earlier owner gained,
|
|
110
110
|
* say). Mirrors `re-export --files` (its projected-state conflict) and the
|
|
111
|
-
* `--scan` adoption gate, which only
|
|
112
|
-
* re-export of an unchanged file set with new CONTENT
|
|
113
|
-
* queue
|
|
111
|
+
* `--scan` adoption gate, which only covers files a scan adopted: without
|
|
112
|
+
* this a plain re-export of an unchanged file set with new CONTENT would
|
|
113
|
+
* pass, and the queue would carry the error until the next `export`
|
|
114
|
+
* placement refused it.
|
|
114
115
|
*
|
|
115
116
|
* Runs in dry-run. `--force-unsafe` downgrades the refusal to warnings;
|
|
116
117
|
* `--skip-lint` does not bypass it, same as `--files`. The baseline is the
|
|
@@ -221,9 +221,10 @@ export async function storeReExportLintResult(lintCtx, patchFilename, projectedM
|
|
|
221
221
|
* when the projection introduces error-severity cross-patch findings the
|
|
222
222
|
* current queue does not have (a forward import an earlier owner gained,
|
|
223
223
|
* say). Mirrors `re-export --files` (its projected-state conflict) and the
|
|
224
|
-
* `--scan` adoption gate, which only
|
|
225
|
-
* re-export of an unchanged file set with new CONTENT
|
|
226
|
-
* queue
|
|
224
|
+
* `--scan` adoption gate, which only covers files a scan adopted: without
|
|
225
|
+
* this a plain re-export of an unchanged file set with new CONTENT would
|
|
226
|
+
* pass, and the queue would carry the error until the next `export`
|
|
227
|
+
* placement refused it.
|
|
227
228
|
*
|
|
228
229
|
* Runs in dry-run. `--force-unsafe` downgrades the refusal to warnings;
|
|
229
230
|
* `--skip-lint` does not bypass it, same as `--files`. The baseline is the
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
* Under contention the lock wait runs to many minutes, and an argument the
|
|
7
7
|
* run was always going to refuse (an empty patch name from an unquoted
|
|
8
8
|
* `"$@"`, a changed-file path passed as a positional, a path flag that
|
|
9
|
-
* swallowed the next flag)
|
|
10
|
-
* checks read the manifest and a path-scoped `git status` without the lock.
|
|
9
|
+
* swallowed the next flag) is reported before that wait: these checks read the manifest and a path-scoped `git status` without the lock.
|
|
11
10
|
* The command repeats its own resolution under the lock, so a manifest that
|
|
12
11
|
* changes in between is still read fresh there.
|
|
13
12
|
*/
|
|
@@ -13,6 +13,6 @@ export declare function fingerprintScopedDryRunState(engineDir: string, patchesD
|
|
|
13
13
|
/**
|
|
14
14
|
* Names whoever else wrote during the dry run: history records from other
|
|
15
15
|
* invocations since it started, and a live holder of the engine-session
|
|
16
|
-
* lock (which a dry run
|
|
16
|
+
* lock (which a dry run does not take). Empty when there is no evidence.
|
|
17
17
|
*/
|
|
18
18
|
export declare function describeForeignWriters(patchesDir: string, sinceMs: number): Promise<string[]>;
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* The scoped half of the `re-export --dry-run` purity guard.
|
|
4
4
|
*
|
|
5
|
-
* A dry run takes no engine-session lock
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* immediate repeat was clean. The scoped fingerprint covers only what the
|
|
5
|
+
* A dry run takes no engine-session lock, so peers keep writing while it
|
|
6
|
+
* runs. A whole-tree fingerprint would report every peer's write as
|
|
7
|
+
* "invariant violated … This is a FireForge bug". The scoped fingerprint covers only what the
|
|
9
8
|
* dry run could touch: the selected patches' bodies and manifest rows, the
|
|
10
9
|
* engine files they own, the engine HEAD and the engine index. A mismatch
|
|
11
10
|
* that remains is attributed to a foreign writer when the history log or
|
|
@@ -17,6 +16,7 @@ import { readEngineSessionLockStatus } from '../core/engine-session-lock.js';
|
|
|
17
16
|
import { git } from '../core/git-base.js';
|
|
18
17
|
import { historyRecordIdentity, resolveHistoryLogPath } from '../core/history-log-file.js';
|
|
19
18
|
import { PATCHES_MANIFEST } from '../core/patch-manifest-io.js';
|
|
19
|
+
import { canonicalizePatchRow } from '../core/patch-manifest-key-order.js';
|
|
20
20
|
import { GeneralError } from '../errors/base.js';
|
|
21
21
|
import { getNodeErrorCode, toError } from '../utils/errors.js';
|
|
22
22
|
import { sha256Hex } from '../utils/hash.js';
|
|
@@ -33,7 +33,11 @@ async function hashOrAbsent(path, label) {
|
|
|
33
33
|
throw new GeneralError(`[dry-run] cannot fingerprint ${label} for the purity guard: ${toError(error).message}`, error);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* The selected rows of `patches.json`, so a peer's write to another row is
|
|
38
|
+
* not a violation. Rows are hashed in canonical key order: a peer's write
|
|
39
|
+
* that only canonicalizes a selected row's key order changes no content.
|
|
40
|
+
*/
|
|
37
41
|
async function selectedRowsFingerprint(patchesDir, filenames) {
|
|
38
42
|
let raw;
|
|
39
43
|
try {
|
|
@@ -47,7 +51,9 @@ async function selectedRowsFingerprint(patchesDir, filenames) {
|
|
|
47
51
|
try {
|
|
48
52
|
const parsed = JSON.parse(raw);
|
|
49
53
|
const rows = isObject(parsed) && isArray(parsed['patches']) ? parsed['patches'] : [];
|
|
50
|
-
const selected = rows
|
|
54
|
+
const selected = rows
|
|
55
|
+
.filter((row) => isObject(row) && typeof row['filename'] === 'string' && filenames.has(row['filename']))
|
|
56
|
+
.map((row) => (isObject(row) ? canonicalizePatchRow(row) : row));
|
|
51
57
|
return sha256Hex(JSON.stringify(selected));
|
|
52
58
|
}
|
|
53
59
|
catch {
|
|
@@ -112,7 +118,7 @@ async function foreignHistoryRecords(projectRoot, sinceMs) {
|
|
|
112
118
|
/**
|
|
113
119
|
* Names whoever else wrote during the dry run: history records from other
|
|
114
120
|
* invocations since it started, and a live holder of the engine-session
|
|
115
|
-
* lock (which a dry run
|
|
121
|
+
* lock (which a dry run does not take). Empty when there is no evidence.
|
|
116
122
|
*/
|
|
117
123
|
export async function describeForeignWriters(patchesDir, sinceMs) {
|
|
118
124
|
const projectRoot = dirname(resolve(patchesDir));
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PatchesManifest, PatchMetadata, ReExportOptions } from '../types/commands/index.js';
|
|
2
|
+
import type { ProjectPaths } from '../types/config.js';
|
|
3
|
+
/** What {@link dropRetiredPathDeclarations} needs from the run. */
|
|
4
|
+
export interface RetiredPathDeclarationsArgs {
|
|
5
|
+
/** Resolved project paths. */
|
|
6
|
+
paths: ProjectPaths;
|
|
7
|
+
/** The queue as the run loaded it (judged in a dry run). */
|
|
8
|
+
manifest: PatchesManifest;
|
|
9
|
+
/** The run's options; `expectRemoved` holds the validated paths. */
|
|
10
|
+
options: ReExportOptions;
|
|
11
|
+
/** The run's selected patches. */
|
|
12
|
+
selectedPatches: readonly PatchMetadata[];
|
|
13
|
+
/** Name the drops without writing. */
|
|
14
|
+
isDryRun: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Drops (or, in a dry run, names) every staged declaration whose `creates`
|
|
18
|
+
* path this run retired. Runs after the selected patches are written, so a
|
|
19
|
+
* path whose owner failed to re-export is still owned and its declarations
|
|
20
|
+
* stay.
|
|
21
|
+
*/
|
|
22
|
+
export declare function dropRetiredPathDeclarations(args: RetiredPathDeclarationsArgs): Promise<void>;
|