feat(FN-4108): persist scope override fields (+4 more)

Commits merged:
- test(FN-4073): finalize invariant regression coverage
- feat(FN-4073): complete Step 4 — document merge invariant
- feat(FN-4073): complete Step 3 — enforce squash file-scope invariant
- feat(FN-4073): complete Step 2 — add file-scope invariant helper
- feat(FN-4073): complete Step 1 — persist scope override fields

Files changed:
.changeset/FN-4073-file-scope-invariant.md         |   9 +
 AGENTS.md                                          |   6 +
 packages/core/src/__tests__/store-update.test.ts   |  18 ++
 packages/core/src/db.ts                            |   2 +
 packages/core/src/store.ts                         |  34 +-
 packages/core/src/types.ts                         |   8 +
 .../__tests__/merger-file-scope-invariant.test.ts  | 357 +++++++++++++++++++++
 .../engine/src/__tests__/merger-test-helpers.ts    |   5 +
 packages/engine/src/merger.ts                      | 155 ++++++++-
 9 files changed, 582 insertions(+), 12 deletions(-)

Fusion-Task-Id: FN-4108
This commit is contained in:
Fusion
2026-05-12 21:49:28 -07:00
committed by gsxdsm
parent 3f5a2fcbf3
commit 173bae9d2c
9 changed files with 582 additions and 12 deletions

View File

@@ -223,6 +223,12 @@ After any squash that auto-resolved conflicts, the merger now runs the post-squa
Before those auto-resolved squash commits are written, the merger also runs a per-file diff-volume gate: it compares each file's staged squash delta against the branch's net delta vs its merge-base, and blocks the merge in `in-review` when a non-allowlisted file loses too much branch volume. This is the pre-commit guard against FN-3936-style silent drops where fallback resolution kept a branch's commit message but discarded the branch's main file edits.
### File-Scope invariant on squash merges
Every squash commit path now enforces a file-scope invariant immediately before writing the commit: the staged file set must overlap the task's declared `## File Scope` from `PROMPT.md`. The invariant runs on the standard squash path, the Attempt 3 `-X ours/theirs` fallback, and the verification-fix rebuild/finalize path. When the staged files have zero overlap with a non-empty declared scope, the merger throws a structured `FileScopeViolationError`, logs the declared scope + staged files to the merger agent log, resets the pre-squash state, and leaves the task in `in-review` for inspection instead of landing the commit.
Tasks can opt out per task via `task.scopeOverride = true`; when present, the merger bypasses the invariant and logs `task.scopeOverrideReason` when provided. Empty declared file scopes are not enforced.
When `mergeConflictStrategy="smart-prefer-main"`, the merger also runs an overlap guard before the Attempt 3 `-X ours` fallback. If recent `main` commits (30-commit lookback) touched files the task branch also changed, the default `mergeStrategyOverlapBehavior="flip-to-prefer-branch"` makes those overlapping files prefer the task branch instead of silently discarding branch hardening; `warn-only` preserves the legacy fallback while logging the risk, and `ignore` disables the guard.
For manual follow-up, standalone auditing, or post-incident inspection, the script remains available: