Code review comment: This should work but forces the caller to keep some state to avoid making the same edit ad-infinitum. An alternative is to:  - Make all edits to the current line (REPLACE, REMOVE, NONE) also allow the insertion of any number of lines prior to the currently edited line.

Code under review:
public boolean converge(TaskContext context) {
        List<String> lines = uncheck(() -> Files.readAllLines(path, ENCODING));
        List<String> newLines = new ArrayList<>();
        StringBuilder diff = new StringBuilder();
        boolean modified = false;

        for (int i = 0; i < lines.size(); i++) {
            String line = lines.get(i);
            LineEdit edit = editor.edit(line);
            switch (edit.getType()) {
                case REMOVE:
                    modified = true;
                    maybeRemove(diff, line);
                    break;
                case REPLACE: {
                    modified = true;
                    String replacementLine = edit.replacementLine();
                    newLines.add(replacementLine);
                    maybeRemove(diff, line);
                    maybeAdd(diff, replacementLine);
                    break;
                }
                case INSERT: {
                    modified = true;
                    String replacementLine = edit.replacementLine();
                    newLines.add(replacementLine);
                    maybeAdd(diff, replacementLine);
                    i--;
                    break;
                }
                case NONE:
                    newLines.add(line);
                    break;
                default: throw new IllegalArgumentException("Unknown EditType " + edit.getType());
            }
        }

        List<String> linesToAppend = editor.onComplete();
        if (!linesToAppend.isEmpty()) {
            modified = true;
            newLines.addAll(linesToAppend);
            linesToAppend.forEach(line -> maybeAdd(diff, line));
        }

        if (!modified) {
            return false;
        }

        String diffDescription = diffTooLarge(diff) ? "" : ":\n" + diff.toString();
        context.recordSystemModification(logger, "Patching file " + path + diffDescription);
        uncheck(() -> Files.write(path, newLines, ENCODING));
        return true;
    }

Line commented on: i--;

Revised code:
public boolean converge(TaskContext context) {
        List<String> lines = uncheck(() -> Files.readAllLines(path, ENCODING));
        List<String> newLines = new ArrayList<>();
        StringBuilder diff = new StringBuilder();
        boolean modified = false;

        for (String line : lines) {
            LineEdit edit = editor.edit(line);
            if (!edit.prependLines().isEmpty()) {
                modified = true;
                maybeAdd(diff, edit.prependLines());
                newLines.addAll(edit.prependLines());
            }

            switch (edit.getType()) {
                case REPLACE:
                    modified = true;
                    maybeRemove(diff, line);
                    break;
                case NONE:
                    newLines.add(line);
                    break;
                default: throw new IllegalArgumentException("Unknown EditType " + edit.getType());
            }

            if (!edit.appendLines().isEmpty()) {
                modified = true;
                maybeAdd(diff, edit.appendLines());
                newLines.addAll(edit.appendLines());
            }
        }

        List<String> linesToAppend = editor.onComplete();
        if (!linesToAppend.isEmpty()) {
            modified = true;
            newLines.addAll(linesToAppend);
            maybeAdd(diff, linesToAppend);
        }

        if (!modified) {
            return false;
        }

        String diffDescription = diffTooLarge(diff) ? "" : ":\n" + diff.toString();
        context.recordSystemModification(logger, "Patching file " + path + diffDescription);
        uncheck(() -> Files.write(path, newLines, ENCODING));
        return true;
    }