@hominis/fireforge 0.42.0 → 0.43.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 +75 -34
- package/dist/build-info.json +4 -4
- package/dist/src/cli.js +21 -2
- package/dist/src/commands/build.js +7 -1
- package/dist/src/commands/config.js +16 -27
- package/dist/src/commands/discard.js +1 -1
- package/dist/src/commands/export-flow.js +13 -3
- package/dist/src/commands/export-placement-conflicts.js +1 -1
- package/dist/src/commands/export-shared.d.ts +1 -1
- package/dist/src/commands/export-shared.js +4 -5
- package/dist/src/commands/furnace/chrome-doc-remove.js +3 -0
- package/dist/src/commands/furnace/chrome-doc.js +3 -0
- package/dist/src/commands/furnace/create.js +3 -0
- package/dist/src/commands/furnace/override.js +3 -0
- package/dist/src/commands/furnace/preview.js +3 -0
- package/dist/src/commands/furnace/refresh.js +3 -0
- package/dist/src/commands/furnace/remove.js +3 -0
- package/dist/src/commands/furnace/rename.js +3 -0
- package/dist/src/commands/furnace/scan.js +3 -0
- package/dist/src/commands/import.js +1 -1
- package/dist/src/commands/lint-per-patch.js +16 -8
- package/dist/src/commands/lint-per-run-checkjs.d.ts +4 -4
- package/dist/src/commands/lint-per-run-checkjs.js +4 -4
- package/dist/src/commands/lint-report.js +1 -1
- package/dist/src/commands/lint.js +2 -2
- package/dist/src/commands/patch/lint-ignore.js +1 -1
- package/dist/src/commands/patch/move-files-create.js +5 -3
- package/dist/src/commands/patch/move-files-into.d.ts +1 -1
- package/dist/src/commands/patch/move-files-into.js +5 -3
- package/dist/src/commands/patch/move-files.js +1 -1
- package/dist/src/commands/patch/rename.js +1 -1
- package/dist/src/commands/patch/reorder.d.ts +1 -2
- package/dist/src/commands/patch/reorder.js +1 -2
- package/dist/src/commands/patch/split.js +5 -3
- package/dist/src/commands/patch/staged-dependency-validate.js +1 -1
- package/dist/src/commands/patch/staged-dependency.js +4 -4
- package/dist/src/commands/re-export-adjacent.js +1 -1
- package/dist/src/commands/re-export-bulk-scan.d.ts +1 -1
- package/dist/src/commands/re-export-bulk-scan.js +5 -4
- package/dist/src/commands/re-export-drift.d.ts +4 -4
- package/dist/src/commands/re-export-drift.js +47 -8
- package/dist/src/commands/re-export-lint.d.ts +7 -5
- package/dist/src/commands/re-export-lint.js +9 -8
- package/dist/src/commands/re-export-single.d.ts +1 -1
- package/dist/src/commands/re-export-single.js +5 -4
- package/dist/src/commands/re-export.js +26 -14
- package/dist/src/commands/reset.js +1 -1
- package/dist/src/commands/run.js +1 -1
- package/dist/src/commands/source.js +6 -6
- package/dist/src/commands/status-check.d.ts +1 -1
- package/dist/src/commands/status-check.js +1 -1
- package/dist/src/commands/status-json.d.ts +2 -2
- package/dist/src/commands/status-json.js +1 -1
- package/dist/src/commands/status-output.d.ts +1 -1
- package/dist/src/commands/status-output.js +1 -1
- package/dist/src/commands/status.js +21 -7
- package/dist/src/commands/test-build-phase.d.ts +2 -2
- package/dist/src/commands/test-build-phase.js +9 -6
- package/dist/src/commands/test-diagnose.js +8 -1
- package/dist/src/commands/test-modes.d.ts +2 -2
- package/dist/src/commands/test-modes.js +3 -3
- package/dist/src/commands/test-register.js +7 -2
- package/dist/src/commands/test-run.d.ts +6 -0
- package/dist/src/commands/test-run.js +32 -3
- package/dist/src/commands/test-stale-gate.js +7 -2
- package/dist/src/commands/test.js +4 -4
- package/dist/src/commands/tree.js +2 -2
- package/dist/src/commands/typecheck.d.ts +2 -1
- package/dist/src/commands/typecheck.js +59 -3
- package/dist/src/commands/verify.js +26 -1
- package/dist/src/core/ast-utils.js +14 -2
- package/dist/src/core/browser-wire.js +9 -1
- package/dist/src/core/build-baseline-types.d.ts +1 -1
- package/dist/src/core/build-baseline.d.ts +1 -1
- package/dist/src/core/build-baseline.js +1 -1
- package/dist/src/core/build-overwrite-guard.d.ts +49 -0
- package/dist/src/core/build-overwrite-guard.js +112 -0
- package/dist/src/core/build-prepare.d.ts +13 -5
- package/dist/src/core/build-prepare.js +42 -9
- package/dist/src/core/config-mutate.d.ts +3 -2
- package/dist/src/core/config-mutate.js +5 -2
- package/dist/src/core/config.d.ts +3 -2
- package/dist/src/core/config.js +2 -0
- package/dist/src/core/coverage-extend.d.ts +1 -1
- package/dist/src/core/coverage-extend.js +1 -1
- package/dist/src/core/destructive.d.ts +23 -0
- package/dist/src/core/destructive.js +45 -4
- package/dist/src/core/diff-hunks.js +31 -17
- package/dist/src/core/discard-baseline.d.ts +1 -1
- package/dist/src/core/discard-baseline.js +1 -1
- package/dist/src/core/display-state.d.ts +52 -0
- package/dist/src/core/display-state.js +89 -0
- package/dist/src/core/engine-session-lock.d.ts +20 -0
- package/dist/src/core/engine-session-lock.js +84 -14
- package/dist/src/core/export-moved-code-shape.d.ts +60 -0
- package/dist/src/core/export-moved-code-shape.js +129 -0
- package/dist/src/core/file-lock.d.ts +44 -0
- package/dist/src/core/file-lock.js +157 -42
- package/dist/src/core/furnace-apply-helpers.d.ts +1 -1
- package/dist/src/core/furnace-apply-helpers.js +1 -1
- package/dist/src/core/furnace-apply-output.js +1 -1
- package/dist/src/core/furnace-apply-overwrite-warn.d.ts +2 -2
- package/dist/src/core/furnace-apply-overwrite-warn.js +2 -2
- package/dist/src/core/furnace-apply-undeploy-actions.d.ts +1 -1
- package/dist/src/core/furnace-apply-undeploy-actions.js +1 -1
- package/dist/src/core/furnace-apply.js +13 -1
- package/dist/src/core/furnace-config-custom.d.ts +2 -1
- package/dist/src/core/furnace-config-order.d.ts +2 -1
- package/dist/src/core/furnace-config-order.js +9 -4
- package/dist/src/core/furnace-config-validate.d.ts +4 -3
- package/dist/src/core/furnace-config-validate.js +3 -3
- package/dist/src/core/furnace-config.js +5 -0
- package/dist/src/core/furnace-operation.d.ts +12 -0
- package/dist/src/core/furnace-operation.js +125 -1
- package/dist/src/core/furnace-rollback.d.ts +17 -0
- package/dist/src/core/furnace-rollback.js +35 -1
- package/dist/src/core/git-base.js +7 -2
- package/dist/src/core/git-diff.js +11 -2
- package/dist/src/core/git-readonly-index.d.ts +59 -0
- package/dist/src/core/git-readonly-index.js +137 -0
- package/dist/src/core/license-headers.d.ts +1 -1
- package/dist/src/core/license-headers.js +1 -1
- package/dist/src/core/lint-cache.d.ts +15 -5
- package/dist/src/core/lint-cache.js +35 -8
- package/dist/src/core/mach-known-noise-filter.d.ts +21 -0
- package/dist/src/core/mach-known-noise-filter.js +23 -0
- package/dist/src/core/mach-mozconfig.js +12 -2
- package/dist/src/core/module-resolution-preflight.d.ts +51 -0
- package/dist/src/core/module-resolution-preflight.js +155 -0
- package/dist/src/core/patch-export.js +2 -1
- package/dist/src/core/patch-identifier-suggest.js +29 -3
- package/dist/src/core/patch-lint-checkjs.d.ts +5 -5
- package/dist/src/core/patch-lint-checkjs.js +6 -6
- package/dist/src/core/patch-lint-creators.d.ts +1 -1
- package/dist/src/core/patch-lint-creators.js +1 -1
- package/dist/src/core/patch-lint-cross.d.ts +1 -1
- package/dist/src/core/patch-lint-cross.js +4 -4
- package/dist/src/core/patch-lint-css.js +1 -1
- package/dist/src/core/patch-lint-diff.d.ts +1 -1
- package/dist/src/core/patch-lint-diff.js +1 -1
- package/dist/src/core/patch-lint-jsdoc.js +4 -2
- package/dist/src/core/patch-lint-module-registration.d.ts +13 -0
- package/dist/src/core/patch-lint-module-registration.js +13 -4
- package/dist/src/core/patch-lint-mozbuild.d.ts +1 -1
- package/dist/src/core/patch-lint-mozbuild.js +1 -1
- package/dist/src/core/patch-lint-ownership.d.ts +2 -2
- package/dist/src/core/patch-lint-ownership.js +2 -2
- package/dist/src/core/patch-lint.d.ts +2 -2
- package/dist/src/core/patch-lint.js +3 -3
- package/dist/src/core/patch-lock.d.ts +21 -0
- package/dist/src/core/patch-lock.js +29 -2
- package/dist/src/core/patch-manifest-io.d.ts +2 -1
- package/dist/src/core/patch-manifest-io.js +49 -0
- package/dist/src/core/patch-parse.d.ts +18 -0
- package/dist/src/core/patch-parse.js +24 -1
- package/dist/src/core/patch-policy.d.ts +2 -2
- package/dist/src/core/patch-policy.js +2 -2
- package/dist/src/core/smoke-patterns.d.ts +1 -1
- package/dist/src/core/status-binary.d.ts +1 -1
- package/dist/src/core/status-binary.js +1 -1
- package/dist/src/core/status-classify.d.ts +1 -1
- package/dist/src/core/status-classify.js +2 -2
- package/dist/src/core/test-harness-crash.d.ts +69 -3
- package/dist/src/core/test-harness-crash.js +174 -9
- package/dist/src/core/test-stale-check.d.ts +1 -1
- package/dist/src/core/test-stale-check.js +1 -1
- package/dist/src/core/token-category.js +1 -1
- package/dist/src/core/token-manager.d.ts +1 -1
- package/dist/src/core/token-manager.js +5 -5
- package/dist/src/core/tree-cow.js +1 -1
- package/dist/src/core/tree-guard.js +2 -2
- package/dist/src/core/tree-store.js +1 -1
- package/dist/src/core/typecheck-shim.d.ts +2 -2
- package/dist/src/core/typecheck-shim.js +2 -2
- package/dist/src/core/typecheck.js +10 -0
- package/dist/src/core/xpcshell-profile-dir.js +1 -1
- package/dist/src/errors/base.d.ts +22 -1
- package/dist/src/errors/base.js +32 -1
- package/dist/src/errors/codes.d.ts +10 -0
- package/dist/src/errors/codes.js +10 -0
- package/dist/src/types/commands/options.d.ts +27 -11
- package/dist/src/types/commands/patches.d.ts +1 -1
- package/dist/src/types/config.d.ts +2 -2
- package/dist/src/types/furnace.d.ts +1 -1
- package/dist/src/types/json.d.ts +22 -0
- package/dist/src/types/json.js +11 -0
- package/dist/src/utils/assert.d.ts +46 -0
- package/dist/src/utils/assert.js +83 -0
- package/dist/src/utils/build-info.d.ts +1 -1
- package/dist/src/utils/build-info.js +1 -1
- package/dist/src/utils/logger.d.ts +20 -0
- package/dist/src/utils/logger.js +26 -0
- package/dist/src/utils/options.d.ts +12 -0
- package/dist/src/utils/options.js +39 -0
- package/dist/src/utils/stdio-drain.d.ts +1 -1
- package/dist/src/utils/stdio-drain.js +1 -1
- package/dist/src/utils/validation.d.ts +12 -1
- package/dist/src/utils/validation.js +13 -1
- package/package.json +3 -3
|
@@ -87,6 +87,9 @@ async function performChromeDocRemoveMutations(args) {
|
|
|
87
87
|
await removeEmptyDirIfPresent(join(args.engineDir, 'browser/base/content/test', `${args.binaryName}-xpcshell`), journal);
|
|
88
88
|
}
|
|
89
89
|
catch (error) {
|
|
90
|
+
// This body owns its rollback end to end, so tell the lifecycle wrapper
|
|
91
|
+
// not to restore the same journal again on the way out.
|
|
92
|
+
args.operationContext.markRolledBack();
|
|
90
93
|
try {
|
|
91
94
|
await restoreRollbackJournalOrThrow(journal, `Failed to remove chrome-doc "${args.name}"`);
|
|
92
95
|
}
|
|
@@ -274,6 +274,9 @@ async function performChromeDocMutations(args) {
|
|
|
274
274
|
}
|
|
275
275
|
}
|
|
276
276
|
catch (error) {
|
|
277
|
+
// This body owns its rollback end to end, so tell the lifecycle wrapper
|
|
278
|
+
// not to restore the same journal again on the way out.
|
|
279
|
+
args.operationContext.markRolledBack();
|
|
277
280
|
try {
|
|
278
281
|
await restoreRollbackJournalOrThrow(journal, `Failed to scaffold chrome-doc "${args.name}"`);
|
|
279
282
|
}
|
|
@@ -199,6 +199,9 @@ async function performCreateMutations(args) {
|
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
catch (error) {
|
|
202
|
+
// This body owns its rollback end to end, so tell the lifecycle wrapper
|
|
203
|
+
// not to restore the same journal again on the way out.
|
|
204
|
+
args.operationContext?.markRolledBack();
|
|
202
205
|
try {
|
|
203
206
|
await restoreRollbackJournalOrThrow(journal, `Failed to create custom component "${args.componentName}"`);
|
|
204
207
|
}
|
|
@@ -137,6 +137,9 @@ async function performOverrideMutations(args) {
|
|
|
137
137
|
return filesCopied;
|
|
138
138
|
}
|
|
139
139
|
catch (error) {
|
|
140
|
+
// This body owns its rollback end to end, so tell the lifecycle
|
|
141
|
+
// wrapper not to restore the same journal again on the way out.
|
|
142
|
+
ctx.markRolledBack();
|
|
140
143
|
try {
|
|
141
144
|
await restoreRollbackJournalOrThrow(journal, `Failed to override component "${args.componentName}"`);
|
|
142
145
|
}
|
|
@@ -325,6 +325,9 @@ export async function furnacePreviewCommand(projectRoot, options = {}) {
|
|
|
325
325
|
// message with no guidance. We now collect both failures and, if anything
|
|
326
326
|
// went wrong, persist a `pendingRepair` marker that `fireforge doctor`
|
|
327
327
|
// consumes to finish the reconciliation.
|
|
328
|
+
// Teardown restores the journal itself, so the lifecycle wrapper must not
|
|
329
|
+
// restore it again when the throw below propagates out of this body.
|
|
330
|
+
ctx.markRolledBack();
|
|
328
331
|
const teardownErrors = await runPreviewTeardown(paths.engine, storiesCleanupRequired, previewJournal);
|
|
329
332
|
if (teardownErrors.length > 0) {
|
|
330
333
|
const teardownSummary = teardownErrors.map((err) => err.message).join('; ');
|
|
@@ -145,6 +145,9 @@ async function refreshSingleOverride(projectRoot, name, options = {}) {
|
|
|
145
145
|
}
|
|
146
146
|
catch (error) {
|
|
147
147
|
if (!dryRun) {
|
|
148
|
+
// This body owns its rollback end to end, so tell the lifecycle
|
|
149
|
+
// wrapper not to restore the same journal again on the way out.
|
|
150
|
+
ctx.markRolledBack();
|
|
148
151
|
try {
|
|
149
152
|
await restoreRollbackJournalOrThrow(journal, `Failed to refresh override "${name}"`);
|
|
150
153
|
}
|
|
@@ -405,6 +405,9 @@ export async function furnaceRemoveCommand(projectRoot, name, options = {}) {
|
|
|
405
405
|
}
|
|
406
406
|
}
|
|
407
407
|
catch (error) {
|
|
408
|
+
// This body owns its rollback end to end, so tell the lifecycle wrapper
|
|
409
|
+
// not to restore the same journal again on the way out.
|
|
410
|
+
ctx.markRolledBack();
|
|
408
411
|
try {
|
|
409
412
|
await restoreRollbackJournalOrThrow(journal, `Failed to remove component "${name}"`);
|
|
410
413
|
}
|
|
@@ -308,6 +308,9 @@ async function performRenameMutations(args) {
|
|
|
308
308
|
info(`Renamed ${componentType} component: ${oldName} → ${newName}`);
|
|
309
309
|
}
|
|
310
310
|
catch (error) {
|
|
311
|
+
// This body owns its rollback end to end, so tell the lifecycle wrapper
|
|
312
|
+
// not to restore the same journal again on the way out.
|
|
313
|
+
ctx.markRolledBack();
|
|
311
314
|
try {
|
|
312
315
|
if (await pathExists(newDir)) {
|
|
313
316
|
await removeDir(newDir);
|
|
@@ -92,6 +92,9 @@ async function persistStockComponents(projectRoot, names) {
|
|
|
92
92
|
await writeFurnaceConfig(projectRoot, config);
|
|
93
93
|
}
|
|
94
94
|
catch (error) {
|
|
95
|
+
// This body owns its rollback end to end, so tell the lifecycle wrapper
|
|
96
|
+
// not to restore the same journal again on the way out.
|
|
97
|
+
ctx.markRolledBack();
|
|
95
98
|
try {
|
|
96
99
|
await restoreRollbackJournalOrThrow(journal, 'Failed to update furnace.json during scan');
|
|
97
100
|
}
|
|
@@ -408,7 +408,7 @@ export async function importCommand(projectRoot, options = {}) {
|
|
|
408
408
|
}
|
|
409
409
|
// The re-applied queue may have moved components.conf away from what
|
|
410
410
|
// the last full build compiled in — surface that now instead of at the
|
|
411
|
-
// next test refusal
|
|
411
|
+
// next test refusal.
|
|
412
412
|
await warnIfStaticComponentsStale(projectRoot, paths.engine);
|
|
413
413
|
outro('All patches applied successfully!');
|
|
414
414
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { getProjectPaths, loadConfig } from '../core/config.js';
|
|
4
4
|
import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
|
|
5
|
+
import { withPrivateGitIndex } from '../core/git-readonly-index.js';
|
|
5
6
|
import { buildPerPatchLintCacheKey, getCachedPerPatchLintIssues, getPerPatchLintCacheHeadSha, loadPerPatchLintCache, savePerPatchLintCache, setCachedPerPatchLintIssues, } from '../core/lint-cache.js';
|
|
6
7
|
import { buildPatchQueueContext, countNonBinaryDiffLines, formatPatchLintIssue, lintExportedPatch, lintPatchQueue, resolvePatchSizeTier, } from '../core/patch-lint.js';
|
|
7
8
|
import { loadPatchesManifest } from '../core/patch-manifest.js';
|
|
@@ -59,13 +60,13 @@ async function lintQueuedPatch(patch, lintCtx) {
|
|
|
59
60
|
queueContext: ctx,
|
|
60
61
|
...(engineHeadSha === undefined ? {} : { engineHeadSha }),
|
|
61
62
|
});
|
|
62
|
-
const cached = getCachedPerPatchLintIssues(cache, patch.filename, cacheKey);
|
|
63
|
+
const cached = getCachedPerPatchLintIssues(cache, patch.filename, cacheKey, ignore);
|
|
63
64
|
if (cached) {
|
|
64
65
|
// Returning before the empty-diff probe below is safe: the cache key
|
|
65
66
|
// hashes every affected engine file's content plus engineHeadSha, so
|
|
66
67
|
// an engine-side revert that would empty the diff always changes the
|
|
67
68
|
// key and misses the cache (pinned by the "engine-side content
|
|
68
|
-
// revert invalidates the per-patch cache" test
|
|
69
|
+
// revert invalidates the per-patch cache" test).
|
|
69
70
|
return {
|
|
70
71
|
status: 'cached',
|
|
71
72
|
existingFiles: existing,
|
|
@@ -115,11 +116,11 @@ async function lintQueuedPatch(patch, lintCtx) {
|
|
|
115
116
|
usedCheckJs,
|
|
116
117
|
};
|
|
117
118
|
if (cache && cacheKey) {
|
|
118
|
-
result.cacheWrite = { key: cacheKey };
|
|
119
|
+
result.cacheWrite = { key: cacheKey, lintIgnore: [...(ignore ?? [])] };
|
|
119
120
|
}
|
|
120
121
|
return result;
|
|
121
122
|
}
|
|
122
|
-
/** Size-rule check IDs whose waived measurement is still reported
|
|
123
|
+
/** Size-rule check IDs whose waived measurement is still reported. */
|
|
123
124
|
const SUPPRESSED_SIZE_CHECKS = new Set([
|
|
124
125
|
'large-patch-lines',
|
|
125
126
|
'large-patch-files',
|
|
@@ -156,20 +157,20 @@ async function applyPerPatchResults(subset, results, issues, checkJs, cache) {
|
|
|
156
157
|
// Deliberately NOT gated on `result.usedCheckJs`: global findings are
|
|
157
158
|
// run-level, never cached, and an all-cache-hit run previously dropped
|
|
158
159
|
// them entirely, letting a warm run report fewer errors than a cold
|
|
159
|
-
// one (
|
|
160
|
+
// one (hardening). PerRunCheckJs builds its program lazily,
|
|
160
161
|
// so the cost only materialises when checkJs is configured.
|
|
161
162
|
if (checkJs && !globalCheckJsEmitted) {
|
|
162
163
|
globalCheckJsEmitted = true;
|
|
163
164
|
issues.push(...(await checkJs.getGlobal()));
|
|
164
165
|
}
|
|
165
166
|
if (result.cacheWrite && cache) {
|
|
166
|
-
setCachedPerPatchLintIssues(cache, patch.filename, result.cacheWrite.key, result.rawIssues, result.suppressedIssues, result.lineCount);
|
|
167
|
+
setCachedPerPatchLintIssues(cache, patch.filename, result.cacheWrite.key, result.rawIssues, result.suppressedIssues, result.lineCount, result.cacheWrite.lintIgnore);
|
|
167
168
|
totals.cacheDirty = true;
|
|
168
169
|
}
|
|
169
170
|
for (const issue of result.rawIssues) {
|
|
170
171
|
issues.push({ ...issue, file: `${patch.filename} :: ${issue.file}` });
|
|
171
172
|
}
|
|
172
|
-
// A waived size finding still reports its MEASUREMENT
|
|
173
|
+
// A waived size finding still reports its MEASUREMENT:
|
|
173
174
|
// the finding stays suppressed (no exit-code / --max-warnings effect)
|
|
174
175
|
// but the current count is readable from the tool that enforces it,
|
|
175
176
|
// so a waiver's cited size can be calibrated without hand-measuring.
|
|
@@ -310,6 +311,13 @@ function selectPatchSubset(manifest, requested) {
|
|
|
310
311
|
* queue-level findings are scoped to files those patches touch).
|
|
311
312
|
*/
|
|
312
313
|
export async function lintPerPatch(projectRoot, paths, options = {}) {
|
|
314
|
+
// Read-only to the operator, an index WRITER to git: the per-patch diffs
|
|
315
|
+
// run `git diff HEAD` (and, for untracked binaries, a real stage/unstage
|
|
316
|
+
// pair) against the primary checkout. A private index absorbs all of it
|
|
317
|
+
// so a concurrent `fireforge test` still gets an evidential verdict.
|
|
318
|
+
return withPrivateGitIndex(paths.engine, () => lintPerPatchInner(projectRoot, paths, options));
|
|
319
|
+
}
|
|
320
|
+
async function lintPerPatchInner(projectRoot, paths, options = {}) {
|
|
313
321
|
const manifest = await loadPatchesManifest(paths.patches);
|
|
314
322
|
if (!manifest || manifest.patches.length === 0) {
|
|
315
323
|
info('No patches in manifest — nothing to lint per-patch.');
|
|
@@ -352,7 +360,7 @@ export async function lintPerPatch(projectRoot, paths, options = {}) {
|
|
|
352
360
|
}
|
|
353
361
|
// With `--patches`, the checkJs program roots at only the subset's owned
|
|
354
362
|
// files — the full queue stays resolvable, so a cold subset run costs the
|
|
355
|
-
// subset's import closure instead of the whole queue
|
|
363
|
+
// subset's import closure instead of the whole queue.
|
|
356
364
|
const checkJs = buildPerRunCheckJs(projectRoot, paths, config, ctx, isSubset ? subsetNames : undefined);
|
|
357
365
|
// Lint patches concurrently, then apply every side effect in patch order so
|
|
358
366
|
// the issue rows, the run-level checkJs errors, and the saved cache are
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Per-run checkJs program controller, shared by `lint --per-patch` and
|
|
3
|
-
* `re-export
|
|
3
|
+
* `re-export`.
|
|
4
4
|
*
|
|
5
5
|
* The dominant fixed cost of patch lint is one `ts.createProgram` over the
|
|
6
6
|
* queue's patch-owned `.sys.mjs` files (~37 s on a 290-patch consumer
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
* - **Root scoping** (`rootScopePatches`): with `--patches <subset>` the
|
|
14
14
|
* program's roots are only the subset's owned files; the full queue
|
|
15
15
|
* stays resolvable, so cross-patch imports type-check identically while
|
|
16
|
-
*
|
|
16
|
+
* unrelated files are never parsed.
|
|
17
17
|
* - **Warm-run probe**: run-level ("global") checkJs findings come only
|
|
18
18
|
* from a missing `typescript` package or an unreadable shim — never
|
|
19
19
|
* from the built program — so an all-cache-hit run satisfies "warm
|
|
20
|
-
*
|
|
20
|
+
* never reports less than cold" via
|
|
21
21
|
* `probeCheckJsGlobalIssues` instead of building anything.
|
|
22
22
|
*/
|
|
23
23
|
import type { getProjectPaths, loadConfig } from '../core/config.js';
|
|
@@ -43,7 +43,7 @@ export interface PerRunCheckJs {
|
|
|
43
43
|
getGrouped: () => Promise<GroupedCheckJsResult>;
|
|
44
44
|
/** Run-level checkJs errors (e.g. TypeScript missing). When the program
|
|
45
45
|
* was never built (all-warm run) this probes the two global failure
|
|
46
|
-
*
|
|
46
|
+
* sources directly instead of building it holds because the
|
|
47
47
|
* built program never contributes globals of its own. */
|
|
48
48
|
getGlobal: () => Promise<PatchLintIssue[]>;
|
|
49
49
|
/** The byFile slices for `files` (globals deliberately EXCLUDED — they
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
3
|
* Per-run checkJs program controller, shared by `lint --per-patch` and
|
|
4
|
-
* `re-export
|
|
4
|
+
* `re-export`.
|
|
5
5
|
*
|
|
6
6
|
* The dominant fixed cost of patch lint is one `ts.createProgram` over the
|
|
7
7
|
* queue's patch-owned `.sys.mjs` files (~37 s on a 290-patch consumer
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
* - **Root scoping** (`rootScopePatches`): with `--patches <subset>` the
|
|
15
15
|
* program's roots are only the subset's owned files; the full queue
|
|
16
16
|
* stays resolvable, so cross-patch imports type-check identically while
|
|
17
|
-
*
|
|
17
|
+
* unrelated files are never parsed.
|
|
18
18
|
* - **Warm-run probe**: run-level ("global") checkJs findings come only
|
|
19
19
|
* from a missing `typescript` package or an unreadable shim — never
|
|
20
20
|
* from the built program — so an all-cache-hit run satisfies "warm
|
|
21
|
-
*
|
|
21
|
+
* never reports less than cold" via
|
|
22
22
|
* `probeCheckJsGlobalIssues` instead of building anything.
|
|
23
23
|
*/
|
|
24
24
|
import { invokePatchLintCheckJsGrouped, probeCheckJsGlobalIssues, runCheckJsTestFilesGrouped, } from '../core/patch-lint-checkjs.js';
|
|
@@ -64,7 +64,7 @@ export function buildPerRunCheckJs(projectRoot, paths, config, ctx, rootScopePat
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
// One build for the whole run: the queue-wide `.sys.mjs` program plus —
|
|
67
|
-
// when `patchLint.checkJsTestFiles` is on
|
|
67
|
+
// when `patchLint.checkJsTestFiles` is on — one small
|
|
68
68
|
// script-scope program per patch-owned test file, merged by file.
|
|
69
69
|
const buildAll = async () => {
|
|
70
70
|
const sys = await invokePatchLintCheckJsGrouped(paths.engine, resolvePatchOwnedSysMjs(new Set(), ctx), patchLint, projectRoot, scopedRoots);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
3
|
* Machine-readable per-patch lint report (`lint --per-patch --report
|
|
4
|
-
* <path>`,
|
|
4
|
+
* <path>`, + G10). One mechanism serves both observability
|
|
5
5
|
* gaps: consumers get each patch's size metrics against the SAME
|
|
6
6
|
* thresholds the size rules fire on (no more mirroring
|
|
7
7
|
* `countNonBinaryDiffLines`), and lintIgnore-suppressed issues stay
|
|
@@ -220,7 +220,7 @@ export function applyAggregateLintIgnoreSuppression(issues, ctx) {
|
|
|
220
220
|
* without `--since` and non-integer `--max-warnings` — each a
|
|
221
221
|
* misconfiguration that should fail loud rather than silently narrow the
|
|
222
222
|
* result. Positional arguments in per-patch mode are patch selectors
|
|
223
|
-
*
|
|
223
|
+
* and are validated downstream by `selectPatchSubset`.
|
|
224
224
|
*/
|
|
225
225
|
function validateLintFlags(options) {
|
|
226
226
|
// `--only-introduced` scopes the exit code to `--since`-tagged issues, so
|
|
@@ -434,7 +434,7 @@ export async function lintCommand(projectRoot, files, options = {}) {
|
|
|
434
434
|
}
|
|
435
435
|
if (options.perPatch) {
|
|
436
436
|
// Positional arguments in per-patch mode select PATCHES through the
|
|
437
|
-
// same alias resolution as --patches
|
|
437
|
+
// same alias resolution as --patches — a non-matching
|
|
438
438
|
// positional fails loud against the queue listing, so an engine path
|
|
439
439
|
// can never silently lint. Explicit --patches entries come first.
|
|
440
440
|
const perPatchOptions = files.length > 0 ? { ...options, patches: [...(options.patches ?? []), ...files] } : options;
|
|
@@ -32,7 +32,7 @@ import { requirePatchQueue, requirePatchTarget } from './patch-context.js';
|
|
|
32
32
|
* writes turns per-patch lint green immediately, but consumer projects
|
|
33
33
|
* commonly audit lintIgnore lists against a reviewed allow-map — the
|
|
34
34
|
* discovery that the map also needs updating otherwise costs a full
|
|
35
|
-
* downstream gate run
|
|
35
|
+
* downstream gate run.
|
|
36
36
|
*/
|
|
37
37
|
const LINT_IGNORE_REVIEW_WARNING = "This lint waiver is subject to the project's patch-policy review process. " +
|
|
38
38
|
'Downstream policy audits that mirror lintIgnore lists in a reviewed allow-map ' +
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* new patch's name/slug the way `split --name` does.
|
|
12
12
|
*/
|
|
13
13
|
import { getProjectPaths, loadConfig } from '../../core/config.js';
|
|
14
|
-
import { confirmDestructive } from '../../core/destructive.js';
|
|
14
|
+
import { assertConfirmationAvailable, confirmDestructive } from '../../core/destructive.js';
|
|
15
15
|
import { formatPatchNotFoundError } from '../../core/patch-identifier-suggest.js';
|
|
16
16
|
import { buildPatchQueueContext } from '../../core/patch-lint.js';
|
|
17
17
|
import { loadPatchesManifest, resolvePatchIdentifier } from '../../core/patch-manifest.js';
|
|
@@ -31,6 +31,8 @@ export async function patchMoveFilesCreateCommand(projectRoot, fromIdentifier, n
|
|
|
31
31
|
intro(options.dryRun === true
|
|
32
32
|
? 'FireForge patch move-files --create (dry run)'
|
|
33
33
|
: 'FireForge patch move-files --create');
|
|
34
|
+
// Refuse a prompt-less run BEFORE the diff/lint work, not after it.
|
|
35
|
+
assertConfirmationAvailable('patch move-files --create', options);
|
|
34
36
|
const paths = getProjectPaths(projectRoot);
|
|
35
37
|
const config = await loadConfig(projectRoot);
|
|
36
38
|
const manifest = await loadPatchesManifest(paths.patches);
|
|
@@ -64,7 +66,7 @@ export async function patchMoveFilesCreateCommand(projectRoot, fromIdentifier, n
|
|
|
64
66
|
const placement = await resolvePlacementPlan(paths.patches, placementOptions, category, newPatchName, config);
|
|
65
67
|
// The filename slug pipeline (resolvePlacementPlan above) strips redundant
|
|
66
68
|
// category prefixes; the manifest display name must agree with the bare-slug
|
|
67
|
-
// naming policy, exactly as `export --name` already does
|
|
69
|
+
// naming policy, exactly as `export --name` already does.
|
|
68
70
|
const displayName = normalizePatchDisplayName(newPatchName, category);
|
|
69
71
|
if (displayName !== newPatchName) {
|
|
70
72
|
info(`Patch name normalized: "${newPatchName}" → "${displayName}" (the filename carries the order and category prefix).`);
|
|
@@ -90,7 +92,7 @@ export async function patchMoveFilesCreateCommand(projectRoot, fromIdentifier, n
|
|
|
90
92
|
// same patch-policy shape as the committed `lint --per-patch` gate) makes
|
|
91
93
|
// cross-patch `resource:///` imports and sibling head.js harness roots
|
|
92
94
|
// resolve exactly as they will after the move lands — without it the
|
|
93
|
-
// projection lint was blind
|
|
95
|
+
// projection lint was blind.
|
|
94
96
|
const patchQueueCtx = await buildPatchQueueContext(paths.patches, config);
|
|
95
97
|
const ignoreChecks = source.lintIgnore ? new Set(source.lintIgnore) : undefined;
|
|
96
98
|
await runPatchLint(paths.engine, remainingFiles, remainingDiff, config, options.skipLint, patchQueueCtx, ignoreChecks, source.tier);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Transactional file-ownership move into an EXISTING patch
|
|
2
|
+
* Transactional file-ownership move into an EXISTING patch.
|
|
3
3
|
*
|
|
4
4
|
* Before 0.39.0, `patch move-files <from> <to>` with an existing target was
|
|
5
5
|
* preview-only: it printed two `re-export --files` commands whose real
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
|
-
* Transactional file-ownership move into an EXISTING patch
|
|
3
|
+
* Transactional file-ownership move into an EXISTING patch.
|
|
4
4
|
*
|
|
5
5
|
* Before 0.39.0, `patch move-files <from> <to>` with an existing target was
|
|
6
6
|
* preview-only: it printed two `re-export --files` commands whose real
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* the worktree.
|
|
16
16
|
*/
|
|
17
17
|
import { join } from 'node:path';
|
|
18
|
-
import { appendHistory, confirmDestructive } from '../../core/destructive.js';
|
|
18
|
+
import { appendHistory, assertConfirmationAvailable, confirmDestructive, } from '../../core/destructive.js';
|
|
19
19
|
import { computeProjectedLintRegressions } from '../../core/lint-projection.js';
|
|
20
20
|
import { normalizePatchArtifact } from '../../core/patch-artifact-normalize.js';
|
|
21
21
|
import { buildModifiedFileAdditionsFromDiff, buildPatchQueueContext, collectForwardImportEdges, detectNewFilesInDiff, formatPatchLintIssue, lintPatchQueue, } from '../../core/patch-lint.js';
|
|
@@ -193,12 +193,14 @@ async function commitMoveInto(patchesDir, plan, waitLockSeconds) {
|
|
|
193
193
|
*/
|
|
194
194
|
export async function runMoveFilesInto(args) {
|
|
195
195
|
const { enginePath, patchesDir, source, target, files, sourceAfter, targetAfter, options } = args;
|
|
196
|
+
// Refuse a prompt-less run BEFORE the diff/lint work, not after it.
|
|
197
|
+
assertConfirmationAvailable('patch move-files', options);
|
|
196
198
|
const sourceDiff = await buildSplitDiff(enginePath, sourceAfter, 'remaining', source.filename);
|
|
197
199
|
const targetDiff = await buildSplitDiff(enginePath, targetAfter, 'moved', target.filename);
|
|
198
200
|
// Per-patch lint both projected bodies with each patch's own waivers.
|
|
199
201
|
// The whole-queue context (built once, with the config for committed-gate
|
|
200
202
|
// parity) resolves cross-patch imports and sibling head.js harness roots
|
|
201
|
-
// instead of linting each projected body blind
|
|
203
|
+
// instead of linting each projected body blind.
|
|
202
204
|
const patchQueueCtx = await buildPatchQueueContext(patchesDir, args.config);
|
|
203
205
|
await runPatchLint(enginePath, sourceAfter, sourceDiff, args.config, options.skipLint, patchQueueCtx, source.lintIgnore ? new Set(source.lintIgnore) : undefined, source.tier);
|
|
204
206
|
await runPatchLint(enginePath, targetAfter, targetDiff, args.config, options.skipLint, patchQueueCtx, target.lintIgnore ? new Set(target.lintIgnore) : undefined, target.tier);
|
|
@@ -133,7 +133,7 @@ export async function patchMoveFilesCommand(projectRoot, fromIdentifier, toIdent
|
|
|
133
133
|
// content right now): the equivalent re-export choreography, in
|
|
134
134
|
// EXECUTABLE order — the shrink must land before the grow, or the
|
|
135
135
|
// projected cross-patch lint refuses the pair with
|
|
136
|
-
// duplicate-new-file-creation
|
|
136
|
+
// duplicate-new-file-creation.
|
|
137
137
|
const applySource = formatReExportCommand(source.filename, sourceAfter, [
|
|
138
138
|
'--allow-shrink',
|
|
139
139
|
'--yes',
|
|
@@ -208,7 +208,7 @@ function resolveRenamePlan(target, options, config) {
|
|
|
208
208
|
* Pre-flight refusals: order collision (with a pointer to the verb that
|
|
209
209
|
* renumbers siblings — `--order` means "this exact unused sparse slot",
|
|
210
210
|
* mirroring `export --order`), projected cross-patch lint on an order
|
|
211
|
-
* change (forward imports resolve by queue position
|
|
211
|
+
* change (forward imports resolve by queue position), and the
|
|
212
212
|
* filename collision. The authoritative collision checks run again inside
|
|
213
213
|
* the lock to close the TOCTOU window.
|
|
214
214
|
*/
|
|
@@ -17,8 +17,7 @@ export declare function rebuildFilenameForOrder(existing: PatchMetadata, newOrde
|
|
|
17
17
|
/**
|
|
18
18
|
* Applies a rename map to a {@link PatchQueueContext} so cross-patch lint
|
|
19
19
|
* can run against the projected state without touching disk. Exported for
|
|
20
|
-
* `patch rename --order`, which projects the same single-row move shape
|
|
21
|
-
* (FORGE J10).
|
|
20
|
+
* `patch rename --order`, which projects the same single-row move shape.
|
|
22
21
|
*/
|
|
23
22
|
export declare function projectReorder(base: PatchQueueContext, renameMap: Map<string, PatchRenameEntry>): PatchQueueContext;
|
|
24
23
|
/**
|
|
@@ -120,8 +120,7 @@ function renameMapsEqual(left, right) {
|
|
|
120
120
|
/**
|
|
121
121
|
* Applies a rename map to a {@link PatchQueueContext} so cross-patch lint
|
|
122
122
|
* can run against the projected state without touching disk. Exported for
|
|
123
|
-
* `patch rename --order`, which projects the same single-row move shape
|
|
124
|
-
* (FORGE J10).
|
|
123
|
+
* `patch rename --order`, which projects the same single-row move shape.
|
|
125
124
|
*/
|
|
126
125
|
export function projectReorder(base, renameMap) {
|
|
127
126
|
const ownerLookup = (oldFilename) => renameMap.get(oldFilename)?.newFilename;
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import { join } from 'node:path';
|
|
21
21
|
import { Command } from 'commander';
|
|
22
22
|
import { getProjectPaths, loadConfig } from '../../core/config.js';
|
|
23
|
-
import { appendHistory, confirmDestructive } from '../../core/destructive.js';
|
|
23
|
+
import { appendHistory, assertConfirmationAvailable, confirmDestructive, } from '../../core/destructive.js';
|
|
24
24
|
import { normalizePatchArtifact } from '../../core/patch-artifact-normalize.js';
|
|
25
25
|
import { formatPatchNotFoundError } from '../../core/patch-identifier-suggest.js';
|
|
26
26
|
import { buildPatchQueueContext } from '../../core/patch-lint.js';
|
|
@@ -166,6 +166,8 @@ export async function commitPatchSplit(patchesDir, plan, newMetadata, options, c
|
|
|
166
166
|
*/
|
|
167
167
|
export async function patchSplitCommand(projectRoot, sourceId, options) {
|
|
168
168
|
intro(options.dryRun ? 'FireForge patch split (dry run)' : 'FireForge patch split');
|
|
169
|
+
// Refuse a prompt-less run BEFORE the diff/lint work, not after it.
|
|
170
|
+
assertConfirmationAvailable('patch split', options);
|
|
169
171
|
const paths = getProjectPaths(projectRoot);
|
|
170
172
|
const config = await loadConfig(projectRoot);
|
|
171
173
|
const manifest = await loadPatchesManifest(paths.patches);
|
|
@@ -199,7 +201,7 @@ export async function patchSplitCommand(projectRoot, sourceId, options) {
|
|
|
199
201
|
const placement = await resolvePlacementPlan(paths.patches, placementOptions, category, options.name, config);
|
|
200
202
|
// The filename slug pipeline strips redundant category prefixes; the
|
|
201
203
|
// manifest display name must agree with the bare-slug naming policy,
|
|
202
|
-
// exactly as `export --name` already does
|
|
204
|
+
// exactly as `export --name` already does.
|
|
203
205
|
const displayName = normalizePatchDisplayName(options.name, category);
|
|
204
206
|
if (displayName !== options.name) {
|
|
205
207
|
info(`Patch name normalized: "${options.name}" → "${displayName}" (the filename carries the order and category prefix).`);
|
|
@@ -223,7 +225,7 @@ export async function patchSplitCommand(projectRoot, sourceId, options) {
|
|
|
223
225
|
// tier/lintIgnore so an intentional-advisory patch can still split.
|
|
224
226
|
// The whole-queue context (built once, with the config for committed-gate
|
|
225
227
|
// parity) resolves cross-patch imports and sibling head.js harness roots
|
|
226
|
-
// instead of linting each projected body blind
|
|
228
|
+
// instead of linting each projected body blind.
|
|
227
229
|
const patchQueueCtx = await buildPatchQueueContext(paths.patches, config);
|
|
228
230
|
const ignoreChecks = source.lintIgnore ? new Set(source.lintIgnore) : undefined;
|
|
229
231
|
await runPatchLint(paths.engine, remainingFiles, remainingDiff, config, options.skipLint, patchQueueCtx, ignoreChecks, source.tier);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
|
-
* Shape validation for `patch staged-dependency --add
|
|
3
|
+
* Shape validation for `patch staged-dependency --add`.
|
|
4
4
|
*
|
|
5
5
|
* `--creates`/`--file` take engine-relative FILE paths and `--owner`
|
|
6
6
|
* takes a patch FILENAME, but all three were accepted as arbitrary
|
|
@@ -11,7 +11,7 @@ import { toError } from '../../utils/errors.js';
|
|
|
11
11
|
import { info, intro, outro, warn } from '../../utils/logger.js';
|
|
12
12
|
import { requirePatchQueue, requirePatchTarget } from './patch-context.js';
|
|
13
13
|
import { validateStagedDependencyAdd } from './staged-dependency-validate.js';
|
|
14
|
-
/** Joins the names of the missing flags for error attribution
|
|
14
|
+
/** Joins the names of the missing flags for error attribution. */
|
|
15
15
|
function missingFlagList(pairs) {
|
|
16
16
|
return pairs
|
|
17
17
|
.filter(([, value]) => value === undefined || value === '')
|
|
@@ -83,7 +83,7 @@ function requireRegistrationOptions(options) {
|
|
|
83
83
|
return dependency;
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
|
-
* Resolves the `--remove` target for forward-imports
|
|
86
|
+
* Resolves the `--remove` target for forward-imports:
|
|
87
87
|
* `--file` + `--specifier` suffice when they identify at most one staged
|
|
88
88
|
* entry — `--creates` is inferred from a unique match, an ambiguous match
|
|
89
89
|
* refuses with the candidate list, and no match falls through to the
|
|
@@ -219,7 +219,7 @@ export async function patchStagedDependencyCommand(projectRoot, identifier, opti
|
|
|
219
219
|
const kind = kindFromOptions(options);
|
|
220
220
|
// The patch queue loads BEFORE the declaration fields resolve so
|
|
221
221
|
// `--remove` can infer `--creates` from the target patch's staged
|
|
222
|
-
// entries
|
|
222
|
+
// entries.
|
|
223
223
|
const { paths, manifest } = await requirePatchQueue(projectRoot);
|
|
224
224
|
const targetPatch = requirePatchTarget(identifier, manifest.patches);
|
|
225
225
|
const importDependency = mode === 'clear' || kind !== 'import'
|
|
@@ -232,7 +232,7 @@ export async function patchStagedDependencyCommand(projectRoot, identifier, opti
|
|
|
232
232
|
: mode === 'add'
|
|
233
233
|
? requireRegistrationOptions(options)
|
|
234
234
|
: resolveRegistrationRemovalTarget(options, targetPatch.stagedDependencies);
|
|
235
|
-
// Shape-check --add declarations against the loaded queue
|
|
235
|
+
// Shape-check --add declarations against the loaded queue:
|
|
236
236
|
// a patch-name-shaped --creates/--file is refused HERE instead of
|
|
237
237
|
// surfacing later as an undischargeable staged-dependency-unused.
|
|
238
238
|
if (mode === 'add') {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
|
-
* Scan-less re-export adjacency advisory
|
|
3
|
+
* Scan-less re-export adjacency advisory. A plain `re-export`
|
|
4
4
|
* keeps the manifest's filesAffected unchanged, so a brand-new file an
|
|
5
5
|
* author just created beside a patch's owned files is silently left out
|
|
6
6
|
* of the refreshed body. This module detects such adjacent files, filters
|
|
@@ -2,7 +2,7 @@ import type { PatchesManifest } from '../types/commands/index.js';
|
|
|
2
2
|
/** Loads and validates a `re-export --scan-files` assignment manifest. */
|
|
3
3
|
export declare function loadScanFilesAssignments(manifestPath: string, manifest: PatchesManifest): Promise<Map<string, string[]>>;
|
|
4
4
|
/**
|
|
5
|
-
* Runtime enforcement of the dry-run purity contract
|
|
5
|
+
* Runtime enforcement of the dry-run purity contract: fingerprints
|
|
6
6
|
* the engine tree and every patch artifact before and after `operation`, and
|
|
7
7
|
* throws when a dry-run changed anything it promised only to inspect. This
|
|
8
8
|
* turns any recurrence of the 0.40.0 "dry-run reverted a just-written export"
|
|
@@ -4,6 +4,7 @@ import { readdir, readFile } from 'node:fs/promises';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { isUnavailableGenerationToken, snapshotEngineGeneration, unavailableGenerationReason, } from '../core/engine-session-lock.js';
|
|
6
6
|
import { createSiblingLockPath, withFileLock } from '../core/file-lock.js';
|
|
7
|
+
import { formatPatchNotFoundError } from '../core/patch-identifier-suggest.js';
|
|
7
8
|
import { resolvePatchIdentifier } from '../core/patch-manifest.js';
|
|
8
9
|
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
9
10
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
@@ -55,8 +56,8 @@ export async function loadScanFilesAssignments(manifestPath, manifest) {
|
|
|
55
56
|
for (const assignment of parsed.assignments) {
|
|
56
57
|
const patch = resolvePatchIdentifier(assignment.patch, manifest.patches);
|
|
57
58
|
if (!patch) {
|
|
58
|
-
|
|
59
|
-
throw new InvalidArgumentError(`--scan-files
|
|
59
|
+
// Suggest, never dump.
|
|
60
|
+
throw new InvalidArgumentError(`--scan-files: ${formatPatchNotFoundError(assignment.patch, manifest.patches)}`, '--scan-files');
|
|
60
61
|
}
|
|
61
62
|
const patchFiles = filesByPatch.get(patch.filename) ?? new Set();
|
|
62
63
|
filesByPatch.set(patch.filename, patchFiles);
|
|
@@ -77,7 +78,7 @@ export async function loadScanFilesAssignments(manifestPath, manifest) {
|
|
|
77
78
|
* disturb: the engine's commit + working-tree status (via the same generation
|
|
78
79
|
* token `fireforge test` uses) and the byte content of every regular file
|
|
79
80
|
* under `patches/` — ALL of them, not just the selected patches, because the
|
|
80
|
-
*
|
|
81
|
+
* field incident was a dry-run of patch B reverting a just-written
|
|
81
82
|
* export of patch A. A missing patches directory (ENOENT/ENOTDIR) fingerprints
|
|
82
83
|
* as empty — there is nothing there to protect — but any other listing failure
|
|
83
84
|
* (EACCES, EIO, …) throws: an unreadable directory is not evidence it is
|
|
@@ -152,7 +153,7 @@ async function fingerprintDryRunState(engineDir, patchesDir) {
|
|
|
152
153
|
return fingerprint;
|
|
153
154
|
}
|
|
154
155
|
/**
|
|
155
|
-
* Runtime enforcement of the dry-run purity contract
|
|
156
|
+
* Runtime enforcement of the dry-run purity contract: fingerprints
|
|
156
157
|
* the engine tree and every patch artifact before and after `operation`, and
|
|
157
158
|
* throws when a dry-run changed anything it promised only to inspect. This
|
|
158
159
|
* turns any recurrence of the 0.40.0 "dry-run reverted a just-written export"
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Foreign-drift preview + `--refuse-foreign-drift` gate for `re-export
|
|
3
|
-
* (FORGE J2).
|
|
2
|
+
* Foreign-drift preview + `--refuse-foreign-drift` gate for `re-export`.
|
|
4
3
|
*
|
|
5
4
|
* A scan-less re-export rebuilds each patch body as a whole-file
|
|
6
5
|
* `git diff HEAD` over the owned files — so a CONCURRENT session's
|
|
@@ -23,11 +22,11 @@
|
|
|
23
22
|
import type { PatchMetadata } from '../types/commands/index.js';
|
|
24
23
|
/** Why a patch was refused under `--refuse-foreign-drift`. */
|
|
25
24
|
export type ForeignDriftRefusalReason = 'foreign-drift' | 'baseline-unreadable';
|
|
26
|
-
/** Per-run context threaded through the re-export loop (mirrors
|
|
25
|
+
/** Per-run context threaded through the re-export loop (mirrors). */
|
|
27
26
|
export interface ForeignDriftContext {
|
|
28
27
|
refuseForeignDrift: boolean;
|
|
29
28
|
/**
|
|
30
|
-
* Engine-relative files whose drift is expected (`--expect
|
|
29
|
+
* Engine-relative files whose drift is expected (`--expect`):
|
|
31
30
|
* drift confined to these files does not refuse. Files here that never
|
|
32
31
|
* drift are reported after the run as likely typos.
|
|
33
32
|
*/
|
|
@@ -69,6 +68,7 @@ export declare function computeForeignDrift(oldBody: string, newBody: string, pr
|
|
|
69
68
|
export declare function reportForeignDrift(args: {
|
|
70
69
|
patch: PatchMetadata;
|
|
71
70
|
patchesDir: string;
|
|
71
|
+
engineDir: string;
|
|
72
72
|
newDiffContent: string;
|
|
73
73
|
ctx: ForeignDriftContext;
|
|
74
74
|
}): Promise<boolean>;
|